From 00e526400050261e6d6e23f1565f41b7c0811479 Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Fri, 5 Aug 2022 15:52:44 +0100 Subject: [PATCH 1/9] start dates --- lib/src/base/return_period.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 lib/src/base/return_period.dart diff --git a/lib/src/base/return_period.dart b/lib/src/base/return_period.dart new file mode 100644 index 0000000..6564825 --- /dev/null +++ b/lib/src/base/return_period.dart @@ -0,0 +1,20 @@ +import 'dart:core'; +import 'dart:math'; + +/// ReturnPeriods are a denomination format that allow for easy +/// conversion between [TradingPeriod]s and [CalendarPeriod]s. +/// +/// +class ReturnPeriod { + /// When the period the return was generated begins + final DateTime startDate; + + /// When the period the return was generated ends + final DateTime endDate; + + ReturnPeriod({required this.startDate, required this.endDate}); + + Duration get period { + return endDate.difference(startDate); + } +} From 494061709119594769818df826f64fc5d57e1b18 Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Fri, 5 Aug 2022 17:45:02 +0100 Subject: [PATCH 2/9] feat(dates): start dates, some tests --- lib/src/base/return_period.dart | 23 ++++++++-- test/base/return_period_test.dart | 73 +++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 test/base/return_period_test.dart diff --git a/lib/src/base/return_period.dart b/lib/src/base/return_period.dart index 6564825..d7f0189 100644 --- a/lib/src/base/return_period.dart +++ b/lib/src/base/return_period.dart @@ -9,12 +9,29 @@ class ReturnPeriod { /// When the period the return was generated begins final DateTime startDate; - /// When the period the return was generated ends + /// When the period the return was generated ends. Note it's exclusive, + /// so M-F is 4 days, M-S is 5 days. final DateTime endDate; - ReturnPeriod({required this.startDate, required this.endDate}); + ReturnPeriod({required this.startDate, required this.endDate}) + : assert(endDate.difference(startDate) > Duration(seconds: 0)); - Duration get period { + Duration get calendarPeriod { return endDate.difference(startDate); } + + Duration get tradingPeriod { + // for each full week, subtract 2. + // for the remainder, find # of weekend days + + int days = calendarPeriod.inDays; + //int numWeeks = (days / 7).floor(); + int tradingWeekDays = days - 2 * ((days + startDate.weekday) ~/ 7); + final totalDays = tradingWeekDays + + (startDate.weekday == 7 ? 1 : 0) + + (endDate.weekday == 7 ? 1 : 0); + + //adjust for starting and ending on a Sunday: + return Duration(days: totalDays); + } } diff --git a/test/base/return_period_test.dart b/test/base/return_period_test.dart new file mode 100644 index 0000000..c2a7b72 --- /dev/null +++ b/test/base/return_period_test.dart @@ -0,0 +1,73 @@ +import 'dart:math'; + +import 'package:finances/src/base/return_period.dart'; +import 'package:test/test.dart'; +import 'package:finances/finance.dart'; + +void main() { + group('ReturnPeriod', () { + group('below one full week', () { + final dateMon = DateTime(2022, 08, 08, 00, 00); + final dateMon2 = DateTime(2022, 08, 15, 00, 00); + final dateFri = DateTime(2022, 08, 12, 00, 00); + final dateSat = DateTime(2022, 08, 13, 00, 00); + final dateSun = DateTime(2022, 08, 14, 00, 00); + + test('normal week', () { + expect( + ReturnPeriod( + startDate: dateMon, + endDate: dateFri, + ).tradingPeriod, + Duration(days: 4), + ); + }); + test('weekends have no trading days', () { + expect( + ReturnPeriod( + startDate: dateMon, + endDate: dateSat, + ).tradingPeriod, + Duration(days: 5), + ); + expect( + ReturnPeriod( + startDate: dateMon, + endDate: dateSun, + ).tradingPeriod, + Duration(days: 5), + ); + expect( + ReturnPeriod( + startDate: dateMon, + endDate: dateMon2, + ).tradingPeriod, + Duration(days: 5), + ); + }); + test('weekends have no trading days', () { + expect( + ReturnPeriod( + startDate: dateSat, + endDate: dateSun, + ).tradingPeriod, + Duration(days: 0), + ); + }); + }); + group('more than one full week', () { + final startDate = DateTime(2022, 08, 08, 00, 00); // Monday + final endDate = DateTime(2022, 08, 15, 00, 00); // Friday + final period = ReturnPeriod( + startDate: startDate, + endDate: endDate, + ); + test('weekends have no trading days', () { + expect( + period.tradingPeriod, + Duration(days: 5), + ); + }); + }); + }); +} From e13dd3c85d139691418ae9c68a900a23e6fdcffd Mon Sep 17 00:00:00 2001 From: "Daniel P. Egan" Date: Fri, 5 Aug 2022 17:45:49 +0100 Subject: [PATCH 3/9] feat(annualized return): add annualized return, fix example, (#16) --- README.md | 29 ----------------------------- example/example.dart | 22 +++++++++------------- lib/src/base/return.dart | 16 +++++++++++++--- test/base/return_test.dart | 8 ++++++++ 4 files changed, 30 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 3c79023..bcac38b 100644 --- a/README.md +++ b/README.md @@ -13,32 +13,3 @@ An opinionated, strongly-typed package for common financial types and tasks. ## Example See `example/example.dart` for some running examples. - -```dart -import 'dart:math'; -import 'package:finances/finance.dart'; - -void main() { - // Returns - // Daily 1% return scaled to annual - final dailyReturn = Return(nreturn: 0.01); - print(dailyReturn.scale(newPeriod: Duration(days: 252))); - - // Annual return, scaled to daily - final annualReturn = Return(nreturn: 0.05, period: Duration(days: 252)); - print(annualReturn.scale(newPeriod: Duration(days: 1))); - - // ReturnStream - final rng = Random(); - final rstream = ReturnStream.fromDoubles( - List.generate(48, (_) => (rng.nextDouble() - 0.4 / 5))); - - // Show cumulative return - print(rstream.cumulativeReturn); - - // Cumulative value - final cashflows = List.generate(48, (_) => 1.0); - final finalValue = cumulateValueFinal(cashflows: cashflows, returns: rstream); - print(finalValue); -} -``` diff --git a/example/example.dart b/example/example.dart index 02688b3..961959b 100644 --- a/example/example.dart +++ b/example/example.dart @@ -1,17 +1,15 @@ import 'dart:math'; import 'package:finances/finance.dart'; +import 'package:finances/src/base/return.dart'; void main() { - // Returns - // Daily 1% return scaled to annual - final dailyReturn = Return(nreturn: 0.01); - print(dailyReturn.scale(newPeriod: Duration(days: 252))); + // Return + final yearReturn = Return(nreturn: 0.05, period: oneyear); - // Annual return, scaled to daily - final annualReturn = Return(nreturn: 0.05, period: Duration(days: 252)); - print(annualReturn.scale(newPeriod: Duration(days: 1))); + // Five year return, annualized + print(yearReturn.annualize.nreturn); - // ReturnStream + // A ReturnStream final rng = Random(); final rstream = ReturnStream.fromDoubles( List.generate(48, (_) => (rng.nextDouble() - 0.4 / 5)), @@ -19,10 +17,8 @@ void main() { ); // Show cumulative return - print(rstream.cumulativeReturn); + print(rstream.cumulativeReturn.nreturn); - // Cumulative value - //final cashflows = List.generate(48, (_) => 1.0); - //final finalValue = cumulateValueFinal(cashflows: cashflows, returns: rstream); - //print(finalValue); + // Show last cumulative return in the stream + print(rstream.cumulativeReturnStream.nreturns.last.nreturn); } diff --git a/lib/src/base/return.dart b/lib/src/base/return.dart index 2a9343f..576d0e7 100644 --- a/lib/src/base/return.dart +++ b/lib/src/base/return.dart @@ -2,18 +2,18 @@ import 'dart:core'; import 'dart:math'; const oneday = Duration(days: 1); +const oneyear = Duration(days: 252); /// A core class for working with returns. class Return { /// The numeric return, expressed as `0.05` for a 5\% return. final double nreturn; - /// The period over which the return occured + /// The period over which the return occured. Defaults to one day, if not specified. final Duration period; /// default `false`: the calculation method of the return. /// Logarithmic if true, else Arithmetic. - final bool isLog; Return({ @@ -24,8 +24,14 @@ class Return { /// Rescales the return up or down over a given time period. /// - /// Warning: scale up returns of less than a year can be very misleading. + /// Warning: scaling returns up can be misleading. Return scale({required Duration newPeriod}) { + double periodRatio = newPeriod.inSeconds / period.inSeconds; + + if (periodRatio > 1.0) { + print('Warning: scaling returns up can be misleading.'); + } + final newReturn = (pow(1.0 + this.toArithmetic.nreturn, newPeriod.inSeconds / period.inSeconds) .toDouble()) - @@ -46,4 +52,8 @@ class Return { ? Return(nreturn: exp(this.nreturn) - 1.0, isLog: false) : this; } + + Return get annualize { + return scale(newPeriod: oneyear); + } } diff --git a/test/base/return_test.dart b/test/base/return_test.dart index 7597187..5dd7c1a 100644 --- a/test/base/return_test.dart +++ b/test/base/return_test.dart @@ -53,6 +53,14 @@ void main() { closeTo(-0.0199, 0.0000001), ); }); + test('convenience function annualize works', () { + expect( + Return(nreturn: 0.27628156, period: Duration(days: tradingDays * 5)) + .annualize + .nreturn, + closeTo(0.05, 0.00001), + ); + }); }); }); } From c6bfb6d09cb2765eb588f58665882eabedc32480 Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Fri, 5 Aug 2022 17:47:22 +0100 Subject: [PATCH 4/9] imports --- test/base/return_period_test.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/base/return_period_test.dart b/test/base/return_period_test.dart index c2a7b72..9cf8236 100644 --- a/test/base/return_period_test.dart +++ b/test/base/return_period_test.dart @@ -1,8 +1,5 @@ -import 'dart:math'; - import 'package:finances/src/base/return_period.dart'; import 'package:test/test.dart'; -import 'package:finances/finance.dart'; void main() { group('ReturnPeriod', () { From d130a0b73d1f52bc6fe76f6fa1ecf8455ecc5b07 Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Sat, 6 Aug 2022 01:38:23 +0100 Subject: [PATCH 5/9] returnperiod --- example/example.dart | 5 ++-- lib/finance.dart | 1 + lib/src/base/base.dart | 1 + lib/src/base/calc_trading_period.dart | 16 +++++++++++ lib/src/base/return.dart | 27 ++++++++++++------ lib/src/base/return_period.dart | 40 +++++++++++++-------------- lib/src/base/return_stream.dart | 24 ++++++++++------ test/base/return_period_test.dart | 12 ++++---- test/base/return_stream_test.dart | 26 +++++++++++------ test/base/return_test.dart | 32 ++++++++++++++------- 10 files changed, 120 insertions(+), 64 deletions(-) create mode 100644 lib/src/base/calc_trading_period.dart diff --git a/example/example.dart b/example/example.dart index 961959b..bf191c6 100644 --- a/example/example.dart +++ b/example/example.dart @@ -1,10 +1,11 @@ import 'dart:math'; import 'package:finances/finance.dart'; -import 'package:finances/src/base/return.dart'; void main() { // Return - final yearReturn = Return(nreturn: 0.05, period: oneyear); + final yearReturn = Return( + nreturn: 0.05, + returnPeriod: ReturnPeriod(tradingPeriod: Duration(days: 252 * 3))); // Five year return, annualized print(yearReturn.annualize.nreturn); diff --git a/lib/finance.dart b/lib/finance.dart index 4afa026..0ce2095 100644 --- a/lib/finance.dart +++ b/lib/finance.dart @@ -1,2 +1,3 @@ export 'src/base/return.dart' show Return; export 'src/base/return_stream.dart' show ReturnStream, ReturnStreamType; +export 'src/base/return_period.dart' show ReturnPeriod; diff --git a/lib/src/base/base.dart b/lib/src/base/base.dart index bf28c84..52a39d6 100644 --- a/lib/src/base/base.dart +++ b/lib/src/base/base.dart @@ -1,2 +1,3 @@ export 'return.dart'; export 'return_stream.dart'; +export 'return_period.dart'; diff --git a/lib/src/base/calc_trading_period.dart b/lib/src/base/calc_trading_period.dart new file mode 100644 index 0000000..85bf1f5 --- /dev/null +++ b/lib/src/base/calc_trading_period.dart @@ -0,0 +1,16 @@ +import 'dart:core'; + +Duration calcTradingPeriod(DateTime startDate, DateTime endDate) { + // for each full week, subtract 2. + // for the remainder, find # of weekend days + + int days = endDate.difference(startDate).inDays; + //int numWeeks = (days / 7).floor(); + int tradingWeekDays = days - 2 * ((days + startDate.weekday) ~/ 7); + final totalDays = tradingWeekDays + + (startDate.weekday == 7 ? 1 : 0) + + (endDate.weekday == 7 ? 1 : 0); + + //adjust for starting and ending on a Sunday: + return Duration(days: totalDays); +} diff --git a/lib/src/base/return.dart b/lib/src/base/return.dart index 576d0e7..a281c3a 100644 --- a/lib/src/base/return.dart +++ b/lib/src/base/return.dart @@ -1,8 +1,7 @@ import 'dart:core'; import 'dart:math'; - -const oneday = Duration(days: 1); -const oneyear = Duration(days: 252); +import 'package:finances/src/base/return_period.dart'; +import 'package:finances/src/base/calc_trading_period.dart'; /// A core class for working with returns. class Return { @@ -10,7 +9,7 @@ class Return { final double nreturn; /// The period over which the return occured. Defaults to one day, if not specified. - final Duration period; + final ReturnPeriod returnPeriod; /// default `false`: the calculation method of the return. /// Logarithmic if true, else Arithmetic. @@ -19,24 +18,36 @@ class Return { Return({ required this.nreturn, this.isLog = false, - this.period = oneday, + this.returnPeriod = oneReturnDay, }); + Return.fromDates( + {required this.nreturn, + this.isLog = false, + required DateTime startDate, + required DateTime endDate}) + : returnPeriod = + ReturnPeriod(tradingPeriod: calcTradingPeriod(startDate, endDate)); + /// Rescales the return up or down over a given time period. /// /// Warning: scaling returns up can be misleading. Return scale({required Duration newPeriod}) { - double periodRatio = newPeriod.inSeconds / period.inSeconds; + double periodRatio = + newPeriod.inSeconds / returnPeriod.tradingPeriod.inSeconds; if (periodRatio > 1.0) { print('Warning: scaling returns up can be misleading.'); } final newReturn = (pow(1.0 + this.toArithmetic.nreturn, - newPeriod.inSeconds / period.inSeconds) + newPeriod.inSeconds / returnPeriod.tradingPeriod.inSeconds) .toDouble()) - 1.0; - return Return(nreturn: newReturn, period: newPeriod, isLog: false); + return Return( + nreturn: newReturn, + returnPeriod: ReturnPeriod(tradingPeriod: newPeriod), + isLog: false); } /// converts arithmetic return to log diff --git a/lib/src/base/return_period.dart b/lib/src/base/return_period.dart index d7f0189..4113a4b 100644 --- a/lib/src/base/return_period.dart +++ b/lib/src/base/return_period.dart @@ -1,37 +1,35 @@ import 'dart:core'; -import 'dart:math'; +import 'package:finances/src/base/calc_trading_period.dart'; + +const oneday = Duration(days: 1); +const oneReturnDay = ReturnPeriod(tradingPeriod: oneday); +const oneyear = Duration(days: 252); /// ReturnPeriods are a denomination format that allow for easy /// conversion between [TradingPeriod]s and [CalendarPeriod]s. /// /// class ReturnPeriod { + final Duration tradingPeriod; + /// When the period the return was generated begins - final DateTime startDate; + final DateTime? startDate; /// When the period the return was generated ends. Note it's exclusive, /// so M-F is 4 days, M-S is 5 days. - final DateTime endDate; - - ReturnPeriod({required this.startDate, required this.endDate}) - : assert(endDate.difference(startDate) > Duration(seconds: 0)); - - Duration get calendarPeriod { - return endDate.difference(startDate); - } + final DateTime? endDate; - Duration get tradingPeriod { - // for each full week, subtract 2. - // for the remainder, find # of weekend days + const ReturnPeriod( + {required this.tradingPeriod, this.startDate, this.endDate}); - int days = calendarPeriod.inDays; - //int numWeeks = (days / 7).floor(); - int tradingWeekDays = days - 2 * ((days + startDate.weekday) ~/ 7); - final totalDays = tradingWeekDays + - (startDate.weekday == 7 ? 1 : 0) + - (endDate.weekday == 7 ? 1 : 0); + ReturnPeriod.fromDates({required this.startDate, required this.endDate}) + : tradingPeriod = calcTradingPeriod(startDate!, endDate!); - //adjust for starting and ending on a Sunday: - return Duration(days: totalDays); + Duration? get calendarPeriod { + if (endDate == null || startDate == null) { + // TODO(dan): is it possible to construct? + return null; + } else + return endDate!.difference(startDate!); } } diff --git a/lib/src/base/return_stream.dart b/lib/src/base/return_stream.dart index a5976da..1f57326 100644 --- a/lib/src/base/return_stream.dart +++ b/lib/src/base/return_stream.dart @@ -1,4 +1,5 @@ import 'package:finances/src/base/return.dart'; +import 'package:finances/src/base/return_period.dart'; /// Indicates whether a [ReturnStream] is `incremental` or `cumulative`. /// @@ -33,9 +34,11 @@ class ReturnStream { final totalReturn = nreturns.fold( 1.0, (double p, Return c) => (p * (1 + c.nreturn))) - 1.0; - final totalPeriod = nreturns.fold( - Duration(days: 0), (Duration p, Return c) => (p + c.period)); - return Return(nreturn: totalReturn, period: totalPeriod); + final totalPeriod = nreturns.fold(Duration(days: 0), + (Duration p, Return c) => (p + c.returnPeriod.tradingPeriod)); + return Return( + nreturn: totalReturn, + returnPeriod: ReturnPeriod(tradingPeriod: totalPeriod)); } } } @@ -56,8 +59,10 @@ class ReturnStream { for (var i = 0; i < nreturns.length; i++) { totalReturn = (totalReturn * (1.0 + nreturns[i].nreturn)); - totalPeriod = totalPeriod + nreturns[i].period; - result[i] = Return(nreturn: totalReturn, period: totalPeriod); + totalPeriod = totalPeriod + nreturns[i].returnPeriod.tradingPeriod; + result[i] = Return( + nreturn: totalReturn, + returnPeriod: ReturnPeriod(tradingPeriod: totalPeriod)); } return ReturnStream(result, ReturnStreamType.cumulative); } @@ -83,14 +88,17 @@ class ReturnStream { for (var i = 0; i < nreturns.length; i++) { if (i == 0) { thisReturn = nreturns[i].nreturn - 1.0; - thisPeriod = nreturns[i].period; + thisPeriod = nreturns[i].returnPeriod.tradingPeriod; } else { thisReturn = (nreturns[i].nreturn / nreturns[(i - 1)].nreturn) - 1.0; - thisPeriod = nreturns[i].period - nreturns[(i - 1)].period; + thisPeriod = nreturns[i].returnPeriod.tradingPeriod - + nreturns[(i - 1)].returnPeriod.tradingPeriod; } - result[i] = Return(nreturn: thisReturn, period: thisPeriod); + result[i] = Return( + nreturn: thisReturn, + returnPeriod: ReturnPeriod(tradingPeriod: thisPeriod)); } return ReturnStream(result, ReturnStreamType.incremental); } diff --git a/test/base/return_period_test.dart b/test/base/return_period_test.dart index 9cf8236..8ebece3 100644 --- a/test/base/return_period_test.dart +++ b/test/base/return_period_test.dart @@ -12,7 +12,7 @@ void main() { test('normal week', () { expect( - ReturnPeriod( + ReturnPeriod.fromDates( startDate: dateMon, endDate: dateFri, ).tradingPeriod, @@ -21,21 +21,21 @@ void main() { }); test('weekends have no trading days', () { expect( - ReturnPeriod( + ReturnPeriod.fromDates( startDate: dateMon, endDate: dateSat, ).tradingPeriod, Duration(days: 5), ); expect( - ReturnPeriod( + ReturnPeriod.fromDates( startDate: dateMon, endDate: dateSun, ).tradingPeriod, Duration(days: 5), ); expect( - ReturnPeriod( + ReturnPeriod.fromDates( startDate: dateMon, endDate: dateMon2, ).tradingPeriod, @@ -44,7 +44,7 @@ void main() { }); test('weekends have no trading days', () { expect( - ReturnPeriod( + ReturnPeriod.fromDates( startDate: dateSat, endDate: dateSun, ).tradingPeriod, @@ -55,7 +55,7 @@ void main() { group('more than one full week', () { final startDate = DateTime(2022, 08, 08, 00, 00); // Monday final endDate = DateTime(2022, 08, 15, 00, 00); // Friday - final period = ReturnPeriod( + final period = ReturnPeriod.fromDates( startDate: startDate, endDate: endDate, ); diff --git a/test/base/return_stream_test.dart b/test/base/return_stream_test.dart index b6bb47d..67d85fc 100644 --- a/test/base/return_stream_test.dart +++ b/test/base/return_stream_test.dart @@ -10,8 +10,12 @@ void main() { final returnStreamVol = ReturnStream( [ Return(nreturn: -0.01), - Return(nreturn: 0.02, period: Duration(days: 5)), - Return(nreturn: 0.02, period: Duration(days: 10)), + Return( + nreturn: 0.02, + returnPeriod: ReturnPeriod(tradingPeriod: Duration(days: 5))), + Return( + nreturn: 0.02, + returnPeriod: ReturnPeriod(tradingPeriod: Duration(days: 10))), ], ReturnStreamType.incremental, ); @@ -23,7 +27,7 @@ void main() { expect( returnStreamOne.cumulativeReturn.nreturn, closeTo(0.01, 0.000001)); expect( - returnStreamOne.cumulativeReturn.period, + returnStreamOne.cumulativeReturn.returnPeriod.tradingPeriod, Duration(days: 3), ); expect( @@ -61,11 +65,14 @@ void main() { }); test('cumulates time correctly', () { expect( - returnStreamVolCumulative.nreturns[0].period, Duration(days: 1)); + returnStreamVolCumulative.nreturns[0].returnPeriod.tradingPeriod, + Duration(days: 1)); expect( - returnStreamVolCumulative.nreturns[1].period, Duration(days: 6)); + returnStreamVolCumulative.nreturns[1].returnPeriod.tradingPeriod, + Duration(days: 6)); expect( - returnStreamVolCumulative.nreturns[2].period, Duration(days: 16)); + returnStreamVolCumulative.nreturns[2].returnPeriod.tradingPeriod, + Duration(days: 16)); }); }); group('incrementalReturnStream: ', () { @@ -79,9 +86,10 @@ void main() { closeTo(returnStreamVol.nreturns[2].nreturn, 0.00001)); }); test('increments time correctly', () { - expect(irs.nreturns[0].period, Duration(days: 1)); - expect(irs.nreturns[1].period, Duration(days: 5)); - expect(irs.nreturns[2].period, Duration(days: 10)); + expect(irs.nreturns[0].returnPeriod.tradingPeriod, Duration(days: 1)); + expect(irs.nreturns[1].returnPeriod.tradingPeriod, Duration(days: 5)); + expect( + irs.nreturns[2].returnPeriod.tradingPeriod, Duration(days: 10)); }); }); }); diff --git a/test/base/return_test.dart b/test/base/return_test.dart index 5dd7c1a..50b0c44 100644 --- a/test/base/return_test.dart +++ b/test/base/return_test.dart @@ -5,14 +5,19 @@ import 'package:finances/finance.dart'; void main() { const int tradingDays = 252; - final ReturnZero = Return(nreturn: 0, period: Duration(days: 252)); + final ReturnZero = Return( + nreturn: 0, + returnPeriod: ReturnPeriod(tradingPeriod: Duration(days: 252))); group("Return: ", () { group('isLog functionality: ', () { double baseReturn = 2.40; - final returnArith = - Return(nreturn: baseReturn, period: Duration(days: 252)); - final returnLog = - Return(nreturn: 0.74, period: Duration(days: 252), isLog: true); + final returnArith = Return( + nreturn: baseReturn, + returnPeriod: ReturnPeriod(tradingPeriod: Duration(days: 252))); + final returnLog = Return( + nreturn: 0.74, + returnPeriod: ReturnPeriod(tradingPeriod: Duration(days: 252)), + isLog: true); test('conversion to log works', (() { expect(returnArith.toLog.isLog, true); @@ -30,8 +35,10 @@ void main() { }); test('postive returns scale', () { - final baseReturn = - Return(nreturn: 0.01, period: Duration(days: tradingDays)); + final baseReturn = Return( + nreturn: 0.01, + returnPeriod: + ReturnPeriod(tradingPeriod: Duration(days: tradingDays))); expect( baseReturn.scale(newPeriod: Duration(days: 126)).nreturn, closeTo(0.00498756, 0.00001), @@ -42,8 +49,10 @@ void main() { ); }); test('negative returns scale', () { - final baseReturn = - Return(nreturn: -0.01, period: Duration(days: tradingDays)); + final baseReturn = Return( + nreturn: -0.01, + returnPeriod: + ReturnPeriod(tradingPeriod: Duration(days: tradingDays))); expect( baseReturn.scale(newPeriod: Duration(days: 126)).nreturn, closeTo(-0.00501256, 0.00001), @@ -55,7 +64,10 @@ void main() { }); test('convenience function annualize works', () { expect( - Return(nreturn: 0.27628156, period: Duration(days: tradingDays * 5)) + Return( + nreturn: 0.27628156, + returnPeriod: ReturnPeriod( + tradingPeriod: Duration(days: tradingDays * 5))) .annualize .nreturn, closeTo(0.05, 0.00001), From 9b5b5573e49c90d540b2d39ac9ae3e9725418bb3 Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Sat, 6 Aug 2022 07:30:53 +0100 Subject: [PATCH 6/9] addi constants file --- lib/src/base/finance_constants.dart | 10 ++++++++++ lib/src/base/return.dart | 5 +++-- lib/src/base/return_period.dart | 6 +----- lib/src/base/return_stream.dart | 4 ++-- 4 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 lib/src/base/finance_constants.dart diff --git a/lib/src/base/finance_constants.dart b/lib/src/base/finance_constants.dart new file mode 100644 index 0000000..40884ef --- /dev/null +++ b/lib/src/base/finance_constants.dart @@ -0,0 +1,10 @@ +import 'package:finances/src/base/return_period.dart'; + +class FiConstants { + /// A constant of one trading day + static const oneTradingDay = ReturnPeriod(tradingPeriod: Duration(days: 1)); + + /// A constant of 252 days for the American trading year + static const oneTradingYear = + ReturnPeriod(tradingPeriod: Duration(days: 252)); +} diff --git a/lib/src/base/return.dart b/lib/src/base/return.dart index a281c3a..73c3931 100644 --- a/lib/src/base/return.dart +++ b/lib/src/base/return.dart @@ -1,5 +1,6 @@ import 'dart:core'; import 'dart:math'; +import 'package:finances/src/base/finance_constants.dart'; import 'package:finances/src/base/return_period.dart'; import 'package:finances/src/base/calc_trading_period.dart'; @@ -18,7 +19,7 @@ class Return { Return({ required this.nreturn, this.isLog = false, - this.returnPeriod = oneReturnDay, + this.returnPeriod = FiConstants.oneTradingDay, }); Return.fromDates( @@ -65,6 +66,6 @@ class Return { } Return get annualize { - return scale(newPeriod: oneyear); + return scale(newPeriod: FiConstants.oneTradingYear.tradingPeriod); } } diff --git a/lib/src/base/return_period.dart b/lib/src/base/return_period.dart index 4113a4b..0d896b3 100644 --- a/lib/src/base/return_period.dart +++ b/lib/src/base/return_period.dart @@ -1,10 +1,6 @@ import 'dart:core'; import 'package:finances/src/base/calc_trading_period.dart'; -const oneday = Duration(days: 1); -const oneReturnDay = ReturnPeriod(tradingPeriod: oneday); -const oneyear = Duration(days: 252); - /// ReturnPeriods are a denomination format that allow for easy /// conversion between [TradingPeriod]s and [CalendarPeriod]s. /// @@ -27,7 +23,7 @@ class ReturnPeriod { Duration? get calendarPeriod { if (endDate == null || startDate == null) { - // TODO(dan): is it possible to construct? + // TODO(dpe): is it possible to construct? return null; } else return endDate!.difference(startDate!); diff --git a/lib/src/base/return_stream.dart b/lib/src/base/return_stream.dart index 1f57326..e8cd3e5 100644 --- a/lib/src/base/return_stream.dart +++ b/lib/src/base/return_stream.dart @@ -50,7 +50,7 @@ class ReturnStream { return this; case ReturnStreamType.incremental: { - // #TODO: could be faster? + // #TODO(dpe): could be faster? var totalReturn = 1.0; var totalPeriod = Duration(days: 0); var result = List.generate( @@ -78,7 +78,7 @@ class ReturnStream { return this; case ReturnStreamType.cumulative: { - // #TODO: could be faster? + // #TODO(dpe): could be faster? var thisReturn = 0.0; var thisPeriod = Duration.zero; var result = List.generate( From 74125092b391ac1771722d4156c23d4f63709d8c Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Sat, 6 Aug 2022 07:33:32 +0100 Subject: [PATCH 7/9] remove comment --- lib/src/base/calc_trading_period.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/base/calc_trading_period.dart b/lib/src/base/calc_trading_period.dart index 85bf1f5..d1e9356 100644 --- a/lib/src/base/calc_trading_period.dart +++ b/lib/src/base/calc_trading_period.dart @@ -5,7 +5,7 @@ Duration calcTradingPeriod(DateTime startDate, DateTime endDate) { // for the remainder, find # of weekend days int days = endDate.difference(startDate).inDays; - //int numWeeks = (days / 7).floor(); + int tradingWeekDays = days - 2 * ((days + startDate.weekday) ~/ 7); final totalDays = tradingWeekDays + (startDate.weekday == 7 ? 1 : 0) + From dcd7f7516e12ef6348dac55c4620770e9dee7589 Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Sat, 6 Aug 2022 07:39:41 +0100 Subject: [PATCH 8/9] remove unnecessary import --- example/example.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/example/example.dart b/example/example.dart index 2f961a7..bf191c6 100644 --- a/example/example.dart +++ b/example/example.dart @@ -1,6 +1,5 @@ import 'dart:math'; import 'package:finances/finance.dart'; -import 'package:finances/src/base/return.dart'; void main() { // Return @@ -8,7 +7,6 @@ void main() { nreturn: 0.05, returnPeriod: ReturnPeriod(tradingPeriod: Duration(days: 252 * 3))); - // Five year return, annualized print(yearReturn.annualize.nreturn); From e71256472f0fb4a3e68906aa300826ce228473f2 Mon Sep 17 00:00:00 2001 From: Dan Egan Date: Sat, 6 Aug 2022 11:02:14 +0100 Subject: [PATCH 9/9] not passing, but progress --- test/base/return_period_test.dart | 19 +++++++++++++++++-- test/base/return_test.dart | 19 ++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/test/base/return_period_test.dart b/test/base/return_period_test.dart index 8ebece3..66ae6ec 100644 --- a/test/base/return_period_test.dart +++ b/test/base/return_period_test.dart @@ -1,9 +1,10 @@ +import 'package:finances/src/base/finance_constants.dart'; import 'package:finances/src/base/return_period.dart'; import 'package:test/test.dart'; void main() { group('ReturnPeriod', () { - group('below one full week', () { + group('below one full week: ', () { final dateMon = DateTime(2022, 08, 08, 00, 00); final dateMon2 = DateTime(2022, 08, 15, 00, 00); final dateFri = DateTime(2022, 08, 12, 00, 00); @@ -52,7 +53,7 @@ void main() { ); }); }); - group('more than one full week', () { + group('more than one full week: ', () { final startDate = DateTime(2022, 08, 08, 00, 00); // Monday final endDate = DateTime(2022, 08, 15, 00, 00); // Friday final period = ReturnPeriod.fromDates( @@ -65,6 +66,20 @@ void main() { Duration(days: 5), ); }); + group('more than a year: ', () { + final startDate = DateTime(2000, 01, 01); + final endDate = DateTime(2005, 01, 01); + final period = ReturnPeriod.fromDates( + startDate: startDate, + endDate: endDate, + ); + test('constants and constructor give same results', () { + expect( + period.tradingPeriod, + FiConstants.oneTradingYear.tradingPeriod * 5, + ); + }); + }); }); }); } diff --git a/test/base/return_test.dart b/test/base/return_test.dart index 9b14410..a324aa3 100644 --- a/test/base/return_test.dart +++ b/test/base/return_test.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:finances/src/base/finance_constants.dart'; import 'package:test/test.dart'; import 'package:finances/finance.dart'; @@ -64,7 +65,6 @@ void main() { }); test('convenience function annualize works', () { expect( - Return( nreturn: 0.27628156, returnPeriod: ReturnPeriod( @@ -74,6 +74,23 @@ void main() { closeTo(0.05, 0.00001), ); }); + group('using fromDates constructor: ', () { + final fiveYearReturn = Return.fromDates( + nreturn: 0.27628156, + startDate: DateTime(2000, 01, 01), + endDate: DateTime(2004, 12, 31), + ); + test('returns correct tradingPeriod', () { + expect( + fiveYearReturn.returnPeriod.tradingPeriod.inDays, + (FiConstants.oneTradingYear.tradingPeriod * 5 + + FiConstants.oneTradingDay.tradingPeriod) + .inDays); + }); + test('annualized correctly', () { + expect(fiveYearReturn.annualize.nreturn, closeTo(0.05, 0.00001)); + }); + }); }); }); }