investing-algorithm-framework 6.9.1__py3-none-any.whl → 7.19.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of investing-algorithm-framework might be problematic. Click here for more details.

Files changed (192) hide show
  1. investing_algorithm_framework/__init__.py +147 -44
  2. investing_algorithm_framework/app/__init__.py +23 -6
  3. investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
  4. investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
  5. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  6. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  7. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  8. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  9. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  10. investing_algorithm_framework/app/app.py +1322 -707
  11. investing_algorithm_framework/app/context.py +196 -88
  12. investing_algorithm_framework/app/eventloop.py +590 -0
  13. investing_algorithm_framework/app/reporting/__init__.py +16 -5
  14. investing_algorithm_framework/app/reporting/ascii.py +57 -202
  15. investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
  16. investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
  17. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  18. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  19. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
  20. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  21. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  22. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
  23. investing_algorithm_framework/app/reporting/generate.py +100 -114
  24. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
  25. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
  26. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
  27. investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
  28. investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
  29. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
  30. investing_algorithm_framework/app/strategy.py +315 -175
  31. investing_algorithm_framework/app/task.py +5 -3
  32. investing_algorithm_framework/cli/cli.py +30 -12
  33. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
  34. investing_algorithm_framework/cli/initialize_app.py +20 -1
  35. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
  36. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  37. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  38. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
  39. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
  40. investing_algorithm_framework/create_app.py +3 -5
  41. investing_algorithm_framework/dependency_container.py +25 -39
  42. investing_algorithm_framework/domain/__init__.py +45 -38
  43. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  44. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  45. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  46. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  47. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  48. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  49. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  50. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  51. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  52. investing_algorithm_framework/domain/config.py +27 -0
  53. investing_algorithm_framework/domain/constants.py +6 -34
  54. investing_algorithm_framework/domain/data_provider.py +200 -56
  55. investing_algorithm_framework/domain/exceptions.py +34 -1
  56. investing_algorithm_framework/domain/models/__init__.py +10 -19
  57. investing_algorithm_framework/domain/models/base_model.py +0 -6
  58. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  59. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  60. investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
  61. investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
  62. investing_algorithm_framework/domain/models/order/order.py +34 -13
  63. investing_algorithm_framework/domain/models/order/order_status.py +1 -1
  64. investing_algorithm_framework/domain/models/order/order_type.py +1 -1
  65. investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
  66. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
  67. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
  68. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  69. investing_algorithm_framework/domain/models/position/position.py +9 -0
  70. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  71. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  72. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  73. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  74. investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
  75. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  76. investing_algorithm_framework/domain/models/time_frame.py +7 -0
  77. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  78. investing_algorithm_framework/domain/models/time_unit.py +63 -1
  79. investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
  80. investing_algorithm_framework/domain/models/trade/trade.py +56 -32
  81. investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
  82. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
  83. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
  84. investing_algorithm_framework/domain/order_executor.py +19 -0
  85. investing_algorithm_framework/domain/portfolio_provider.py +20 -1
  86. investing_algorithm_framework/domain/services/__init__.py +0 -13
  87. investing_algorithm_framework/domain/strategy.py +1 -29
  88. investing_algorithm_framework/domain/utils/__init__.py +5 -1
  89. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  90. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  91. investing_algorithm_framework/domain/utils/polars.py +17 -14
  92. investing_algorithm_framework/download_data.py +40 -10
  93. investing_algorithm_framework/infrastructure/__init__.py +13 -25
  94. investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
  95. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
  96. investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
  97. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  98. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  99. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
  100. investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
  101. investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
  102. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
  103. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
  104. investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
  105. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  106. investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
  107. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
  108. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
  109. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
  110. investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
  111. investing_algorithm_framework/services/__init__.py +105 -8
  112. investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
  113. investing_algorithm_framework/services/configuration_service.py +14 -4
  114. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  115. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  116. investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
  117. investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
  118. investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
  119. investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
  120. investing_algorithm_framework/services/metrics/generate.py +358 -0
  121. investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
  122. investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
  123. investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
  124. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  125. investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
  126. investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
  127. investing_algorithm_framework/services/metrics/trades.py +500 -0
  128. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  129. investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
  130. investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
  131. investing_algorithm_framework/services/order_service/order_service.py +9 -71
  132. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
  133. investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
  134. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
  135. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
  136. investing_algorithm_framework/services/repository_service.py +5 -2
  137. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  138. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  139. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  140. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  141. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  142. investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
  143. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  144. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  145. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  146. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
  147. investing_algorithm_framework/app/reporting/evaluation.py +0 -243
  148. investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
  149. investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
  150. investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
  151. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
  152. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
  153. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  154. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
  155. investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
  156. investing_algorithm_framework/domain/models/data_source.py +0 -21
  157. investing_algorithm_framework/domain/models/date_range.py +0 -64
  158. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
  159. investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
  160. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  161. investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
  162. investing_algorithm_framework/domain/services/market_service.py +0 -153
  163. investing_algorithm_framework/domain/services/observable.py +0 -51
  164. investing_algorithm_framework/domain/services/observer.py +0 -19
  165. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
  166. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
  167. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
  168. investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
  169. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  170. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
  171. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  172. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  173. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
  174. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
  175. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
  176. investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
  177. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
  178. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
  179. investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
  180. /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
  181. /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
  182. /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
  183. /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
  184. /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
  185. /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
  186. /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
  187. /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
  188. /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
  189. /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
  190. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  191. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
  192. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
@@ -1,141 +1,25 @@
1
- from .base_model import BaseModel
2
- from .time_unit import TimeUnit
3
-
4
-
5
- class StrategyProfile(BaseModel):
6
-
7
- def __init__(
8
- self,
9
- strategy_id=None,
10
- interval=None,
11
- time_unit=None,
12
- trading_time_frame=None,
13
- trading_time_frame_start_date=None,
14
- symbols=None,
15
- market=None,
16
- backtest_start_date_data=None,
17
- backtest_data_index_date=None,
18
- trading_data_type=None,
19
- trading_data_types=None,
20
- market_data_sources=None,
21
- ):
22
- self._strategy_id = strategy_id
23
- self._interval = interval
24
- self._time_unit = time_unit
25
- self._number_of_runs = 0
26
- self._trading_time_frame = trading_time_frame
27
- self._trading_time_frame_start_date = trading_time_frame_start_date
28
- self._backtest_start_date_data = backtest_start_date_data
29
- self._backtest_data_index_date = backtest_data_index_date
30
- self._symbols = symbols
31
- self._market = market
32
- self._trading_data_type = trading_data_type
33
- self._trading_data_types = trading_data_types
34
- self._market_data_sources = market_data_sources
35
-
36
- @property
37
- def strategy_id(self):
38
- return self._strategy_id
39
-
40
- @strategy_id.setter
41
- def strategy_id(self, strategy_id):
42
- self._strategy_id = strategy_id
43
-
44
- @property
45
- def interval(self):
46
- return self._interval
47
-
48
- @interval.setter
49
- def interval(self, value):
50
- self._interval = value
51
-
52
- @property
53
- def time_unit(self):
54
- return self._time_unit
55
-
56
- @time_unit.setter
57
- def time_unit(self, value):
58
- self._time_unit = value
59
-
60
- @property
61
- def symbols(self):
62
- return self._symbols
63
-
64
- @property
65
- def trading_time_frame(self):
66
- return self._trading_time_frame
67
-
68
- @property
69
- def trading_time_frame_start_date(self):
70
- return self._trading_time_frame_start_date
71
-
72
- @property
73
- def number_of_runs(self):
74
- return self._number_of_runs
75
-
76
- @property
77
- def market(self):
78
- return self._market
1
+ from dataclasses import dataclass
79
2
 
80
- @symbols.setter
81
- def symbols(self, value):
82
- self._symbols = value
83
-
84
- @market.setter
85
- def market(self, value):
86
- self._market = value
87
-
88
- @number_of_runs.setter
89
- def number_of_runs(self, value):
90
- self._number_of_runs = value
91
-
92
- @trading_time_frame.setter
93
- def trading_time_frame(self, value):
94
- self._trading_time_frame = value
95
-
96
- @property
97
- def backtest_start_date_data(self):
98
- return self._backtest_start_date_data
99
-
100
- @backtest_start_date_data.setter
101
- def backtest_start_date_data(self, value):
102
- self._backtest_start_date_data = value
103
-
104
- @property
105
- def backtest_data_index_date(self):
106
- return self._backtest_data_index_date
107
-
108
- @backtest_data_index_date.setter
109
- def backtest_data_index_date(self, value):
110
- self._backtest_data_index_date = value
111
-
112
- @property
113
- def trading_data_type(self):
114
- return self._trading_data_type
115
-
116
- @trading_data_type.setter
117
- def trading_data_type(self, value):
118
- self._trading_data_type = value
119
-
120
- @property
121
- def trading_data_types(self):
122
-
123
- if self.trading_data_type is not None:
124
- return [self.trading_data_type]
125
-
126
- return self._trading_data_types
127
-
128
- @trading_data_types.setter
129
- def trading_data_types(self, value):
130
- self._trading_data_types = value
3
+ from .time_unit import TimeUnit
131
4
 
132
- @property
133
- def market_data_sources(self):
134
- return self._market_data_sources
135
5
 
136
- @market_data_sources.setter
137
- def market_data_sources(self, value):
138
- self._market_data_sources = value
6
+ @dataclass(frozen=True)
7
+ class StrategyProfile:
8
+ """
9
+ StrategyProfile class that represents the profile of a trading strategy.
10
+ """
11
+ strategy_id: str = None
12
+ interval: int = None
13
+ time_unit: TimeUnit = None
14
+ trading_time_frame: str = None
15
+ trading_time_frame_start_date: str = None
16
+ backtest_start_date_data: str = None
17
+ backtest_data_index_date: str = None
18
+ symbols: list = None
19
+ market: str = None
20
+ trading_data_type: str = None
21
+ trading_data_types: list = None
22
+ data_sources: list = None
139
23
 
140
24
  def get_runs_per_day(self):
141
25
 
@@ -147,19 +31,3 @@ class StrategyProfile(BaseModel):
147
31
  return 1440 / self.interval
148
32
  else:
149
33
  return 24 / self.interval
150
-
151
- def __repr__(self):
152
- return self.repr(
153
- strategy_id=self._strategy_id,
154
- number_of_runs=self._number_of_runs,
155
- trading_time_frame=self._trading_time_frame,
156
- trading_time_frame_start_date=self._trading_time_frame_start_date,
157
- symbols=self._symbols,
158
- time_unit=self.time_unit,
159
- interval=self.interval,
160
- market=self._market,
161
- backtest_start_date_data=self._backtest_start_date_data,
162
- backtest_data_index_date=self._backtest_data_index_date,
163
- trading_data_type=self._trading_data_type,
164
- trading_data_types=self._trading_data_types,
165
- )
@@ -124,6 +124,13 @@ class TimeFrame(Enum):
124
124
  if self.equals(TimeFrame.ONE_MONTH):
125
125
  return 40320
126
126
 
127
+ if self.equals(TimeFrame.ONE_YEAR):
128
+ return 525600
129
+
130
+ raise ValueError(
131
+ f"Could not determine amount of minutes for {self.value}"
132
+ )
133
+
127
134
  # Add comparison methods for ordering
128
135
  def __lt__(self, other):
129
136
  if isinstance(other, TimeFrame):
@@ -25,6 +25,39 @@ class TimeInterval(Enum):
25
25
  f"Could not convert {value} to TimeInterval"
26
26
  )
27
27
 
28
+ @staticmethod
29
+ def from_ohlcv_data_file(file_path: str):
30
+ """
31
+ Extracts the time interval from the file name of an OHLCV data file.
32
+ The file name should contain the time interval in the format
33
+ 'symbol_timeinterval.csv'.
34
+
35
+ Args:
36
+ file_path (str): The file path of the OHLCV data file.
37
+
38
+ Returns:
39
+ TimeInterval: The extracted time interval.
40
+ """
41
+ if not isinstance(file_path, str):
42
+ raise ValueError("File path must be a string.")
43
+
44
+ parts = file_path.split('_')
45
+ if len(parts) < 2:
46
+ raise ValueError(
47
+ "File name does not contain a valid time interval."
48
+ )
49
+
50
+ time_interval_str = parts[-1].split('.')[0].upper()
51
+ try:
52
+ return TimeInterval.from_string(time_interval_str)
53
+ except ValueError:
54
+ raise ValueError(
55
+ "Could not extract time interval from "
56
+ f"file name: {file_path}. "
57
+ "Expected format 'symbol_timeinterval.csv', "
58
+ f"got '{time_interval_str}'."
59
+ )
60
+
28
61
  def equals(self, other):
29
62
 
30
63
  if isinstance(other, Enum):
@@ -1,8 +1,16 @@
1
1
  from datetime import timedelta
2
2
  from enum import Enum
3
+ from investing_algorithm_framework.domain.exceptions import \
4
+ OperationalException
3
5
 
4
6
 
5
7
  class TimeUnit(Enum):
8
+ """
9
+ Enum class the represents a time unit such as
10
+ second, minute, hour or day. This can class
11
+ can be used to specify time specification within
12
+ the framework.
13
+ """
6
14
  SECOND = "SECOND"
7
15
  MINUTE = "MINUTE"
8
16
  HOUR = "HOUR"
@@ -18,12 +26,50 @@ class TimeUnit(Enum):
18
26
  if value.upper() == entry.value:
19
27
  return entry
20
28
 
21
- raise ValueError(
29
+ raise OperationalException(
30
+ f"Could not convert string {value} to time unit"
31
+ )
32
+
33
+ raise OperationalException(
22
34
  f"Could not convert value {value} to time unit," +
23
35
  " please make sure that the value is either of type string or" +
24
36
  f"TimeUnit. Its current type is {type(value)}"
25
37
  )
26
38
 
39
+ @staticmethod
40
+ def from_ohlcv_data_file(file_path: str):
41
+ """
42
+ Extracts the time unit from the file name of an OHLCV data file.
43
+ The file name should contain the time unit in the
44
+ format 'symbol_timeunit.csv'.
45
+
46
+ Args:
47
+ file_path (str): The file path of the OHLCV data file.
48
+
49
+ Returns:
50
+ TimeUnit: The extracted time unit.
51
+ """
52
+ if not isinstance(file_path, str):
53
+ raise OperationalException(
54
+ "File path must be a string."
55
+ )
56
+
57
+ parts = file_path.split('_')
58
+ if len(parts) < 2:
59
+ raise OperationalException(
60
+ "File name does not contain a valid time unit."
61
+ )
62
+
63
+ time_unit_str = parts[-1].split('.')[0].upper()
64
+ try:
65
+ return TimeUnit.from_string(time_unit_str)
66
+ except ValueError:
67
+ raise OperationalException(
68
+ f"Could not extract time unit from file name: {file_path}. "
69
+ "Expected format 'symbol_timeunit.csv', "
70
+ f"got '{time_unit_str}'."
71
+ )
72
+
27
73
  def equals(self, other):
28
74
 
29
75
  if isinstance(other, Enum):
@@ -85,3 +131,19 @@ class TimeUnit(Enum):
85
131
 
86
132
  if TimeUnit.DAY.equals(self.value):
87
133
  return "days"
134
+
135
+ @property
136
+ def amount_of_minutes(self):
137
+ if TimeUnit.SECOND.equals(self):
138
+ return 1 / 60
139
+
140
+ if TimeUnit.MINUTE.equals(self):
141
+ return 1
142
+
143
+ if TimeUnit.HOUR.equals(self):
144
+ return 60
145
+
146
+ if TimeUnit.DAY.equals(self):
147
+ return 60 * 24
148
+
149
+ raise ValueError(f"Unsupported time unit: {self}")
@@ -2,12 +2,10 @@ from .trade import Trade
2
2
  from .trade_status import TradeStatus
3
3
  from .trade_stop_loss import TradeStopLoss
4
4
  from .trade_take_profit import TradeTakeProfit
5
- from .trade_risk_type import TradeRiskType
6
5
 
7
6
  __all__ = [
8
7
  "Trade",
9
8
  "TradeStatus",
10
9
  "TradeStopLoss",
11
10
  "TradeTakeProfit",
12
- "TradeRiskType",
13
11
  ]
@@ -1,4 +1,5 @@
1
1
  from dateutil.parser import parse
2
+ from datetime import timezone
2
3
 
3
4
  from investing_algorithm_framework.domain.models.base_model import BaseModel
4
5
  from investing_algorithm_framework.domain.models.order import OrderSide, Order
@@ -40,9 +41,10 @@ class Trade(BaseModel):
40
41
  last_reported_price: float, the last reported price of the trade
41
42
  last_reported_price_datetime: datetime, the datetime when the last
42
43
  reported price was reported
43
- created_at: datetime, the datetime when the trade was created
44
44
  updated_at: datetime, the datetime when the trade was last updated
45
45
  status: str, the status of the trade
46
+ metadata: dict, the metadata of the trade, this can be used to store
47
+ additional information about the trade.
46
48
  """
47
49
 
48
50
  def __init__(
@@ -68,6 +70,7 @@ class Trade(BaseModel):
68
70
  updated_at=None,
69
71
  stop_losses=None,
70
72
  take_profits=None,
73
+ metadata=None,
71
74
  ):
72
75
  self.id = id
73
76
  self.orders = orders
@@ -86,15 +89,16 @@ class Trade(BaseModel):
86
89
  self.last_reported_price_datetime = last_reported_price_datetime
87
90
  self.high_water_mark = high_water_mark
88
91
  self.high_water_mark_datetime = high_water_mark_datetime
89
- self.status = status
92
+ self.status = TradeStatus.from_value(status).value
90
93
  self.updated_at = updated_at
91
94
  self.stop_losses = stop_losses
92
95
  self.take_profits = take_profits
96
+ self.metadata = metadata if metadata is not None else {}
93
97
 
94
98
  def update(self, data):
95
99
 
96
100
  if "status" in data:
97
- self.status = TradeStatus.from_value(data["status"])
101
+ self.status = TradeStatus.from_value(data["status"]).value
98
102
 
99
103
  if TradeStatus.CLOSED.equals(self.status):
100
104
 
@@ -174,16 +178,25 @@ class Trade(BaseModel):
174
178
 
175
179
  @property
176
180
  def change(self):
181
+ """
182
+ Property to calculate the change in value of the trade.
183
+
184
+ This is the difference between the current value of the trade
185
+ and the cost of the trade.
186
+ """
177
187
  if TradeStatus.CLOSED.equals(self.status):
178
- cost = self.amount * self.open_price
179
- return self.net_gain - cost
188
+ return self.net_gain
180
189
 
181
190
  if self.last_reported_price is None:
182
191
  return 0
183
192
 
184
- cost = self.remaining * self.open_price
185
- gain = (self.remaining * self.last_reported_price) - cost
186
- gain += self.net_gain
193
+ if self.remaining is None or self.remaining == 0:
194
+ amount = self.amount
195
+ else:
196
+ amount = self.amount - self.remaining
197
+
198
+ cost = amount * self.open_price
199
+ gain = (amount * self.last_reported_price) - cost
187
200
  return gain
188
201
 
189
202
  @property
@@ -196,7 +209,7 @@ class Trade(BaseModel):
196
209
 
197
210
  if self.last_reported_price is not None:
198
211
  gain = (
199
- self.remaining *
212
+ self.available_amount *
200
213
  (self.last_reported_price - self.open_price)
201
214
  )
202
215
 
@@ -216,7 +229,7 @@ class Trade(BaseModel):
216
229
 
217
230
  if self.last_reported_price is not None:
218
231
  gain = (
219
- self.remaining *
232
+ self.available_amount *
220
233
  (self.last_reported_price - self.open_price)
221
234
  )
222
235
 
@@ -231,31 +244,36 @@ class Trade(BaseModel):
231
244
  def percentage_change(self):
232
245
 
233
246
  if TradeStatus.CLOSED.equals(self.status):
234
- cost = self.amount * self.open_price
235
- gain = self.net_gain - cost
236
- return (gain / cost) * 100
247
+
248
+ if self.cost != 0:
249
+ return (self.net_gain / self.cost) * 100
237
250
 
238
251
  if self.last_reported_price is None:
239
252
  return 0
240
253
 
241
- cost = self.remaining * self.open_price
242
- gain = (self.remaining * self.last_reported_price) - cost
254
+ cost = self.available_amount * self.open_price
255
+ gain = (self.available_amount * self.last_reported_price) - cost
243
256
  gain += self.net_gain
244
- return (gain / cost) * 100
257
+
258
+ if cost != 0:
259
+ return (gain / cost) * 100
260
+
261
+ return 0
245
262
 
246
263
  def to_dict(self, datetime_format=None):
264
+ def ensure_iso(value):
265
+ if hasattr(value, "isoformat"):
266
+ if value.tzinfo is None:
267
+ value = value.replace(tzinfo=timezone.utc)
268
+ return value.isoformat()
269
+ return value
247
270
 
248
- if datetime_format is not None:
249
- opened_at = self.opened_at.strftime(datetime_format) \
250
- if self.opened_at else None
251
- closed_at = self.closed_at.strftime(datetime_format) \
252
- if self.closed_at else None
253
- updated_at = self.updated_at.strftime(datetime_format) \
254
- if self.updated_at else None
255
- else:
256
- opened_at = self.opened_at
257
- closed_at = self.closed_at
258
- updated_at = self.updated_at
271
+ opened_at = ensure_iso(self.opened_at) if self.opened_at else None
272
+ closed_at = ensure_iso(self.closed_at) if self.closed_at else None
273
+ updated_at = ensure_iso(self.updated_at) if self.updated_at else None
274
+
275
+ # Ensure status is a string
276
+ self.status = TradeStatus.from_value(self.status).value
259
277
 
260
278
  return {
261
279
  "id": self.id,
@@ -267,14 +285,14 @@ class Trade(BaseModel):
267
285
  "trading_symbol": self.trading_symbol,
268
286
  "status": self.status,
269
287
  "amount": self.amount,
270
- "remaining": self.remaining,
288
+ "remaining": self.remaining if self.remaining is not None else 0,
271
289
  "open_price": self.open_price,
272
290
  "last_reported_price": self.last_reported_price,
273
291
  "opened_at": opened_at,
274
292
  "closed_at": closed_at,
275
293
  "updated_at": updated_at,
276
- "net_gain": self.net_gain,
277
- "cost": self.cost,
294
+ "net_gain": self.net_gain if self.net_gain is not None else 0,
295
+ "cost": self.cost if self.cost is not None else 0,
278
296
  "stop_losses": [
279
297
  stop_loss.to_dict(datetime_format=datetime_format)
280
298
  for stop_loss in self.stop_losses
@@ -283,6 +301,9 @@ class Trade(BaseModel):
283
301
  take_profit.to_dict(datetime_format=datetime_format)
284
302
  for take_profit in self.take_profits
285
303
  ] if self.take_profits else None,
304
+ "filled_amount": self.filled_amount,
305
+ "available_amount": self.available_amount,
306
+ "metadata": self.metadata if self.metadata else {},
286
307
  }
287
308
 
288
309
  @staticmethod
@@ -320,7 +341,6 @@ class Trade(BaseModel):
320
341
  Order.from_dict(order)
321
342
  for order in data["orders"]
322
343
  ]
323
-
324
344
  return Trade(
325
345
  id=data.get("id", None),
326
346
  orders=orders,
@@ -335,16 +355,18 @@ class Trade(BaseModel):
335
355
  remaining=data.get("remaining", 0),
336
356
  net_gain=data.get("net_gain", 0),
337
357
  last_reported_price=data.get("last_reported_price"),
338
- status=data["status"],
358
+ status=TradeStatus.from_value(data["status"]).value,
339
359
  cost=data.get("cost", 0),
340
360
  updated_at=updated_at,
341
361
  stop_losses=stop_losses,
342
362
  take_profits=take_profits,
363
+ metadata=data.get("metadata", {}),
343
364
  )
344
365
 
345
366
  def __repr__(self):
346
367
  return self.repr(
347
368
  id=self.id,
369
+ symbol=self.symbol,
348
370
  target_symbol=self.target_symbol,
349
371
  trading_symbol=self.trading_symbol,
350
372
  status=self.status,
@@ -357,6 +379,8 @@ class Trade(BaseModel):
357
379
  closed_at=self.closed_at,
358
380
  net_gain=self.net_gain,
359
381
  last_reported_price=self.last_reported_price,
382
+ updated_at=self.updated_at,
383
+ metadata=self.metadata,
360
384
  )
361
385
 
362
386
  def __lt__(self, other):
@@ -1,4 +1,6 @@
1
1
  from enum import Enum
2
+ from investing_algorithm_framework.domain.exceptions import \
3
+ OperationalException
2
4
 
3
5
 
4
6
  class TradeStatus(Enum):
@@ -15,7 +17,9 @@ class TradeStatus(Enum):
15
17
  if value.upper() == status.value:
16
18
  return status
17
19
 
18
- raise ValueError("Could not convert value to TradeStatus")
20
+ raise OperationalException(
21
+ f"Could not convert value: '{value}' to TradeStatus"
22
+ )
19
23
 
20
24
  @staticmethod
21
25
  def from_value(value):
@@ -28,7 +32,9 @@ class TradeStatus(Enum):
28
32
  elif isinstance(value, str):
29
33
  return TradeStatus.from_string(value)
30
34
 
31
- raise ValueError("Could not convert value to TradeStatus")
35
+ raise OperationalException(
36
+ f"Could not convert value: {value} to TradeStatus"
37
+ )
32
38
 
33
39
  def equals(self, other):
34
40
  return TradeStatus.from_value(other) == self