investing-algorithm-framework 7.19.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of investing-algorithm-framework might be problematic. Click here for more details.
- investing_algorithm_framework/__init__.py +197 -0
- investing_algorithm_framework/app/__init__.py +47 -0
- 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 +2204 -0
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1667 -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/__init__.py +35 -0
- investing_algorithm_framework/app/stateless/action_handlers/__init__.py +84 -0
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +8 -0
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +15 -0
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +40 -0
- investing_algorithm_framework/app/stateless/exception_handler.py +40 -0
- investing_algorithm_framework/app/strategy.py +675 -0
- investing_algorithm_framework/app/task.py +41 -0
- investing_algorithm_framework/app/web/__init__.py +5 -0
- investing_algorithm_framework/app/web/controllers/__init__.py +13 -0
- investing_algorithm_framework/app/web/controllers/orders.py +20 -0
- investing_algorithm_framework/app/web/controllers/portfolio.py +20 -0
- investing_algorithm_framework/app/web/controllers/positions.py +18 -0
- investing_algorithm_framework/app/web/create_app.py +20 -0
- investing_algorithm_framework/app/web/error_handler.py +59 -0
- investing_algorithm_framework/app/web/responses.py +20 -0
- investing_algorithm_framework/app/web/run_strategies.py +4 -0
- investing_algorithm_framework/app/web/schemas/__init__.py +12 -0
- investing_algorithm_framework/app/web/schemas/order.py +12 -0
- investing_algorithm_framework/app/web/schemas/portfolio.py +22 -0
- investing_algorithm_framework/app/web/schemas/position.py +15 -0
- investing_algorithm_framework/app/web/setup_cors.py +6 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +207 -0
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +499 -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 +54 -0
- investing_algorithm_framework/dependency_container.py +155 -0
- investing_algorithm_framework/domain/__init__.py +148 -0
- 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 +435 -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 +111 -0
- investing_algorithm_framework/domain/constants.py +83 -0
- investing_algorithm_framework/domain/data_provider.py +334 -0
- investing_algorithm_framework/domain/data_structures.py +42 -0
- investing_algorithm_framework/domain/decimal_parsing.py +40 -0
- investing_algorithm_framework/domain/exceptions.py +112 -0
- investing_algorithm_framework/domain/models/__init__.py +43 -0
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +25 -0
- 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/__init__.py +5 -0
- investing_algorithm_framework/domain/models/market/market_credential.py +88 -0
- investing_algorithm_framework/domain/models/order/__init__.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +384 -0
- investing_algorithm_framework/domain/models/order/order_side.py +36 -0
- investing_algorithm_framework/domain/models/order/order_status.py +37 -0
- investing_algorithm_framework/domain/models/order/order_type.py +30 -0
- investing_algorithm_framework/domain/models/portfolio/__init__.py +9 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +169 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +93 -0
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +4 -0
- investing_algorithm_framework/domain/models/position/position.py +68 -0
- investing_algorithm_framework/domain/models/position/position_snapshot.py +47 -0
- investing_algorithm_framework/domain/models/snapshot_interval.py +45 -0
- investing_algorithm_framework/domain/models/strategy_profile.py +33 -0
- investing_algorithm_framework/domain/models/time_frame.py +153 -0
- investing_algorithm_framework/domain/models/time_interval.py +124 -0
- investing_algorithm_framework/domain/models/time_unit.py +149 -0
- investing_algorithm_framework/domain/models/tracing/__init__.py +0 -0
- investing_algorithm_framework/domain/models/tracing/trace.py +23 -0
- investing_algorithm_framework/domain/models/trade/__init__.py +13 -0
- investing_algorithm_framework/domain/models/trade/trade.py +388 -0
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +34 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +267 -0
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +303 -0
- investing_algorithm_framework/domain/order_executor.py +112 -0
- investing_algorithm_framework/domain/portfolio_provider.py +118 -0
- investing_algorithm_framework/domain/positions/__init__.py +4 -0
- investing_algorithm_framework/domain/positions/position_size.py +41 -0
- investing_algorithm_framework/domain/services/__init__.py +11 -0
- investing_algorithm_framework/domain/services/market_credential_service.py +37 -0
- investing_algorithm_framework/domain/services/portfolios/__init__.py +5 -0
- investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py +9 -0
- investing_algorithm_framework/domain/services/rounding_service.py +27 -0
- investing_algorithm_framework/domain/services/state_handler.py +38 -0
- investing_algorithm_framework/domain/stateless_actions.py +7 -0
- investing_algorithm_framework/domain/strategy.py +44 -0
- investing_algorithm_framework/domain/utils/__init__.py +27 -0
- investing_algorithm_framework/domain/utils/csv.py +104 -0
- 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 +41 -0
- investing_algorithm_framework/domain/utils/signatures.py +17 -0
- investing_algorithm_framework/domain/utils/stoppable_thread.py +26 -0
- investing_algorithm_framework/domain/utils/synchronized.py +12 -0
- investing_algorithm_framework/download_data.py +108 -0
- investing_algorithm_framework/infrastructure/__init__.py +50 -0
- 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 +10 -0
- investing_algorithm_framework/infrastructure/database/sql_alchemy.py +120 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +16 -0
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/model_extension.py +6 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/order/order.py +124 -0
- 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 +4 -0
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/sql_portfolio.py +114 -0
- investing_algorithm_framework/infrastructure/models/position/__init__.py +4 -0
- investing_algorithm_framework/infrastructure/models/position/position.py +63 -0
- investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +23 -0
- 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 +40 -0
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +41 -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 +21 -0
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +96 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +30 -0
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +66 -0
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +299 -0
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +71 -0
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +23 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +23 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +7 -0
- 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 +132 -0
- investing_algorithm_framework/services/backtesting/__init__.py +5 -0
- investing_algorithm_framework/services/backtesting/backtest_service.py +651 -0
- investing_algorithm_framework/services/configuration_service.py +96 -0
- 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 +40 -0
- 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/price_efficiency.py +57 -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 +9 -0
- investing_algorithm_framework/services/order_service/order_backtest_service.py +178 -0
- investing_algorithm_framework/services/order_service/order_executor_lookup.py +110 -0
- investing_algorithm_framework/services/order_service/order_service.py +826 -0
- investing_algorithm_framework/services/portfolios/__init__.py +16 -0
- investing_algorithm_framework/services/portfolios/backtest_portfolio_service.py +54 -0
- investing_algorithm_framework/services/portfolios/portfolio_configuration_service.py +75 -0
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +106 -0
- investing_algorithm_framework/services/portfolios/portfolio_service.py +188 -0
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +136 -0
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +182 -0
- investing_algorithm_framework/services/positions/__init__.py +7 -0
- investing_algorithm_framework/services/positions/position_service.py +210 -0
- investing_algorithm_framework/services/positions/position_snapshot_service.py +18 -0
- investing_algorithm_framework/services/repository_service.py +40 -0
- investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
- investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +132 -0
- investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +66 -0
- investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +41 -0
- investing_algorithm_framework/services/trade_service/__init__.py +3 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1083 -0
- investing_algorithm_framework-7.19.14.dist-info/LICENSE +201 -0
- investing_algorithm_framework-7.19.14.dist-info/METADATA +459 -0
- investing_algorithm_framework-7.19.14.dist-info/RECORD +260 -0
- investing_algorithm_framework-7.19.14.dist-info/WHEEL +4 -0
- investing_algorithm_framework-7.19.14.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .order_repository import SQLOrderRepository
|
|
2
|
+
from .order_metadata_repository import SQLOrderMetadataRepository
|
|
3
|
+
from .portfolio_repository import SQLPortfolioRepository
|
|
4
|
+
from .portfolio_snapshot_repository import SQLPortfolioSnapshotRepository
|
|
5
|
+
from .position_repository import SQLPositionRepository
|
|
6
|
+
from .position_snapshot_repository import SQLPositionSnapshotRepository
|
|
7
|
+
from .trade_repository import SQLTradeRepository
|
|
8
|
+
from .trade_stop_loss_repository import SQLTradeStopLossRepository
|
|
9
|
+
from .trade_take_profit_repository import SQLTradeTakeProfitRepository
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"SQLOrderRepository",
|
|
13
|
+
"SQLPositionRepository",
|
|
14
|
+
"SQLPositionSnapshotRepository",
|
|
15
|
+
"SQLPortfolioRepository",
|
|
16
|
+
"SQLPortfolioSnapshotRepository",
|
|
17
|
+
"SQLTradeRepository",
|
|
18
|
+
"SQLTradeTakeProfitRepository",
|
|
19
|
+
"SQLTradeStopLossRepository",
|
|
20
|
+
"SQLOrderMetadataRepository"
|
|
21
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from investing_algorithm_framework.infrastructure.models import \
|
|
2
|
+
SQLOrderMetadata
|
|
3
|
+
from .repository import Repository
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SQLOrderMetadataRepository(Repository):
|
|
7
|
+
base_class = SQLOrderMetadata
|
|
8
|
+
DEFAULT_NOT_FOUND_MESSAGE = "The requested order metadata was not found"
|
|
9
|
+
|
|
10
|
+
def _apply_query_params(self, db, query, query_params):
|
|
11
|
+
|
|
12
|
+
if "order_id" in query_params:
|
|
13
|
+
query = query.filter(
|
|
14
|
+
SQLOrderMetadata.order_id == query_params["order_id"]
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return query
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from investing_algorithm_framework.domain import OrderStatus, OrderType, \
|
|
2
|
+
OrderSide
|
|
3
|
+
from investing_algorithm_framework.infrastructure.models import SQLOrder, \
|
|
4
|
+
SQLPosition, SQLPortfolio
|
|
5
|
+
from .repository import Repository
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SQLOrderRepository(Repository):
|
|
9
|
+
base_class = SQLOrder
|
|
10
|
+
DEFAULT_NOT_FOUND_MESSAGE = "The requested order was not found"
|
|
11
|
+
|
|
12
|
+
def _apply_query_params(self, db, query, query_params):
|
|
13
|
+
id_query_param = self.get_query_param("id", query_params)
|
|
14
|
+
external_id_query_param = self.get_query_param(
|
|
15
|
+
"external_id", query_params
|
|
16
|
+
)
|
|
17
|
+
portfolio_query_param = self.get_query_param(
|
|
18
|
+
"portfolio_id", query_params
|
|
19
|
+
)
|
|
20
|
+
side_query_param = self.get_query_param("order_side", query_params)
|
|
21
|
+
type_query_param = self.get_query_param("order_type", query_params)
|
|
22
|
+
status_query_param = self.get_query_param("status", query_params)
|
|
23
|
+
price_query_param = self.get_query_param("price", query_params)
|
|
24
|
+
amount_query_param = self.get_query_param("amount", query_params)
|
|
25
|
+
position_query_param = self.get_query_param(
|
|
26
|
+
"position", query_params, many=True
|
|
27
|
+
)
|
|
28
|
+
target_symbol_query_param = self.get_query_param(
|
|
29
|
+
"target_symbol", query_params
|
|
30
|
+
)
|
|
31
|
+
trading_symbol_query_param = self.get_query_param(
|
|
32
|
+
"trading_symbol", query_params
|
|
33
|
+
)
|
|
34
|
+
order_by_created_at_asc = self.get_query_param(
|
|
35
|
+
"order_by_created_at_asc", query_params
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if id_query_param:
|
|
39
|
+
query = query.filter_by(id=id_query_param)
|
|
40
|
+
|
|
41
|
+
if portfolio_query_param is not None:
|
|
42
|
+
portfolio = db.query(SQLPortfolio).filter_by(
|
|
43
|
+
id=portfolio_query_param
|
|
44
|
+
).first()
|
|
45
|
+
|
|
46
|
+
if portfolio:
|
|
47
|
+
positions = db.query(SQLPosition).filter_by(
|
|
48
|
+
portfolio_id=portfolio.id
|
|
49
|
+
).all()
|
|
50
|
+
position_ids = [p.id for p in positions]
|
|
51
|
+
query = query.filter(SQLOrder.position_id.in_(position_ids))
|
|
52
|
+
else:
|
|
53
|
+
query = query.filter_by(id=None)
|
|
54
|
+
|
|
55
|
+
if external_id_query_param:
|
|
56
|
+
query = query.filter_by(external_id=external_id_query_param)
|
|
57
|
+
|
|
58
|
+
if side_query_param:
|
|
59
|
+
order_side = OrderSide.from_value(side_query_param)
|
|
60
|
+
query = query.filter_by(order_side=order_side.value)
|
|
61
|
+
|
|
62
|
+
if type_query_param:
|
|
63
|
+
order_type = OrderType.from_value(type_query_param)
|
|
64
|
+
query = query.filter_by(order_type=order_type.value)
|
|
65
|
+
|
|
66
|
+
if status_query_param:
|
|
67
|
+
status = OrderStatus.from_value(status_query_param)
|
|
68
|
+
query = query.filter_by(status=status.value)
|
|
69
|
+
|
|
70
|
+
if price_query_param:
|
|
71
|
+
query = query.filter(SQLOrder.price == price_query_param)
|
|
72
|
+
|
|
73
|
+
if amount_query_param:
|
|
74
|
+
query = query.filter_by(amount=amount_query_param)
|
|
75
|
+
|
|
76
|
+
if position_query_param:
|
|
77
|
+
query = query.filter(SQLOrder.position_id.in_(
|
|
78
|
+
position_query_param)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if target_symbol_query_param:
|
|
82
|
+
query = query.filter(
|
|
83
|
+
SQLOrder.target_symbol == target_symbol_query_param
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if trading_symbol_query_param:
|
|
87
|
+
query = query.filter(
|
|
88
|
+
SQLOrder.trading_symbol == trading_symbol_query_param
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if order_by_created_at_asc:
|
|
92
|
+
query = query.order_by(SQLOrder.created_at.asc())
|
|
93
|
+
else:
|
|
94
|
+
query = query.order_by(SQLOrder.created_at.desc())
|
|
95
|
+
|
|
96
|
+
return query
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from investing_algorithm_framework.infrastructure.models import SQLPortfolio, \
|
|
2
|
+
SQLPosition
|
|
3
|
+
from .repository import Repository
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SQLPortfolioRepository(Repository):
|
|
7
|
+
base_class = SQLPortfolio
|
|
8
|
+
DEFAULT_NOT_FOUND_MESSAGE = "Portfolio not found"
|
|
9
|
+
|
|
10
|
+
def _apply_query_params(self, db, query, query_params):
|
|
11
|
+
id_query_param = query_params.get("id")
|
|
12
|
+
market_query_param = query_params.get("market")
|
|
13
|
+
identifier_query_param = query_params.get("identifier")
|
|
14
|
+
position_query_param = query_params.get("position")
|
|
15
|
+
|
|
16
|
+
if id_query_param:
|
|
17
|
+
query = query.filter_by(id=id_query_param)
|
|
18
|
+
|
|
19
|
+
if market_query_param:
|
|
20
|
+
query = query.filter_by(market=market_query_param.upper())
|
|
21
|
+
|
|
22
|
+
if identifier_query_param:
|
|
23
|
+
query = query.filter_by(identifier=identifier_query_param.upper())
|
|
24
|
+
|
|
25
|
+
if position_query_param:
|
|
26
|
+
position = db.query(SQLPosition)\
|
|
27
|
+
.filter_by(id=position_query_param).first()
|
|
28
|
+
query = query.filter_by(id=position.portfolio_id)
|
|
29
|
+
|
|
30
|
+
return query
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from investing_algorithm_framework.infrastructure.models import \
|
|
2
|
+
SQLPortfolioSnapshot
|
|
3
|
+
from .repository import Repository
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SQLPortfolioSnapshotRepository(Repository):
|
|
7
|
+
base_class = SQLPortfolioSnapshot
|
|
8
|
+
DEFAULT_NOT_FOUND_MESSAGE = "Portfolio snapshot not found"
|
|
9
|
+
|
|
10
|
+
def _apply_query_params(self, db, query, query_params):
|
|
11
|
+
portfolio_id_query_param = self.get_query_param(
|
|
12
|
+
"portfolio_id", query_params
|
|
13
|
+
)
|
|
14
|
+
created_at_query_param = self.get_query_param(
|
|
15
|
+
"created_at", query_params
|
|
16
|
+
)
|
|
17
|
+
created_at_gt_query_param = self.get_query_param(
|
|
18
|
+
"created_at_gt", query_params
|
|
19
|
+
)
|
|
20
|
+
created_at_gte_query_param = self.get_query_param(
|
|
21
|
+
"created_at_gte", query_params
|
|
22
|
+
)
|
|
23
|
+
created_at_lt_query_param = self.get_query_param(
|
|
24
|
+
"created_at_lt", query_params
|
|
25
|
+
)
|
|
26
|
+
created_at_lte_query_param = self.get_query_param(
|
|
27
|
+
"created_at_lte", query_params
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if portfolio_id_query_param is not None:
|
|
31
|
+
query = query.filter_by(portfolio_id=portfolio_id_query_param)
|
|
32
|
+
|
|
33
|
+
if created_at_query_param is not None:
|
|
34
|
+
query = query.filter_by(created_at=created_at_query_param)
|
|
35
|
+
|
|
36
|
+
if created_at_gt_query_param is not None:
|
|
37
|
+
query = query.filter(
|
|
38
|
+
SQLPortfolioSnapshot.created_at > created_at_gt_query_param
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if created_at_gte_query_param is not None:
|
|
42
|
+
query = query.filter(
|
|
43
|
+
SQLPortfolioSnapshot.created_at >= created_at_gt_query_param
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
if created_at_lt_query_param is not None:
|
|
47
|
+
query = query.filter(
|
|
48
|
+
SQLPortfolioSnapshot.created_at < created_at_lt_query_param
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if created_at_lte_query_param is not None:
|
|
52
|
+
query = query.filter(
|
|
53
|
+
SQLPortfolioSnapshot.created_at <= created_at_lte_query_param
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return query
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from sqlalchemy import cast, Numeric, Float
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.infrastructure.models import SQLPosition
|
|
4
|
+
from .repository import Repository
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SQLPositionRepository(Repository):
|
|
8
|
+
base_class = SQLPosition
|
|
9
|
+
DEFAULT_NOT_FOUND_MESSAGE = "Position not found"
|
|
10
|
+
|
|
11
|
+
def _apply_query_params(self, db, query, query_params):
|
|
12
|
+
id_query_param = self.get_query_param("id", query_params)
|
|
13
|
+
amount_query_param = self.get_query_param("amount", query_params)
|
|
14
|
+
symbol_query_param = self.get_query_param("symbol", query_params)
|
|
15
|
+
portfolio_query_param = self.get_query_param("portfolio", query_params)
|
|
16
|
+
amount_gt_query_param = self.get_query_param("amount_gt", query_params)
|
|
17
|
+
amount_gte_query_param = self.get_query_param(
|
|
18
|
+
"amount_gte", query_params
|
|
19
|
+
)
|
|
20
|
+
amount_lt_query_param = self.get_query_param("amount_lt", query_params)
|
|
21
|
+
amount_lte_query_param = self.get_query_param(
|
|
22
|
+
"amount_lte", query_params
|
|
23
|
+
)
|
|
24
|
+
order_id_query_param = self.get_query_param("order_id", query_params)
|
|
25
|
+
|
|
26
|
+
if id_query_param:
|
|
27
|
+
query = query.filter_by(id=id_query_param)
|
|
28
|
+
|
|
29
|
+
if amount_query_param:
|
|
30
|
+
query = query.filter(
|
|
31
|
+
cast(SQLPosition.amount, Float) == amount_query_param
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if symbol_query_param:
|
|
35
|
+
query = query.filter_by(symbol=symbol_query_param)
|
|
36
|
+
|
|
37
|
+
if portfolio_query_param is not None:
|
|
38
|
+
query = query.filter_by(portfolio_id=portfolio_query_param)
|
|
39
|
+
|
|
40
|
+
if amount_gt_query_param is not None:
|
|
41
|
+
query = query.filter(
|
|
42
|
+
cast(SQLPosition.amount, Numeric) > amount_gt_query_param
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
if amount_gte_query_param is not None:
|
|
46
|
+
query = query.filter(
|
|
47
|
+
cast(SQLPosition.amount, Numeric) >= amount_gte_query_param
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if amount_lt_query_param is not None:
|
|
51
|
+
query = query.filter(
|
|
52
|
+
cast(SQLPosition.amount, Numeric) < amount_lt_query_param
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if amount_lte_query_param:
|
|
56
|
+
query = query.filter(
|
|
57
|
+
cast(SQLPosition.amount, Numeric) <= amount_lte_query_param
|
|
58
|
+
)
|
|
59
|
+
# Filter by order_id, orders is a one-to-many relationship
|
|
60
|
+
# with 3 position
|
|
61
|
+
if order_id_query_param:
|
|
62
|
+
query = query.filter(
|
|
63
|
+
SQLPosition.orders.any(id=order_id_query_param)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return query
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from investing_algorithm_framework.infrastructure.models import \
|
|
2
|
+
SQLPositionSnapshot
|
|
3
|
+
from .repository import Repository
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SQLPositionSnapshotRepository(Repository):
|
|
7
|
+
base_class = SQLPositionSnapshot
|
|
8
|
+
DEFAULT_NOT_FOUND_MESSAGE = "Position snapshot not found"
|
|
9
|
+
|
|
10
|
+
def _apply_query_params(self, db, query, query_params):
|
|
11
|
+
portfolio_snapshot_query_param = self.get_query_param(
|
|
12
|
+
"portfolio_snapshot", query_params
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
if portfolio_snapshot_query_param is not None:
|
|
16
|
+
query = query\
|
|
17
|
+
.filter_by(
|
|
18
|
+
portfolio_snapshot_id=portfolio_snapshot_query_param
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return query
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Callable
|
|
4
|
+
from dateutil.parser import parse
|
|
5
|
+
|
|
6
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
7
|
+
from werkzeug.datastructures import MultiDict
|
|
8
|
+
|
|
9
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
10
|
+
DEFAULT_PAGE_VALUE, DEFAULT_PER_PAGE_VALUE
|
|
11
|
+
from investing_algorithm_framework.infrastructure.database import Session
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def convert_datetime_fields(data, datetime_fields):
|
|
17
|
+
for field in datetime_fields:
|
|
18
|
+
if field in data and isinstance(data[field], str):
|
|
19
|
+
try:
|
|
20
|
+
data[field] = parse(data[field])
|
|
21
|
+
except Exception:
|
|
22
|
+
pass # Ignore if not a valid datetime string
|
|
23
|
+
return data
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Repository(ABC):
|
|
27
|
+
base_class: Callable
|
|
28
|
+
DEFAULT_NOT_FOUND_MESSAGE = "The requested resource was not found"
|
|
29
|
+
DEFAULT_PER_PAGE = DEFAULT_PER_PAGE_VALUE
|
|
30
|
+
DEFAULT_PAGE = DEFAULT_PAGE_VALUE
|
|
31
|
+
|
|
32
|
+
def create(self, data, save=True):
|
|
33
|
+
created_object = self.base_class(**data)
|
|
34
|
+
if save:
|
|
35
|
+
with Session() as db:
|
|
36
|
+
try:
|
|
37
|
+
db.add(created_object)
|
|
38
|
+
db.commit()
|
|
39
|
+
return self.get(created_object.id)
|
|
40
|
+
except SQLAlchemyError as e:
|
|
41
|
+
logger.error(e)
|
|
42
|
+
db.rollback()
|
|
43
|
+
raise OperationalException("Error creating object")
|
|
44
|
+
|
|
45
|
+
return created_object
|
|
46
|
+
|
|
47
|
+
def update(self, object_id, data):
|
|
48
|
+
# List all datetime fields for your model
|
|
49
|
+
datetime_fields = [
|
|
50
|
+
"created_at", "updated_at", "closed_at", "opened_at"
|
|
51
|
+
]
|
|
52
|
+
data = convert_datetime_fields(data, datetime_fields)
|
|
53
|
+
with Session() as db:
|
|
54
|
+
try:
|
|
55
|
+
update_object = self.get(object_id)
|
|
56
|
+
update_object.update(data)
|
|
57
|
+
db.add(update_object)
|
|
58
|
+
db.commit()
|
|
59
|
+
return self.get(object_id)
|
|
60
|
+
except SQLAlchemyError as e:
|
|
61
|
+
logger.error(e)
|
|
62
|
+
db.rollback()
|
|
63
|
+
raise OperationalException("Error updating object")
|
|
64
|
+
|
|
65
|
+
def update_all(self, query_params, data):
|
|
66
|
+
|
|
67
|
+
with Session() as db:
|
|
68
|
+
try:
|
|
69
|
+
selection = self.get_all(query_params)
|
|
70
|
+
|
|
71
|
+
for item in selection:
|
|
72
|
+
try:
|
|
73
|
+
item.update(db, data)
|
|
74
|
+
except SQLAlchemyError as e:
|
|
75
|
+
logger.error(e)
|
|
76
|
+
db.rollback()
|
|
77
|
+
|
|
78
|
+
db.commit()
|
|
79
|
+
|
|
80
|
+
except SQLAlchemyError as e:
|
|
81
|
+
logger.error(e)
|
|
82
|
+
db.rollback()
|
|
83
|
+
raise OperationalException("Error updating object")
|
|
84
|
+
|
|
85
|
+
def delete(self, object_id):
|
|
86
|
+
|
|
87
|
+
with Session() as db:
|
|
88
|
+
try:
|
|
89
|
+
delete_object = self.get(object_id)
|
|
90
|
+
db.delete(delete_object)
|
|
91
|
+
db.commit()
|
|
92
|
+
return delete_object
|
|
93
|
+
except SQLAlchemyError as e:
|
|
94
|
+
logger.error(e)
|
|
95
|
+
db.rollback()
|
|
96
|
+
raise OperationalException("Error deleting object")
|
|
97
|
+
|
|
98
|
+
def delete_all(self, query_params):
|
|
99
|
+
|
|
100
|
+
with Session() as db:
|
|
101
|
+
if query_params is None:
|
|
102
|
+
raise OperationalException("No parameters are required")
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
query_set = db.query(self.base_class)
|
|
106
|
+
query_set = self.apply_query_params(
|
|
107
|
+
db, query_set, query_params
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
for item in query_set.all():
|
|
111
|
+
item.delete(db)
|
|
112
|
+
db.commit()
|
|
113
|
+
|
|
114
|
+
except SQLAlchemyError as e:
|
|
115
|
+
logger.error(e)
|
|
116
|
+
db.rollback()
|
|
117
|
+
raise OperationalException("Error deleting all objects")
|
|
118
|
+
|
|
119
|
+
def get_all(self, query_params=None):
|
|
120
|
+
query_params = MultiDict(query_params)
|
|
121
|
+
|
|
122
|
+
with Session() as db:
|
|
123
|
+
try:
|
|
124
|
+
query_set = db.query(self.base_class)
|
|
125
|
+
query_set = self.apply_query_params(
|
|
126
|
+
db, query_set, query_params
|
|
127
|
+
)
|
|
128
|
+
return query_set.all()
|
|
129
|
+
except SQLAlchemyError as e:
|
|
130
|
+
logger.error(e)
|
|
131
|
+
raise OperationalException("Error getting all objects")
|
|
132
|
+
|
|
133
|
+
def get(self, object_id):
|
|
134
|
+
|
|
135
|
+
with Session() as db:
|
|
136
|
+
match = db.query(self.base_class).filter_by(id=object_id) \
|
|
137
|
+
.first()
|
|
138
|
+
|
|
139
|
+
if not match:
|
|
140
|
+
raise OperationalException(
|
|
141
|
+
self.DEFAULT_NOT_FOUND_MESSAGE
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return match
|
|
145
|
+
|
|
146
|
+
@abstractmethod
|
|
147
|
+
def _apply_query_params(self, db, query, query_params):
|
|
148
|
+
raise NotImplementedError()
|
|
149
|
+
|
|
150
|
+
def apply_query_params(self, db, query, query_params):
|
|
151
|
+
|
|
152
|
+
if query_params is not None:
|
|
153
|
+
query_params = MultiDict(query_params)
|
|
154
|
+
query = self._apply_query_params(db, query, query_params)
|
|
155
|
+
|
|
156
|
+
return query
|
|
157
|
+
|
|
158
|
+
def exists(self, query_params):
|
|
159
|
+
with Session() as db:
|
|
160
|
+
try:
|
|
161
|
+
query = db.query(self.base_class)
|
|
162
|
+
query = self.apply_query_params(db, query, query_params)
|
|
163
|
+
return query.first() is not None
|
|
164
|
+
except SQLAlchemyError as e:
|
|
165
|
+
logger.error(e)
|
|
166
|
+
raise OperationalException("Error checking if object exists")
|
|
167
|
+
|
|
168
|
+
def find(self, query_params):
|
|
169
|
+
|
|
170
|
+
if query_params is None or len(query_params) == 0:
|
|
171
|
+
raise OperationalException("Find requires query parameters")
|
|
172
|
+
|
|
173
|
+
with Session() as db:
|
|
174
|
+
try:
|
|
175
|
+
query = db.query(self.base_class)
|
|
176
|
+
query = self.apply_query_params(db, query, query_params)
|
|
177
|
+
result = query.first()
|
|
178
|
+
|
|
179
|
+
if result is None:
|
|
180
|
+
raise OperationalException(self.DEFAULT_NOT_FOUND_MESSAGE)
|
|
181
|
+
|
|
182
|
+
return result
|
|
183
|
+
except SQLAlchemyError as e:
|
|
184
|
+
logger.error(e)
|
|
185
|
+
raise OperationalException(self.DEFAULT_NOT_FOUND_MESSAGE)
|
|
186
|
+
|
|
187
|
+
def count(self, query_params=None):
|
|
188
|
+
|
|
189
|
+
with Session() as db:
|
|
190
|
+
try:
|
|
191
|
+
query = db.query(self.base_class)
|
|
192
|
+
query = self.apply_query_params(db, query, query_params)
|
|
193
|
+
return query.count()
|
|
194
|
+
except SQLAlchemyError as e:
|
|
195
|
+
logger.error(e)
|
|
196
|
+
raise OperationalException("Error counting objects")
|
|
197
|
+
|
|
198
|
+
def normalize_query_param(self, value):
|
|
199
|
+
"""
|
|
200
|
+
Given a non-flattened query parameter value,
|
|
201
|
+
and if the value is a list only containing 1 item,
|
|
202
|
+
then the value is flattened.
|
|
203
|
+
|
|
204
|
+
:param value: a value from a query parameter
|
|
205
|
+
:return: a normalized query parameter value
|
|
206
|
+
"""
|
|
207
|
+
return value if len(value) > 1 else value[0]
|
|
208
|
+
|
|
209
|
+
def is_query_param_present(self, key, params, throw_exception=False):
|
|
210
|
+
query_params = self.normalize_query(params)
|
|
211
|
+
|
|
212
|
+
if key not in query_params:
|
|
213
|
+
|
|
214
|
+
if not throw_exception:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
raise OperationalException(f"{key} is not specified")
|
|
218
|
+
else:
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
def normalize_query(self, params):
|
|
222
|
+
"""
|
|
223
|
+
Converts query parameters from only containing one value for
|
|
224
|
+
each parameter, to include parameters with multiple values as lists.
|
|
225
|
+
|
|
226
|
+
:param params: a flask query parameters data structure
|
|
227
|
+
:return: a dict of normalized query parameters
|
|
228
|
+
"""
|
|
229
|
+
if isinstance(params, MultiDict):
|
|
230
|
+
params = params.to_dict(flat=False)
|
|
231
|
+
|
|
232
|
+
return {k: self.normalize_query_param(v) for k, v in params.items()}
|
|
233
|
+
|
|
234
|
+
def get_query_param(self, key, params, default=None, many=False):
|
|
235
|
+
boolean_array = ["true", "false"]
|
|
236
|
+
|
|
237
|
+
if params is None or key not in params:
|
|
238
|
+
return default
|
|
239
|
+
|
|
240
|
+
params = self.normalize_query(params)
|
|
241
|
+
selection = params.get(key, default)
|
|
242
|
+
|
|
243
|
+
if not isinstance(selection, list):
|
|
244
|
+
|
|
245
|
+
if selection is None:
|
|
246
|
+
selection = []
|
|
247
|
+
else:
|
|
248
|
+
selection = [selection]
|
|
249
|
+
|
|
250
|
+
new_selection = []
|
|
251
|
+
|
|
252
|
+
for index, selected in enumerate(selection):
|
|
253
|
+
|
|
254
|
+
if isinstance(selected, str) and selected.lower() in boolean_array:
|
|
255
|
+
new_selection.append(selected.lower() == "true")
|
|
256
|
+
else:
|
|
257
|
+
new_selection.append(selected)
|
|
258
|
+
|
|
259
|
+
if not many:
|
|
260
|
+
|
|
261
|
+
if len(new_selection) == 0:
|
|
262
|
+
return None
|
|
263
|
+
|
|
264
|
+
return new_selection[0]
|
|
265
|
+
|
|
266
|
+
return new_selection
|
|
267
|
+
|
|
268
|
+
def save(self, object_to_save):
|
|
269
|
+
"""
|
|
270
|
+
Save an object to the database with SQLAlchemy.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
object_to_save: instance of the object to save.
|
|
274
|
+
|
|
275
|
+
Returns:
|
|
276
|
+
Object: The saved object.
|
|
277
|
+
"""
|
|
278
|
+
with Session() as db:
|
|
279
|
+
try:
|
|
280
|
+
db.add(object_to_save)
|
|
281
|
+
db.commit()
|
|
282
|
+
return self.get(object_to_save.id)
|
|
283
|
+
except SQLAlchemyError as e:
|
|
284
|
+
logger.error(e)
|
|
285
|
+
db.rollback()
|
|
286
|
+
raise OperationalException("Error saving object")
|
|
287
|
+
|
|
288
|
+
def save_objects(self, objects):
|
|
289
|
+
|
|
290
|
+
with Session() as db:
|
|
291
|
+
try:
|
|
292
|
+
for object in objects:
|
|
293
|
+
db.add(object)
|
|
294
|
+
db.commit()
|
|
295
|
+
return objects
|
|
296
|
+
except SQLAlchemyError as e:
|
|
297
|
+
logger.error(e)
|
|
298
|
+
db.rollback()
|
|
299
|
+
raise OperationalException("Error saving objects")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
3
|
+
|
|
4
|
+
from investing_algorithm_framework.domain import TradeStatus, ApiException
|
|
5
|
+
from investing_algorithm_framework.infrastructure.models import SQLPosition, \
|
|
6
|
+
SQLPortfolio, SQLTrade, SQLOrder
|
|
7
|
+
from investing_algorithm_framework.infrastructure.database import Session
|
|
8
|
+
|
|
9
|
+
from .repository import Repository
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SQLTradeRepository(Repository):
|
|
15
|
+
base_class = SQLTrade
|
|
16
|
+
DEFAULT_NOT_FOUND_MESSAGE = "The requested trade was not found"
|
|
17
|
+
|
|
18
|
+
def _apply_query_params(self, db, query, query_params):
|
|
19
|
+
portfolio_query_param = self.get_query_param(
|
|
20
|
+
"portfolio_id", query_params
|
|
21
|
+
)
|
|
22
|
+
status_query_param = self.get_query_param("status", query_params)
|
|
23
|
+
target_symbol = self.get_query_param(
|
|
24
|
+
"target_symbol", query_params
|
|
25
|
+
)
|
|
26
|
+
trading_symbol = self.get_query_param("trading_symbol", query_params)
|
|
27
|
+
order_id_query_param = self.get_query_param("order_id", query_params)
|
|
28
|
+
|
|
29
|
+
if order_id_query_param:
|
|
30
|
+
query = query.filter(SQLTrade.orders.any(id=order_id_query_param))
|
|
31
|
+
|
|
32
|
+
if portfolio_query_param is not None:
|
|
33
|
+
portfolio = db.query(SQLPortfolio).filter_by(
|
|
34
|
+
id=portfolio_query_param
|
|
35
|
+
).first()
|
|
36
|
+
|
|
37
|
+
if portfolio is None:
|
|
38
|
+
raise ApiException("Portfolio not found")
|
|
39
|
+
|
|
40
|
+
# Query trades belonging to the portfolio
|
|
41
|
+
query = db.query(SQLTrade).join(SQLOrder, SQLTrade.orders) \
|
|
42
|
+
.join(SQLPosition, SQLOrder.position_id == SQLPosition.id) \
|
|
43
|
+
.filter(SQLPosition.portfolio_id == portfolio.id)
|
|
44
|
+
|
|
45
|
+
if status_query_param:
|
|
46
|
+
status = TradeStatus.from_value(status_query_param)
|
|
47
|
+
# Explicitly filter on SQLTrade.status
|
|
48
|
+
query = query.filter(SQLTrade.status == status.value)
|
|
49
|
+
|
|
50
|
+
if target_symbol:
|
|
51
|
+
# Explicitly filter on SQLTrade.target_symbol
|
|
52
|
+
query = query.filter(SQLTrade.target_symbol == target_symbol)
|
|
53
|
+
|
|
54
|
+
if trading_symbol:
|
|
55
|
+
# Explicitly filter on SQLTrade.trading_symbol
|
|
56
|
+
query = query.filter(SQLTrade.trading_symbol == trading_symbol)
|
|
57
|
+
|
|
58
|
+
return query
|
|
59
|
+
|
|
60
|
+
def add_order_to_trade(self, trade, order):
|
|
61
|
+
with Session() as db:
|
|
62
|
+
try:
|
|
63
|
+
db.add(order)
|
|
64
|
+
db.add(trade)
|
|
65
|
+
trade.orders.append(order)
|
|
66
|
+
db.commit()
|
|
67
|
+
return trade
|
|
68
|
+
except SQLAlchemyError as e:
|
|
69
|
+
logger.error(f"Error saving trade: {e}")
|
|
70
|
+
db.rollback()
|
|
71
|
+
raise ApiException("Error saving trade")
|