investing-algorithm-framework 3.7.0__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.
- investing_algorithm_framework/__init__.py +168 -45
- investing_algorithm_framework/app/__init__.py +32 -1
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +239 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +114 -0
- investing_algorithm_framework/app/analysis/__init__.py +15 -0
- investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
- investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
- investing_algorithm_framework/app/analysis/permutation.py +116 -0
- investing_algorithm_framework/app/analysis/ranking.py +297 -0
- investing_algorithm_framework/app/app.py +1933 -589
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1725 -0
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +27 -0
- investing_algorithm_framework/app/reporting/ascii.py +921 -0
- investing_algorithm_framework/app/reporting/backtest_report.py +349 -0
- investing_algorithm_framework/app/reporting/charts/__init__.py +19 -0
- investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
- investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +74 -0
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/monthly_returns_heatmap.py +70 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +79 -0
- investing_algorithm_framework/app/reporting/charts/yearly_returns_barchart.py +55 -0
- investing_algorithm_framework/app/reporting/generate.py +185 -0
- investing_algorithm_framework/app/reporting/tables/__init__.py +11 -0
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +217 -0
- investing_algorithm_framework/app/reporting/tables/stop_loss_table.py +0 -0
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +80 -0
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +147 -0
- investing_algorithm_framework/app/reporting/tables/trades_table.py +75 -0
- investing_algorithm_framework/app/reporting/tables/utils.py +29 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +154 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +4 -2
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/strategy.py +664 -84
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +226 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +501 -0
- investing_algorithm_framework/cli/deploy_to_azure_function.py +718 -0
- investing_algorithm_framework/cli/initialize_app.py +603 -0
- investing_algorithm_framework/cli/templates/.gitignore.template +178 -0
- investing_algorithm_framework/cli/templates/app.py.template +18 -0
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +48 -0
- investing_algorithm_framework/cli/templates/app_azure_function.py.template +14 -0
- investing_algorithm_framework/cli/templates/app_web.py.template +18 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
- investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
- investing_algorithm_framework/cli/templates/aws_lambda_readme.md.template +110 -0
- investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/azure_function_function_app.py.template +65 -0
- investing_algorithm_framework/cli/templates/azure_function_host.json.template +15 -0
- investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template +8 -0
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +3 -0
- investing_algorithm_framework/cli/templates/data_providers.py.template +17 -0
- investing_algorithm_framework/cli/templates/env.example.template +2 -0
- investing_algorithm_framework/cli/templates/env_azure_function.example.template +4 -0
- investing_algorithm_framework/cli/templates/market_data_providers.py.template +9 -0
- investing_algorithm_framework/cli/templates/readme.md.template +135 -0
- investing_algorithm_framework/cli/templates/requirements.txt.template +2 -0
- investing_algorithm_framework/cli/templates/run_backtest.py.template +20 -0
- investing_algorithm_framework/cli/templates/strategy.py.template +124 -0
- investing_algorithm_framework/create_app.py +40 -6
- investing_algorithm_framework/dependency_container.py +72 -56
- investing_algorithm_framework/domain/__init__.py +71 -47
- investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
- investing_algorithm_framework/domain/config.py +59 -91
- investing_algorithm_framework/domain/constants.py +13 -38
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +3 -2
- investing_algorithm_framework/domain/exceptions.py +51 -1
- investing_algorithm_framework/domain/models/__init__.py +17 -12
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +214 -0
- investing_algorithm_framework/domain/models/data/data_type.py +46 -0
- investing_algorithm_framework/domain/models/event.py +35 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +55 -1
- investing_algorithm_framework/domain/models/order/order.py +77 -83
- investing_algorithm_framework/domain/models/order/order_status.py +2 -2
- investing_algorithm_framework/domain/models/order/order_type.py +1 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +81 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +26 -3
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +108 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +12 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
- investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
- investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +37 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +66 -2
- investing_algorithm_framework/domain/models/trade/__init__.py +8 -1
- investing_algorithm_framework/domain/models/trade/trade.py +295 -171
- investing_algorithm_framework/domain/models/trade/trade_status.py +9 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +332 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +365 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/services/__init__.py +2 -9
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +0 -6
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +12 -7
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/dates.py +57 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +53 -0
- investing_algorithm_framework/domain/utils/random.py +29 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +31 -18
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1143 -0
- investing_algorithm_framework/infrastructure/data_providers/csv.py +568 -0
- investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
- investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +86 -12
- investing_algorithm_framework/infrastructure/models/__init__.py +6 -11
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -1
- investing_algorithm_framework/infrastructure/models/order/order.py +35 -49
- investing_algorithm_framework/infrastructure/models/order/order_metadata.py +44 -0
- investing_algorithm_framework/infrastructure/models/order_trade_association.py +10 -0
- investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +1 -1
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +8 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +17 -5
- investing_algorithm_framework/infrastructure/models/trades/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/models/trades/trade.py +130 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +59 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +55 -0
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +21 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/order_executors/ccxt_order_executor.py +200 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py +19 -0
- investing_algorithm_framework/infrastructure/portfolio_providers/ccxt_portfolio_provider.py +199 -0
- investing_algorithm_framework/infrastructure/repositories/__init__.py +8 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +5 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +11 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +81 -27
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +29 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +29 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +4 -4
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +113 -0
- investing_algorithm_framework/infrastructure/services/azure/__init__.py +5 -0
- investing_algorithm_framework/infrastructure/services/azure/state_handler.py +158 -0
- investing_algorithm_framework/services/__init__.py +113 -16
- investing_algorithm_framework/services/backtesting/__init__.py +0 -7
- investing_algorithm_framework/services/backtesting/backtest_service.py +566 -359
- investing_algorithm_framework/services/configuration_service.py +77 -11
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
- investing_algorithm_framework/services/market_credential_service.py +16 -1
- investing_algorithm_framework/services/metrics/__init__.py +114 -0
- investing_algorithm_framework/services/metrics/alpha.py +0 -0
- investing_algorithm_framework/services/metrics/beta.py +0 -0
- investing_algorithm_framework/services/metrics/cagr.py +60 -0
- investing_algorithm_framework/services/metrics/calmar_ratio.py +40 -0
- investing_algorithm_framework/services/metrics/drawdown.py +181 -0
- investing_algorithm_framework/services/metrics/equity_curve.py +24 -0
- investing_algorithm_framework/services/metrics/exposure.py +210 -0
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/services/metrics/mean_daily_return.py +83 -0
- investing_algorithm_framework/services/metrics/profit_factor.py +165 -0
- investing_algorithm_framework/services/metrics/recovery.py +113 -0
- investing_algorithm_framework/services/metrics/returns.py +452 -0
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/services/metrics/sharpe_ratio.py +137 -0
- investing_algorithm_framework/services/metrics/sortino_ratio.py +74 -0
- investing_algorithm_framework/services/metrics/standard_deviation.py +157 -0
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/treynor_ratio.py +0 -0
- investing_algorithm_framework/services/metrics/ulcer.py +0 -0
- investing_algorithm_framework/services/metrics/value_at_risk.py +0 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/services/metrics/win_rate.py +177 -0
- investing_algorithm_framework/services/order_service/__init__.py +3 -1
- investing_algorithm_framework/services/order_service/order_backtest_service.py +76 -89
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +407 -326
- investing_algorithm_framework/services/portfolios/__init__.py +3 -1
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +37 -3
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +22 -8
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +96 -28
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +97 -28
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +116 -313
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/repository_service.py +8 -2
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
- investing_algorithm_framework/services/trade_service/__init__.py +7 -1
- investing_algorithm_framework/services/trade_service/trade_service.py +1013 -315
- investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
- investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
- investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
- investing_algorithm_framework-7.19.15.dist-info/RECORD +263 -0
- investing_algorithm_framework-7.19.15.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -1105
- investing_algorithm_framework/domain/graphs.py +0 -382
- investing_algorithm_framework/domain/metrics/__init__.py +0 -6
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -11
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -43
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_report.py +0 -580
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -243
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -344
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/domain/utils/backtesting.py +0 -472
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -12
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -559
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -254
- investing_algorithm_framework/infrastructure/models/market_data_sources/us_treasury_yield.py +0 -47
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -455
- investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
- investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
- investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -350
- investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +0 -53
- investing_algorithm_framework/services/backtesting/graphs.py +0 -61
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -8
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -150
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -189
- investing_algorithm_framework/services/position_service.py +0 -31
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -264
- investing_algorithm_framework-3.7.0.dist-info/METADATA +0 -339
- investing_algorithm_framework-3.7.0.dist-info/RECORD +0 -147
- /investing_algorithm_framework/{domain → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/services/{position_snapshot_service.py → positions/position_snapshot_service.py} +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-3.7.0.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
|
@@ -1,1105 +0,0 @@
|
|
|
1
|
-
import inspect
|
|
2
|
-
import logging
|
|
3
|
-
from typing import List, Dict
|
|
4
|
-
|
|
5
|
-
from investing_algorithm_framework.domain import OrderStatus, \
|
|
6
|
-
Position, Order, Portfolio, OrderType, OrderSide, \
|
|
7
|
-
BACKTESTING_FLAG, BACKTESTING_INDEX_DATETIME, MarketService, TimeUnit, \
|
|
8
|
-
OperationalException, random_string, RoundingService
|
|
9
|
-
from investing_algorithm_framework.services import MarketCredentialService, \
|
|
10
|
-
MarketDataSourceService, PortfolioService, PositionService, TradeService, \
|
|
11
|
-
OrderService, ConfigurationService, StrategyOrchestratorService, \
|
|
12
|
-
PortfolioConfigurationService
|
|
13
|
-
from .task import Task
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger("investing_algorithm_framework")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Algorithm:
|
|
19
|
-
"""
|
|
20
|
-
Class to represent an algorithm. An algorithm is a collection of
|
|
21
|
-
strategies that are executed in a specific order. The algorithm
|
|
22
|
-
class is responsible for managing the strategies and executing
|
|
23
|
-
them in the correct order.
|
|
24
|
-
|
|
25
|
-
:param (optional) name: The name of the algorithm
|
|
26
|
-
:param (optional) description: The description of the algorithm
|
|
27
|
-
:param (optional) context: The context of the algorithm,
|
|
28
|
-
for backtest references
|
|
29
|
-
:param (optional) strategy: A single strategy to add to the algorithm
|
|
30
|
-
:param (optional) data_sources: The list of data sources to
|
|
31
|
-
add to the algorithm
|
|
32
|
-
"""
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
name: str = None,
|
|
36
|
-
description: str = None,
|
|
37
|
-
context: Dict = None,
|
|
38
|
-
strategy=None,
|
|
39
|
-
data_sources=None
|
|
40
|
-
):
|
|
41
|
-
self._name = name
|
|
42
|
-
self._context = {}
|
|
43
|
-
|
|
44
|
-
if name is None:
|
|
45
|
-
self._name = f"algorithm_{random_string(10)}"
|
|
46
|
-
|
|
47
|
-
self._description = None
|
|
48
|
-
|
|
49
|
-
if description is not None:
|
|
50
|
-
self._description = description
|
|
51
|
-
|
|
52
|
-
if context is not None:
|
|
53
|
-
self.add_context(context)
|
|
54
|
-
|
|
55
|
-
self._strategies = []
|
|
56
|
-
self._tasks = []
|
|
57
|
-
self.portfolio_service: PortfolioService
|
|
58
|
-
self.position_service: PositionService
|
|
59
|
-
self.order_service: OrderService
|
|
60
|
-
self.market_service: MarketService
|
|
61
|
-
self.configuration_service: ConfigurationService
|
|
62
|
-
self.portfolio_configuration_service: PortfolioConfigurationService
|
|
63
|
-
self.strategy_orchestrator_service: StrategyOrchestratorService
|
|
64
|
-
self._data_sources = {}
|
|
65
|
-
self._strategies = []
|
|
66
|
-
self._market_credential_service: MarketCredentialService
|
|
67
|
-
self._market_data_source_service: MarketDataSourceService
|
|
68
|
-
self.trade_service: TradeService
|
|
69
|
-
|
|
70
|
-
if strategy is not None:
|
|
71
|
-
self.add_strategy(strategy)
|
|
72
|
-
|
|
73
|
-
if data_sources is not None:
|
|
74
|
-
self.add_data_sources(data_sources)
|
|
75
|
-
|
|
76
|
-
def initialize_services(
|
|
77
|
-
self,
|
|
78
|
-
configuration_service,
|
|
79
|
-
portfolio_configuration_service,
|
|
80
|
-
portfolio_service,
|
|
81
|
-
position_service,
|
|
82
|
-
order_service,
|
|
83
|
-
market_service,
|
|
84
|
-
strategy_orchestrator_service,
|
|
85
|
-
market_credential_service,
|
|
86
|
-
market_data_source_service,
|
|
87
|
-
trade_service
|
|
88
|
-
):
|
|
89
|
-
self.portfolio_service: PortfolioService = portfolio_service
|
|
90
|
-
self.position_service: PositionService = position_service
|
|
91
|
-
self.order_service: OrderService = order_service
|
|
92
|
-
self.market_service: MarketService = market_service
|
|
93
|
-
self.configuration_service: ConfigurationService \
|
|
94
|
-
= configuration_service
|
|
95
|
-
self.portfolio_configuration_service: PortfolioConfigurationService \
|
|
96
|
-
= portfolio_configuration_service
|
|
97
|
-
self.strategy_orchestrator_service: StrategyOrchestratorService \
|
|
98
|
-
= strategy_orchestrator_service
|
|
99
|
-
self._data_sources = {}
|
|
100
|
-
self._market_credential_service: MarketCredentialService \
|
|
101
|
-
= market_credential_service
|
|
102
|
-
self._market_data_source_service: MarketDataSourceService \
|
|
103
|
-
= market_data_source_service
|
|
104
|
-
self.trade_service: TradeService = trade_service
|
|
105
|
-
|
|
106
|
-
# Add all registered strategies to the orchestrator
|
|
107
|
-
self.strategy_orchestrator_service.add_strategies(
|
|
108
|
-
self._strategies
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
def start(self, number_of_iterations=None, stateless=False):
|
|
112
|
-
|
|
113
|
-
if not stateless:
|
|
114
|
-
self.strategy_orchestrator_service.start(
|
|
115
|
-
algorithm=self,
|
|
116
|
-
number_of_iterations=number_of_iterations
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
@property
|
|
120
|
-
def name(self):
|
|
121
|
-
return self._name
|
|
122
|
-
|
|
123
|
-
@property
|
|
124
|
-
def data_sources(self):
|
|
125
|
-
return self._data_sources
|
|
126
|
-
|
|
127
|
-
@property
|
|
128
|
-
def identifier(self):
|
|
129
|
-
"""
|
|
130
|
-
Function to get a config instance. This allows users when
|
|
131
|
-
having access to the algorithm instance also to read the
|
|
132
|
-
configs of the app.
|
|
133
|
-
"""
|
|
134
|
-
return self.configuration_service.config
|
|
135
|
-
|
|
136
|
-
@property
|
|
137
|
-
def config(self):
|
|
138
|
-
"""
|
|
139
|
-
Function to get a config instance. This allows users when
|
|
140
|
-
having access to the algorithm instance also to read the
|
|
141
|
-
configs of the app.
|
|
142
|
-
"""
|
|
143
|
-
return self.configuration_service.config
|
|
144
|
-
|
|
145
|
-
@property
|
|
146
|
-
def description(self):
|
|
147
|
-
"""
|
|
148
|
-
Function to get the description of the algorithm
|
|
149
|
-
"""
|
|
150
|
-
return self._description
|
|
151
|
-
|
|
152
|
-
@property
|
|
153
|
-
def context(self):
|
|
154
|
-
"""
|
|
155
|
-
Function to get the context of the algorithm
|
|
156
|
-
"""
|
|
157
|
-
return self._context
|
|
158
|
-
|
|
159
|
-
def add_context(self, context: Dict):
|
|
160
|
-
# Check if the context is a dictionary with only string,
|
|
161
|
-
# float or int values
|
|
162
|
-
for key, value in self.context.items():
|
|
163
|
-
if not isinstance(key, str) or \
|
|
164
|
-
not isinstance(value, (str, float, int)):
|
|
165
|
-
raise OperationalException(
|
|
166
|
-
"The context for an algorithm must be a dictionary with "
|
|
167
|
-
"only string, float or int values."
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
self._context = context
|
|
171
|
-
|
|
172
|
-
@property
|
|
173
|
-
def running(self) -> bool:
|
|
174
|
-
"""
|
|
175
|
-
Returns True if the algorithm is running, False otherwise.
|
|
176
|
-
|
|
177
|
-
The algorithm is considered to be running if has strategies
|
|
178
|
-
scheduled to run in the strategy orchestrator service.
|
|
179
|
-
"""
|
|
180
|
-
return self.strategy_orchestrator_service.running
|
|
181
|
-
|
|
182
|
-
def run_jobs(self):
|
|
183
|
-
"""
|
|
184
|
-
Function run all pending jobs in the strategy orchestrator
|
|
185
|
-
"""
|
|
186
|
-
self.strategy_orchestrator_service.run_pending_jobs()
|
|
187
|
-
|
|
188
|
-
def create_order(
|
|
189
|
-
self,
|
|
190
|
-
target_symbol,
|
|
191
|
-
price,
|
|
192
|
-
order_type,
|
|
193
|
-
order_side,
|
|
194
|
-
amount,
|
|
195
|
-
market=None,
|
|
196
|
-
execute=True,
|
|
197
|
-
validate=True,
|
|
198
|
-
sync=True
|
|
199
|
-
):
|
|
200
|
-
"""
|
|
201
|
-
Function to create an order. This function will create an order
|
|
202
|
-
and execute it if the execute parameter is set to True. If the
|
|
203
|
-
validate parameter is set to True, the order will be validated
|
|
204
|
-
|
|
205
|
-
:param target_symbol: The symbol of the asset to trade
|
|
206
|
-
:param price: The price of the asset
|
|
207
|
-
:param order_type: The type of the order
|
|
208
|
-
:param order_side: The side of the order
|
|
209
|
-
:param amount: The amount of the asset to trade
|
|
210
|
-
:param market: The market to trade the asset
|
|
211
|
-
:param execute: If set to True, the order will be executed
|
|
212
|
-
:param validate: If set to True, the order will be validated
|
|
213
|
-
:param sync: If set to True, the created order will be synced
|
|
214
|
-
with the portfolio of the algorithm.
|
|
215
|
-
:return: The order created
|
|
216
|
-
"""
|
|
217
|
-
portfolio = self.portfolio_service.find({"market": market})
|
|
218
|
-
order_data = {
|
|
219
|
-
"target_symbol": target_symbol,
|
|
220
|
-
"price": price,
|
|
221
|
-
"amount": amount,
|
|
222
|
-
"order_type": order_type,
|
|
223
|
-
"order_side": order_side,
|
|
224
|
-
"portfolio_id": portfolio.id,
|
|
225
|
-
"status": OrderStatus.CREATED.value,
|
|
226
|
-
"trading_symbol": portfolio.trading_symbol,
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
230
|
-
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
231
|
-
order_data["created_at"] = \
|
|
232
|
-
self.configuration_service.config[BACKTESTING_INDEX_DATETIME]
|
|
233
|
-
|
|
234
|
-
return self.order_service.create(
|
|
235
|
-
order_data, execute=execute, validate=validate, sync=sync
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
def create_limit_order(
|
|
239
|
-
self,
|
|
240
|
-
target_symbol,
|
|
241
|
-
price,
|
|
242
|
-
order_side,
|
|
243
|
-
amount=None,
|
|
244
|
-
percentage_of_portfolio=None,
|
|
245
|
-
percentage_of_position=None,
|
|
246
|
-
precision=None,
|
|
247
|
-
market=None,
|
|
248
|
-
execute=True,
|
|
249
|
-
validate=True,
|
|
250
|
-
sync=True
|
|
251
|
-
):
|
|
252
|
-
portfolio = self.portfolio_service.find({"market": market})
|
|
253
|
-
|
|
254
|
-
if percentage_of_portfolio is not None:
|
|
255
|
-
if not OrderSide.BUY.equals(order_side):
|
|
256
|
-
raise OperationalException(
|
|
257
|
-
"Percentage of portfolio is only supported for BUY orders."
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
percentage_of_portfolio = percentage_of_portfolio
|
|
261
|
-
net_size = portfolio.get_net_size()
|
|
262
|
-
size = net_size * percentage_of_portfolio / 100
|
|
263
|
-
amount = size / price
|
|
264
|
-
|
|
265
|
-
elif percentage_of_position is not None:
|
|
266
|
-
|
|
267
|
-
if not OrderSide.SELL.equals(order_side):
|
|
268
|
-
raise OperationalException(
|
|
269
|
-
"Percentage of position is only supported for SELL orders."
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
position = self.position_service.find(
|
|
273
|
-
{
|
|
274
|
-
"symbol": target_symbol,
|
|
275
|
-
"portfolio": portfolio.id
|
|
276
|
-
}
|
|
277
|
-
)
|
|
278
|
-
amount = position.get_amount() * (percentage_of_position / 100)
|
|
279
|
-
|
|
280
|
-
if precision is not None:
|
|
281
|
-
amount = RoundingService.round_down(amount, precision)
|
|
282
|
-
|
|
283
|
-
order_data = {
|
|
284
|
-
"target_symbol": target_symbol,
|
|
285
|
-
"price": price,
|
|
286
|
-
"amount": amount,
|
|
287
|
-
"order_type": OrderType.LIMIT.value,
|
|
288
|
-
"order_side": OrderSide.from_value(order_side).value,
|
|
289
|
-
"portfolio_id": portfolio.id,
|
|
290
|
-
"status": OrderStatus.CREATED.value,
|
|
291
|
-
"trading_symbol": portfolio.trading_symbol,
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
295
|
-
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
296
|
-
order_data["created_at"] = \
|
|
297
|
-
self.configuration_service.config[BACKTESTING_INDEX_DATETIME]
|
|
298
|
-
|
|
299
|
-
return self.order_service.create(
|
|
300
|
-
order_data, execute=execute, validate=validate, sync=sync
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
def create_market_order(
|
|
304
|
-
self,
|
|
305
|
-
target_symbol,
|
|
306
|
-
order_side,
|
|
307
|
-
amount,
|
|
308
|
-
market=None,
|
|
309
|
-
execute=False,
|
|
310
|
-
validate=False,
|
|
311
|
-
sync=True
|
|
312
|
-
):
|
|
313
|
-
|
|
314
|
-
if market is None:
|
|
315
|
-
portfolio = self.portfolio_service.get_all()[0]
|
|
316
|
-
else:
|
|
317
|
-
portfolio = self.portfolio_service.find({"market": market})
|
|
318
|
-
order_data = {
|
|
319
|
-
"target_symbol": target_symbol,
|
|
320
|
-
"amount": amount,
|
|
321
|
-
"order_type": OrderType.MARKET.value,
|
|
322
|
-
"order_side": OrderSide.from_value(order_side).value,
|
|
323
|
-
"portfolio_id": portfolio.id,
|
|
324
|
-
"status": OrderStatus.CREATED.value,
|
|
325
|
-
"trading_symbol": portfolio.trading_symbol,
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if BACKTESTING_FLAG in self.configuration_service.config \
|
|
329
|
-
and self.configuration_service.config[BACKTESTING_FLAG]:
|
|
330
|
-
order_data["created_at"] = \
|
|
331
|
-
self.configuration_service.config[BACKTESTING_INDEX_DATETIME]
|
|
332
|
-
|
|
333
|
-
return self.order_service.create(
|
|
334
|
-
order_data, execute=execute, validate=validate, sync=sync
|
|
335
|
-
)
|
|
336
|
-
|
|
337
|
-
def get_portfolio(self, market=None) -> Portfolio:
|
|
338
|
-
|
|
339
|
-
if market is None:
|
|
340
|
-
return self.portfolio_service.find({})
|
|
341
|
-
|
|
342
|
-
return self.portfolio_service.find({{"market": market}})
|
|
343
|
-
|
|
344
|
-
def get_unallocated(self, market=None) -> float:
|
|
345
|
-
|
|
346
|
-
if market:
|
|
347
|
-
portfolio = self.portfolio_service.find({{"market": market}})
|
|
348
|
-
else:
|
|
349
|
-
portfolio = self.portfolio_service.find({})
|
|
350
|
-
|
|
351
|
-
trading_symbol = portfolio.trading_symbol
|
|
352
|
-
return self.position_service.find(
|
|
353
|
-
{"portfolio": portfolio.id, "symbol": trading_symbol}
|
|
354
|
-
).get_amount()
|
|
355
|
-
|
|
356
|
-
def get_total_size(self):
|
|
357
|
-
"""
|
|
358
|
-
Returns the total size of the portfolio.
|
|
359
|
-
"""
|
|
360
|
-
return self.get_unallocated() + self.get_allocated()
|
|
361
|
-
|
|
362
|
-
def reset(self):
|
|
363
|
-
self._workers = []
|
|
364
|
-
self._running_workers = []
|
|
365
|
-
|
|
366
|
-
def get_order(
|
|
367
|
-
self,
|
|
368
|
-
reference_id=None,
|
|
369
|
-
market=None,
|
|
370
|
-
target_symbol=None,
|
|
371
|
-
trading_symbol=None,
|
|
372
|
-
order_side=None,
|
|
373
|
-
order_type=None
|
|
374
|
-
) -> Order:
|
|
375
|
-
query_params = {}
|
|
376
|
-
|
|
377
|
-
if reference_id:
|
|
378
|
-
query_params["reference_id"] = reference_id
|
|
379
|
-
|
|
380
|
-
if target_symbol:
|
|
381
|
-
query_params["target_symbol"] = target_symbol
|
|
382
|
-
|
|
383
|
-
if trading_symbol:
|
|
384
|
-
query_params["trading_symbol"] = trading_symbol
|
|
385
|
-
|
|
386
|
-
if order_side:
|
|
387
|
-
query_params["order_side"] = order_side
|
|
388
|
-
|
|
389
|
-
if order_type:
|
|
390
|
-
query_params["order_type"] = order_type
|
|
391
|
-
|
|
392
|
-
if market:
|
|
393
|
-
portfolio = self.portfolio_service.find({"market": market})
|
|
394
|
-
positions = self.position_service.get_all(
|
|
395
|
-
{"portfolio": portfolio.id}
|
|
396
|
-
)
|
|
397
|
-
query_params["position"] = [position.id for position in positions]
|
|
398
|
-
|
|
399
|
-
return self.order_service.find(query_params)
|
|
400
|
-
|
|
401
|
-
def get_orders(
|
|
402
|
-
self,
|
|
403
|
-
target_symbol=None,
|
|
404
|
-
status=None,
|
|
405
|
-
order_type=None,
|
|
406
|
-
order_side=None,
|
|
407
|
-
market=None
|
|
408
|
-
) -> List[Order]:
|
|
409
|
-
|
|
410
|
-
if market is None:
|
|
411
|
-
portfolio = self.portfolio_service.get_all()[0]
|
|
412
|
-
else:
|
|
413
|
-
portfolio = self.portfolio_service.find({"market": market})
|
|
414
|
-
|
|
415
|
-
positions = self.position_service.get_all({"portfolio": portfolio.id})
|
|
416
|
-
return self.order_service.get_all(
|
|
417
|
-
{
|
|
418
|
-
"position": [position.id for position in positions],
|
|
419
|
-
"target_symbol": target_symbol,
|
|
420
|
-
"status": status,
|
|
421
|
-
"order_type": order_type,
|
|
422
|
-
"order_side": order_side
|
|
423
|
-
}
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
def get_positions(
|
|
427
|
-
self,
|
|
428
|
-
market=None,
|
|
429
|
-
identifier=None,
|
|
430
|
-
amount_gt=None,
|
|
431
|
-
amount_gte=None,
|
|
432
|
-
amount_lt=None,
|
|
433
|
-
amount_lte=None
|
|
434
|
-
) -> List[Position]:
|
|
435
|
-
query_params = {}
|
|
436
|
-
|
|
437
|
-
if market is not None:
|
|
438
|
-
query_params["market"] = market
|
|
439
|
-
|
|
440
|
-
if identifier is not None:
|
|
441
|
-
query_params["identifier"] = identifier
|
|
442
|
-
|
|
443
|
-
if amount_gt is not None:
|
|
444
|
-
query_params["amount_gt"] = amount_gt
|
|
445
|
-
|
|
446
|
-
if amount_gte is not None:
|
|
447
|
-
query_params["amount_gte"] = amount_gte
|
|
448
|
-
|
|
449
|
-
if amount_lt is not None:
|
|
450
|
-
query_params["amount_lt"] = amount_lt
|
|
451
|
-
|
|
452
|
-
if amount_lte is not None:
|
|
453
|
-
query_params["amount_lte"] = amount_lte
|
|
454
|
-
|
|
455
|
-
portfolios = self.portfolio_service.get_all(query_params)
|
|
456
|
-
|
|
457
|
-
if not portfolios:
|
|
458
|
-
raise OperationalException("No portfolio found.")
|
|
459
|
-
|
|
460
|
-
portfolio = portfolios[0]
|
|
461
|
-
return self.position_service.get_all(
|
|
462
|
-
{"portfolio": portfolio.id}
|
|
463
|
-
)
|
|
464
|
-
|
|
465
|
-
def get_position(self, symbol, market=None, identifier=None) -> Position:
|
|
466
|
-
query_params = {}
|
|
467
|
-
|
|
468
|
-
if market is not None:
|
|
469
|
-
query_params["market"] = market
|
|
470
|
-
|
|
471
|
-
if identifier is not None:
|
|
472
|
-
query_params["identifier"] = identifier
|
|
473
|
-
|
|
474
|
-
portfolios = self.portfolio_service.get_all(query_params)
|
|
475
|
-
|
|
476
|
-
if not portfolios:
|
|
477
|
-
raise OperationalException("No portfolio found.")
|
|
478
|
-
|
|
479
|
-
portfolio = portfolios[0]
|
|
480
|
-
|
|
481
|
-
try:
|
|
482
|
-
return self.position_service.find(
|
|
483
|
-
{"portfolio": portfolio.id, "symbol": symbol}
|
|
484
|
-
)
|
|
485
|
-
except OperationalException:
|
|
486
|
-
return None
|
|
487
|
-
|
|
488
|
-
def has_position(
|
|
489
|
-
self,
|
|
490
|
-
symbol,
|
|
491
|
-
market=None,
|
|
492
|
-
identifier=None,
|
|
493
|
-
amount_gt=0,
|
|
494
|
-
amount_gte=None,
|
|
495
|
-
amount_lt=None,
|
|
496
|
-
amount_lte=None
|
|
497
|
-
):
|
|
498
|
-
"""
|
|
499
|
-
Function to check if a position exists. This function will return
|
|
500
|
-
True if a position exists, False otherwise. This function will check
|
|
501
|
-
if the amount > 0 condition by default.
|
|
502
|
-
|
|
503
|
-
param symbol: The symbol of the asset
|
|
504
|
-
param market: The market of the asset
|
|
505
|
-
param identifier: The identifier of the portfolio
|
|
506
|
-
param amount_gt: The amount of the asset must be greater than this
|
|
507
|
-
param amount_gte: The amount of the asset must be greater than
|
|
508
|
-
or equal to this
|
|
509
|
-
param amount_lt: The amount of the asset must be less than this
|
|
510
|
-
param amount_lte: The amount of the asset must be less than
|
|
511
|
-
or equal to this
|
|
512
|
-
|
|
513
|
-
return: True if a position exists, False otherwise
|
|
514
|
-
"""
|
|
515
|
-
|
|
516
|
-
return self.position_exists(
|
|
517
|
-
symbol=symbol,
|
|
518
|
-
market=market,
|
|
519
|
-
identifier=identifier,
|
|
520
|
-
amount_gt=amount_gt,
|
|
521
|
-
amount_gte=amount_gte,
|
|
522
|
-
amount_lt=amount_lt,
|
|
523
|
-
amount_lte=amount_lte
|
|
524
|
-
)
|
|
525
|
-
|
|
526
|
-
def position_exists(
|
|
527
|
-
self,
|
|
528
|
-
symbol,
|
|
529
|
-
market=None,
|
|
530
|
-
identifier=None,
|
|
531
|
-
amount_gt=None,
|
|
532
|
-
amount_gte=None,
|
|
533
|
-
amount_lt=None,
|
|
534
|
-
amount_lte=None
|
|
535
|
-
) -> bool:
|
|
536
|
-
"""
|
|
537
|
-
Function to check if a position exists. This function will return
|
|
538
|
-
True if a position exists, False otherwise. This function will
|
|
539
|
-
not check the amount > 0 condition by default. If you want to
|
|
540
|
-
check if a position exists with an amount greater than 0, you
|
|
541
|
-
can use the amount_gt parameter. If you want to check if a
|
|
542
|
-
position exists with an amount greater than or equal to a
|
|
543
|
-
certain amount, you can use the amount_gte parameter. If you
|
|
544
|
-
want to check if a position exists with an amount less than a
|
|
545
|
-
certain amount, you can use the amount_lt parameter. If you want
|
|
546
|
-
to check if a position exists with an amount less than or equal
|
|
547
|
-
to a certain amount, you can use the amount_lte parameter.
|
|
548
|
-
|
|
549
|
-
It is not recommended to use this method directly because it can
|
|
550
|
-
have adverse effects on the algorithm. It is recommended to use
|
|
551
|
-
the has_position method instead.
|
|
552
|
-
|
|
553
|
-
param symbol: The symbol of the asset
|
|
554
|
-
param market: The market of the asset
|
|
555
|
-
param identifier: The identifier of the portfolio
|
|
556
|
-
param amount_gt: The amount of the asset must be greater than this
|
|
557
|
-
param amount_gte: The amount of the asset must be greater than
|
|
558
|
-
or equal to this
|
|
559
|
-
param amount_lt: The amount of the asset must be less than this
|
|
560
|
-
param amount_lte: The amount of the asset must be less than
|
|
561
|
-
or equal to this
|
|
562
|
-
|
|
563
|
-
return: True if a position exists, False otherwise
|
|
564
|
-
"""
|
|
565
|
-
query_params = {}
|
|
566
|
-
|
|
567
|
-
if market is not None:
|
|
568
|
-
query_params["market"] = market
|
|
569
|
-
|
|
570
|
-
if identifier is not None:
|
|
571
|
-
query_params["identifier"] = identifier
|
|
572
|
-
|
|
573
|
-
if amount_gt is not None:
|
|
574
|
-
query_params["amount_gt"] = amount_gt
|
|
575
|
-
|
|
576
|
-
if amount_gte is not None:
|
|
577
|
-
query_params["amount_gte"] = amount_gte
|
|
578
|
-
|
|
579
|
-
if amount_lt is not None:
|
|
580
|
-
query_params["amount_lt"] = amount_lt
|
|
581
|
-
|
|
582
|
-
if amount_lte is not None:
|
|
583
|
-
query_params["amount_lte"] = amount_lte
|
|
584
|
-
|
|
585
|
-
query_params["symbol"] = symbol
|
|
586
|
-
return self.position_service.exists(query_params)
|
|
587
|
-
|
|
588
|
-
def get_position_percentage_of_portfolio(
|
|
589
|
-
self, symbol, market=None, identifier=None
|
|
590
|
-
) -> float:
|
|
591
|
-
"""
|
|
592
|
-
Returns the percentage of the current total value of the portfolio
|
|
593
|
-
that is allocated to a position. This is calculated by dividing
|
|
594
|
-
the current value of the position by the total current value
|
|
595
|
-
of the portfolio.
|
|
596
|
-
"""
|
|
597
|
-
|
|
598
|
-
query_params = {}
|
|
599
|
-
|
|
600
|
-
if market is not None:
|
|
601
|
-
query_params["market"] = market
|
|
602
|
-
|
|
603
|
-
if identifier is not None:
|
|
604
|
-
query_params["identifier"] = identifier
|
|
605
|
-
|
|
606
|
-
portfolios = self.portfolio_service.get_all(query_params)
|
|
607
|
-
|
|
608
|
-
if not portfolios:
|
|
609
|
-
raise OperationalException("No portfolio found.")
|
|
610
|
-
|
|
611
|
-
portfolio = portfolios[0]
|
|
612
|
-
position = self.position_service.find(
|
|
613
|
-
{"portfolio": portfolio.id, "symbol": symbol}
|
|
614
|
-
)
|
|
615
|
-
full_symbol = f"{position.symbol.upper()}/" \
|
|
616
|
-
f"{portfolio.trading_symbol.upper()}"
|
|
617
|
-
ticker = self._market_data_source_service.get_ticker(
|
|
618
|
-
symbol=full_symbol, market=market
|
|
619
|
-
)
|
|
620
|
-
total = self.get_unallocated() + self.get_allocated()
|
|
621
|
-
return (position.amount * ticker["bid"] / total) * 100
|
|
622
|
-
|
|
623
|
-
def get_position_percentage_of_portfolio_by_net_size(
|
|
624
|
-
self, symbol, market=None, identifier=None
|
|
625
|
-
) -> float:
|
|
626
|
-
"""
|
|
627
|
-
Returns the percentage of the portfolio that is allocated to a
|
|
628
|
-
position. This is calculated by dividing the cost of the position
|
|
629
|
-
by the total net size of the portfolio.
|
|
630
|
-
|
|
631
|
-
The total net size of the portfolio is the initial balance of the
|
|
632
|
-
portfolio plus the all the net gains of your trades.
|
|
633
|
-
"""
|
|
634
|
-
query_params = {}
|
|
635
|
-
|
|
636
|
-
if market is not None:
|
|
637
|
-
query_params["market"] = market
|
|
638
|
-
|
|
639
|
-
if identifier is not None:
|
|
640
|
-
query_params["identifier"] = identifier
|
|
641
|
-
|
|
642
|
-
portfolios = self.portfolio_service.get_all(query_params)
|
|
643
|
-
|
|
644
|
-
if not portfolios:
|
|
645
|
-
raise OperationalException("No portfolio found.")
|
|
646
|
-
|
|
647
|
-
portfolio = portfolios[0]
|
|
648
|
-
position = self.position_service.find(
|
|
649
|
-
{"portfolio": portfolio.id, "symbol": symbol}
|
|
650
|
-
)
|
|
651
|
-
net_size = portfolio.get_net_size()
|
|
652
|
-
return (position.cost / net_size) * 100
|
|
653
|
-
|
|
654
|
-
def close_position(
|
|
655
|
-
self, symbol, market=None, identifier=None, precision=None
|
|
656
|
-
):
|
|
657
|
-
portfolio = self.portfolio_service.find(
|
|
658
|
-
{"market": market, "identifier": identifier}
|
|
659
|
-
)
|
|
660
|
-
position = self.position_service.find(
|
|
661
|
-
{"portfolio": portfolio.id, "symbol": symbol}
|
|
662
|
-
)
|
|
663
|
-
|
|
664
|
-
if position.get_amount() == 0:
|
|
665
|
-
return
|
|
666
|
-
|
|
667
|
-
for order in self.order_service \
|
|
668
|
-
.get_all(
|
|
669
|
-
{
|
|
670
|
-
"position": position.id,
|
|
671
|
-
"status": OrderStatus.OPEN.value
|
|
672
|
-
}
|
|
673
|
-
):
|
|
674
|
-
self.order_service.cancel_order(order)
|
|
675
|
-
|
|
676
|
-
symbol = f"{symbol.upper()}/{portfolio.trading_symbol.upper()}"
|
|
677
|
-
ticker = self._market_data_source_service.get_ticker(
|
|
678
|
-
symbol=symbol, market=market
|
|
679
|
-
)
|
|
680
|
-
self.create_limit_order(
|
|
681
|
-
target_symbol=position.symbol,
|
|
682
|
-
amount=position.get_amount(),
|
|
683
|
-
order_side=OrderSide.SELL.value,
|
|
684
|
-
price=ticker["bid"],
|
|
685
|
-
precision=precision,
|
|
686
|
-
)
|
|
687
|
-
|
|
688
|
-
def add_strategies(self, strategies):
|
|
689
|
-
"""
|
|
690
|
-
Function to add multiple strategies to the algorithm.
|
|
691
|
-
This function will check if there are any duplicate strategies
|
|
692
|
-
with the same name and raise an exception if there are.
|
|
693
|
-
"""
|
|
694
|
-
has_duplicates = False
|
|
695
|
-
|
|
696
|
-
for strategy in strategies:
|
|
697
|
-
from .strategy import TradingStrategy
|
|
698
|
-
if not issubclass(strategy, TradingStrategy):
|
|
699
|
-
raise OperationalException(
|
|
700
|
-
"The strategy must be a subclass of TradingStrategy"
|
|
701
|
-
)
|
|
702
|
-
|
|
703
|
-
if inspect.isclass(strategy):
|
|
704
|
-
strategy = strategy()
|
|
705
|
-
|
|
706
|
-
assert isinstance(strategy, TradingStrategy), \
|
|
707
|
-
OperationalException(
|
|
708
|
-
"Strategy object is not an instance of a TradingStrategy"
|
|
709
|
-
)
|
|
710
|
-
|
|
711
|
-
# Check if there are any duplicate strategies
|
|
712
|
-
for i in range(len(strategies)):
|
|
713
|
-
for j in range(i + 1, len(strategies)):
|
|
714
|
-
if strategies[i].worker_id == strategies[j].worker_id:
|
|
715
|
-
has_duplicates = True
|
|
716
|
-
break
|
|
717
|
-
|
|
718
|
-
if has_duplicates:
|
|
719
|
-
raise OperationalException(
|
|
720
|
-
"There are duplicate strategies with the same name"
|
|
721
|
-
)
|
|
722
|
-
|
|
723
|
-
self._strategies = strategies
|
|
724
|
-
|
|
725
|
-
def add_strategy(self, strategy):
|
|
726
|
-
"""
|
|
727
|
-
Function to add multiple strategies to the algorithm.
|
|
728
|
-
This function will check if there are any duplicate strategies
|
|
729
|
-
with the same name and raise an exception if there are.
|
|
730
|
-
"""
|
|
731
|
-
has_duplicates = False
|
|
732
|
-
from .strategy import TradingStrategy
|
|
733
|
-
|
|
734
|
-
if inspect.isclass(strategy):
|
|
735
|
-
|
|
736
|
-
if not issubclass(strategy, TradingStrategy):
|
|
737
|
-
raise OperationalException(
|
|
738
|
-
"The strategy must be a subclass of TradingStrategy"
|
|
739
|
-
)
|
|
740
|
-
|
|
741
|
-
strategy = strategy()
|
|
742
|
-
|
|
743
|
-
assert isinstance(strategy, TradingStrategy), \
|
|
744
|
-
OperationalException(
|
|
745
|
-
"Strategy object is not an instance of a TradingStrategy"
|
|
746
|
-
)
|
|
747
|
-
|
|
748
|
-
for i in range(len(self._strategies)):
|
|
749
|
-
for j in range(i + 1, len(self._strategies)):
|
|
750
|
-
if self._strategies[i].worker_id == strategy.worker_id:
|
|
751
|
-
has_duplicates = True
|
|
752
|
-
break
|
|
753
|
-
|
|
754
|
-
if has_duplicates:
|
|
755
|
-
raise OperationalException(
|
|
756
|
-
"Can't add strategy, there already exists a strategy "
|
|
757
|
-
"with the same id in the algorithm"
|
|
758
|
-
)
|
|
759
|
-
|
|
760
|
-
if strategy.market_data_sources is not None:
|
|
761
|
-
self.add_data_sources(strategy.market_data_sources)
|
|
762
|
-
|
|
763
|
-
self._strategies.append(strategy)
|
|
764
|
-
|
|
765
|
-
@property
|
|
766
|
-
def strategies(self):
|
|
767
|
-
return self._strategies
|
|
768
|
-
|
|
769
|
-
def get_strategy(self, strategy_id):
|
|
770
|
-
for strategy in self.strategy_orchestrator_service.get_strategies():
|
|
771
|
-
if strategy.worker_id == strategy_id:
|
|
772
|
-
return strategy
|
|
773
|
-
|
|
774
|
-
return None
|
|
775
|
-
|
|
776
|
-
def add_tasks(self, tasks):
|
|
777
|
-
self.strategy_orchestrator_service.add_tasks(tasks)
|
|
778
|
-
|
|
779
|
-
def get_allocated(self, market=None, identifier=None) -> float:
|
|
780
|
-
|
|
781
|
-
if self.portfolio_configuration_service.count() > 1 \
|
|
782
|
-
and identifier is None and market is None:
|
|
783
|
-
raise OperationalException(
|
|
784
|
-
"Multiple portfolios found. Please specify a "
|
|
785
|
-
"portfolio identifier."
|
|
786
|
-
)
|
|
787
|
-
|
|
788
|
-
if market is not None and identifier is not None:
|
|
789
|
-
portfolio_configurations = self.portfolio_configuration_service \
|
|
790
|
-
.get_all()
|
|
791
|
-
|
|
792
|
-
else:
|
|
793
|
-
query_params = {"market": market, "identifier": identifier}
|
|
794
|
-
portfolio_configuration = self.portfolio_configuration_service \
|
|
795
|
-
.find(query_params)
|
|
796
|
-
|
|
797
|
-
if not portfolio_configuration:
|
|
798
|
-
raise OperationalException("No portfolio found.")
|
|
799
|
-
|
|
800
|
-
portfolio_configurations = [portfolio_configuration]
|
|
801
|
-
|
|
802
|
-
if len(portfolio_configurations) == 0:
|
|
803
|
-
raise OperationalException("No portfolio found.")
|
|
804
|
-
|
|
805
|
-
portfolios = []
|
|
806
|
-
|
|
807
|
-
for portfolio_configuration in portfolio_configurations:
|
|
808
|
-
portfolio = self.portfolio_service.find(
|
|
809
|
-
{"identifier": portfolio_configuration.identifier}
|
|
810
|
-
)
|
|
811
|
-
portfolio.configuration = portfolio_configuration
|
|
812
|
-
portfolios.append(portfolio)
|
|
813
|
-
|
|
814
|
-
allocated = 0
|
|
815
|
-
|
|
816
|
-
for portfolio in portfolios:
|
|
817
|
-
positions = self.position_service.get_all(
|
|
818
|
-
{"portfolio": portfolio.id}
|
|
819
|
-
)
|
|
820
|
-
|
|
821
|
-
for position in positions:
|
|
822
|
-
if portfolio.trading_symbol == position.symbol:
|
|
823
|
-
continue
|
|
824
|
-
|
|
825
|
-
symbol = f"{position.symbol.upper()}/" \
|
|
826
|
-
f"{portfolio.trading_symbol.upper()}"
|
|
827
|
-
ticker = self._market_data_source_service.get_ticker(
|
|
828
|
-
symbol=symbol, market=market,
|
|
829
|
-
)
|
|
830
|
-
allocated = allocated + \
|
|
831
|
-
(position.get_amount() * ticker["bid"])
|
|
832
|
-
|
|
833
|
-
return allocated
|
|
834
|
-
|
|
835
|
-
def get_unfilled(self, market=None, identifier=None) -> float:
|
|
836
|
-
|
|
837
|
-
if self.portfolio_configuration_service.count() > 1 \
|
|
838
|
-
and identifier is None and market is None:
|
|
839
|
-
raise OperationalException(
|
|
840
|
-
"Multiple portfolios found. Please specify a "
|
|
841
|
-
"portfolio identifier."
|
|
842
|
-
)
|
|
843
|
-
|
|
844
|
-
if market is not None and identifier is not None:
|
|
845
|
-
portfolio_configurations = self.portfolio_configuration_service \
|
|
846
|
-
.get_all()
|
|
847
|
-
|
|
848
|
-
else:
|
|
849
|
-
query_params = {
|
|
850
|
-
"market": market,
|
|
851
|
-
"identifier": identifier
|
|
852
|
-
}
|
|
853
|
-
portfolio_configurations = [self.portfolio_configuration_service
|
|
854
|
-
.find(query_params)]
|
|
855
|
-
|
|
856
|
-
portfolios = []
|
|
857
|
-
|
|
858
|
-
for portfolio_configuration in portfolio_configurations:
|
|
859
|
-
portfolio = self.portfolio_service.find(
|
|
860
|
-
{"identifier": portfolio_configuration.identifier}
|
|
861
|
-
)
|
|
862
|
-
portfolios.append(portfolio)
|
|
863
|
-
|
|
864
|
-
unfilled = 0
|
|
865
|
-
|
|
866
|
-
for portfolio in portfolios:
|
|
867
|
-
orders = self.order_service.get_all(
|
|
868
|
-
{"status": OrderStatus.OPEN.value, "portfolio": portfolio.id}
|
|
869
|
-
)
|
|
870
|
-
unfilled = unfilled + sum(
|
|
871
|
-
[order.get_amount() * order.get_price() for order in orders]
|
|
872
|
-
)
|
|
873
|
-
|
|
874
|
-
return unfilled
|
|
875
|
-
|
|
876
|
-
def get_portfolio_configurations(self):
|
|
877
|
-
return self.portfolio_configuration_service.get_all()
|
|
878
|
-
|
|
879
|
-
def has_open_buy_orders(self, target_symbol, identifier=None, market=None):
|
|
880
|
-
query_params = {}
|
|
881
|
-
|
|
882
|
-
if identifier is not None:
|
|
883
|
-
portfolio = self.portfolio_service.find(
|
|
884
|
-
{"identifier": identifier}
|
|
885
|
-
)
|
|
886
|
-
query_params["portfolio"] = portfolio.id
|
|
887
|
-
|
|
888
|
-
if market is not None:
|
|
889
|
-
portfolio = self.portfolio_service.find(
|
|
890
|
-
{"market": market}
|
|
891
|
-
)
|
|
892
|
-
query_params["portfolio"] = portfolio.id
|
|
893
|
-
|
|
894
|
-
query_params["target_symbol"] = target_symbol
|
|
895
|
-
query_params["order_side"] = OrderSide.BUY.value
|
|
896
|
-
query_params["status"] = OrderStatus.OPEN.value
|
|
897
|
-
return self.order_service.exists(query_params)
|
|
898
|
-
|
|
899
|
-
def has_open_sell_orders(self, target_symbol, identifier=None,
|
|
900
|
-
market=None):
|
|
901
|
-
query_params = {}
|
|
902
|
-
|
|
903
|
-
if identifier is not None:
|
|
904
|
-
portfolio = self.portfolio_service.find(
|
|
905
|
-
{"identifier": identifier}
|
|
906
|
-
)
|
|
907
|
-
query_params["portfolio"] = portfolio.id
|
|
908
|
-
|
|
909
|
-
if market is not None:
|
|
910
|
-
portfolio = self.portfolio_service.find(
|
|
911
|
-
{"market": market}
|
|
912
|
-
)
|
|
913
|
-
query_params["portfolio"] = portfolio.id
|
|
914
|
-
|
|
915
|
-
query_params["target_symbol"] = target_symbol
|
|
916
|
-
query_params["order_side"] = OrderSide.SELL.value
|
|
917
|
-
query_params["status"] = OrderStatus.OPEN.value
|
|
918
|
-
return self.order_service.exists(query_params)
|
|
919
|
-
|
|
920
|
-
def has_open_orders(
|
|
921
|
-
self, target_symbol=None, identifier=None, market=None
|
|
922
|
-
):
|
|
923
|
-
query_params = {}
|
|
924
|
-
|
|
925
|
-
if identifier is not None:
|
|
926
|
-
portfolio = self.portfolio_service.find(
|
|
927
|
-
{"identifier": identifier}
|
|
928
|
-
)
|
|
929
|
-
query_params["portfolio"] = portfolio.id
|
|
930
|
-
|
|
931
|
-
if market is not None:
|
|
932
|
-
portfolio = self.portfolio_service.find(
|
|
933
|
-
{"market": market}
|
|
934
|
-
)
|
|
935
|
-
query_params["portfolio"] = portfolio.id
|
|
936
|
-
|
|
937
|
-
if target_symbol is not None:
|
|
938
|
-
query_params["target_symbol"] = target_symbol
|
|
939
|
-
|
|
940
|
-
query_params["status"] = OrderStatus.OPEN.value
|
|
941
|
-
return self.order_service.exists(query_params)
|
|
942
|
-
|
|
943
|
-
def check_pending_orders(self):
|
|
944
|
-
self.order_service.check_pending_orders()
|
|
945
|
-
|
|
946
|
-
def get_trades(self, market=None):
|
|
947
|
-
return self.trade_service.get_trades(market)
|
|
948
|
-
|
|
949
|
-
def get_closed_trades(self):
|
|
950
|
-
return self.trade_service.get_closed_trades()
|
|
951
|
-
|
|
952
|
-
def get_open_trades(self, target_symbol=None, market=None):
|
|
953
|
-
return self.trade_service.get_open_trades(target_symbol, market)
|
|
954
|
-
|
|
955
|
-
def close_trade(self, trade, market=None, precision=None) -> None:
|
|
956
|
-
self.trade_service.close_trade(
|
|
957
|
-
trade=trade, market=market, precision=precision
|
|
958
|
-
)
|
|
959
|
-
|
|
960
|
-
def get_number_of_positions(self):
|
|
961
|
-
"""
|
|
962
|
-
Returns the number of positions that have a positive amount.
|
|
963
|
-
"""
|
|
964
|
-
return self.position_service.count({"amount_gt": 0})
|
|
965
|
-
|
|
966
|
-
def has_trading_symbol_position_available(
|
|
967
|
-
self,
|
|
968
|
-
amount_gt=None,
|
|
969
|
-
amount_gte=None,
|
|
970
|
-
percentage_of_portfolio=None,
|
|
971
|
-
market=None
|
|
972
|
-
):
|
|
973
|
-
"""
|
|
974
|
-
Checks if there is a position available for the trading symbol of the
|
|
975
|
-
portfolio. If the amount_gt or amount_gte parameters are specified,
|
|
976
|
-
the amount of the position must be greater than the specified amount.
|
|
977
|
-
If the percentage_of_portfolio parameter is specified, the amount of
|
|
978
|
-
the position must be greater than the net_size of the
|
|
979
|
-
portfolio.
|
|
980
|
-
|
|
981
|
-
:param amount_gt: The amount of the position must be greater than
|
|
982
|
-
this amount.
|
|
983
|
-
:param amount_gte: The amount of the position must be greater than
|
|
984
|
-
or equal to this amount.
|
|
985
|
-
:param percentage_of_portfolio: The amount of the position must be
|
|
986
|
-
greater than the net_size of the portfolio.
|
|
987
|
-
:param market: The market of the portfolio.
|
|
988
|
-
:return: True if there is a trading symbol position available with the
|
|
989
|
-
specified parameters, False otherwise.
|
|
990
|
-
"""
|
|
991
|
-
portfolio = self.portfolio_service.find({"market": market})
|
|
992
|
-
position = self.position_service.find(
|
|
993
|
-
{"portfolio": portfolio.id, "symbol": portfolio.trading_symbol}
|
|
994
|
-
)
|
|
995
|
-
|
|
996
|
-
if amount_gt is not None:
|
|
997
|
-
return position.get_amount() > amount_gt
|
|
998
|
-
|
|
999
|
-
if amount_gte is not None:
|
|
1000
|
-
return position.get_amount() >= amount_gte
|
|
1001
|
-
|
|
1002
|
-
if percentage_of_portfolio is not None:
|
|
1003
|
-
net_size = portfolio.get_net_size()
|
|
1004
|
-
return position.get_amount() >= net_size \
|
|
1005
|
-
* percentage_of_portfolio / 100
|
|
1006
|
-
|
|
1007
|
-
return position.get_amount() > 0
|
|
1008
|
-
|
|
1009
|
-
def strategy(
|
|
1010
|
-
self,
|
|
1011
|
-
function=None,
|
|
1012
|
-
time_unit: TimeUnit = TimeUnit.MINUTE,
|
|
1013
|
-
interval=10,
|
|
1014
|
-
market_data_sources=None,
|
|
1015
|
-
):
|
|
1016
|
-
from .strategy import TradingStrategy
|
|
1017
|
-
|
|
1018
|
-
if function:
|
|
1019
|
-
strategy_object = TradingStrategy(
|
|
1020
|
-
decorated=function,
|
|
1021
|
-
time_unit=time_unit,
|
|
1022
|
-
interval=interval,
|
|
1023
|
-
market_data_sources=market_data_sources
|
|
1024
|
-
)
|
|
1025
|
-
self.add_strategy(strategy_object)
|
|
1026
|
-
else:
|
|
1027
|
-
|
|
1028
|
-
def wrapper(f):
|
|
1029
|
-
self.add_strategy(
|
|
1030
|
-
TradingStrategy(
|
|
1031
|
-
decorated=f,
|
|
1032
|
-
time_unit=time_unit,
|
|
1033
|
-
interval=interval,
|
|
1034
|
-
market_data_sources=market_data_sources,
|
|
1035
|
-
worker_id=f.__name__
|
|
1036
|
-
)
|
|
1037
|
-
)
|
|
1038
|
-
return f
|
|
1039
|
-
|
|
1040
|
-
return wrapper
|
|
1041
|
-
|
|
1042
|
-
def add_task(self, task):
|
|
1043
|
-
if inspect.isclass(task):
|
|
1044
|
-
task = task()
|
|
1045
|
-
|
|
1046
|
-
assert isinstance(task, Task), \
|
|
1047
|
-
OperationalException(
|
|
1048
|
-
"Task object is not an instance of a Task"
|
|
1049
|
-
)
|
|
1050
|
-
|
|
1051
|
-
self._tasks.append(task)
|
|
1052
|
-
|
|
1053
|
-
def add_data_sources(self, data_sources):
|
|
1054
|
-
self._data_sources = data_sources
|
|
1055
|
-
|
|
1056
|
-
@property
|
|
1057
|
-
def tasks(self):
|
|
1058
|
-
return self._tasks
|
|
1059
|
-
|
|
1060
|
-
def get_pending_orders(
|
|
1061
|
-
self, order_side=None, target_symbol=None, portfolio_id=None
|
|
1062
|
-
):
|
|
1063
|
-
"""
|
|
1064
|
-
Function to get all pending orders of the algorithm. If the
|
|
1065
|
-
portfolio_id parameter is specified, the function will return
|
|
1066
|
-
all pending orders of the portfolio with the specified id.
|
|
1067
|
-
"""
|
|
1068
|
-
query_params = {}
|
|
1069
|
-
|
|
1070
|
-
if portfolio_id:
|
|
1071
|
-
query_params["portfolio"] = portfolio_id
|
|
1072
|
-
|
|
1073
|
-
if target_symbol:
|
|
1074
|
-
query_params["target_symbol"] = target_symbol
|
|
1075
|
-
|
|
1076
|
-
if order_side:
|
|
1077
|
-
query_params["order_side"] = order_side
|
|
1078
|
-
|
|
1079
|
-
return self.order_service.get_all({"status": OrderStatus.OPEN.value})
|
|
1080
|
-
|
|
1081
|
-
def get_unfilled_buy_value(self):
|
|
1082
|
-
"""
|
|
1083
|
-
Returns the total value of all unfilled buy orders.
|
|
1084
|
-
"""
|
|
1085
|
-
pending_orders = self.get_pending_orders(
|
|
1086
|
-
order_side=OrderSide.BUY.value
|
|
1087
|
-
)
|
|
1088
|
-
|
|
1089
|
-
return sum(
|
|
1090
|
-
[order.get_amount() * order.get_price()
|
|
1091
|
-
for order in pending_orders]
|
|
1092
|
-
)
|
|
1093
|
-
|
|
1094
|
-
def get_unfilled_sell_value(self):
|
|
1095
|
-
"""
|
|
1096
|
-
Returns the total value of all unfilled buy orders.
|
|
1097
|
-
"""
|
|
1098
|
-
pending_orders = self.get_pending_orders(
|
|
1099
|
-
order_side=OrderSide.SELL.value
|
|
1100
|
-
)
|
|
1101
|
-
|
|
1102
|
-
return sum(
|
|
1103
|
-
[order.get_amount() * order.get_price()
|
|
1104
|
-
for order in pending_orders]
|
|
1105
|
-
)
|