investing-algorithm-framework 1.3.1__py3-none-any.whl → 7.25.6__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.
- investing_algorithm_framework/__init__.py +195 -16
- investing_algorithm_framework/analysis/__init__.py +16 -0
- investing_algorithm_framework/analysis/backtest_data_ranges.py +202 -0
- investing_algorithm_framework/analysis/data.py +170 -0
- investing_algorithm_framework/analysis/markdown.py +91 -0
- investing_algorithm_framework/analysis/ranking.py +298 -0
- investing_algorithm_framework/app/__init__.py +31 -4
- investing_algorithm_framework/app/algorithm/__init__.py +7 -0
- investing_algorithm_framework/app/algorithm/algorithm.py +193 -0
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +118 -0
- investing_algorithm_framework/app/app.py +2233 -264
- investing_algorithm_framework/app/app_hook.py +28 -0
- investing_algorithm_framework/app/context.py +1724 -0
- investing_algorithm_framework/app/eventloop.py +620 -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 +6 -3
- investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +1 -1
- investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +2 -1
- investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +14 -7
- investing_algorithm_framework/app/stateless/exception_handler.py +1 -1
- investing_algorithm_framework/app/strategy.py +873 -52
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/app/web/__init__.py +2 -1
- investing_algorithm_framework/app/web/controllers/__init__.py +2 -2
- investing_algorithm_framework/app/web/controllers/orders.py +4 -3
- investing_algorithm_framework/app/web/controllers/portfolio.py +1 -1
- investing_algorithm_framework/app/web/controllers/positions.py +3 -3
- investing_algorithm_framework/app/web/create_app.py +4 -2
- investing_algorithm_framework/app/web/error_handler.py +1 -1
- investing_algorithm_framework/app/web/schemas/order.py +2 -2
- investing_algorithm_framework/app/web/schemas/position.py +1 -0
- investing_algorithm_framework/cli/__init__.py +0 -0
- investing_algorithm_framework/cli/cli.py +231 -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/cli/validate_backtest_checkpoints.py +197 -0
- investing_algorithm_framework/create_app.py +43 -9
- investing_algorithm_framework/dependency_container.py +121 -33
- investing_algorithm_framework/domain/__init__.py +109 -22
- investing_algorithm_framework/domain/algorithm_id.py +69 -0
- investing_algorithm_framework/domain/backtesting/__init__.py +25 -0
- investing_algorithm_framework/domain/backtesting/backtest.py +548 -0
- investing_algorithm_framework/domain/backtesting/backtest_date_range.py +113 -0
- investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +241 -0
- investing_algorithm_framework/domain/backtesting/backtest_metrics.py +470 -0
- investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
- investing_algorithm_framework/domain/backtesting/backtest_run.py +663 -0
- investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
- investing_algorithm_framework/domain/backtesting/backtest_utils.py +198 -0
- investing_algorithm_framework/domain/backtesting/combine_backtests.py +392 -0
- investing_algorithm_framework/domain/config.py +60 -138
- investing_algorithm_framework/domain/constants.py +23 -34
- 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 +51 -1
- investing_algorithm_framework/domain/models/__init__.py +29 -14
- investing_algorithm_framework/domain/models/app_mode.py +34 -0
- investing_algorithm_framework/domain/models/base_model.py +3 -1
- investing_algorithm_framework/domain/models/data/__init__.py +7 -0
- investing_algorithm_framework/domain/models/data/data_source.py +222 -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 +3 -4
- investing_algorithm_framework/domain/models/order/order.py +243 -86
- 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/__init__.py +7 -2
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +134 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +37 -37
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +208 -0
- investing_algorithm_framework/domain/models/position/__init__.py +3 -2
- investing_algorithm_framework/domain/models/position/position.py +29 -0
- investing_algorithm_framework/domain/models/position/position_size.py +41 -0
- investing_algorithm_framework/domain/models/position/{position_cost.py → position_snapshot.py} +16 -8
- 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 +33 -0
- investing_algorithm_framework/domain/models/time_frame.py +94 -98
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +111 -2
- 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 +11 -0
- investing_algorithm_framework/domain/models/trade/trade.py +389 -0
- investing_algorithm_framework/domain/models/trade/trade_status.py +40 -0
- 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 +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/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +16 -4
- investing_algorithm_framework/domain/utils/csv.py +22 -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 +29 -0
- investing_algorithm_framework/download_data.py +244 -0
- investing_algorithm_framework/infrastructure/__init__.py +39 -11
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +36 -0
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +1152 -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 +87 -13
- investing_algorithm_framework/infrastructure/models/__init__.py +13 -4
- investing_algorithm_framework/infrastructure/models/decimal_parser.py +14 -0
- investing_algorithm_framework/infrastructure/models/order/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/order/order.py +73 -73
- 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 +3 -2
- investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +37 -0
- investing_algorithm_framework/infrastructure/models/portfolio/{portfolio.py → sql_portfolio.py} +57 -3
- investing_algorithm_framework/infrastructure/models/position/__init__.py +2 -2
- investing_algorithm_framework/infrastructure/models/position/position.py +16 -11
- 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 +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 +13 -5
- investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py +17 -0
- investing_algorithm_framework/infrastructure/repositories/order_repository.py +32 -19
- investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +56 -0
- investing_algorithm_framework/infrastructure/repositories/position_repository.py +47 -4
- investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +21 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +85 -31
- 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 +9 -2
- investing_algorithm_framework/infrastructure/services/aws/__init__.py +6 -0
- investing_algorithm_framework/infrastructure/services/aws/state_handler.py +193 -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/infrastructure/services/backtesting/__init__.py +9 -0
- investing_algorithm_framework/infrastructure/services/backtesting/backtest_service.py +2596 -0
- investing_algorithm_framework/infrastructure/services/backtesting/event_backtest_service.py +285 -0
- investing_algorithm_framework/infrastructure/services/backtesting/vector_backtest_service.py +468 -0
- investing_algorithm_framework/services/__init__.py +127 -10
- investing_algorithm_framework/services/configuration_service.py +95 -0
- investing_algorithm_framework/services/data_providers/__init__.py +5 -0
- investing_algorithm_framework/services/data_providers/data_provider_service.py +1058 -0
- investing_algorithm_framework/services/market_credential_service.py +40 -0
- investing_algorithm_framework/services/metrics/__init__.py +119 -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 +218 -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 +84 -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 +156 -0
- investing_algorithm_framework/services/metrics/trades.py +473 -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 +118 -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/{portfolio_configuration_service.py → portfolios/portfolio_configuration_service.py} +27 -12
- 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 +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 +117 -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 +9 -0
- investing_algorithm_framework/services/trade_service/trade_service.py +1099 -0
- 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.25.6.dist-info/METADATA +535 -0
- investing_algorithm_framework-7.25.6.dist-info/RECORD +268 -0
- {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/WHEEL +1 -2
- investing_algorithm_framework-7.25.6.dist-info/entry_points.txt +3 -0
- investing_algorithm_framework/app/algorithm.py +0 -410
- investing_algorithm_framework/domain/models/market_data/__init__.py +0 -11
- investing_algorithm_framework/domain/models/market_data/asset_price.py +0 -50
- investing_algorithm_framework/domain/models/market_data/ohlcv.py +0 -76
- investing_algorithm_framework/domain/models/market_data/order_book.py +0 -63
- investing_algorithm_framework/domain/models/market_data/ticker.py +0 -92
- investing_algorithm_framework/domain/models/order/order_fee.py +0 -45
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -47
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -205
- investing_algorithm_framework/domain/singleton.py +0 -9
- investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -21
- investing_algorithm_framework/infrastructure/models/position/position_cost.py +0 -32
- investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -15
- investing_algorithm_framework/infrastructure/repositories/position_cost_repository.py +0 -16
- investing_algorithm_framework/infrastructure/services/market_service.py +0 -422
- investing_algorithm_framework/services/market_data_service.py +0 -75
- investing_algorithm_framework/services/order_service.py +0 -464
- investing_algorithm_framework/services/portfolio_service.py +0 -105
- investing_algorithm_framework/services/position_cost_service.py +0 -5
- investing_algorithm_framework/services/position_service.py +0 -50
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -219
- investing_algorithm_framework/setup_logging.py +0 -40
- investing_algorithm_framework-1.3.1.dist-info/AUTHORS.md +0 -8
- investing_algorithm_framework-1.3.1.dist-info/METADATA +0 -172
- investing_algorithm_framework-1.3.1.dist-info/RECORD +0 -103
- investing_algorithm_framework-1.3.1.dist-info/top_level.txt +0 -1
- {investing_algorithm_framework-1.3.1.dist-info → investing_algorithm_framework-7.25.6.dist-info}/LICENSE +0 -0
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from abc import ABC
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
3
|
from typing import Callable
|
|
4
|
+
from dateutil.parser import parse
|
|
4
5
|
|
|
5
6
|
from sqlalchemy.exc import SQLAlchemyError
|
|
6
7
|
from werkzeug.datastructures import MultiDict
|
|
7
8
|
|
|
8
|
-
from investing_algorithm_framework.domain import
|
|
9
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
9
10
|
DEFAULT_PAGE_VALUE, DEFAULT_PER_PAGE_VALUE
|
|
10
11
|
from investing_algorithm_framework.infrastructure.database import Session
|
|
11
12
|
|
|
12
|
-
logger = logging.getLogger(
|
|
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
|
|
13
24
|
|
|
14
25
|
|
|
15
26
|
class Repository(ABC):
|
|
@@ -18,21 +29,27 @@ class Repository(ABC):
|
|
|
18
29
|
DEFAULT_PER_PAGE = DEFAULT_PER_PAGE_VALUE
|
|
19
30
|
DEFAULT_PAGE = DEFAULT_PAGE_VALUE
|
|
20
31
|
|
|
21
|
-
def create(self, data):
|
|
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")
|
|
22
44
|
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
created_object = self.base_class(**data)
|
|
26
|
-
db.add(created_object)
|
|
27
|
-
db.commit()
|
|
28
|
-
return self.get(created_object.id)
|
|
29
|
-
except SQLAlchemyError as e:
|
|
30
|
-
logger.error(e)
|
|
31
|
-
db.rollback()
|
|
32
|
-
raise ApiException("Error creating object")
|
|
45
|
+
return created_object
|
|
33
46
|
|
|
34
47
|
def update(self, object_id, data):
|
|
35
|
-
|
|
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)
|
|
36
53
|
with Session() as db:
|
|
37
54
|
try:
|
|
38
55
|
update_object = self.get(object_id)
|
|
@@ -43,7 +60,7 @@ class Repository(ABC):
|
|
|
43
60
|
except SQLAlchemyError as e:
|
|
44
61
|
logger.error(e)
|
|
45
62
|
db.rollback()
|
|
46
|
-
raise
|
|
63
|
+
raise OperationalException("Error updating object")
|
|
47
64
|
|
|
48
65
|
def update_all(self, query_params, data):
|
|
49
66
|
|
|
@@ -63,7 +80,7 @@ class Repository(ABC):
|
|
|
63
80
|
except SQLAlchemyError as e:
|
|
64
81
|
logger.error(e)
|
|
65
82
|
db.rollback()
|
|
66
|
-
raise
|
|
83
|
+
raise OperationalException("Error updating object")
|
|
67
84
|
|
|
68
85
|
def delete(self, object_id):
|
|
69
86
|
|
|
@@ -71,17 +88,18 @@ class Repository(ABC):
|
|
|
71
88
|
try:
|
|
72
89
|
delete_object = self.get(object_id)
|
|
73
90
|
db.delete(delete_object)
|
|
91
|
+
db.commit()
|
|
74
92
|
return delete_object
|
|
75
93
|
except SQLAlchemyError as e:
|
|
76
94
|
logger.error(e)
|
|
77
95
|
db.rollback()
|
|
78
|
-
raise
|
|
96
|
+
raise OperationalException("Error deleting object")
|
|
79
97
|
|
|
80
98
|
def delete_all(self, query_params):
|
|
81
99
|
|
|
82
100
|
with Session() as db:
|
|
83
101
|
if query_params is None:
|
|
84
|
-
raise
|
|
102
|
+
raise OperationalException("No parameters are required")
|
|
85
103
|
|
|
86
104
|
try:
|
|
87
105
|
query_set = db.query(self.base_class)
|
|
@@ -96,7 +114,7 @@ class Repository(ABC):
|
|
|
96
114
|
except SQLAlchemyError as e:
|
|
97
115
|
logger.error(e)
|
|
98
116
|
db.rollback()
|
|
99
|
-
raise
|
|
117
|
+
raise OperationalException("Error deleting all objects")
|
|
100
118
|
|
|
101
119
|
def get_all(self, query_params=None):
|
|
102
120
|
query_params = MultiDict(query_params)
|
|
@@ -110,7 +128,7 @@ class Repository(ABC):
|
|
|
110
128
|
return query_set.all()
|
|
111
129
|
except SQLAlchemyError as e:
|
|
112
130
|
logger.error(e)
|
|
113
|
-
raise
|
|
131
|
+
raise OperationalException("Error getting all objects")
|
|
114
132
|
|
|
115
133
|
def get(self, object_id):
|
|
116
134
|
|
|
@@ -119,14 +137,15 @@ class Repository(ABC):
|
|
|
119
137
|
.first()
|
|
120
138
|
|
|
121
139
|
if not match:
|
|
122
|
-
raise
|
|
123
|
-
self.DEFAULT_NOT_FOUND_MESSAGE
|
|
140
|
+
raise OperationalException(
|
|
141
|
+
self.DEFAULT_NOT_FOUND_MESSAGE
|
|
124
142
|
)
|
|
125
143
|
|
|
126
144
|
return match
|
|
127
145
|
|
|
146
|
+
@abstractmethod
|
|
128
147
|
def _apply_query_params(self, db, query, query_params):
|
|
129
|
-
|
|
148
|
+
raise NotImplementedError()
|
|
130
149
|
|
|
131
150
|
def apply_query_params(self, db, query, query_params):
|
|
132
151
|
|
|
@@ -137,7 +156,6 @@ class Repository(ABC):
|
|
|
137
156
|
return query
|
|
138
157
|
|
|
139
158
|
def exists(self, query_params):
|
|
140
|
-
|
|
141
159
|
with Session() as db:
|
|
142
160
|
try:
|
|
143
161
|
query = db.query(self.base_class)
|
|
@@ -145,10 +163,13 @@ class Repository(ABC):
|
|
|
145
163
|
return query.first() is not None
|
|
146
164
|
except SQLAlchemyError as e:
|
|
147
165
|
logger.error(e)
|
|
148
|
-
raise
|
|
166
|
+
raise OperationalException("Error checking if object exists")
|
|
149
167
|
|
|
150
168
|
def find(self, query_params):
|
|
151
169
|
|
|
170
|
+
if query_params is None or len(query_params) == 0:
|
|
171
|
+
raise OperationalException("Find requires query parameters")
|
|
172
|
+
|
|
152
173
|
with Session() as db:
|
|
153
174
|
try:
|
|
154
175
|
query = db.query(self.base_class)
|
|
@@ -156,12 +177,12 @@ class Repository(ABC):
|
|
|
156
177
|
result = query.first()
|
|
157
178
|
|
|
158
179
|
if result is None:
|
|
159
|
-
raise
|
|
180
|
+
raise OperationalException(self.DEFAULT_NOT_FOUND_MESSAGE)
|
|
160
181
|
|
|
161
182
|
return result
|
|
162
183
|
except SQLAlchemyError as e:
|
|
163
184
|
logger.error(e)
|
|
164
|
-
raise
|
|
185
|
+
raise OperationalException(self.DEFAULT_NOT_FOUND_MESSAGE)
|
|
165
186
|
|
|
166
187
|
def count(self, query_params=None):
|
|
167
188
|
|
|
@@ -172,7 +193,7 @@ class Repository(ABC):
|
|
|
172
193
|
return query.count()
|
|
173
194
|
except SQLAlchemyError as e:
|
|
174
195
|
logger.error(e)
|
|
175
|
-
raise
|
|
196
|
+
raise OperationalException("Error counting objects")
|
|
176
197
|
|
|
177
198
|
def normalize_query_param(self, value):
|
|
178
199
|
"""
|
|
@@ -193,7 +214,7 @@ class Repository(ABC):
|
|
|
193
214
|
if not throw_exception:
|
|
194
215
|
return False
|
|
195
216
|
|
|
196
|
-
raise
|
|
217
|
+
raise OperationalException(f"{key} is not specified")
|
|
197
218
|
else:
|
|
198
219
|
return True
|
|
199
220
|
|
|
@@ -213,7 +234,7 @@ class Repository(ABC):
|
|
|
213
234
|
def get_query_param(self, key, params, default=None, many=False):
|
|
214
235
|
boolean_array = ["true", "false"]
|
|
215
236
|
|
|
216
|
-
if params is None:
|
|
237
|
+
if params is None or key not in params:
|
|
217
238
|
return default
|
|
218
239
|
|
|
219
240
|
params = self.normalize_query(params)
|
|
@@ -243,3 +264,36 @@ class Repository(ABC):
|
|
|
243
264
|
return new_selection[0]
|
|
244
265
|
|
|
245
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")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.infrastructure.models import \
|
|
4
|
+
SQLTradeStopLoss
|
|
5
|
+
|
|
6
|
+
from .repository import Repository
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SQLTradeStopLossRepository(Repository):
|
|
12
|
+
base_class = SQLTradeStopLoss
|
|
13
|
+
DEFAULT_NOT_FOUND_MESSAGE = "The requested trade stop loss was not found"
|
|
14
|
+
|
|
15
|
+
def _apply_query_params(self, db, query, query_params):
|
|
16
|
+
trade_query_param = self.get_query_param("trade_id", query_params)
|
|
17
|
+
triggered_query_param = self.get_query_param(
|
|
18
|
+
"triggered", query_params
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if trade_query_param:
|
|
22
|
+
query = query.filter(
|
|
23
|
+
SQLTradeStopLoss.trade_id == trade_query_param
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if triggered_query_param is not None:
|
|
27
|
+
query = query.filter_by(triggered=triggered_query_param)
|
|
28
|
+
|
|
29
|
+
return query
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from investing_algorithm_framework.infrastructure.models import \
|
|
4
|
+
SQLTradeTakeProfit
|
|
5
|
+
|
|
6
|
+
from .repository import Repository
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SQLTradeTakeProfitRepository(Repository):
|
|
12
|
+
base_class = SQLTradeTakeProfit
|
|
13
|
+
DEFAULT_NOT_FOUND_MESSAGE = "The requested trade take profit was not found"
|
|
14
|
+
|
|
15
|
+
def _apply_query_params(self, db, query, query_params):
|
|
16
|
+
trade_query_param = self.get_query_param("trade_id", query_params)
|
|
17
|
+
triggered_query_param = self.get_query_param(
|
|
18
|
+
"triggered", query_params
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if trade_query_param:
|
|
22
|
+
query = query.filter(
|
|
23
|
+
SQLTradeTakeProfit.trade_id == trade_query_param
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if triggered_query_param is not None:
|
|
27
|
+
query = query.filter_by(triggered=triggered_query_param)
|
|
28
|
+
|
|
29
|
+
return query
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .azure import AzureBlobStorageStateHandler
|
|
2
|
+
from .aws import AWSS3StorageStateHandler
|
|
3
|
+
from .backtesting import BacktestService, EventBacktestService
|
|
2
4
|
|
|
3
|
-
__all__ = [
|
|
5
|
+
__all__ = [
|
|
6
|
+
"AzureBlobStorageStateHandler",
|
|
7
|
+
"AWSS3StorageStateHandler",
|
|
8
|
+
"BacktestService",
|
|
9
|
+
"EventBacktestService",
|
|
10
|
+
]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import boto3
|
|
4
|
+
import stat
|
|
5
|
+
from botocore.exceptions import NoCredentialsError, PartialCredentialsError
|
|
6
|
+
from investing_algorithm_framework.domain import OperationalException, \
|
|
7
|
+
StateHandler
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("investing_algorithm_framework")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _fix_permissions(target_directory: str):
|
|
13
|
+
"""
|
|
14
|
+
Fix permissions on downloaded files to make them writable.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
target_directory (str): Directory to fix permissions for
|
|
18
|
+
"""
|
|
19
|
+
try:
|
|
20
|
+
# Fix the target directory itself
|
|
21
|
+
os.chmod(target_directory, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
|
22
|
+
|
|
23
|
+
# Recursively fix all subdirectories and files
|
|
24
|
+
for root, dirs, files in os.walk(target_directory):
|
|
25
|
+
# Fix current directory permissions
|
|
26
|
+
os.chmod(root, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
|
27
|
+
|
|
28
|
+
# Fix all subdirectories
|
|
29
|
+
for dir_name in dirs:
|
|
30
|
+
dir_path = os.path.join(root, dir_name)
|
|
31
|
+
os.chmod(dir_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)
|
|
32
|
+
|
|
33
|
+
# Fix all files - make them readable and writable
|
|
34
|
+
for file_name in files:
|
|
35
|
+
file_path = os.path.join(root, file_name)
|
|
36
|
+
os.chmod(
|
|
37
|
+
file_path,
|
|
38
|
+
stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
logger.info(f"Update permissions for {target_directory}")
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.warning(f"Error fixing permissions: {e}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AWSS3StorageStateHandler(StateHandler):
|
|
47
|
+
"""
|
|
48
|
+
A state handler for AWS S3 storage.
|
|
49
|
+
|
|
50
|
+
This class provides methods to save and load state to and from
|
|
51
|
+
AWS S3 storage.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
bucket_name (str): The name of the AWS S3 bucket.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, bucket_name: str = None):
|
|
58
|
+
self.bucket_name = bucket_name
|
|
59
|
+
self.s3_client = None
|
|
60
|
+
|
|
61
|
+
def initialize(self):
|
|
62
|
+
self.bucket_name = self.bucket_name or os.getenv("AWS_S3_BUCKET_NAME")
|
|
63
|
+
|
|
64
|
+
if not self.bucket_name:
|
|
65
|
+
raise OperationalException(
|
|
66
|
+
"AWS S3 state handler requires a bucket_name para or the "
|
|
67
|
+
"AWS_S3_BUCKET_NAME environment variable needs to be set "
|
|
68
|
+
"in the environment."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
self.s3_client = boto3.client("s3")
|
|
72
|
+
|
|
73
|
+
def save(self, source_directory: str):
|
|
74
|
+
"""
|
|
75
|
+
Save the state to AWS S3.
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
source_directory (str): Directory to save the state
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
None
|
|
82
|
+
"""
|
|
83
|
+
logger.info("Saving state to AWS S3 ...")
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
# Walk through the directory
|
|
87
|
+
for root, _, files in os.walk(source_directory):
|
|
88
|
+
for file_name in files:
|
|
89
|
+
# Get the full path of the file
|
|
90
|
+
file_path = os.path.join(root, file_name)
|
|
91
|
+
|
|
92
|
+
# Construct the S3 object key (relative path in the bucket)
|
|
93
|
+
s3_key = os.path.relpath(file_path, source_directory)
|
|
94
|
+
# Convert to forward slashes for S3 compatibility
|
|
95
|
+
s3_key = s3_key.replace(os.sep, "/")
|
|
96
|
+
|
|
97
|
+
self.s3_client.upload_file(
|
|
98
|
+
file_path,
|
|
99
|
+
self.bucket_name,
|
|
100
|
+
s3_key,
|
|
101
|
+
ExtraArgs={'ACL': 'private'}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
except (NoCredentialsError, PartialCredentialsError) as ex:
|
|
105
|
+
logger.error(f"Error saving state to AWS S3: {ex}")
|
|
106
|
+
raise OperationalException(
|
|
107
|
+
"AWS credentials are missing or incomplete."
|
|
108
|
+
)
|
|
109
|
+
except Exception as ex:
|
|
110
|
+
logger.error(f"Error saving state to AWS S3: {ex}")
|
|
111
|
+
raise ex
|
|
112
|
+
|
|
113
|
+
def load(self, target_directory: str):
|
|
114
|
+
"""
|
|
115
|
+
Load the state from AWS S3.
|
|
116
|
+
"""
|
|
117
|
+
logger.info("Loading state from AWS S3 ...")
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
if not os.path.exists(target_directory):
|
|
121
|
+
os.makedirs(
|
|
122
|
+
target_directory,
|
|
123
|
+
mode=stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
os.chmod(
|
|
127
|
+
target_directory, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
response = self.s3_client.list_objects_v2(Bucket=self.bucket_name)
|
|
131
|
+
|
|
132
|
+
if "Contents" in response:
|
|
133
|
+
for obj in response["Contents"]:
|
|
134
|
+
s3_key = obj["Key"]
|
|
135
|
+
# Convert S3 forward slashes to OS-specific separators
|
|
136
|
+
file_path = os.path.join(
|
|
137
|
+
target_directory, s3_key.replace("/", os.sep)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
os.makedirs(
|
|
141
|
+
os.path.dirname(file_path),
|
|
142
|
+
exist_ok=True,
|
|
143
|
+
mode=stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self.s3_client.download_file(
|
|
147
|
+
self.bucket_name, s3_key, file_path
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if os.path.isfile(file_path):
|
|
151
|
+
os.chmod(
|
|
152
|
+
file_path,
|
|
153
|
+
stat.S_IRUSR |
|
|
154
|
+
stat.S_IWUSR |
|
|
155
|
+
stat.S_IRGRP |
|
|
156
|
+
stat.S_IROTH
|
|
157
|
+
)
|
|
158
|
+
else:
|
|
159
|
+
os.chmod(
|
|
160
|
+
file_path,
|
|
161
|
+
stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# Final recursive fix
|
|
165
|
+
_fix_permissions(target_directory)
|
|
166
|
+
|
|
167
|
+
# Add write permission to database file
|
|
168
|
+
db_file = os.path.join(
|
|
169
|
+
target_directory, "databases", "prod-database.sqlite3"
|
|
170
|
+
)
|
|
171
|
+
if os.path.exists(db_file):
|
|
172
|
+
os.chmod(
|
|
173
|
+
db_file,
|
|
174
|
+
stat.S_IRUSR |
|
|
175
|
+
stat.S_IWUSR |
|
|
176
|
+
stat.S_IRGRP |
|
|
177
|
+
stat.S_IWGRP |
|
|
178
|
+
stat.S_IROTH |
|
|
179
|
+
stat.S_IWOTH
|
|
180
|
+
)
|
|
181
|
+
logger.info(
|
|
182
|
+
f"Database file permissions "
|
|
183
|
+
f"after fix: {oct(os.stat(db_file).st_mode)}"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
except (NoCredentialsError, PartialCredentialsError) as ex:
|
|
187
|
+
logger.error(f"Error loading state from AWS S3: {ex}")
|
|
188
|
+
raise OperationalException(
|
|
189
|
+
"AWS credentials are missing or incomplete."
|
|
190
|
+
)
|
|
191
|
+
except Exception as ex:
|
|
192
|
+
logger.error(f"Error loading state from AWS S3: {ex}")
|
|
193
|
+
raise ex
|