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
|
@@ -3,6 +3,7 @@ from .portfolio_configuration_service import PortfolioConfigurationService
|
|
|
3
3
|
from .portfolio_service import PortfolioService
|
|
4
4
|
from .portfolio_snapshot_service import PortfolioSnapshotService
|
|
5
5
|
from .portfolio_sync_service import PortfolioSyncService
|
|
6
|
+
from .portfolio_provider_lookup import PortfolioProviderLookup
|
|
6
7
|
|
|
7
8
|
__all__ = [
|
|
8
9
|
"PortfolioConfigurationService",
|
|
@@ -10,5 +11,6 @@ __all__ = [
|
|
|
10
11
|
"PortfolioSnapshotService",
|
|
11
12
|
"PortfolioService",
|
|
12
13
|
"PortfolioSnapshotService",
|
|
13
|
-
"BacktestPortfolioService"
|
|
14
|
+
"BacktestPortfolioService",
|
|
15
|
+
"PortfolioProviderLookup"
|
|
14
16
|
]
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
from
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.domain import PortfolioConfiguration, \
|
|
4
|
+
OperationalException
|
|
2
5
|
from .portfolio_service import PortfolioService
|
|
3
6
|
|
|
4
7
|
|
|
@@ -9,12 +12,43 @@ class BacktestPortfolioService(PortfolioService):
|
|
|
9
12
|
not check if the initial balance is present on the exchange or broker.
|
|
10
13
|
"""
|
|
11
14
|
def create_portfolio_from_configuration(
|
|
12
|
-
self,
|
|
15
|
+
self,
|
|
16
|
+
portfolio_configuration: PortfolioConfiguration,
|
|
17
|
+
initial_amount=None,
|
|
18
|
+
created_at: datetime = None
|
|
13
19
|
):
|
|
20
|
+
"""
|
|
21
|
+
Wil create a portfolio from a portfolio configuration for backtesting.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
portfolio_configuration (PortfolioConfiguration):
|
|
25
|
+
Portfolio configuration to create the portfolio from
|
|
26
|
+
initial_amount (Decimal): Initial balance for the portfolio
|
|
27
|
+
created_at (datetime): The date and time when the portfolio
|
|
28
|
+
is created. If not provided, the current date and time
|
|
29
|
+
will be used.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Portfolio: The created portfolio
|
|
33
|
+
"""
|
|
34
|
+
amount = portfolio_configuration.initial_balance
|
|
35
|
+
|
|
36
|
+
if initial_amount is not None:
|
|
37
|
+
amount = initial_amount
|
|
38
|
+
|
|
39
|
+
if amount is None:
|
|
40
|
+
raise OperationalException(
|
|
41
|
+
"Initial amount is required as a parameter or the " +
|
|
42
|
+
"'initial_balance' attribute needs to be set on the "
|
|
43
|
+
"portfolio configuration before running the backtest."
|
|
44
|
+
)
|
|
45
|
+
|
|
14
46
|
data = {
|
|
15
47
|
"identifier": portfolio_configuration.identifier,
|
|
16
48
|
"market": portfolio_configuration.market,
|
|
17
49
|
"trading_symbol": portfolio_configuration.trading_symbol,
|
|
18
|
-
"unallocated":
|
|
50
|
+
"unallocated": amount,
|
|
51
|
+
"initialized": True,
|
|
52
|
+
"created_at": created_at
|
|
19
53
|
}
|
|
20
54
|
return self.create(data)
|
|
@@ -6,6 +6,11 @@ logger = logging.getLogger(__name__)
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class PortfolioConfigurationService:
|
|
9
|
+
"""
|
|
10
|
+
Service to manage portfolio configurations. This service will
|
|
11
|
+
manage the portfolio configurations that the user has
|
|
12
|
+
registered in the app.
|
|
13
|
+
"""
|
|
9
14
|
|
|
10
15
|
def __init__(self, portfolio_repository, position_repository):
|
|
11
16
|
self.portfolio_repository = portfolio_repository
|
|
@@ -17,14 +22,22 @@ class PortfolioConfigurationService:
|
|
|
17
22
|
|
|
18
23
|
def get(self, identifier):
|
|
19
24
|
portfolio_configuration = next(
|
|
20
|
-
(
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
(
|
|
26
|
+
portfolio_configuration for portfolio_configuration in
|
|
27
|
+
self.portfolio_configurations
|
|
28
|
+
if portfolio_configuration.identifier.upper() ==
|
|
29
|
+
identifier.upper()),
|
|
23
30
|
None
|
|
24
31
|
)
|
|
25
32
|
|
|
26
|
-
if portfolio_configuration is None:
|
|
27
|
-
|
|
33
|
+
# if portfolio_configuration is None:
|
|
34
|
+
# raise ApiException(
|
|
35
|
+
# f'Portfolio configuration not '
|
|
36
|
+
# f'found for {identifier}'
|
|
37
|
+
# " Please make sure that you have registered a portfolio "
|
|
38
|
+
# "configuration for the portfolio you are trying to use",
|
|
39
|
+
# 404
|
|
40
|
+
# )
|
|
28
41
|
|
|
29
42
|
return portfolio_configuration
|
|
30
43
|
|
|
@@ -36,14 +49,15 @@ class PortfolioConfigurationService:
|
|
|
36
49
|
return next(
|
|
37
50
|
(portfolio_configuration for portfolio_configuration in
|
|
38
51
|
self.portfolio_configurations if
|
|
39
|
-
portfolio_configuration.market == market.
|
|
52
|
+
portfolio_configuration.market.upper() == market.upper()),
|
|
40
53
|
None
|
|
41
54
|
)
|
|
42
55
|
elif identifier is not None:
|
|
43
56
|
return next(
|
|
44
57
|
(portfolio_configuration for portfolio_configuration in
|
|
45
|
-
|
|
46
|
-
|
|
58
|
+
self.portfolio_configurations if
|
|
59
|
+
portfolio_configuration.identifier.upper()
|
|
60
|
+
== identifier.upper()),
|
|
47
61
|
None
|
|
48
62
|
)
|
|
49
63
|
elif market is None and identifier is None:
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from typing import List, Union
|
|
4
|
+
|
|
5
|
+
from investing_algorithm_framework.domain import ImproperlyConfigured, \
|
|
6
|
+
PortfolioProvider
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PortfolioProviderLookup:
|
|
12
|
+
"""
|
|
13
|
+
Efficient lookup for portfolio providers based on market in O(1) time.
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.portfolio_providers = []
|
|
17
|
+
|
|
18
|
+
# These will be our lookup tables
|
|
19
|
+
self.portfolio_provider_lookup = defaultdict()
|
|
20
|
+
|
|
21
|
+
def add_portfolio_provider(self, portfolio_provider: PortfolioProvider):
|
|
22
|
+
"""
|
|
23
|
+
Add a portfolio provider to the lookup table.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
portfolio_provider (PortfolioProvider): The portfolio provider
|
|
27
|
+
to be added.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
None
|
|
31
|
+
"""
|
|
32
|
+
self.portfolio_providers.append(portfolio_provider)
|
|
33
|
+
|
|
34
|
+
def register_portfolio_provider_for_market(self, market) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Register a portfolio provider for a specific market.
|
|
37
|
+
This method will create a lookup table for efficient access to
|
|
38
|
+
portfolio providers based on market. It will use the
|
|
39
|
+
portfolio providers that are currently registered in the
|
|
40
|
+
portfolio_providers list. The lookup table will be a dictionary
|
|
41
|
+
where the key is the market and the value is the portfolio provider.
|
|
42
|
+
|
|
43
|
+
This method will also check if the portfolio provider supports
|
|
44
|
+
the market. If no portfolio provider is found for the market,
|
|
45
|
+
it will raise an ImproperlyConfigured exception.
|
|
46
|
+
|
|
47
|
+
If multiple portfolio providers are found for the market,
|
|
48
|
+
it will sort them by priority and pick the best one.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
market:
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
None
|
|
55
|
+
"""
|
|
56
|
+
matches = []
|
|
57
|
+
for portfolio_provider in self.portfolio_providers:
|
|
58
|
+
if portfolio_provider.supports_market(market):
|
|
59
|
+
matches.append(portfolio_provider)
|
|
60
|
+
|
|
61
|
+
if len(matches) == 0:
|
|
62
|
+
raise ImproperlyConfigured(
|
|
63
|
+
f"No portfolio provider found for market "
|
|
64
|
+
f"{market}. Cannot configure portfolio."
|
|
65
|
+
f" Please make sure that you have registered a portfolio "
|
|
66
|
+
f"provider for the market you are trying to use"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Sort by priority and pick the best one
|
|
70
|
+
best_provider = sorted(matches, key=lambda x: x.priority)[0]
|
|
71
|
+
self.portfolio_provider_lookup[market] = best_provider
|
|
72
|
+
|
|
73
|
+
def get_portfolio_provider(self, market) -> Union[PortfolioProvider, None]:
|
|
74
|
+
"""
|
|
75
|
+
Get the portfolio provider for a specific market.
|
|
76
|
+
This method will return the portfolio provider for the given market.
|
|
77
|
+
If no portfolio provider is found, it will return None.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
market:
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
PortfolioProvider: The portfolio provider for the given market.
|
|
84
|
+
"""
|
|
85
|
+
return self.portfolio_provider_lookup.get(market, None)
|
|
86
|
+
|
|
87
|
+
def get_all(self) -> List[PortfolioProvider]:
|
|
88
|
+
"""
|
|
89
|
+
Get all portfolio providers.
|
|
90
|
+
This method will return all portfolio providers that are currently
|
|
91
|
+
registered in the portfolio_providers list.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
List[PortfolioProvider]: A list of all portfolio providers.
|
|
95
|
+
"""
|
|
96
|
+
return self.portfolio_providers
|
|
97
|
+
|
|
98
|
+
def reset(self):
|
|
99
|
+
"""
|
|
100
|
+
Function to reset the order executor lookup table
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
None
|
|
104
|
+
"""
|
|
105
|
+
self.portfolio_provider_lookup = defaultdict()
|
|
106
|
+
self.portfolio_providers = []
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
|
-
from investing_algorithm_framework.domain import
|
|
5
|
-
|
|
4
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
5
|
+
MarketCredentialService, Portfolio, Environment, ENVIRONMENT
|
|
6
6
|
from investing_algorithm_framework.services.configuration_service import \
|
|
7
7
|
ConfigurationService
|
|
8
8
|
from investing_algorithm_framework.services.repository_service \
|
|
@@ -21,22 +21,22 @@ class PortfolioService(RepositoryService):
|
|
|
21
21
|
def __init__(
|
|
22
22
|
self,
|
|
23
23
|
configuration_service: ConfigurationService,
|
|
24
|
-
market_service: MarketService,
|
|
25
24
|
market_credential_service: MarketCredentialService,
|
|
26
25
|
order_service,
|
|
27
|
-
portfolio_repository,
|
|
28
26
|
portfolio_configuration_service,
|
|
29
27
|
portfolio_snapshot_service,
|
|
30
|
-
position_service
|
|
28
|
+
position_service,
|
|
29
|
+
portfolio_repository,
|
|
30
|
+
portfolio_provider_lookup
|
|
31
31
|
):
|
|
32
|
+
super().__init__(repository=portfolio_repository)
|
|
32
33
|
self.configuration_service = configuration_service
|
|
33
34
|
self.market_credential_service = market_credential_service
|
|
34
|
-
self.market_service = market_service
|
|
35
35
|
self.portfolio_configuration_service = portfolio_configuration_service
|
|
36
36
|
self.order_service = order_service
|
|
37
37
|
self.portfolio_snapshot_service = portfolio_snapshot_service
|
|
38
38
|
self.position_service = position_service
|
|
39
|
-
|
|
39
|
+
self.portfolio_provider_lookup = portfolio_provider_lookup
|
|
40
40
|
|
|
41
41
|
def find(self, query_params):
|
|
42
42
|
portfolio = self.repository.find(query_params)
|
|
@@ -47,6 +47,36 @@ class PortfolioService(RepositoryService):
|
|
|
47
47
|
|
|
48
48
|
def create(self, data):
|
|
49
49
|
unallocated = data.get("unallocated", 0)
|
|
50
|
+
market = data.get("market")
|
|
51
|
+
|
|
52
|
+
# Check if the app is in backtest mode
|
|
53
|
+
config = self.configuration_service.get_config()
|
|
54
|
+
environment = config[ENVIRONMENT]
|
|
55
|
+
|
|
56
|
+
if not Environment.BACKTEST.equals(environment):
|
|
57
|
+
# Check if there is a market credential
|
|
58
|
+
# for the portfolio configuration
|
|
59
|
+
market_credential = self.market_credential_service.get(market)
|
|
60
|
+
|
|
61
|
+
if market_credential is None:
|
|
62
|
+
raise OperationalException(
|
|
63
|
+
f"No market credential found for market "
|
|
64
|
+
f"{market}. Cannot "
|
|
65
|
+
f"initialize portfolio configuration."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
identifier = data.get("identifier")
|
|
69
|
+
# Check if the portfolio already exists
|
|
70
|
+
# If the portfolio already exists, return the portfolio after checking
|
|
71
|
+
# the unallocated balance of the portfolio on the exchange
|
|
72
|
+
if identifier is not None and self.repository.exists(
|
|
73
|
+
{"identifier": identifier}
|
|
74
|
+
):
|
|
75
|
+
portfolio = self.repository.find(
|
|
76
|
+
{"identifier": identifier}
|
|
77
|
+
)
|
|
78
|
+
return portfolio
|
|
79
|
+
data["initial_balance"] = unallocated
|
|
50
80
|
portfolio = super(PortfolioService, self).create(data)
|
|
51
81
|
self.position_service.create(
|
|
52
82
|
{
|
|
@@ -56,30 +86,13 @@ class PortfolioService(RepositoryService):
|
|
|
56
86
|
"cost": unallocated
|
|
57
87
|
}
|
|
58
88
|
)
|
|
59
|
-
self.create_snapshot(portfolio.id, created_at=portfolio.created_at)
|
|
60
89
|
return portfolio
|
|
61
90
|
|
|
62
|
-
def create_snapshot(self, portfolio_id, created_at=None):
|
|
63
|
-
|
|
64
|
-
if created_at is None:
|
|
65
|
-
created_at = datetime.utcnow()
|
|
66
|
-
|
|
67
|
-
portfolio = self.get(portfolio_id)
|
|
68
|
-
pending_orders = self.order_service.get_all(
|
|
69
|
-
{
|
|
70
|
-
"order_side": OrderSide.BUY.value,
|
|
71
|
-
"status": OrderStatus.OPEN.value,
|
|
72
|
-
"portfolio_id": portfolio.id
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
return self.portfolio_snapshot_service.create_snapshot(
|
|
76
|
-
portfolio,
|
|
77
|
-
pending_orders=pending_orders,
|
|
78
|
-
created_at=created_at
|
|
79
|
-
)
|
|
80
|
-
|
|
81
91
|
def create_portfolio_from_configuration(
|
|
82
|
-
self,
|
|
92
|
+
self,
|
|
93
|
+
portfolio_configuration,
|
|
94
|
+
initial_amount=None,
|
|
95
|
+
created_at: datetime = None
|
|
83
96
|
) -> Portfolio:
|
|
84
97
|
"""
|
|
85
98
|
Method to create a portfolio based on a portfolio configuration.
|
|
@@ -87,6 +100,17 @@ class PortfolioService(RepositoryService):
|
|
|
87
100
|
provided. If the portfolio already exists, it will be returned.
|
|
88
101
|
|
|
89
102
|
If the portfolio does not exist, it will be created.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
portfolio_configuration (PortfolioConfiguration)
|
|
106
|
+
Portfolio configuration to create the portfolio from
|
|
107
|
+
initial_amount (Decimal): Initial balance for the portfolio
|
|
108
|
+
created_at (datetime): The date and time when the portfolio
|
|
109
|
+
is created. If not provided, the current date and time
|
|
110
|
+
will be used.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Portfolio: Portfolio created from the configuration
|
|
90
114
|
"""
|
|
91
115
|
logger.info("Creating portfolios")
|
|
92
116
|
|
|
@@ -117,4 +141,48 @@ class PortfolioService(RepositoryService):
|
|
|
117
141
|
portfolio = Portfolio.from_portfolio_configuration(
|
|
118
142
|
portfolio_configuration
|
|
119
143
|
)
|
|
144
|
+
data = portfolio.to_dict()
|
|
145
|
+
|
|
146
|
+
if created_at is not None:
|
|
147
|
+
data["created_at"] = created_at
|
|
148
|
+
|
|
149
|
+
if initial_amount is not None:
|
|
150
|
+
data["unallocated"] = initial_amount
|
|
151
|
+
|
|
152
|
+
self.create(data)
|
|
153
|
+
|
|
120
154
|
return portfolio
|
|
155
|
+
|
|
156
|
+
def update_portfolio_with_filled_order(self, order, filled_amount) -> None:
|
|
157
|
+
"""
|
|
158
|
+
Function to update the portfolio with filled order.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
order: Order object
|
|
162
|
+
filled_amount: float
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
None
|
|
166
|
+
"""
|
|
167
|
+
filled_size = filled_amount * order.get_price()
|
|
168
|
+
|
|
169
|
+
if filled_size <= 0:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
logger.info(
|
|
173
|
+
f"Syncing portfolio with filled sell "
|
|
174
|
+
f"order {order.get_id()} with filled amount "
|
|
175
|
+
f"{filled_amount}"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
position = self.position_service.get(order.position_id)
|
|
179
|
+
portfolio = self.get(position.portfolio_id)
|
|
180
|
+
|
|
181
|
+
self.update(
|
|
182
|
+
portfolio.id,
|
|
183
|
+
{
|
|
184
|
+
"unallocated": portfolio.get_unallocated() + filled_size,
|
|
185
|
+
"total_trade_volume":
|
|
186
|
+
portfolio.get_total_trade_volume() + filled_size,
|
|
187
|
+
}
|
|
188
|
+
)
|
|
@@ -1,67 +1,136 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from investing_algorithm_framework.domain import OrderStatus, \
|
|
2
|
+
PortfolioSnapshot
|
|
3
3
|
from investing_algorithm_framework.services.repository_service import \
|
|
4
4
|
RepositoryService
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class PortfolioSnapshotService(RepositoryService):
|
|
8
|
+
"""
|
|
9
|
+
Service to manage portfolio snapshots. This service will create snapshots
|
|
10
|
+
of the portfolio at specific intervals or based on certain events.
|
|
11
|
+
"""
|
|
8
12
|
|
|
9
13
|
def __init__(
|
|
10
14
|
self,
|
|
11
15
|
repository,
|
|
16
|
+
portfolio_repository,
|
|
17
|
+
order_repository,
|
|
12
18
|
position_repository,
|
|
13
|
-
position_snapshot_service
|
|
19
|
+
position_snapshot_service,
|
|
20
|
+
data_provider_service,
|
|
14
21
|
):
|
|
22
|
+
self.order_repository = order_repository
|
|
15
23
|
self.position_snapshot_service = position_snapshot_service
|
|
24
|
+
self.portfolio_repository = portfolio_repository
|
|
16
25
|
self.position_repository = position_repository
|
|
26
|
+
self.data_provider_service = data_provider_service
|
|
17
27
|
super(PortfolioSnapshotService, self).__init__(repository)
|
|
18
28
|
|
|
19
29
|
def create_snapshot(
|
|
20
30
|
self,
|
|
21
31
|
portfolio,
|
|
22
|
-
|
|
32
|
+
created_at,
|
|
33
|
+
cash_flow=0,
|
|
23
34
|
created_orders=None,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
open_orders=None,
|
|
36
|
+
positions=None,
|
|
37
|
+
save=True
|
|
38
|
+
) -> PortfolioSnapshot:
|
|
39
|
+
"""
|
|
40
|
+
Function to create a snapshot of the portfolio. During
|
|
41
|
+
creation, it will calculate the pending value of the
|
|
42
|
+
portfolio based on the pending orders and created orders. Also,
|
|
43
|
+
it will calculate the total value of the portfolio based on the
|
|
44
|
+
current positions and the unallocated cash. It will do this by
|
|
45
|
+
fetching the current ticker prices for each position in the portfolio.
|
|
46
|
+
This function will also create position snapshots for each position
|
|
47
|
+
in the portfolio and associate them with the snapshot.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
portfolio (Portfolio): The portfolio to create a snapshot for.
|
|
51
|
+
created_at (datetime, optional): The date and time when the
|
|
52
|
+
snapshot was created. If not provided, the current date
|
|
53
|
+
and time will be used.
|
|
54
|
+
cash_flow (float, optional): The cash flow to include
|
|
55
|
+
in the snapshot.
|
|
56
|
+
created_orders (list, optional): A list of created orders
|
|
57
|
+
to consider when calculating the pending value.
|
|
58
|
+
open_orders (list, optional): A list of open orders
|
|
59
|
+
to consider when calculating the pending value.
|
|
60
|
+
positions (list, optional): A list of positions to consider
|
|
61
|
+
when calculating the total value of the portfolio.
|
|
62
|
+
save (bool, optional): Whether to save the snapshot to
|
|
63
|
+
the database.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
PortfolioSnapshot: The created snapshot of the portfolio.
|
|
67
|
+
"""
|
|
27
68
|
pending_value = 0
|
|
69
|
+
pending_symbols = set()
|
|
70
|
+
allocated = 0
|
|
71
|
+
|
|
72
|
+
if open_orders is None:
|
|
73
|
+
open_orders = self.order_repository.get_all(
|
|
74
|
+
{
|
|
75
|
+
"status": OrderStatus.OPEN.value,
|
|
76
|
+
"portfolio_id": portfolio.id
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
if created_orders is None:
|
|
81
|
+
created_orders = self.order_repository.get_all(
|
|
82
|
+
{
|
|
83
|
+
"status": OrderStatus.CREATED.value,
|
|
84
|
+
"portfolio_id": portfolio.id
|
|
85
|
+
}
|
|
86
|
+
)
|
|
28
87
|
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
88
|
+
if positions is None:
|
|
89
|
+
positions = self.position_repository.get_all(
|
|
90
|
+
{"portfolio": portfolio.id}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
for order in created_orders:
|
|
94
|
+
pending_value += order.get_price() * order.get_amount()
|
|
95
|
+
pending_symbols.add(order.get_symbol())
|
|
96
|
+
|
|
97
|
+
for order in open_orders:
|
|
98
|
+
pending_value += order.get_price() * order.get_remaining()
|
|
99
|
+
pending_symbols.add(order.get_symbol())
|
|
100
|
+
|
|
101
|
+
total_value = portfolio.get_unallocated() + pending_value
|
|
32
102
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
pending_value += order.get_price() * order.get_remaining()
|
|
103
|
+
for position in \
|
|
104
|
+
self.position_repository.get_all({"portfolio": portfolio.id}):
|
|
36
105
|
|
|
37
|
-
|
|
38
|
-
|
|
106
|
+
if position.get_symbol() != portfolio.get_trading_symbol():
|
|
107
|
+
symbol_pair = f"{position.get_symbol()}/" \
|
|
108
|
+
f"{portfolio.get_trading_symbol()}"
|
|
109
|
+
ticker = self.data_provider_service.get_ticker_data(
|
|
110
|
+
symbol=symbol_pair,
|
|
111
|
+
market=portfolio.market,
|
|
112
|
+
date=created_at
|
|
113
|
+
)
|
|
114
|
+
# Calculate the position worth
|
|
115
|
+
position_worth = position.get_amount() * ticker["bid"]
|
|
116
|
+
allocated += position_worth
|
|
117
|
+
total_value += position_worth
|
|
39
118
|
|
|
40
119
|
data = {
|
|
41
120
|
"portfolio_id": portfolio.id,
|
|
42
121
|
"trading_symbol": portfolio.trading_symbol,
|
|
43
122
|
"pending_value": pending_value,
|
|
44
123
|
"unallocated": portfolio.unallocated,
|
|
124
|
+
"net_size": portfolio.net_size,
|
|
45
125
|
"total_net_gain": portfolio.total_net_gain,
|
|
46
126
|
"total_revenue": portfolio.total_revenue,
|
|
47
127
|
"total_cost": portfolio.total_cost,
|
|
48
128
|
"cash_flow": cash_flow,
|
|
49
129
|
"created_at": created_at,
|
|
130
|
+
"total_value": total_value,
|
|
50
131
|
}
|
|
51
|
-
snapshot = self.create(data)
|
|
52
|
-
positions = self.position_repository.get_all(
|
|
53
|
-
{"portfolio": portfolio.id}
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
for position in positions:
|
|
57
|
-
self.position_snapshot_service.create_snapshot(
|
|
58
|
-
snapshot.id, position
|
|
59
|
-
)
|
|
60
|
-
|
|
132
|
+
snapshot = self.create(data, save=save)
|
|
61
133
|
return snapshot
|
|
62
134
|
|
|
63
135
|
def get_latest_snapshot(self, portfolio_id):
|
|
64
136
|
pass
|
|
65
|
-
|
|
66
|
-
def get_snapshots(self, portfolio_id, start_date=None, end_date=None):
|
|
67
|
-
pass
|