investing-algorithm-framework 7.19.14__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 (260) hide show
  1. investing_algorithm_framework/__init__.py +197 -0
  2. investing_algorithm_framework/app/__init__.py +47 -0
  3. investing_algorithm_framework/app/algorithm/__init__.py +7 -0
  4. investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
  5. investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
  6. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  7. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  8. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  9. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  10. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  11. investing_algorithm_framework/app/app.py +2204 -0
  12. investing_algorithm_framework/app/app_hook.py +28 -0
  13. investing_algorithm_framework/app/context.py +1667 -0
  14. investing_algorithm_framework/app/eventloop.py +590 -0
  15. investing_algorithm_framework/app/reporting/__init__.py +27 -0
  16. investing_algorithm_framework/app/reporting/ascii.py +921 -0
  17. investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
  18. investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
  19. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  20. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  21. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
  22. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  23. investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
  24. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  25. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
  26. investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
  27. investing_algorithm_framework/app/reporting/generate.py +185 -0
  28. investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
  29. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
  30. investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
  31. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
  32. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
  33. investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
  34. investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
  35. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
  36. investing_algorithm_framework/app/stateless/__init__.py +35 -0
  37. investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
  38. investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
  39. investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
  40. investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
  41. investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
  42. investing_algorithm_framework/app/strategy.py +675 -0
  43. investing_algorithm_framework/app/task.py +41 -0
  44. investing_algorithm_framework/app/web/__init__.py +5 -0
  45. investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
  46. investing_algorithm_framework/app/web/controllers/orders.py +20 -0
  47. investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
  48. investing_algorithm_framework/app/web/controllers/positions.py +18 -0
  49. investing_algorithm_framework/app/web/create_app.py +20 -0
  50. investing_algorithm_framework/app/web/error_handler.py +59 -0
  51. investing_algorithm_framework/app/web/responses.py +20 -0
  52. investing_algorithm_framework/app/web/run_strategies.py +4 -0
  53. investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
  54. investing_algorithm_framework/app/web/schemas/order.py +12 -0
  55. investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
  56. investing_algorithm_framework/app/web/schemas/position.py +15 -0
  57. investing_algorithm_framework/app/web/setup_cors.py +6 -0
  58. investing_algorithm_framework/cli/__init__.py +0 -0
  59. investing_algorithm_framework/cli/cli.py +207 -0
  60. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -0
  61. investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
  62. investing_algorithm_framework/cli/initialize_app.py +603 -0
  63. investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
  64. investing_algorithm_framework/cli/templates/app.py.template +18 -0
  65. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
  66. investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
  67. investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
  68. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  69. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  70. investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
  71. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
  72. investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
  73. investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
  74. investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
  75. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
  76. investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
  77. investing_algorithm_framework/cli/templates/env.example.template +2 -0
  78. investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
  79. investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
  80. investing_algorithm_framework/cli/templates/readme.md.template +135 -0
  81. investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
  82. investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
  83. investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
  84. investing_algorithm_framework/create_app.py +54 -0
  85. investing_algorithm_framework/dependency_container.py +155 -0
  86. investing_algorithm_framework/domain/__init__.py +148 -0
  87. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  88. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  89. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  90. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  91. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  92. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  93. investing_algorithm_framework/domain/backtesting/backtest_run.py +435 -0
  94. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  95. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  96. investing_algorithm_framework/domain/config.py +111 -0
  97. investing_algorithm_framework/domain/constants.py +83 -0
  98. investing_algorithm_framework/domain/data_provider.py +334 -0
  99. investing_algorithm_framework/domain/data_structures.py +42 -0
  100. investing_algorithm_framework/domain/decimal_parsing.py +40 -0
  101. investing_algorithm_framework/domain/exceptions.py +112 -0
  102. investing_algorithm_framework/domain/models/__init__.py +43 -0
  103. investing_algorithm_framework/domain/models/app_mode.py +34 -0
  104. investing_algorithm_framework/domain/models/base_model.py +25 -0
  105. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  106. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  107. investing_algorithm_framework/domain/models/data/data_type.py +46 -0
  108. investing_algorithm_framework/domain/models/event.py +35 -0
  109. investing_algorithm_framework/domain/models/market/__init__.py +5 -0
  110. investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
  111. investing_algorithm_framework/domain/models/order/__init__.py +6 -0
  112. investing_algorithm_framework/domain/models/order/order.py +384 -0
  113. investing_algorithm_framework/domain/models/order/order_side.py +36 -0
  114. investing_algorithm_framework/domain/models/order/order_status.py +37 -0
  115. investing_algorithm_framework/domain/models/order/order_type.py +30 -0
  116. investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
  117. investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
  118. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
  119. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
  120. investing_algorithm_framework/domain/models/position/__init__.py +4 -0
  121. investing_algorithm_framework/domain/models/position/position.py +68 -0
  122. investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
  123. investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
  124. investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
  125. investing_algorithm_framework/domain/models/time_frame.py +153 -0
  126. investing_algorithm_framework/domain/models/time_interval.py +124 -0
  127. investing_algorithm_framework/domain/models/time_unit.py +149 -0
  128. investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
  129. investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
  130. investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
  131. investing_algorithm_framework/domain/models/trade/trade.py +388 -0
  132. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
  133. investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
  134. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
  135. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
  136. investing_algorithm_framework/domain/order_executor.py +112 -0
  137. investing_algorithm_framework/domain/portfolio_provider.py +118 -0
  138. investing_algorithm_framework/domain/positions/__init__.py +4 -0
  139. investing_algorithm_framework/domain/positions/position_size.py +41 -0
  140. investing_algorithm_framework/domain/services/__init__.py +11 -0
  141. investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
  142. investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
  143. investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
  144. investing_algorithm_framework/domain/services/rounding_service.py +27 -0
  145. investing_algorithm_framework/domain/services/state_handler.py +38 -0
  146. investing_algorithm_framework/domain/stateless_actions.py +7 -0
  147. investing_algorithm_framework/domain/strategy.py +44 -0
  148. investing_algorithm_framework/domain/utils/__init__.py +27 -0
  149. investing_algorithm_framework/domain/utils/csv.py +104 -0
  150. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  151. investing_algorithm_framework/domain/utils/dates.py +57 -0
  152. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  153. investing_algorithm_framework/domain/utils/polars.py +53 -0
  154. investing_algorithm_framework/domain/utils/random.py +41 -0
  155. investing_algorithm_framework/domain/utils/signatures.py +17 -0
  156. investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
  157. investing_algorithm_framework/domain/utils/synchronized.py +12 -0
  158. investing_algorithm_framework/download_data.py +108 -0
  159. investing_algorithm_framework/infrastructure/__init__.py +50 -0
  160. investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
  161. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
  162. investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
  163. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  164. investing_algorithm_framework/infrastructure/database/__init__.py +10 -0
  165. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
  166. investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
  167. investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
  168. investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
  169. investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
  170. investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
  171. investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
  172. investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
  173. investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +4 -0
  174. investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
  175. investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
  176. investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
  177. investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
  178. investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
  179. investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
  180. investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
  181. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +40 -0
  182. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -0
  183. investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
  184. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  185. investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
  186. investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
  187. investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
  188. investing_algorithm_framework/infrastructure/repositories/__init__.py +21 -0
  189. investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
  190. investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
  191. investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
  192. investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
  193. investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
  194. investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
  195. investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
  196. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
  197. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
  198. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
  199. investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
  200. investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
  201. investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
  202. investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
  203. investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
  204. investing_algorithm_framework/services/__init__.py +132 -0
  205. investing_algorithm_framework/services/backtesting/__init__.py +5 -0
  206. investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
  207. investing_algorithm_framework/services/configuration_service.py +96 -0
  208. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  209. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  210. investing_algorithm_framework/services/market_credential_service.py +40 -0
  211. investing_algorithm_framework/services/metrics/__init__.py +114 -0
  212. investing_algorithm_framework/services/metrics/alpha.py +0 -0
  213. investing_algorithm_framework/services/metrics/beta.py +0 -0
  214. investing_algorithm_framework/services/metrics/cagr.py +60 -0
  215. investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
  216. investing_algorithm_framework/services/metrics/drawdown.py +181 -0
  217. investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
  218. investing_algorithm_framework/services/metrics/exposure.py +210 -0
  219. investing_algorithm_framework/services/metrics/generate.py +358 -0
  220. investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
  221. investing_algorithm_framework/services/metrics/price_efficiency.py +57 -0
  222. investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
  223. investing_algorithm_framework/services/metrics/recovery.py +113 -0
  224. investing_algorithm_framework/services/metrics/returns.py +452 -0
  225. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  226. investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
  227. investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
  228. investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
  229. investing_algorithm_framework/services/metrics/trades.py +500 -0
  230. investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
  231. investing_algorithm_framework/services/metrics/ulcer.py +0 -0
  232. investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
  233. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  234. investing_algorithm_framework/services/metrics/win_rate.py +177 -0
  235. investing_algorithm_framework/services/order_service/__init__.py +9 -0
  236. investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
  237. investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
  238. investing_algorithm_framework/services/order_service/order_service.py +826 -0
  239. investing_algorithm_framework/services/portfolios/__init__.py +16 -0
  240. investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
  241. investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
  242. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
  243. investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
  244. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
  245. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
  246. investing_algorithm_framework/services/positions/__init__.py +7 -0
  247. investing_algorithm_framework/services/positions/position_service.py +210 -0
  248. investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
  249. investing_algorithm_framework/services/repository_service.py +40 -0
  250. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  251. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
  252. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
  253. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
  254. investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  255. investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
  256. investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
  257. investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
  258. investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
  259. investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
  260. investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,1667 @@
1
+ import logging
2
+ from datetime import datetime, timezone
3
+ from typing import List
4
+
5
+ from investing_algorithm_framework.services import ConfigurationService, \
6
+ MarketCredentialService, OrderService, PortfolioConfigurationService, \
7
+ PortfolioService, PositionService, TradeService, DataProviderService
8
+ from investing_algorithm_framework.domain import OrderStatus, OrderType, \
9
+ OrderSide, OperationalException, Portfolio, RoundingService, \
10
+ BACKTESTING_FLAG, INDEX_DATETIME, TradeRiskType, Order, \
11
+ Position, Trade, TradeStatus, MarketCredential
12
+
13
+ logger = logging.getLogger("investing_algorithm_framework")
14
+
15
+
16
+ class Context:
17
+ """
18
+ Context class to store the state of the algorithm and
19
+ give access to objects such as orders, positions, trades and
20
+ portfolio.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ configuration_service: ConfigurationService,
26
+ portfolio_configuration_service: PortfolioConfigurationService,
27
+ portfolio_service: PortfolioService,
28
+ position_service: PositionService,
29
+ order_service: OrderService,
30
+ market_credential_service: MarketCredentialService,
31
+ trade_service: TradeService,
32
+ data_provider_service: DataProviderService
33
+ ):
34
+ self.configuration_service: ConfigurationService = \
35
+ configuration_service
36
+ self.portfolio_configuration_service: PortfolioConfigurationService = \
37
+ portfolio_configuration_service
38
+ self.portfolio_service: PortfolioService = portfolio_service
39
+ self.position_service: PositionService = position_service
40
+ self.order_service: OrderService = order_service
41
+ self.market_credential_service: MarketCredentialService = \
42
+ market_credential_service
43
+ self.data_provider_service: DataProviderService = data_provider_service
44
+ self.trade_service: TradeService = trade_service
45
+
46
+ @property
47
+ def config(self):
48
+ """
49
+ Function to get a config instance. This allows users when
50
+ having access to the algorithm instance also to read the
51
+ configs of the app.
52
+ """
53
+ return self.configuration_service.get_config()
54
+
55
+ def get_config(self):
56
+ """
57
+ Function to get a config instance. This allows users when
58
+ having access to the algorithm instance also to read the
59
+ configs of the app.
60
+ """
61
+ return self.configuration_service.get_config()
62
+
63
+ def create_order(
64
+ self,
65
+ target_symbol,
66
+ price,
67
+ order_type,
68
+ order_side,
69
+ amount,
70
+ market=None,
71
+ execute=True,
72
+ validate=True,
73
+ sync=True
74
+ ) -> Order:
75
+ """
76
+ Function to create an order. This function will create an order
77
+ and execute it if the execute parameter is set to True. If the
78
+ validate parameter is set to True, the order will be validated
79
+
80
+ Args:
81
+ target_symbol: The symbol of the asset to trade
82
+ price: The price of the asset
83
+ order_type: The type of the order
84
+ order_side: The side of the order
85
+ amount: The amount of the asset to trade
86
+ market: The market to trade the asset
87
+ execute: If set to True, the order will be executed
88
+ validate: If set to True, the order will be validated
89
+ sync: If set to True, the created order will be synced
90
+ with the portfolio of the algorithm.
91
+
92
+ Returns:
93
+ The order created
94
+ """
95
+ portfolio = self.portfolio_service.find({"market": market})
96
+ order_data = {
97
+ "target_symbol": target_symbol,
98
+ "price": price,
99
+ "amount": amount,
100
+ "order_type": order_type,
101
+ "order_side": order_side,
102
+ "portfolio_id": portfolio.id,
103
+ "status": OrderStatus.CREATED.value,
104
+ "trading_symbol": portfolio.trading_symbol,
105
+ }
106
+
107
+ if BACKTESTING_FLAG in self.configuration_service.config \
108
+ and self.configuration_service.config[BACKTESTING_FLAG]:
109
+ order_data["created_at"] = \
110
+ self.configuration_service.config[INDEX_DATETIME]
111
+
112
+ return self.order_service.create(
113
+ order_data, execute=execute, validate=validate, sync=sync
114
+ )
115
+
116
+ def has_balance(self, symbol, amount, market=None):
117
+ """
118
+ Function to check if the portfolio has enough balance to
119
+ create an order. This function will return True if the
120
+ portfolio has enough balance to create an order, False
121
+ otherwise.
122
+
123
+ Parameters:
124
+ symbol: The symbol of the asset
125
+ amount: The amount of the asset
126
+ market: The market of the asset
127
+
128
+ Returns:
129
+ Boolean: True if the portfolio has enough balance
130
+ """
131
+
132
+ portfolio = self.portfolio_service.find({"market": market})
133
+ position = self.position_service.find(
134
+ {"portfolio": portfolio.id, "symbol": symbol}
135
+ )
136
+
137
+ if position is None:
138
+ return False
139
+
140
+ return position.get_amount() >= amount
141
+
142
+ def create_limit_order(
143
+ self,
144
+ target_symbol,
145
+ price,
146
+ order_side,
147
+ amount=None,
148
+ amount_trading_symbol=None,
149
+ percentage=None,
150
+ percentage_of_portfolio=None,
151
+ percentage_of_position=None,
152
+ precision=None,
153
+ market=None,
154
+ execute=True,
155
+ validate=True,
156
+ sync=True
157
+ ) -> Order:
158
+ """
159
+ Function to create a limit order. This function will create a limit
160
+ order and execute it if the execute parameter is set to True. If the
161
+ validate parameter is set to True, the order will be validated
162
+
163
+ Args:
164
+ target_symbol: The symbol of the asset to trade
165
+ price: The price of the asset
166
+ order_side: The side of the order
167
+ amount (optional): The amount of the asset to trade
168
+ amount_trading_symbol (optional): The amount of the
169
+ trading symbol to trade
170
+ percentage (optional): The percentage of the portfolio
171
+ to allocate to the
172
+ order
173
+ percentage_of_portfolio (optional): The percentage
174
+ of the portfolio to allocate to the order
175
+ percentage_of_position (optional): The percentage
176
+ of the position to allocate to
177
+ the order. (Only supported for SELL orders)
178
+ precision (optional): The precision of the amount
179
+ market (optional): The market to trade the asset
180
+ execute (optional): Default True. If set to True,
181
+ the order will be executed
182
+ validate (optional): Default True. If set to
183
+ True, the order will be validated
184
+ sync (optional): Default True. If set to True,
185
+ the created order will be synced with the
186
+ portfolio of the algorithm
187
+
188
+ Returns:
189
+ Order: Instance of the order created
190
+ """
191
+ portfolio = self.portfolio_service.find({"market": market})
192
+
193
+ if percentage_of_portfolio is not None:
194
+ if not OrderSide.BUY.equals(order_side):
195
+ raise OperationalException(
196
+ "Percentage of portfolio is only supported for BUY orders."
197
+ )
198
+
199
+ net_size = portfolio.get_net_size()
200
+ size = net_size * (percentage_of_portfolio / 100)
201
+ amount = size / price
202
+
203
+ elif percentage_of_position is not None:
204
+
205
+ if not OrderSide.SELL.equals(order_side):
206
+ raise OperationalException(
207
+ "Percentage of position is only supported for SELL orders."
208
+ )
209
+
210
+ position = self.position_service.find(
211
+ {
212
+ "symbol": target_symbol,
213
+ "portfolio": portfolio.id
214
+ }
215
+ )
216
+ amount = position.get_amount() * (percentage_of_position / 100)
217
+
218
+ elif percentage is not None:
219
+ net_size = portfolio.get_net_size()
220
+ size = net_size * (percentage / 100)
221
+ amount = size / price
222
+
223
+ if precision is not None:
224
+ amount = RoundingService.round_down(amount, precision)
225
+
226
+ if amount_trading_symbol is not None:
227
+ amount = amount_trading_symbol / price
228
+
229
+ if amount is None:
230
+ raise OperationalException(
231
+ "The amount parameter is required to create a limit order." +
232
+ "Either the amount, amount_trading_symbol, percentage, " +
233
+ "percentage_of_portfolio or percentage_of_position "
234
+ "parameter must be specified."
235
+ )
236
+
237
+ logger.info(
238
+ f"Creating limit order: {target_symbol} "
239
+ f"{order_side} {amount} @ {price}"
240
+ )
241
+ order_data = {
242
+ "target_symbol": target_symbol,
243
+ "price": price,
244
+ "amount": amount,
245
+ "order_type": OrderType.LIMIT.value,
246
+ "order_side": OrderSide.from_value(order_side).value,
247
+ "portfolio_id": portfolio.id,
248
+ "status": OrderStatus.CREATED.value,
249
+ "trading_symbol": portfolio.trading_symbol,
250
+ }
251
+
252
+ if BACKTESTING_FLAG in self.configuration_service.config \
253
+ and self.configuration_service.config[BACKTESTING_FLAG]:
254
+ order_data["created_at"] = \
255
+ self.configuration_service.config[INDEX_DATETIME]
256
+
257
+ return self.order_service.create(
258
+ order_data, execute=execute, validate=validate, sync=sync
259
+ )
260
+
261
+ def create_limit_sell_order(
262
+ self,
263
+ target_symbol,
264
+ price,
265
+ amount=None,
266
+ percentage_of_position=None,
267
+ market=None,
268
+ portfolio_id=None
269
+ ) -> Order:
270
+ """
271
+ Function to create a limit sell order. This function will create
272
+ a limit sell order. If the amount parameter is specified, the
273
+ order will be created with the specified amount. If the
274
+ percentage_of_position parameter is specified, the order will be
275
+ created with the percentage of the position specified. If neither
276
+ the amount nor the percentage_of_position parameter is specified,
277
+ an OperationalException will be raised.
278
+
279
+ Args:
280
+ target_symbol (str): The symbol of the asset to sell
281
+ price (float): The price at which to sell the asset
282
+ amount (float, optional): The amount of the asset to sell
283
+ percentage_of_position (float, optional): The percentage of the
284
+ position to sell.
285
+ market (str, optional): the portfolio corresponding to the market
286
+ to sell the asset
287
+ portfolio_id: (str, optional): The ID of the portfolio to sell
288
+ the asset from.
289
+
290
+ Returns:
291
+ Order: The order created
292
+ """
293
+ if amount is None and percentage_of_position is None:
294
+ raise OperationalException(
295
+ "Either amount or percentage_of_position must be specified "
296
+ "to create a limit sell order."
297
+ )
298
+
299
+ if portfolio_id is not None:
300
+ portfolio = self.portfolio_service.get(portfolio_id)
301
+ elif market is not None:
302
+ portfolio = self.portfolio_service.find({"market": market})
303
+ else:
304
+ portfolio = self.portfolio_service.get_all()[0]
305
+
306
+ if percentage_of_position is not None:
307
+ position = self.position_service.find(
308
+ {
309
+ "symbol": target_symbol,
310
+ "portfolio": portfolio.id
311
+ }
312
+ )
313
+ amount = position.get_amount() * (percentage_of_position / 100)
314
+
315
+ logger.info(
316
+ f"Creating limit order: {target_symbol} "
317
+ f"SELL {amount} @ {price}"
318
+ )
319
+ order_data = {
320
+ "target_symbol": target_symbol,
321
+ "price": price,
322
+ "amount": amount,
323
+ "order_type": OrderType.LIMIT.value,
324
+ "order_side": OrderSide.SELL.value,
325
+ "portfolio_id": portfolio.id,
326
+ "status": OrderStatus.CREATED.value,
327
+ "trading_symbol": portfolio.trading_symbol,
328
+ }
329
+
330
+ if BACKTESTING_FLAG in self.configuration_service.config \
331
+ and self.configuration_service.config[BACKTESTING_FLAG]:
332
+ order_data["created_at"] = \
333
+ self.configuration_service.config[INDEX_DATETIME]
334
+
335
+ return self.order_service.create(order_data)
336
+
337
+ def create_limit_buy_order(
338
+ self,
339
+ target_symbol,
340
+ price,
341
+ amount=None,
342
+ percentage_of_portfolio=None,
343
+ market=None,
344
+ portfolio_id=None
345
+ ) -> Order:
346
+ """
347
+ Function to create a limit buy order. This function will create
348
+ a limit buy order. If the amount parameter is specified, the
349
+ order will be created with the specified amount. If the
350
+ percentage_of_portfolio parameter is specified, the order will be
351
+ created with the percentage of the portfolio specified. If neither
352
+ the amount nor the percentage_of_portfolio parameter is specified,
353
+ an OperationalException will be raised.
354
+
355
+ Args:
356
+ target_symbol (str): The symbol of the asset to buy
357
+ price (float): The price at which to buy the asset
358
+ amount (float, optional): The amount of the asset to buy
359
+ percentage_of_portfolio (float, optional): The percentage of the
360
+ portfolio to buy.
361
+ market (str, optional): the portfolio corresponding to the market
362
+ to buy the asset
363
+ portfolio_id (str, optional): The ID of the portfolio to buy
364
+ the asset from.
365
+
366
+ Returns:
367
+ Order: The order created
368
+ """
369
+
370
+ if amount is None and percentage_of_portfolio is None:
371
+ raise OperationalException(
372
+ "Either amount or percentage_of_portfolio must be specified "
373
+ "to create a limit buy order."
374
+ )
375
+
376
+ if portfolio_id is not None:
377
+ portfolio = self.portfolio_service.get(portfolio_id)
378
+ elif market is not None:
379
+ portfolio = self.portfolio_service.find({"market": market})
380
+ else:
381
+ portfolio = self.portfolio_service.get_all()[0]
382
+
383
+ if percentage_of_portfolio is not None:
384
+ net_size = portfolio.get_net_size()
385
+ size = net_size * (percentage_of_portfolio / 100)
386
+ amount = size / price
387
+ logger.info(
388
+ f"Creating limit order: {target_symbol} "
389
+ f"BUY {amount} @ {price}"
390
+ )
391
+ order_data = {
392
+ "target_symbol": target_symbol,
393
+ "price": price,
394
+ "amount": amount,
395
+ "order_type": OrderType.LIMIT.value,
396
+ "order_side": OrderSide.BUY.value,
397
+ "portfolio_id": portfolio.id,
398
+ "status": OrderStatus.CREATED.value,
399
+ "trading_symbol": portfolio.trading_symbol,
400
+ }
401
+ if BACKTESTING_FLAG in self.configuration_service.config \
402
+ and self.configuration_service.config[BACKTESTING_FLAG]:
403
+ order_data["created_at"] = \
404
+ self.configuration_service.config[INDEX_DATETIME]
405
+ return self.order_service.create(
406
+ order_data, execute=True, validate=True, sync=True
407
+ )
408
+
409
+ def get_portfolio(self, market=None) -> Portfolio:
410
+ """
411
+ Function to get the portfolio of the algorithm. This function
412
+ will return the portfolio of the algorithm. If the market
413
+ parameter is specified, the portfolio of the specified market
414
+ will be returned.
415
+
416
+ Parameters:
417
+ market: The market of the portfolio
418
+
419
+ Returns:
420
+ Portfolio: The portfolio of the algorithm
421
+ """
422
+
423
+ if market is None:
424
+ portfolio = self.portfolio_service.get_all()[0]
425
+ else:
426
+ portfolio = self.portfolio_service.find({"market": market})
427
+
428
+ # Retrieve positions
429
+ positions = self.position_service.get_all(
430
+ {"portfolio": portfolio.id}
431
+ )
432
+
433
+ if BACKTESTING_FLAG in self.configuration_service.config \
434
+ and self.configuration_service.config[BACKTESTING_FLAG]:
435
+ date = self.configuration_service.config[INDEX_DATETIME]
436
+ else:
437
+ date = datetime.now(tz=timezone.utc)
438
+
439
+ allocated = 0.0
440
+
441
+ for position in positions:
442
+
443
+ if position.symbol != portfolio.trading_symbol:
444
+ ticker = self.data_provider_service.get_ticker_data(
445
+ symbol=f"{position.symbol}/{portfolio.trading_symbol}",
446
+ market=portfolio.market,
447
+ date=date
448
+ )
449
+ if ticker is not None and "bid" in ticker:
450
+ allocated += position.get_amount() * ticker["bid"]
451
+
452
+ portfolio.allocated = allocated
453
+ return portfolio
454
+
455
+ def get_latest_price(self, symbol, market=None):
456
+
457
+ if BACKTESTING_FLAG in self.configuration_service.config \
458
+ and self.configuration_service.config[BACKTESTING_FLAG]:
459
+ date = self.configuration_service.config[INDEX_DATETIME]
460
+ else:
461
+ date = datetime.now(tz=timezone.utc)
462
+
463
+ ticker = self.data_provider_service.get_ticker_data(
464
+ symbol=symbol,
465
+ market=market,
466
+ date=date
467
+ )
468
+
469
+ return ticker['bid'] if ticker and 'bid' in ticker else None
470
+
471
+ def get_portfolios(self):
472
+ """
473
+ Function to get all portfolios of the algorithm. This function
474
+ will return all portfolios of the algorithm.
475
+
476
+ Returns:
477
+ List[Portfolio]: A list of all portfolios of the algorithm
478
+ """
479
+ return self.portfolio_service.get_all()
480
+
481
+ def get_unallocated(self, market=None) -> float:
482
+ """
483
+ Function to get the unallocated balance of the portfolio. This
484
+ function will return the unallocated balance of the portfolio.
485
+ If the market parameter is specified, the unallocated balance
486
+ of the specified market will be returned.
487
+
488
+ Args:
489
+ market: The market of the portfolio
490
+
491
+ Returns:
492
+ float: The unallocated balance of the portfolio
493
+ """
494
+
495
+ if market:
496
+ portfolio = self.portfolio_service.find({{"market": market}})
497
+ else:
498
+ portfolio = self.portfolio_service.get_all()[0]
499
+
500
+ trading_symbol = portfolio.trading_symbol
501
+ return self.position_service.find(
502
+ {"portfolio": portfolio.id, "symbol": trading_symbol}
503
+ ).get_amount()
504
+
505
+ def get_total_size(self):
506
+ """
507
+ Returns the total size of the portfolio.
508
+
509
+ The total size of the portfolio is the unallocated balance and the
510
+ allocated balance of the portfolio.
511
+
512
+ Returns:
513
+ float: The total size of the portfolio
514
+ """
515
+ return self.get_unallocated() + self.get_allocated()
516
+
517
+ def get_order(
518
+ self,
519
+ reference_id=None,
520
+ market=None,
521
+ target_symbol=None,
522
+ trading_symbol=None,
523
+ order_side=None,
524
+ order_type=None
525
+ ) -> Order:
526
+ """
527
+ Function to retrieve an order.
528
+
529
+ Exception is thrown when no param has been provided.
530
+
531
+ Args:
532
+ reference_id [optional] (int): id given by the external
533
+ market or exchange.
534
+ market [optional] (str): the market that the order was
535
+ executed on.
536
+ target_symbol [optional] (str): the symbol of the asset
537
+ that the order was executed
538
+ """
539
+ query_params = {}
540
+
541
+ if reference_id:
542
+ query_params["reference_id"] = reference_id
543
+
544
+ if target_symbol:
545
+ query_params["target_symbol"] = target_symbol
546
+
547
+ if trading_symbol:
548
+ query_params["trading_symbol"] = trading_symbol
549
+
550
+ if order_side:
551
+ query_params["order_side"] = order_side
552
+
553
+ if order_type:
554
+ query_params["order_type"] = order_type
555
+
556
+ if market:
557
+ portfolio = self.portfolio_service.find({"market": market})
558
+ positions = self.position_service.get_all(
559
+ {"portfolio": portfolio.id}
560
+ )
561
+ query_params["position"] = [position.id for position in positions]
562
+
563
+ if not query_params:
564
+ raise OperationalException(
565
+ "No parameters provided to get order."
566
+ )
567
+
568
+ return self.order_service.find(query_params)
569
+
570
+ def get_orders(
571
+ self,
572
+ target_symbol=None,
573
+ status=None,
574
+ order_type=None,
575
+ order_side=None,
576
+ market=None
577
+ ) -> List[Order]:
578
+
579
+ if market is None:
580
+ portfolio = self.portfolio_service.get_all()[0]
581
+ else:
582
+ portfolio = self.portfolio_service.find({"market": market})
583
+
584
+ positions = self.position_service.get_all({"portfolio": portfolio.id})
585
+ return self.order_service.get_all(
586
+ {
587
+ "position": [position.id for position in positions],
588
+ "target_symbol": target_symbol,
589
+ "status": status,
590
+ "order_type": order_type,
591
+ "order_side": order_side
592
+ }
593
+ )
594
+
595
+ def get_positions(
596
+ self,
597
+ market=None,
598
+ identifier=None,
599
+ amount_gt=None,
600
+ amount_gte=None,
601
+ amount_lt=None,
602
+ amount_lte=None
603
+ ) -> List[Position]:
604
+ """
605
+ Function to get all positions. This function will return all
606
+ positions that match the specified query parameters. If the
607
+ market parameter is specified, the positions of the specified
608
+ market will be returned. If the identifier parameter is
609
+ specified, the positions of the specified portfolio will be
610
+ returned. If the amount_gt parameter is specified, the positions
611
+ with an amount greater than the specified amount will be returned.
612
+ If the amount_gte parameter is specified, the positions with an
613
+ amount greater than or equal to the specified amount will be
614
+ returned. If the amount_lt parameter is specified, the positions
615
+ with an amount less than the specified amount will be returned.
616
+ If the amount_lte parameter is specified, the positions with an
617
+ amount less than or equal to the specified amount will be returned.
618
+
619
+ Parameters:
620
+ market: The market of the portfolio where the positions are
621
+ identifier: The identifier of the portfolio
622
+ amount_gt: The amount of the asset must be greater than this
623
+ amount_gte: The amount of the asset must be greater than or
624
+ equal to this
625
+ amount_lt: The amount of the asset must be less than this
626
+ amount_lte: The amount of the asset must be less than or equal
627
+ to this
628
+
629
+ Returns:
630
+ List[Position]: A list of positions that match the query parameters
631
+ """
632
+ query_params = {}
633
+
634
+ if market is not None:
635
+ query_params["market"] = market
636
+
637
+ if identifier is not None:
638
+ query_params["identifier"] = identifier
639
+
640
+ if amount_gt is not None:
641
+ query_params["amount_gt"] = amount_gt
642
+
643
+ if amount_gte is not None:
644
+ query_params["amount_gte"] = amount_gte
645
+
646
+ if amount_lt is not None:
647
+ query_params["amount_lt"] = amount_lt
648
+
649
+ if amount_lte is not None:
650
+ query_params["amount_lte"] = amount_lte
651
+
652
+ portfolios = self.portfolio_service.get_all(query_params)
653
+
654
+ if not portfolios:
655
+ raise OperationalException("No portfolio found.")
656
+
657
+ portfolio = portfolios[0]
658
+ return self.position_service.get_all(
659
+ {"portfolio": portfolio.id}
660
+ )
661
+
662
+ def get_position(self, symbol, market=None, identifier=None) -> Position:
663
+ """
664
+ Function to get a position. This function will return the
665
+ position that matches the specified query parameters. If the
666
+ market parameter is specified, the position of the specified
667
+ market will be returned. If the identifier parameter is
668
+ specified, the position of the specified portfolio will be
669
+ returned.
670
+
671
+ Parameters:
672
+ symbol: The symbol of the asset that represents the position
673
+ market: The market of the portfolio where the position is located
674
+ identifier: The identifier of the portfolio
675
+
676
+ Returns:
677
+ Position: The position that matches the query parameters
678
+ """
679
+
680
+ query_params = {}
681
+
682
+ if market is not None:
683
+ query_params["market"] = market
684
+
685
+ if identifier is not None:
686
+ query_params["identifier"] = identifier
687
+
688
+ portfolios = self.portfolio_service.get_all(query_params)
689
+
690
+ if not portfolios:
691
+ raise OperationalException("No portfolio found.")
692
+
693
+ portfolio = portfolios[0]
694
+
695
+ try:
696
+ return self.position_service.find(
697
+ {"portfolio": portfolio.id, "symbol": symbol}
698
+ )
699
+ except OperationalException:
700
+ return None
701
+
702
+ def has_position(
703
+ self,
704
+ symbol,
705
+ market=None,
706
+ identifier=None,
707
+ amount_gt=0,
708
+ amount_gte=None,
709
+ amount_lt=None,
710
+ amount_lte=None
711
+ ):
712
+ """
713
+ Function to check if a position exists. This function will return
714
+ True if a position exists, False otherwise. This function will check
715
+ if the amount > 0 condition by default.
716
+
717
+ Parameters:
718
+ param symbol: The symbol of the asset
719
+ param market: The market of the asset
720
+ param identifier: The identifier of the portfolio
721
+ param amount_gt: The amount of the asset must be greater than this
722
+ param amount_gte: The amount of the asset must be greater than
723
+ or equal to this
724
+ param amount_lt: The amount of the asset must be less than this
725
+ param amount_lte: The amount of the asset must be less than
726
+ or equal to this
727
+
728
+ Returns:
729
+ Boolean: True if a position exists, False otherwise
730
+ """
731
+
732
+ return self.position_exists(
733
+ symbol=symbol,
734
+ market=market,
735
+ identifier=identifier,
736
+ amount_gt=amount_gt,
737
+ amount_gte=amount_gte,
738
+ amount_lt=amount_lt,
739
+ amount_lte=amount_lte
740
+ )
741
+
742
+ def position_exists(
743
+ self,
744
+ symbol,
745
+ market=None,
746
+ identifier=None,
747
+ amount_gt=None,
748
+ amount_gte=None,
749
+ amount_lt=None,
750
+ amount_lte=None
751
+ ) -> bool:
752
+ """
753
+ Function to check if a position exists. This function will return
754
+ True if a position exists, False otherwise. This function will
755
+ not check the amount > 0 condition by default. If you want to
756
+ check if a position exists with an amount greater than 0, you
757
+ can use the amount_gt parameter. If you want to check if a
758
+ position exists with an amount greater than or equal to a
759
+ certain amount, you can use the amount_gte parameter. If you
760
+ want to check if a position exists with an amount less than a
761
+ certain amount, you can use the amount_lt parameter. If you want
762
+ to check if a position exists with an amount less than or equal
763
+ to a certain amount, you can use the amount_lte parameter.
764
+
765
+ It is not recommended to use this method directly because it can
766
+ have adverse effects on the algorithm. It is recommended to use
767
+ the has_position method instead.
768
+
769
+ param symbol: The symbol of the asset
770
+ param market: The market of the asset
771
+ param identifier: The identifier of the portfolio
772
+ param amount_gt: The amount of the asset must be greater than this
773
+ param amount_gte: The amount of the asset must be greater than
774
+ or equal to this
775
+ param amount_lt: The amount of the asset must be less than this
776
+ param amount_lte: The amount of the asset must be less than
777
+ or equal to this
778
+
779
+ return: True if a position exists, False otherwise
780
+ """
781
+ query_params = {}
782
+
783
+ if market is not None:
784
+ query_params["market"] = market
785
+
786
+ if identifier is not None:
787
+ query_params["identifier"] = identifier
788
+
789
+ if amount_gt is not None:
790
+ query_params["amount_gt"] = amount_gt
791
+
792
+ if amount_gte is not None:
793
+ query_params["amount_gte"] = amount_gte
794
+
795
+ if amount_lt is not None:
796
+ query_params["amount_lt"] = amount_lt
797
+
798
+ if amount_lte is not None:
799
+ query_params["amount_lte"] = amount_lte
800
+
801
+ query_params["symbol"] = symbol
802
+ return self.position_service.exists(query_params)
803
+
804
+ def get_position_percentage_of_portfolio_by_net_size(
805
+ self, symbol, market=None, identifier=None
806
+ ) -> float:
807
+ """
808
+ Returns the percentage of the portfolio that is allocated to a
809
+ position. This is calculated by dividing the cost of the position
810
+ by the total net size of the portfolio.
811
+
812
+ The total net size of the portfolio is the initial balance of the
813
+ portfolio plus the all the net gains of your trades.
814
+ """
815
+ query_params = {}
816
+
817
+ if market is not None:
818
+ query_params["market"] = market
819
+
820
+ if identifier is not None:
821
+ query_params["identifier"] = identifier
822
+
823
+ portfolios = self.portfolio_service.get_all(query_params)
824
+
825
+ if not portfolios:
826
+ raise OperationalException("No portfolio found.")
827
+
828
+ portfolio = portfolios[0]
829
+ position = self.position_service.find(
830
+ {"portfolio": portfolio.id, "symbol": symbol}
831
+ )
832
+ net_size = portfolio.get_net_size()
833
+ return (position.cost / net_size) * 100
834
+
835
+ def close_position(
836
+ self,
837
+ position=None,
838
+ symbol=None,
839
+ portfolio=None,
840
+ precision=None,
841
+ price=None
842
+ ) -> Order:
843
+ """
844
+ Function to close a position. This function will close a position
845
+ by creating a market order to sell the position. If the precision
846
+ parameter is specified, the amount of the order will be rounded
847
+ down to the specified precision.
848
+
849
+ Args:
850
+ position (Optional): The position to close
851
+ symbol (Optional): The symbol of the asset
852
+ portfolio (Optional): The portfolio where the position is located
853
+ precision (Optional): The precision of the amount
854
+ price (Optional[Float]): The price with which the position needs
855
+ to be closed.
856
+
857
+ Returns:
858
+ Order: The order created to close the position
859
+ """
860
+ query_params = {}
861
+
862
+ if position is None and (symbol is None and portfolio is None):
863
+ raise OperationalException(
864
+ "Either position or symbol and portfolio parameters must "
865
+ "be specified to close a position."
866
+ )
867
+
868
+ if position is not None:
869
+ query_params["id"] = position.id
870
+ query_params["symbol"] = position.symbol
871
+
872
+ if symbol is not None:
873
+ query_params["symbol"] = symbol
874
+
875
+ if portfolio is not None:
876
+ query_params["portfolio"] = portfolio.id
877
+
878
+ position = self.position_service.find(query_params)
879
+ portfolio = self.portfolio_service.get(position.portfolio_id)
880
+
881
+ if position.get_amount() == 0:
882
+ logger.warning("Cannot close position. Amount is 0.")
883
+ return None
884
+
885
+ if position.get_symbol() == portfolio.get_trading_symbol():
886
+ raise OperationalException(
887
+ "Cannot close position. The position is the same as the "
888
+ "trading symbol of the portfolio."
889
+ )
890
+
891
+ for order in self.order_service \
892
+ .get_all(
893
+ {
894
+ "position": position.id,
895
+ "status": OrderStatus.OPEN.value
896
+ }
897
+ ):
898
+ self.order_service.cancel_order(order)
899
+
900
+ target_symbol = position.get_symbol()
901
+ symbol = f"{target_symbol.upper()}/{portfolio.trading_symbol.upper()}"
902
+
903
+ if price is None:
904
+ ticker = self.data_provider_service.get_ticker_data(
905
+ symbol=symbol,
906
+ market=portfolio.market,
907
+ date=self.config[INDEX_DATETIME]
908
+ )
909
+ price = ticker["bid"]
910
+
911
+ logger.info(
912
+ f"Closing position {position.symbol} "
913
+ f"with amount {position.get_amount()} "
914
+ f"at price {price}"
915
+ )
916
+ return self.create_limit_order(
917
+ target_symbol=position.symbol,
918
+ amount=position.get_amount(),
919
+ order_side=OrderSide.SELL.value,
920
+ price=price,
921
+ precision=precision,
922
+ )
923
+
924
+ def get_allocated(self, market=None, identifier=None) -> float:
925
+
926
+ if self.portfolio_configuration_service.count() > 1 \
927
+ and identifier is None and market is None:
928
+ raise OperationalException(
929
+ "Multiple portfolios found. Please specify a "
930
+ "portfolio identifier."
931
+ )
932
+
933
+ if market is not None and identifier is not None:
934
+ portfolio_configurations = self.portfolio_configuration_service \
935
+ .get_all()
936
+
937
+ else:
938
+ query_params = {"market": market, "identifier": identifier}
939
+ portfolio_configuration = self.portfolio_configuration_service \
940
+ .find(query_params)
941
+
942
+ if not portfolio_configuration:
943
+ raise OperationalException("No portfolio found.")
944
+
945
+ portfolio_configurations = [portfolio_configuration]
946
+
947
+ if len(portfolio_configurations) == 0:
948
+ raise OperationalException("No portfolio found.")
949
+
950
+ portfolios = []
951
+
952
+ for portfolio_configuration in portfolio_configurations:
953
+ portfolio = self.portfolio_service.find(
954
+ {"identifier": portfolio_configuration.identifier}
955
+ )
956
+ portfolio.configuration = portfolio_configuration
957
+ portfolios.append(portfolio)
958
+
959
+ allocated = 0
960
+
961
+ for portfolio in portfolios:
962
+ positions = self.position_service.get_all(
963
+ {"portfolio": portfolio.id}
964
+ )
965
+
966
+ for position in positions:
967
+ if portfolio.trading_symbol == position.symbol:
968
+ continue
969
+
970
+ symbol = f"{position.symbol.upper()}/" \
971
+ f"{portfolio.trading_symbol.upper()}"
972
+ current_date = self.config[INDEX_DATETIME]
973
+ ticker = self.data_provider_service.get_ticker_data(
974
+ symbol=symbol, market=portfolio.market, date=current_date
975
+ )
976
+ allocated = allocated + \
977
+ (position.get_amount() * ticker["bid"])
978
+
979
+ return allocated
980
+
981
+ def get_unfilled(self, market=None, identifier=None) -> float:
982
+
983
+ if self.portfolio_configuration_service.count() > 1 \
984
+ and identifier is None and market is None:
985
+ raise OperationalException(
986
+ "Multiple portfolios found. Please specify a "
987
+ "portfolio identifier."
988
+ )
989
+
990
+ if market is not None and identifier is not None:
991
+ portfolio_configurations = self.portfolio_configuration_service \
992
+ .get_all()
993
+
994
+ else:
995
+ query_params = {
996
+ "market": market,
997
+ "identifier": identifier
998
+ }
999
+ portfolio_configurations = [self.portfolio_configuration_service
1000
+ .find(query_params)]
1001
+
1002
+ portfolios = []
1003
+
1004
+ for portfolio_configuration in portfolio_configurations:
1005
+ portfolio = self.portfolio_service.find(
1006
+ {"identifier": portfolio_configuration.identifier}
1007
+ )
1008
+ portfolios.append(portfolio)
1009
+
1010
+ unfilled = 0
1011
+
1012
+ for portfolio in portfolios:
1013
+ orders = self.order_service.get_all(
1014
+ {"status": OrderStatus.OPEN.value, "portfolio": portfolio.id}
1015
+ )
1016
+ unfilled = unfilled + sum(
1017
+ [order.get_amount() * order.get_price() for order in orders]
1018
+ )
1019
+
1020
+ return unfilled
1021
+
1022
+ def get_portfolio_configurations(self):
1023
+ return self.portfolio_configuration_service.get_all()
1024
+
1025
+ def has_open_buy_orders(self, target_symbol, identifier=None, market=None):
1026
+ query_params = {}
1027
+
1028
+ if identifier is not None:
1029
+ portfolio = self.portfolio_service.find(
1030
+ {"identifier": identifier}
1031
+ )
1032
+ query_params["portfolio"] = portfolio.id
1033
+
1034
+ if market is not None:
1035
+ portfolio = self.portfolio_service.find(
1036
+ {"market": market}
1037
+ )
1038
+ query_params["portfolio"] = portfolio.id
1039
+
1040
+ query_params["target_symbol"] = target_symbol
1041
+ query_params["order_side"] = OrderSide.BUY.value
1042
+ query_params["status"] = OrderStatus.OPEN.value
1043
+ return self.order_service.exists(query_params)
1044
+
1045
+ def get_sell_orders(self, target_symbol, identifier=None, market=None):
1046
+ query_params = {}
1047
+
1048
+ if identifier is not None:
1049
+ portfolio = self.portfolio_service.find(
1050
+ {"identifier": identifier}
1051
+ )
1052
+ query_params["portfolio"] = portfolio.id
1053
+
1054
+ if market is not None:
1055
+ portfolio = self.portfolio_service.find(
1056
+ {"market": market}
1057
+ )
1058
+ query_params["portfolio"] = portfolio.id
1059
+
1060
+ query_params["target_symbol"] = target_symbol
1061
+ query_params["order_side"] = OrderSide.SELL.value
1062
+ return self.order_service.get_all(query_params)
1063
+
1064
+ def get_open_orders(
1065
+ self, target_symbol=None, identifier=None, market=None
1066
+ ) -> List[Order]:
1067
+ """
1068
+ Function to get all open orders. This function will return all
1069
+ open orders that match the specified query parameters.
1070
+
1071
+ Args:
1072
+ target_symbol (str): the symbol of the asset
1073
+ identifier (str): the identifier of the portfolio
1074
+ market (str): the market of the asset
1075
+
1076
+ Returns:
1077
+ List[Order]: A list of open orders that match the query parameters
1078
+ """
1079
+ query_params = {}
1080
+
1081
+ if identifier is not None:
1082
+ portfolio = self.portfolio_service.find(
1083
+ {"identifier": identifier}
1084
+ )
1085
+ query_params["portfolio"] = portfolio.id
1086
+
1087
+ if market is not None:
1088
+ portfolio = self.portfolio_service.find(
1089
+ {"market": market}
1090
+ )
1091
+ query_params["portfolio"] = portfolio.id
1092
+
1093
+ if target_symbol is not None:
1094
+ query_params["target_symbol"] = target_symbol
1095
+
1096
+ query_params["status"] = OrderStatus.OPEN.value
1097
+ return self.order_service.get_all(query_params)
1098
+
1099
+ def get_closed_orders(
1100
+ self, target_symbol=None, identifier=None, market=None, order_side=None
1101
+ ) -> List[Order]:
1102
+ """
1103
+ Function to get all closed orders. This function will return all
1104
+ closed orders that match the specified query parameters.
1105
+
1106
+ Args:
1107
+ target_symbol (str): the symbol of the asset
1108
+ identifier (str): the identifier of the portfolio
1109
+ market (str): the market of the asset
1110
+ order_side (str): the side of the order
1111
+
1112
+ Returns:
1113
+ List[Order]: A list of closed orders that
1114
+ match the query parameters
1115
+ """
1116
+ query_params = {}
1117
+
1118
+ if identifier is not None:
1119
+ portfolio = self.portfolio_service.find(
1120
+ {"identifier": identifier}
1121
+ )
1122
+ query_params["portfolio"] = portfolio.id
1123
+
1124
+ if order_side is not None:
1125
+ query_params["order_side"] = order_side
1126
+
1127
+ if market is not None:
1128
+ portfolio = self.portfolio_service.find(
1129
+ {"market": market}
1130
+ )
1131
+ query_params["portfolio"] = portfolio.id
1132
+
1133
+ if target_symbol is not None:
1134
+ query_params["target_symbol"] = target_symbol
1135
+
1136
+ query_params["status"] = OrderStatus.CLOSED.value
1137
+ return self.order_service.get_all(query_params)
1138
+
1139
+ def has_open_sell_orders(self, target_symbol, identifier=None,
1140
+ market=None):
1141
+ query_params = {}
1142
+
1143
+ if identifier is not None:
1144
+ portfolio = self.portfolio_service.find(
1145
+ {"identifier": identifier}
1146
+ )
1147
+ query_params["portfolio"] = portfolio.id
1148
+
1149
+ if market is not None:
1150
+ portfolio = self.portfolio_service.find(
1151
+ {"market": market}
1152
+ )
1153
+ query_params["portfolio"] = portfolio.id
1154
+
1155
+ query_params["target_symbol"] = target_symbol
1156
+ query_params["order_side"] = OrderSide.SELL.value
1157
+ query_params["status"] = OrderStatus.OPEN.value
1158
+ return self.order_service.exists(query_params)
1159
+
1160
+ def has_open_orders(
1161
+ self, target_symbol=None, identifier=None, market=None
1162
+ ):
1163
+ query_params = {}
1164
+
1165
+ if identifier is not None:
1166
+ portfolio = self.portfolio_service.find(
1167
+ {"identifier": identifier}
1168
+ )
1169
+ query_params["portfolio"] = portfolio.id
1170
+
1171
+ if market is not None:
1172
+ portfolio = self.portfolio_service.find(
1173
+ {"market": market}
1174
+ )
1175
+ query_params["portfolio"] = portfolio.id
1176
+
1177
+ if target_symbol is not None:
1178
+ query_params["target_symbol"] = target_symbol
1179
+
1180
+ query_params["status"] = OrderStatus.OPEN.value
1181
+ return self.order_service.exists(query_params)
1182
+
1183
+ def get_trade(
1184
+ self,
1185
+ target_symbol=None,
1186
+ trading_symbol=None,
1187
+ market=None,
1188
+ portfolio=None,
1189
+ status=None,
1190
+ order_id=None
1191
+ ) -> List[Trade]:
1192
+ """
1193
+ Function to get all trades. This function will return all trades
1194
+ that match the specified query parameters. If the market parameter
1195
+ is specified, the trades with the specified market will be returned.
1196
+
1197
+ Args:
1198
+ market: The market of the asset
1199
+ portfolio: The portfolio of the asset
1200
+ status: The status of the trade
1201
+ order_id: The order id of the trade
1202
+ target_symbol: The symbol of the asset
1203
+ trading_symbol: The trading symbol of the asset
1204
+
1205
+ Returns:
1206
+ List[Trade]: A list of trades that match the query parameters
1207
+ """
1208
+ query_params = {}
1209
+
1210
+ if market is not None:
1211
+ query_params["market"] = market
1212
+
1213
+ if portfolio is not None:
1214
+ query_params["portfolio"] = portfolio
1215
+
1216
+ if status is not None:
1217
+ query_params["status"] = status
1218
+
1219
+ if order_id is not None:
1220
+ query_params["order_id"] = order_id
1221
+
1222
+ if target_symbol is not None:
1223
+ query_params["target_symbol"] = target_symbol
1224
+
1225
+ if trading_symbol is not None:
1226
+ query_params["trading_symbol"] = trading_symbol
1227
+
1228
+ return self.trade_service.find(query_params)
1229
+
1230
+ def get_trades(
1231
+ self,
1232
+ target_symbol=None,
1233
+ trading_symbol=None,
1234
+ market=None,
1235
+ portfolio=None,
1236
+ status=None,
1237
+ ) -> List[Trade]:
1238
+ """
1239
+ Function to get all trades. This function will return all trades
1240
+ that match the specified query parameters. If the market parameter
1241
+ is specified, the trades with the specified market will be returned.
1242
+
1243
+ Args:
1244
+ market: The market of the asset
1245
+ portfolio: The portfolio of the asset
1246
+ status: The status of the trade
1247
+ target_symbol: The symbol of the asset
1248
+ trading_symbol: The trading symbol of the asset
1249
+
1250
+ Returns:
1251
+ List[Trade]: A list of trades that match the query parameters
1252
+ """
1253
+
1254
+ query_params = {}
1255
+
1256
+ if market is not None:
1257
+ query_params["market"] = market
1258
+
1259
+ if portfolio is not None:
1260
+ query_params["portfolio"] = portfolio
1261
+
1262
+ if status is not None:
1263
+ query_params["status"] = status
1264
+
1265
+ if target_symbol is not None:
1266
+ query_params["target_symbol"] = target_symbol
1267
+
1268
+ if trading_symbol is not None:
1269
+ query_params["trading_symbol"] = trading_symbol
1270
+
1271
+ return self.trade_service.get_all({"market": market})
1272
+
1273
+ def get_closed_trades(self) -> List[Trade]:
1274
+ """
1275
+ Function to get all closed trades. This function will return all
1276
+ closed trades of the algorithm.
1277
+
1278
+ Returns:
1279
+ List[Trade]: A list of closed trades
1280
+ """
1281
+ return self.trade_service.get_all({"status": TradeStatus.CLOSED.value})
1282
+
1283
+ def count_trades(
1284
+ self,
1285
+ target_symbol=None,
1286
+ trading_symbol=None,
1287
+ market=None,
1288
+ portfolio=None
1289
+ ) -> int:
1290
+ """
1291
+ Function to count trades. This function will return the number of
1292
+ trades that match the specified query parameters.
1293
+
1294
+ Args:
1295
+ target_symbol: The symbol of the asset
1296
+ trading_symbol: The trading symbol of the asset
1297
+ market: The market of the asset
1298
+ portfolio: The portfolio of the asset
1299
+
1300
+ Returns:
1301
+ int: The number of trades that match the query parameters
1302
+ """
1303
+
1304
+ query_params = {}
1305
+
1306
+ if market is not None:
1307
+ query_params["market"] = market
1308
+
1309
+ if portfolio is not None:
1310
+ query_params["portfolio"] = portfolio
1311
+
1312
+ if target_symbol is not None:
1313
+ query_params["target_symbol"] = target_symbol
1314
+
1315
+ if trading_symbol is not None:
1316
+ query_params["trading_symbol"] = trading_symbol
1317
+
1318
+ return self.trade_service.count(query_params)
1319
+
1320
+ def get_pending_trades(
1321
+ self, target_symbol=None, market=None
1322
+ ) -> List[Trade]:
1323
+ """
1324
+ Function to get all pending trades. This function will return all
1325
+ pending trades that match the specified query parameters. If the
1326
+ target_symbol parameter is specified, the pending trades with the
1327
+ specified target symbol will be returned. If the market parameter
1328
+ is specified, the pending trades with the specified market will be
1329
+ returned.
1330
+
1331
+ Args:
1332
+ target_symbol: The symbol of the asset
1333
+ market: The market of the asset
1334
+
1335
+ Returns:
1336
+ List[Trade]: A list of pending trades that match
1337
+ the query parameters
1338
+ """
1339
+ return self.trade_service.get_all(
1340
+ {
1341
+ "status": TradeStatus.CREATED.value,
1342
+ "target_symbol": target_symbol,
1343
+ "market": market
1344
+ }
1345
+ )
1346
+
1347
+ def get_open_trades(self, target_symbol=None, market=None) -> List[Trade]:
1348
+ """
1349
+ Function to get all open trades. This function will return all
1350
+ open trades that match the specified query parameters. If the
1351
+ target_symbol parameter is specified, the open trades with the
1352
+ specified target symbol will be returned. If the market parameter
1353
+ is specified, the open trades with the specified market will be
1354
+ returned.
1355
+
1356
+ Args:
1357
+ target_symbol: The symbol of the asset
1358
+ market: The market of the asset
1359
+
1360
+ Returns:
1361
+ List[Trade]: A list of open trades that match the query parameters
1362
+ """
1363
+ return self.trade_service.get_all(
1364
+ {
1365
+ "status": TradeStatus.OPEN.value,
1366
+ "target_symbol": target_symbol,
1367
+ "market": market
1368
+ }
1369
+ )
1370
+
1371
+ def add_stop_loss(
1372
+ self,
1373
+ trade: Trade,
1374
+ percentage: float,
1375
+ trade_risk_type=TradeRiskType.FIXED,
1376
+ sell_percentage: float = 100,
1377
+ ):
1378
+ """
1379
+ Function to add a stop loss to a trade.
1380
+
1381
+ Example of fixed stop loss:
1382
+ * You buy BTC at $40,000.
1383
+ * You set a SL of 5% → SL level at $38,000 (40,000 - 5%).
1384
+ * BTC price increases to $42,000 → SL level remains at $38,000.
1385
+ * BTC price drops to $38,000 → SL level reached, trade closes.
1386
+
1387
+ Example of trailing stop loss:
1388
+ * You buy BTC at $40,000.
1389
+ * You set a TSL of 5%, setting the sell price at $38,000.
1390
+ * BTC price increases to $42,000 → New TSL level
1391
+ at $39,900 (42,000 - 5%).
1392
+ * BTC price drops to $39,900 → SL level reached, trade closes.
1393
+
1394
+ Args:
1395
+ trade (Trade): Trade object representing the trade
1396
+ percentage (float): float representing the percentage
1397
+ of the open price that the stop loss should
1398
+ be set at. This must be a positive
1399
+ number, e.g. 5 for 5%, or 10 for 10%.
1400
+ trade_risk_type (TradeRiskType): The type of the stop
1401
+ loss, fixed or trailing
1402
+ sell_percentage (float): float representing the
1403
+ percentage of the trade that should be sold if the
1404
+ stop loss is triggered
1405
+
1406
+ Returns:
1407
+ None
1408
+ """
1409
+ self.trade_service.add_stop_loss(
1410
+ trade,
1411
+ percentage=percentage,
1412
+ trade_risk_type=trade_risk_type,
1413
+ sell_percentage=sell_percentage,
1414
+ )
1415
+ return self.trade_service.get(trade.id)
1416
+
1417
+ def add_take_profit(
1418
+ self,
1419
+ trade: Trade,
1420
+ percentage: float,
1421
+ trade_risk_type=TradeRiskType.FIXED,
1422
+ sell_percentage: float = 100,
1423
+ ) -> None:
1424
+ """
1425
+ Function to add a take profit to a trade. This function will add a
1426
+ take profit to the specified trade. If the take profit is triggered,
1427
+ the trade will be closed.
1428
+
1429
+ Example of take profit:
1430
+ * You buy BTC at $40,000.
1431
+ * You set a TP of 5% → TP level at $42,000 (40,000 + 5%).
1432
+ * BTC rises to $42,000 → TP level reached, trade
1433
+ closes, securing profit.
1434
+
1435
+ Example of trailing take profit:
1436
+ * You buy BTC at $40,000
1437
+ * You set a TTP of 5%, setting the sell price at $42,000.
1438
+ * BTC rises to $42,000 → TTP level stays at $42,000.
1439
+ * BTC rises to $45,000 → New TTP level at $42,750.
1440
+ * BTC drops to $42,750 → Trade closes, securing profit.
1441
+
1442
+ Args:
1443
+ trade (Trade): Trade object representing the trade
1444
+ percentage (float): float representing the percentage
1445
+ of the open price that the stop loss should
1446
+ be set at. This must be a positive
1447
+ number, e.g. 5 for 5%, or 10 for 10%.
1448
+ trade_risk_type (TradeRiskType): The type of the stop
1449
+ loss, fixed or trailing
1450
+ sell_percentage (float): float representing the
1451
+ percentage of the trade that should be sold if the
1452
+ stop loss is triggered
1453
+
1454
+ Returns:
1455
+ None
1456
+ """
1457
+ self.trade_service.add_take_profit(
1458
+ trade,
1459
+ percentage=percentage,
1460
+ trade_risk_type=trade_risk_type,
1461
+ sell_percentage=sell_percentage,
1462
+ )
1463
+ return self.trade_service.get(trade.id)
1464
+
1465
+ def close_trade(self, trade, precision=None) -> None:
1466
+ """
1467
+ Function to close a trade. This function will close a trade by
1468
+ creating a market order to sell the position. If the precision
1469
+ parameter is specified, the amount of the order will be rounded
1470
+ down to the specified precision.
1471
+
1472
+ Args:
1473
+ trade: Trade - The trade to close
1474
+ precision: int - The precision of the amount
1475
+
1476
+ Returns:
1477
+ None
1478
+ """
1479
+ trade = self.trade_service.get(trade.id)
1480
+
1481
+ if TradeStatus.CLOSED.equals(trade.status):
1482
+ raise OperationalException("Trade already closed.")
1483
+
1484
+ if trade.available_amount <= 0:
1485
+ raise OperationalException("Trade has no amount to close.")
1486
+
1487
+ position_id = trade.orders[0].position_id
1488
+ portfolio = self.portfolio_service.find({"position": position_id})
1489
+ position = self.position_service.find(
1490
+ {"portfolio": portfolio.id, "symbol": trade.target_symbol}
1491
+ )
1492
+ amount = trade.available_amount
1493
+
1494
+ if precision is not None:
1495
+ amount = RoundingService.round_down(amount, precision)
1496
+
1497
+ if position.get_amount() < amount:
1498
+ logger.warning(
1499
+ f"Order amount {amount} is larger then amount "
1500
+ f"of available {position.symbol} "
1501
+ f"position: {position.get_amount()}, "
1502
+ f"changing order amount to size of position"
1503
+ )
1504
+ amount = position.get_amount()
1505
+
1506
+ ticker = self.data_provider_service.get_ticker_data(
1507
+ symbol=trade.symbol,
1508
+ market=portfolio.market,
1509
+ date=self.config[INDEX_DATETIME]
1510
+ )
1511
+ logger.info(f"Closing trade {trade.id} {trade.symbol}")
1512
+ self.order_service.create(
1513
+ {
1514
+ "portfolio_id": portfolio.id,
1515
+ "trading_symbol": trade.trading_symbol,
1516
+ "target_symbol": trade.target_symbol,
1517
+ "amount": amount,
1518
+ "order_side": OrderSide.SELL.value,
1519
+ "order_type": OrderType.LIMIT.value,
1520
+ "price": ticker["bid"],
1521
+ }
1522
+ )
1523
+
1524
+ def get_number_of_positions(self):
1525
+ """
1526
+ Returns the number of positions that have a positive amount.
1527
+
1528
+ Returns:
1529
+ int: The number of positions
1530
+ """
1531
+ return self.position_service.count({"amount_gt": 0})
1532
+
1533
+ def has_trading_symbol_position_available(
1534
+ self,
1535
+ amount_gt=None,
1536
+ amount_gte=None,
1537
+ percentage_of_portfolio=None,
1538
+ market=None
1539
+ ):
1540
+ """
1541
+ Checks if there is a position available for the trading symbol of the
1542
+ portfolio. If the amount_gt or amount_gte parameters are specified,
1543
+ the amount of the position must be greater than the specified amount.
1544
+ If the percentage_of_portfolio parameter is specified, the amount of
1545
+ the position must be greater than the net_size of the
1546
+ portfolio.
1547
+
1548
+ Parameters:
1549
+ amount_gt: The amount of the position must be greater than this
1550
+ amount.
1551
+ :param amount_gte: The amount of the position must be greater than
1552
+ or equal to this amount.
1553
+ :param percentage_of_portfolio: The amount of the position must be
1554
+ greater than the net_size of the portfolio.
1555
+ :param market: The market of the portfolio.
1556
+ :return: True if there is a trading symbol position available with the
1557
+ specified parameters, False otherwise.
1558
+ """
1559
+ portfolio = self.portfolio_service.find({"market": market})
1560
+ position = self.position_service.find(
1561
+ {"portfolio": portfolio.id, "symbol": portfolio.trading_symbol}
1562
+ )
1563
+
1564
+ if amount_gt is not None:
1565
+ return position.get_amount() > amount_gt
1566
+
1567
+ if amount_gte is not None:
1568
+ return position.get_amount() >= amount_gte
1569
+
1570
+ if percentage_of_portfolio is not None:
1571
+ net_size = portfolio.get_net_size()
1572
+ return position.get_amount() >= net_size \
1573
+ * percentage_of_portfolio / 100
1574
+
1575
+ return position.get_amount() > 0
1576
+
1577
+ def get_pending_orders(
1578
+ self, order_side=None, target_symbol=None, portfolio_id=None
1579
+ ):
1580
+ """
1581
+ Function to get all pending orders of the algorithm. If the
1582
+ portfolio_id parameter is specified, the function will return
1583
+ all pending orders of the portfolio with the specified id.
1584
+ """
1585
+ query_params = {}
1586
+
1587
+ if portfolio_id:
1588
+ query_params["portfolio"] = portfolio_id
1589
+
1590
+ if target_symbol:
1591
+ query_params["target_symbol"] = target_symbol
1592
+
1593
+ if order_side:
1594
+ query_params["order_side"] = order_side
1595
+
1596
+ return self.order_service.get_all({"status": OrderStatus.OPEN.value})
1597
+
1598
+ def get_unfilled_buy_value(self):
1599
+ """
1600
+ Returns the total value of all unfilled buy orders.
1601
+ """
1602
+ pending_orders = self.get_pending_orders(
1603
+ order_side=OrderSide.BUY.value
1604
+ )
1605
+
1606
+ return sum(
1607
+ [order.get_remaining() * order.get_price()
1608
+ for order in pending_orders]
1609
+ )
1610
+
1611
+ def get_unfilled_sell_value(self):
1612
+ """
1613
+ Returns the total value of all unfilled buy orders.
1614
+ """
1615
+ pending_orders = self.get_pending_orders(
1616
+ order_side=OrderSide.SELL.value
1617
+ )
1618
+
1619
+ return sum(
1620
+ [order.get_remaining() * order.get_price()
1621
+ for order in pending_orders]
1622
+ )
1623
+
1624
+ def get_market_credential(self, market) -> MarketCredential:
1625
+ """
1626
+ Function to get the market credential for a given market.
1627
+
1628
+ Args:
1629
+ market: The market to get the credential for
1630
+
1631
+ Returns:
1632
+ MarketCredential: The market credential for the given market
1633
+ """
1634
+ return self.market_credential_service.get(market)
1635
+
1636
+ def get_market_credentials(self) -> List[MarketCredential]:
1637
+ """
1638
+ Function to get all market credentials.
1639
+
1640
+ Returns:
1641
+ List[MarketCredential]: A list of all market credentials
1642
+ """
1643
+ return self.market_credential_service.get_all()
1644
+
1645
+ def get_trading_symbol(self, portfolio_id=None):
1646
+ """
1647
+ Function to get the trading symbol of a portfolio. If the
1648
+ portfolio_id parameter is specified, the function will return
1649
+ the trading symbol of the portfolio with the specified id.
1650
+
1651
+ Args:
1652
+ portfolio_id: The id of the portfolio to get the trading symbol for
1653
+
1654
+ Returns:
1655
+ str: The trading symbol of the portfolio
1656
+ """
1657
+ if portfolio_id is None:
1658
+ if self.portfolio_service.count() > 1:
1659
+ raise OperationalException(
1660
+ "Multiple portfolios found. Please specify a "
1661
+ "portfolio identifier."
1662
+ )
1663
+ portfolio = self.portfolio_service.get_all()[0]
1664
+ else:
1665
+ portfolio = self.portfolio_service.get(portfolio_id)
1666
+
1667
+ return portfolio.trading_symbol