investing-algorithm-framework 6.9.1__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 +147 -44
- investing_algorithm_framework/app/__init__.py +23 -6
- investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
- investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
- 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 +1322 -707
- investing_algorithm_framework/app/context.py +196 -88
- investing_algorithm_framework/app/eventloop.py +590 -0
- investing_algorithm_framework/app/reporting/__init__.py +16 -5
- investing_algorithm_framework/app/reporting/ascii.py +57 -202
- investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
- investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
- 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 +11 -26
- investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
- investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
- investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
- investing_algorithm_framework/app/reporting/generate.py +100 -114
- investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
- investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
- investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
- investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
- investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
- investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
- investing_algorithm_framework/app/strategy.py +315 -175
- investing_algorithm_framework/app/task.py +5 -3
- investing_algorithm_framework/cli/cli.py +30 -12
- investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
- investing_algorithm_framework/cli/initialize_app.py +20 -1
- investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
- 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_requirements.txt.template +2 -2
- investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
- investing_algorithm_framework/create_app.py +3 -5
- investing_algorithm_framework/dependency_container.py +25 -39
- investing_algorithm_framework/domain/__init__.py +45 -38
- 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 +27 -0
- investing_algorithm_framework/domain/constants.py +6 -34
- investing_algorithm_framework/domain/data_provider.py +200 -56
- investing_algorithm_framework/domain/exceptions.py +34 -1
- investing_algorithm_framework/domain/models/__init__.py +10 -19
- investing_algorithm_framework/domain/models/base_model.py +0 -6
- 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/{market_data_type.py → data/data_type.py} +7 -7
- investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
- investing_algorithm_framework/domain/models/order/order.py +34 -13
- investing_algorithm_framework/domain/models/order/order_status.py +1 -1
- investing_algorithm_framework/domain/models/order/order_type.py +1 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
- investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
- investing_algorithm_framework/domain/models/position/__init__.py +2 -1
- investing_algorithm_framework/domain/models/position/position.py +9 -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 +0 -1
- investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
- investing_algorithm_framework/domain/models/time_frame.py +7 -0
- investing_algorithm_framework/domain/models/time_interval.py +33 -0
- investing_algorithm_framework/domain/models/time_unit.py +63 -1
- investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
- investing_algorithm_framework/domain/models/trade/trade.py +56 -32
- investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
- investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
- investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
- investing_algorithm_framework/domain/order_executor.py +19 -0
- investing_algorithm_framework/domain/portfolio_provider.py +20 -1
- investing_algorithm_framework/domain/services/__init__.py +0 -13
- investing_algorithm_framework/domain/strategy.py +1 -29
- investing_algorithm_framework/domain/utils/__init__.py +5 -1
- investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
- investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
- investing_algorithm_framework/domain/utils/polars.py +17 -14
- investing_algorithm_framework/download_data.py +40 -10
- investing_algorithm_framework/infrastructure/__init__.py +13 -25
- investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
- investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
- investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
- 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 +81 -0
- investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
- investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
- investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
- investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
- investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
- investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
- investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
- investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
- investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
- investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
- investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
- investing_algorithm_framework/services/__init__.py +105 -8
- investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
- investing_algorithm_framework/services/configuration_service.py +14 -4
- 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/{app/reporting → services}/metrics/__init__.py +48 -17
- investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
- investing_algorithm_framework/services/metrics/generate.py +358 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
- investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
- investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
- investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
- investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
- investing_algorithm_framework/services/metrics/trades.py +500 -0
- investing_algorithm_framework/services/metrics/volatility.py +97 -0
- investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
- investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
- investing_algorithm_framework/services/order_service/order_service.py +9 -71
- investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
- investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
- investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
- investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
- investing_algorithm_framework/services/repository_service.py +5 -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 +51 -29
- 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-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
- investing_algorithm_framework/app/reporting/evaluation.py +0 -243
- investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
- investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
- investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
- investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
- investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
- investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
- investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
- investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
- investing_algorithm_framework/domain/models/data_source.py +0 -21
- investing_algorithm_framework/domain/models/date_range.py +0 -64
- investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
- investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
- investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
- investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
- investing_algorithm_framework/domain/services/market_service.py +0 -153
- investing_algorithm_framework/domain/services/observable.py +0 -51
- investing_algorithm_framework/domain/services/observer.py +0 -19
- investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
- investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
- investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
- investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
- investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
- investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
- 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 -322
- investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
- investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
- investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
- investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
- investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
- investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
- /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
- /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
- {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/entry_points.txt +0 -0
|
@@ -159,24 +159,33 @@ def deploy_azure_function(
|
|
|
159
159
|
required=True,
|
|
160
160
|
help='The AWS region where the Lambda function will be deployed.'
|
|
161
161
|
)
|
|
162
|
-
@click.option(
|
|
163
|
-
'--lambda_handler',
|
|
164
|
-
required=True,
|
|
165
|
-
help='The Lambda handler function in the '
|
|
166
|
-
'format "module_name.function_name".',
|
|
167
|
-
default="aws_function.lambda_handler"
|
|
168
|
-
)
|
|
169
162
|
@click.option(
|
|
170
163
|
'--project_dir',
|
|
171
164
|
default=None,
|
|
172
165
|
help='The path to the project directory containing '
|
|
173
166
|
'the Lambda function code.'
|
|
174
167
|
)
|
|
168
|
+
@click.option(
|
|
169
|
+
'--memory_size',
|
|
170
|
+
default=3000,
|
|
171
|
+
type=int,
|
|
172
|
+
help='The memory size for the Lambda function in MB. Default is 3000 MB.'
|
|
173
|
+
)
|
|
174
|
+
@click.option(
|
|
175
|
+
'--env',
|
|
176
|
+
'-e',
|
|
177
|
+
multiple=True,
|
|
178
|
+
nargs=2,
|
|
179
|
+
type=str,
|
|
180
|
+
help='Environment variables to pass to the Lambda function. '
|
|
181
|
+
'Can be used multiple times: -e KEY VALUE -e KEY2 VALUE2'
|
|
182
|
+
)
|
|
175
183
|
def deploy_aws_lambda(
|
|
176
184
|
lambda_function_name,
|
|
177
185
|
region,
|
|
178
|
-
lambda_handler,
|
|
179
186
|
project_dir=None,
|
|
187
|
+
memory_size=3000,
|
|
188
|
+
env=None
|
|
180
189
|
):
|
|
181
190
|
"""
|
|
182
191
|
Command-line tool for deploying a trading bot to AWS lambda
|
|
@@ -186,20 +195,29 @@ def deploy_aws_lambda(
|
|
|
186
195
|
to deploy.
|
|
187
196
|
region (str): The AWS region where the Lambda function will
|
|
188
197
|
be deployed.
|
|
189
|
-
lambda_handler (str): The Lambda handler function in the format
|
|
190
|
-
"module_name.function_name".
|
|
191
198
|
project_dir (str): The path to the project directory containing the
|
|
192
199
|
Lambda function code. If not provided, it defaults to
|
|
193
200
|
the current directory.
|
|
201
|
+
memory_size (int): The memory size for the Lambda function in MB.
|
|
202
|
+
Default is 3000 MB.
|
|
203
|
+
env (tuple): Environment variables as tuples of (KEY, VALUE).
|
|
204
|
+
Can be specified multiple times.
|
|
194
205
|
|
|
195
206
|
Returns:
|
|
196
207
|
None
|
|
197
208
|
"""
|
|
209
|
+
# Convert env tuples to dictionary
|
|
210
|
+
env_vars = {}
|
|
211
|
+
if env:
|
|
212
|
+
for key, value in env:
|
|
213
|
+
env_vars[key] = value
|
|
214
|
+
|
|
198
215
|
deploy_to_aws_lambda_command(
|
|
199
216
|
lambda_function_name=lambda_function_name,
|
|
200
217
|
region=region,
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
project_dir=project_dir,
|
|
219
|
+
memory_size=memory_size,
|
|
220
|
+
env_vars=env_vars
|
|
203
221
|
)
|
|
204
222
|
|
|
205
223
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
|
+
import subprocess
|
|
4
5
|
import time
|
|
5
|
-
import uuid
|
|
6
6
|
import zipfile
|
|
7
7
|
|
|
8
8
|
import boto3
|
|
@@ -39,7 +39,7 @@ def sanitize_bucket_name(name: str) -> str:
|
|
|
39
39
|
return name # Enforce length limit
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def zip_code(source_dir, zip_file):
|
|
42
|
+
def zip_code(source_dir, zip_file, ignore_dirs=None):
|
|
43
43
|
"""
|
|
44
44
|
Recursively zips the contents of source_dir into zip_file,
|
|
45
45
|
preserving directory structure — suitable for AWS Lambda deployment.
|
|
@@ -47,6 +47,7 @@ def zip_code(source_dir, zip_file):
|
|
|
47
47
|
Args:
|
|
48
48
|
source_dir: str, the directory containing the Lambda function code.
|
|
49
49
|
zip_file: str, the path where the zip file will be created.
|
|
50
|
+
ignore_dirs: list, directories to ignore when zipping the code.
|
|
50
51
|
|
|
51
52
|
Returns:
|
|
52
53
|
None
|
|
@@ -56,6 +57,16 @@ def zip_code(source_dir, zip_file):
|
|
|
56
57
|
# Function should recursively zip all files and directories
|
|
57
58
|
with zipfile.ZipFile(zip_file, 'w', zipfile.ZIP_DEFLATED) as zf:
|
|
58
59
|
for root, _, files in os.walk(source_dir):
|
|
60
|
+
|
|
61
|
+
# Skip ignored directories
|
|
62
|
+
if ignore_dirs is not None:
|
|
63
|
+
relative_root = os.path.relpath(root, source_dir)
|
|
64
|
+
if any(
|
|
65
|
+
relative_root.startswith(ignore) for ignore in ignore_dirs
|
|
66
|
+
):
|
|
67
|
+
click.echo(f"Ignoring directory: {relative_root}")
|
|
68
|
+
continue
|
|
69
|
+
|
|
59
70
|
for file in files:
|
|
60
71
|
click.echo(f"Adding {file} to zip")
|
|
61
72
|
file_path = os.path.join(root, file)
|
|
@@ -115,9 +126,9 @@ def create_iam_role(role_name):
|
|
|
115
126
|
def deploy_lambda(
|
|
116
127
|
function_name,
|
|
117
128
|
region,
|
|
118
|
-
|
|
119
|
-
handler_name,
|
|
129
|
+
image_uri,
|
|
120
130
|
role_arn,
|
|
131
|
+
memory_size,
|
|
121
132
|
runtime="python3.10",
|
|
122
133
|
env_vars=None,
|
|
123
134
|
):
|
|
@@ -127,32 +138,29 @@ def deploy_lambda(
|
|
|
127
138
|
Args:
|
|
128
139
|
function_name: str, the name of the Lambda function to
|
|
129
140
|
create or update.
|
|
130
|
-
zip_file_path: str, the path to the zip file containing
|
|
131
|
-
the Lambda function code.
|
|
132
141
|
region: str, the AWS region where the Lambda
|
|
133
142
|
function will be deployed.
|
|
134
|
-
|
|
135
|
-
in the code (e.g., "main.lambda_handler").
|
|
143
|
+
image_uri: str, the URI of the Docker image in ECR.
|
|
136
144
|
role_arn: str, the ARN of the IAM role that Lambda will assume.
|
|
137
145
|
runtime: str, the runtime environment for the
|
|
138
146
|
Lambda function (default is "python3.10").
|
|
139
147
|
env_vars: dict, optional environment variables
|
|
140
148
|
to set for the Lambda function.
|
|
149
|
+
memory_size: int, the amount of memory allocated
|
|
150
|
+
to the Lambda function.
|
|
151
|
+
|
|
141
152
|
Returns:
|
|
142
153
|
None
|
|
143
154
|
"""
|
|
144
155
|
lambda_client = boto3.client('lambda', region_name=region)
|
|
145
156
|
|
|
146
|
-
with open(zip_file_path, 'rb') as f:
|
|
147
|
-
zipped_code = f.read()
|
|
148
|
-
|
|
149
157
|
try:
|
|
150
158
|
lambda_client.get_function(FunctionName=function_name)
|
|
151
159
|
click.echo(f"Function {function_name} already exists. Updating...")
|
|
152
160
|
|
|
153
161
|
lambda_client.update_function_code(
|
|
154
162
|
FunctionName=function_name,
|
|
155
|
-
|
|
163
|
+
ImageUri=image_uri
|
|
156
164
|
)
|
|
157
165
|
wait_for_lambda_update(lambda_client, function_name, timeout=120)
|
|
158
166
|
lambda_client.update_function_configuration(
|
|
@@ -163,15 +171,18 @@ def deploy_lambda(
|
|
|
163
171
|
click.echo(f"Creating new function: {function_name}")
|
|
164
172
|
|
|
165
173
|
try:
|
|
174
|
+
click.echo(
|
|
175
|
+
"Creating new container-based "
|
|
176
|
+
f"Lambda function: {function_name}"
|
|
177
|
+
)
|
|
166
178
|
lambda_client.create_function(
|
|
167
179
|
FunctionName=function_name,
|
|
168
|
-
Runtime=runtime,
|
|
169
180
|
Role=role_arn,
|
|
170
|
-
|
|
171
|
-
Code={
|
|
181
|
+
PackageType="Image",
|
|
182
|
+
Code={"ImageUri": image_uri},
|
|
172
183
|
Timeout=900,
|
|
173
|
-
MemorySize=
|
|
174
|
-
Environment={
|
|
184
|
+
MemorySize=memory_size,
|
|
185
|
+
Environment={"Variables": env_vars or {}}
|
|
175
186
|
)
|
|
176
187
|
except Exception as e:
|
|
177
188
|
raise click.ClickException(
|
|
@@ -202,6 +213,82 @@ def s3_bucket_exists(bucket_name, region):
|
|
|
202
213
|
raise
|
|
203
214
|
|
|
204
215
|
|
|
216
|
+
def create_ecr_repository(repository_name, region):
|
|
217
|
+
"""
|
|
218
|
+
Function to create an ECR repository for storing Docker images.
|
|
219
|
+
It checks if the repository already exists and creates it if not.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
repository_name: str, the name of the ECR repository to create.
|
|
223
|
+
region: str, the AWS region where the repository will be created.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
None
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
ecr = boto3.client('ecr', region_name=region)
|
|
230
|
+
try:
|
|
231
|
+
response = ecr.create_repository(repositoryName=repository_name)
|
|
232
|
+
click.echo(
|
|
233
|
+
"Created ECR repository: "
|
|
234
|
+
f"{response['repository']['repositoryUri']}"
|
|
235
|
+
)
|
|
236
|
+
except ecr.exceptions.RepositoryAlreadyExistsException:
|
|
237
|
+
click.echo(f"ECR repository {repository_name} already exists.")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def build_and_push_docker_image(
|
|
241
|
+
repository_name, region, dockerfile_path='Dockerfile', tag='latest'
|
|
242
|
+
):
|
|
243
|
+
"""
|
|
244
|
+
Function to build a Docker image and push it to an ECR repository.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
repository_name: str, the name of the ECR repository.
|
|
248
|
+
region: str, the AWS region where the repository is located.
|
|
249
|
+
dockerfile_path: str, path to the Dockerfile (default is 'Dockerfile').
|
|
250
|
+
tag: str, the tag for the Docker image (default is 'latest').
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
None
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
# Retrieve the ECR repository URI
|
|
257
|
+
ecr = boto3.client('ecr', region_name=region)
|
|
258
|
+
try:
|
|
259
|
+
response = ecr.describe_repositories(repositoryNames=[repository_name])
|
|
260
|
+
repository_uri = response['repositories'][0]['repositoryUri']
|
|
261
|
+
except ecr.exceptions.RepositoryNotFoundException:
|
|
262
|
+
raise click.ClickException(
|
|
263
|
+
f"ECR repository {repository_name} does "
|
|
264
|
+
f"not exist in region {region}."
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Authenticate Docker to the ECR registry
|
|
268
|
+
auth = ecr.get_authorization_token()
|
|
269
|
+
proxy = auth['authorizationData'][0]['proxyEndpoint']
|
|
270
|
+
|
|
271
|
+
click.echo(f"Authenticating Docker to ECR repository {repository_name}...")
|
|
272
|
+
subprocess.run(
|
|
273
|
+
f"aws ecr get-login-password --region {region} | "
|
|
274
|
+
f"docker login --username AWS --password-stdin {proxy}",
|
|
275
|
+
shell=True, check=True
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
click.echo(f"Building Docker image {repository_name}:{tag}...")
|
|
279
|
+
# Build and push Docker image with the docker file path
|
|
280
|
+
image_full_uri = f"{repository_uri}:{tag}"
|
|
281
|
+
subprocess.run([
|
|
282
|
+
"docker", "build",
|
|
283
|
+
"--platform=linux/amd64",
|
|
284
|
+
"-t", image_full_uri,
|
|
285
|
+
"-f", dockerfile_path,
|
|
286
|
+
"."
|
|
287
|
+
], check=True)
|
|
288
|
+
subprocess.run(f"docker push {image_full_uri}", shell=True, check=True)
|
|
289
|
+
return image_full_uri
|
|
290
|
+
|
|
291
|
+
|
|
205
292
|
def create_s3_bucket(bucket_name, region):
|
|
206
293
|
"""
|
|
207
294
|
Function to create an S3 bucket for storing Lambda function code.
|
|
@@ -225,7 +312,7 @@ def create_s3_bucket(bucket_name, region):
|
|
|
225
312
|
click.echo(f"S3 bucket {bucket_name} already exists.")
|
|
226
313
|
|
|
227
314
|
|
|
228
|
-
def read_env_file(env_path):
|
|
315
|
+
def read_env_file(env_path) -> dict:
|
|
229
316
|
"""
|
|
230
317
|
Function to read environment variables from a .env file.
|
|
231
318
|
|
|
@@ -262,7 +349,8 @@ def check_lambda_permissions(required_actions=None):
|
|
|
262
349
|
"lambda:GetFunction",
|
|
263
350
|
"lambda:UpdateFunctionCode",
|
|
264
351
|
"lambda:UpdateFunctionConfiguration",
|
|
265
|
-
"lambda:CreateFunction"
|
|
352
|
+
"lambda:CreateFunction",
|
|
353
|
+
"ecr:CreateRepository"
|
|
266
354
|
]
|
|
267
355
|
|
|
268
356
|
sts = boto3.client("sts")
|
|
@@ -345,8 +433,9 @@ def wait_for_lambda_update(lambda_client, function_name, timeout=60):
|
|
|
345
433
|
def command(
|
|
346
434
|
lambda_function_name,
|
|
347
435
|
region,
|
|
348
|
-
lambda_handler,
|
|
349
436
|
project_dir=None,
|
|
437
|
+
memory_size=3000,
|
|
438
|
+
env_vars=None
|
|
350
439
|
):
|
|
351
440
|
"""
|
|
352
441
|
Command-line tool for deploying a trading bot to AWS Lambda.
|
|
@@ -356,8 +445,8 @@ def command(
|
|
|
356
445
|
region: str, the AWS region where the Lambda function will be deployed.
|
|
357
446
|
project_dir: str, the directory containing the Lambda function code.
|
|
358
447
|
If None, it defaults to the current directory.
|
|
359
|
-
|
|
360
|
-
|
|
448
|
+
memory_size: int, the amount of memory allocated
|
|
449
|
+
to the Lambda function
|
|
361
450
|
|
|
362
451
|
Returns:
|
|
363
452
|
None
|
|
@@ -368,12 +457,11 @@ def command(
|
|
|
368
457
|
|
|
369
458
|
check_lambda_permissions()
|
|
370
459
|
|
|
371
|
-
click.echo(
|
|
372
|
-
|
|
460
|
+
click.echo(
|
|
461
|
+
"Deploying to AWS Lambda "
|
|
462
|
+
f"function: {lambda_function_name} in region: {region}"
|
|
463
|
+
)
|
|
373
464
|
click.echo(f"Project directory: {project_dir}")
|
|
374
|
-
zip_file_path = f"/tmp/deploy-{uuid.uuid4().hex}.zip"
|
|
375
|
-
zip_code(project_dir, zip_file_path)
|
|
376
|
-
click.echo(f"Zipped code to {zip_file_path}")
|
|
377
465
|
|
|
378
466
|
# Create s3 bucket for state handler
|
|
379
467
|
bucket_name = f"{lambda_function_name}-state-handler-{region}"
|
|
@@ -382,23 +470,32 @@ def command(
|
|
|
382
470
|
if not s3_bucket_exists(bucket_name, region):
|
|
383
471
|
create_s3_bucket(bucket_name, region)
|
|
384
472
|
|
|
385
|
-
|
|
473
|
+
local_env_vars = read_env_file(
|
|
386
474
|
env_path=os.path.join(project_dir, ".env") if project_dir else ".env"
|
|
387
475
|
)
|
|
388
|
-
|
|
389
|
-
|
|
476
|
+
if env_vars is None:
|
|
477
|
+
env_vars = {}
|
|
478
|
+
|
|
479
|
+
env_vars.update(local_env_vars)
|
|
390
480
|
|
|
391
|
-
click.echo("Read the following environment variables from .env file:")
|
|
392
481
|
click.echo("Adding S3 bucket name to environment variables")
|
|
393
482
|
env_vars[AWS_S3_STATE_BUCKET_NAME] = bucket_name
|
|
394
483
|
|
|
484
|
+
click.echo("Building and pushing Docker image to ECR")
|
|
485
|
+
create_ecr_repository(lambda_function_name, region)
|
|
486
|
+
image_uri = build_and_push_docker_image(
|
|
487
|
+
lambda_function_name,
|
|
488
|
+
region,
|
|
489
|
+
dockerfile_path=os.path.join(project_dir, "Dockerfile"),
|
|
490
|
+
tag="latest"
|
|
491
|
+
)
|
|
395
492
|
click.echo("Creating IAM role for Lambda execution")
|
|
396
493
|
role_arn = create_iam_role("lambda-execution-role")
|
|
397
494
|
deploy_lambda(
|
|
398
495
|
lambda_function_name,
|
|
399
|
-
|
|
400
|
-
handler_name=lambda_handler,
|
|
496
|
+
image_uri=image_uri,
|
|
401
497
|
role_arn=role_arn,
|
|
402
498
|
env_vars=env_vars,
|
|
403
|
-
region=region
|
|
499
|
+
region=region,
|
|
500
|
+
memory_size=memory_size
|
|
404
501
|
)
|
|
@@ -121,7 +121,6 @@ def command(path=None, app_type="default", replace=False):
|
|
|
121
121
|
else:
|
|
122
122
|
# check if directory exists
|
|
123
123
|
if not os.path.exists(path) or not os.path.isdir(path):
|
|
124
|
-
print(f"Directory {path} does not exist.")
|
|
125
124
|
return
|
|
126
125
|
|
|
127
126
|
if AppType.DEFAULT.equals(app_type):
|
|
@@ -372,6 +371,16 @@ def create_aws_lambda_app(path=None, replace=False):
|
|
|
372
371
|
"templates",
|
|
373
372
|
"app_aws_lambda_function.py.template"
|
|
374
373
|
)
|
|
374
|
+
aws_dockerfile_template_path = os.path.join(
|
|
375
|
+
os.path.dirname(current_script_path),
|
|
376
|
+
"templates",
|
|
377
|
+
"aws_lambda_dockerfile.template"
|
|
378
|
+
)
|
|
379
|
+
aws_dockerignore_template_path = os.path.join(
|
|
380
|
+
os.path.dirname(current_script_path),
|
|
381
|
+
"templates",
|
|
382
|
+
"aws_lambda_dockerignore.template"
|
|
383
|
+
)
|
|
375
384
|
run_backtest_template_path = os.path.join(
|
|
376
385
|
os.path.dirname(current_script_path),
|
|
377
386
|
"templates",
|
|
@@ -446,6 +455,16 @@ def create_aws_lambda_app(path=None, replace=False):
|
|
|
446
455
|
os.path.join(path, "aws_function.py"),
|
|
447
456
|
replace=replace
|
|
448
457
|
)
|
|
458
|
+
create_file_from_template(
|
|
459
|
+
aws_dockerfile_template_path,
|
|
460
|
+
os.path.join(path, "Dockerfile"),
|
|
461
|
+
replace=replace
|
|
462
|
+
)
|
|
463
|
+
create_file_from_template(
|
|
464
|
+
aws_dockerignore_template_path,
|
|
465
|
+
os.path.join(path, ".dockerignore"),
|
|
466
|
+
replace=replace
|
|
467
|
+
)
|
|
449
468
|
|
|
450
469
|
|
|
451
470
|
def create_azure_function_app(path=None, replace=False):
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
import logging.config
|
|
1
2
|
import os
|
|
2
|
-
from
|
|
3
|
-
AWSS3StorageStateHandler, AWS_S3_STATE_BUCKET_NAME
|
|
3
|
+
from logging import getLogger
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
from finterion_investing_algorithm_framework import FinterionOrderExecutor, \
|
|
7
|
+
FinterionPingAction, FinterionPortfolioProvider
|
|
8
|
+
from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY, \
|
|
9
|
+
AWSS3StorageStateHandler, AWS_S3_STATE_BUCKET_NAME, AWS_LAMBDA_LOGGING_CONFIG
|
|
10
|
+
|
|
11
|
+
from strategies.strategy import MyTradingStrategy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Make sure to set the resource directory to /tmp because this dir is writable
|
|
15
|
+
app = create_app(config={RESOURCE_DIRECTORY: os.path.join("/tmp", "resources")})
|
|
16
|
+
logging.config.dictConfig(AWS_LAMBDA_LOGGING_CONFIG)
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
7
18
|
|
|
8
19
|
# Get the s3 state bucket name from environment variables for database
|
|
9
20
|
# state storage, this is set during the deployment
|
|
@@ -12,7 +23,7 @@ app.add_state_handler(
|
|
|
12
23
|
AWSS3StorageStateHandler(bucket_name=os.getenv(AWS_S3_STATE_BUCKET_NAME))
|
|
13
24
|
)
|
|
14
25
|
app.add_strategy(MyTradingStrategy)
|
|
15
|
-
app.add_market(market="BITVAVO", trading_symbol="EUR"
|
|
26
|
+
app.add_market(market="BITVAVO", trading_symbol="EUR")
|
|
16
27
|
|
|
17
28
|
|
|
18
29
|
def lambda_handler(event, context):
|
|
@@ -27,10 +38,11 @@ def lambda_handler(event, context):
|
|
|
27
38
|
dict: The result of the trading strategy execution.
|
|
28
39
|
"""
|
|
29
40
|
try:
|
|
30
|
-
app.run(
|
|
41
|
+
app.run(payload={"ACTION": "RUN_STRATEGY"})
|
|
31
42
|
return {
|
|
32
43
|
"statusCode": 200,
|
|
33
44
|
"body": "Trading strategy executed successfully."
|
|
34
45
|
}
|
|
35
46
|
except Exception as e:
|
|
47
|
+
logger.exception(e)
|
|
36
48
|
return {"statusCode": 500, "body": str(e)}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
FROM public.ecr.aws/lambda/python:3.10
|
|
2
|
+
|
|
3
|
+
# Install build tools (add this block)
|
|
4
|
+
RUN yum install -y \
|
|
5
|
+
gcc \
|
|
6
|
+
gcc-c++ \
|
|
7
|
+
make \
|
|
8
|
+
cmake \
|
|
9
|
+
&& yum clean all
|
|
10
|
+
|
|
11
|
+
# Set the working directory
|
|
12
|
+
WORKDIR /var/task
|
|
13
|
+
|
|
14
|
+
# Install Python dependencies
|
|
15
|
+
COPY requirements.txt .
|
|
16
|
+
RUN pip install --upgrade pip
|
|
17
|
+
RUN pip install --no-cache-dir -r requirements.txt
|
|
18
|
+
|
|
19
|
+
# Copy the function code
|
|
20
|
+
COPY . .
|
|
21
|
+
|
|
22
|
+
CMD ["aws_function.lambda_handler"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Git
|
|
2
|
+
.git
|
|
3
|
+
.gitignore
|
|
4
|
+
.gitattributes
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# CI
|
|
8
|
+
.codeclimate.yml
|
|
9
|
+
.travis.yml
|
|
10
|
+
.taskcluster.yml
|
|
11
|
+
|
|
12
|
+
# Docker
|
|
13
|
+
docker-compose.yml
|
|
14
|
+
Dockerfile
|
|
15
|
+
.docker
|
|
16
|
+
.dockerignore
|
|
17
|
+
|
|
18
|
+
# Byte-compiled / optimized / DLL files
|
|
19
|
+
**/__pycache__/
|
|
20
|
+
**/*.py[cod]
|
|
21
|
+
|
|
22
|
+
# C extensions
|
|
23
|
+
*.so
|
|
24
|
+
|
|
25
|
+
# Distribution / packaging
|
|
26
|
+
.Python
|
|
27
|
+
env/
|
|
28
|
+
build/
|
|
29
|
+
develop-eggs/
|
|
30
|
+
dist/
|
|
31
|
+
downloads/
|
|
32
|
+
eggs/
|
|
33
|
+
lib/
|
|
34
|
+
lib64/
|
|
35
|
+
parts/
|
|
36
|
+
sdist/
|
|
37
|
+
var/
|
|
38
|
+
*.egg-info/
|
|
39
|
+
.installed.cfg
|
|
40
|
+
*.egg
|
|
41
|
+
|
|
42
|
+
# PyInstaller
|
|
43
|
+
# Usually these files are written by a python script from a template
|
|
44
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
45
|
+
*.manifest
|
|
46
|
+
*.spec
|
|
47
|
+
|
|
48
|
+
# Installer logs
|
|
49
|
+
pip-log.txt
|
|
50
|
+
pip-delete-this-directory.txt
|
|
51
|
+
|
|
52
|
+
# Unit test / coverage reports
|
|
53
|
+
htmlcov/
|
|
54
|
+
.tox/
|
|
55
|
+
.coverage
|
|
56
|
+
.cache
|
|
57
|
+
nosetests.xml
|
|
58
|
+
coverage.xml
|
|
59
|
+
|
|
60
|
+
# Translations
|
|
61
|
+
*.mo
|
|
62
|
+
*.pot
|
|
63
|
+
|
|
64
|
+
# Django stuff:
|
|
65
|
+
*.log
|
|
66
|
+
|
|
67
|
+
# Sphinx documentation
|
|
68
|
+
docs/_build/
|
|
69
|
+
|
|
70
|
+
# PyBuilder
|
|
71
|
+
target/
|
|
72
|
+
|
|
73
|
+
# Virtual environment
|
|
74
|
+
.env
|
|
75
|
+
.venv/
|
|
76
|
+
venv/
|
|
77
|
+
|
|
78
|
+
# PyCharm
|
|
79
|
+
.idea
|
|
80
|
+
|
|
81
|
+
# Python mode for VIM
|
|
82
|
+
.ropeproject
|
|
83
|
+
**/.ropeproject
|
|
84
|
+
|
|
85
|
+
# Vim swap files
|
|
86
|
+
**/*.swp
|
|
87
|
+
|
|
88
|
+
# VS Code
|
|
89
|
+
.vscode/
|
|
90
|
+
|
|
91
|
+
# Investing algorithm specific files
|
|
92
|
+
/resources
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
pyindicators>=0.5.4
|
|
1
|
+
investing_algorithm_framework>=6.9.4
|
|
2
|
+
pyindicators>=0.5.4
|
|
@@ -5,7 +5,7 @@ from dotenv import load_dotenv
|
|
|
5
5
|
|
|
6
6
|
from .app import App
|
|
7
7
|
from .dependency_container import setup_dependency_container
|
|
8
|
-
from .domain import AppMode, APPLICATION_DIRECTORY
|
|
8
|
+
from .domain import AppMode, APPLICATION_DIRECTORY, APP_MODE
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger("investing_algorithm_framework")
|
|
11
11
|
|
|
@@ -37,20 +37,18 @@ def create_app(
|
|
|
37
37
|
["investing_algorithm_framework"],
|
|
38
38
|
["investing_algorithm_framework"]
|
|
39
39
|
)
|
|
40
|
-
# After the container is setup, initialize the services
|
|
41
|
-
app.initialize_services()
|
|
42
40
|
app.name = name
|
|
43
41
|
|
|
44
42
|
if config is not None:
|
|
45
43
|
app.set_config_with_dict(config)
|
|
46
44
|
|
|
47
45
|
if web:
|
|
48
|
-
app.set_config(
|
|
46
|
+
app.set_config(APP_MODE, AppMode.WEB.value)
|
|
49
47
|
|
|
50
48
|
# Add the application directory to the config
|
|
51
49
|
caller_frame = inspect.stack()[1]
|
|
52
50
|
caller_path = os.path.abspath(caller_frame.filename)
|
|
53
51
|
app.set_config(APPLICATION_DIRECTORY, caller_path)
|
|
54
52
|
|
|
55
|
-
logger.info("Investing
|
|
53
|
+
logger.info("Investing algorithm framework app created")
|
|
56
54
|
return app
|