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.

Files changed (192) hide show
  1. investing_algorithm_framework/__init__.py +147 -44
  2. investing_algorithm_framework/app/__init__.py +23 -6
  3. investing_algorithm_framework/app/algorithm/algorithm.py +5 -41
  4. investing_algorithm_framework/app/algorithm/algorithm_factory.py +17 -10
  5. investing_algorithm_framework/app/analysis/__init__.py +15 -0
  6. investing_algorithm_framework/app/analysis/backtest_data_ranges.py +121 -0
  7. investing_algorithm_framework/app/analysis/backtest_utils.py +107 -0
  8. investing_algorithm_framework/app/analysis/permutation.py +116 -0
  9. investing_algorithm_framework/app/analysis/ranking.py +297 -0
  10. investing_algorithm_framework/app/app.py +1322 -707
  11. investing_algorithm_framework/app/context.py +196 -88
  12. investing_algorithm_framework/app/eventloop.py +590 -0
  13. investing_algorithm_framework/app/reporting/__init__.py +16 -5
  14. investing_algorithm_framework/app/reporting/ascii.py +57 -202
  15. investing_algorithm_framework/app/reporting/backtest_report.py +284 -170
  16. investing_algorithm_framework/app/reporting/charts/__init__.py +10 -2
  17. investing_algorithm_framework/app/reporting/charts/entry_exist_signals.py +66 -0
  18. investing_algorithm_framework/app/reporting/charts/equity_curve.py +37 -0
  19. investing_algorithm_framework/app/reporting/charts/equity_curve_drawdown.py +11 -26
  20. investing_algorithm_framework/app/reporting/charts/line_chart.py +11 -0
  21. investing_algorithm_framework/app/reporting/charts/ohlcv_data_completeness.py +51 -0
  22. investing_algorithm_framework/app/reporting/charts/rolling_sharp_ratio.py +1 -1
  23. investing_algorithm_framework/app/reporting/generate.py +100 -114
  24. investing_algorithm_framework/app/reporting/tables/key_metrics_table.py +40 -32
  25. investing_algorithm_framework/app/reporting/tables/time_metrics_table.py +34 -27
  26. investing_algorithm_framework/app/reporting/tables/trade_metrics_table.py +23 -19
  27. investing_algorithm_framework/app/reporting/tables/trades_table.py +1 -1
  28. investing_algorithm_framework/app/reporting/tables/utils.py +1 -0
  29. investing_algorithm_framework/app/reporting/templates/report_template.html.j2 +10 -16
  30. investing_algorithm_framework/app/strategy.py +315 -175
  31. investing_algorithm_framework/app/task.py +5 -3
  32. investing_algorithm_framework/cli/cli.py +30 -12
  33. investing_algorithm_framework/cli/deploy_to_aws_lambda.py +131 -34
  34. investing_algorithm_framework/cli/initialize_app.py +20 -1
  35. investing_algorithm_framework/cli/templates/app_aws_lambda_function.py.template +18 -6
  36. investing_algorithm_framework/cli/templates/aws_lambda_dockerfile.template +22 -0
  37. investing_algorithm_framework/cli/templates/aws_lambda_dockerignore.template +92 -0
  38. investing_algorithm_framework/cli/templates/aws_lambda_requirements.txt.template +2 -2
  39. investing_algorithm_framework/cli/templates/azure_function_requirements.txt.template +1 -1
  40. investing_algorithm_framework/create_app.py +3 -5
  41. investing_algorithm_framework/dependency_container.py +25 -39
  42. investing_algorithm_framework/domain/__init__.py +45 -38
  43. investing_algorithm_framework/domain/backtesting/__init__.py +21 -0
  44. investing_algorithm_framework/domain/backtesting/backtest.py +503 -0
  45. investing_algorithm_framework/domain/backtesting/backtest_date_range.py +96 -0
  46. investing_algorithm_framework/domain/backtesting/backtest_evaluation_focuss.py +242 -0
  47. investing_algorithm_framework/domain/backtesting/backtest_metrics.py +459 -0
  48. investing_algorithm_framework/domain/backtesting/backtest_permutation_test.py +275 -0
  49. investing_algorithm_framework/domain/backtesting/backtest_run.py +605 -0
  50. investing_algorithm_framework/domain/backtesting/backtest_summary_metrics.py +162 -0
  51. investing_algorithm_framework/domain/backtesting/combine_backtests.py +280 -0
  52. investing_algorithm_framework/domain/config.py +27 -0
  53. investing_algorithm_framework/domain/constants.py +6 -34
  54. investing_algorithm_framework/domain/data_provider.py +200 -56
  55. investing_algorithm_framework/domain/exceptions.py +34 -1
  56. investing_algorithm_framework/domain/models/__init__.py +10 -19
  57. investing_algorithm_framework/domain/models/base_model.py +0 -6
  58. investing_algorithm_framework/domain/models/data/__init__.py +7 -0
  59. investing_algorithm_framework/domain/models/data/data_source.py +214 -0
  60. investing_algorithm_framework/domain/models/{market_data_type.py → data/data_type.py} +7 -7
  61. investing_algorithm_framework/domain/models/market/market_credential.py +6 -0
  62. investing_algorithm_framework/domain/models/order/order.py +34 -13
  63. investing_algorithm_framework/domain/models/order/order_status.py +1 -1
  64. investing_algorithm_framework/domain/models/order/order_type.py +1 -1
  65. investing_algorithm_framework/domain/models/portfolio/portfolio.py +14 -1
  66. investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +5 -1
  67. investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +51 -11
  68. investing_algorithm_framework/domain/models/position/__init__.py +2 -1
  69. investing_algorithm_framework/domain/models/position/position.py +9 -0
  70. investing_algorithm_framework/domain/models/position/position_size.py +41 -0
  71. investing_algorithm_framework/domain/models/risk_rules/__init__.py +7 -0
  72. investing_algorithm_framework/domain/models/risk_rules/stop_loss_rule.py +51 -0
  73. investing_algorithm_framework/domain/models/risk_rules/take_profit_rule.py +55 -0
  74. investing_algorithm_framework/domain/models/snapshot_interval.py +0 -1
  75. investing_algorithm_framework/domain/models/strategy_profile.py +19 -151
  76. investing_algorithm_framework/domain/models/time_frame.py +7 -0
  77. investing_algorithm_framework/domain/models/time_interval.py +33 -0
  78. investing_algorithm_framework/domain/models/time_unit.py +63 -1
  79. investing_algorithm_framework/domain/models/trade/__init__.py +0 -2
  80. investing_algorithm_framework/domain/models/trade/trade.py +56 -32
  81. investing_algorithm_framework/domain/models/trade/trade_status.py +8 -2
  82. investing_algorithm_framework/domain/models/trade/trade_stop_loss.py +106 -41
  83. investing_algorithm_framework/domain/models/trade/trade_take_profit.py +161 -99
  84. investing_algorithm_framework/domain/order_executor.py +19 -0
  85. investing_algorithm_framework/domain/portfolio_provider.py +20 -1
  86. investing_algorithm_framework/domain/services/__init__.py +0 -13
  87. investing_algorithm_framework/domain/strategy.py +1 -29
  88. investing_algorithm_framework/domain/utils/__init__.py +5 -1
  89. investing_algorithm_framework/domain/utils/custom_tqdm.py +22 -0
  90. investing_algorithm_framework/domain/utils/jupyter_notebook_detection.py +19 -0
  91. investing_algorithm_framework/domain/utils/polars.py +17 -14
  92. investing_algorithm_framework/download_data.py +40 -10
  93. investing_algorithm_framework/infrastructure/__init__.py +13 -25
  94. investing_algorithm_framework/infrastructure/data_providers/__init__.py +7 -4
  95. investing_algorithm_framework/infrastructure/data_providers/ccxt.py +811 -546
  96. investing_algorithm_framework/infrastructure/data_providers/csv.py +433 -122
  97. investing_algorithm_framework/infrastructure/data_providers/pandas.py +599 -0
  98. investing_algorithm_framework/infrastructure/database/__init__.py +6 -2
  99. investing_algorithm_framework/infrastructure/database/sql_alchemy.py +81 -0
  100. investing_algorithm_framework/infrastructure/models/__init__.py +0 -13
  101. investing_algorithm_framework/infrastructure/models/order/order.py +9 -3
  102. investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py +27 -8
  103. investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py +21 -7
  104. investing_algorithm_framework/infrastructure/order_executors/__init__.py +2 -0
  105. investing_algorithm_framework/infrastructure/order_executors/backtest_oder_executor.py +28 -0
  106. investing_algorithm_framework/infrastructure/repositories/repository.py +16 -2
  107. investing_algorithm_framework/infrastructure/repositories/trade_repository.py +2 -2
  108. investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py +6 -0
  109. investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py +6 -0
  110. investing_algorithm_framework/infrastructure/services/__init__.py +0 -4
  111. investing_algorithm_framework/services/__init__.py +105 -8
  112. investing_algorithm_framework/services/backtesting/backtest_service.py +536 -476
  113. investing_algorithm_framework/services/configuration_service.py +14 -4
  114. investing_algorithm_framework/services/data_providers/__init__.py +5 -0
  115. investing_algorithm_framework/services/data_providers/data_provider_service.py +850 -0
  116. investing_algorithm_framework/{app/reporting → services}/metrics/__init__.py +48 -17
  117. investing_algorithm_framework/{app/reporting → services}/metrics/drawdown.py +10 -10
  118. investing_algorithm_framework/{app/reporting → services}/metrics/equity_curve.py +2 -2
  119. investing_algorithm_framework/{app/reporting → services}/metrics/exposure.py +60 -2
  120. investing_algorithm_framework/services/metrics/generate.py +358 -0
  121. investing_algorithm_framework/{app/reporting → services}/metrics/profit_factor.py +36 -0
  122. investing_algorithm_framework/{app/reporting → services}/metrics/recovery.py +2 -2
  123. investing_algorithm_framework/{app/reporting → services}/metrics/returns.py +146 -147
  124. investing_algorithm_framework/services/metrics/risk_free_rate.py +28 -0
  125. investing_algorithm_framework/{app/reporting/metrics/sharp_ratio.py → services/metrics/sharpe_ratio.py} +6 -10
  126. investing_algorithm_framework/{app/reporting → services}/metrics/sortino_ratio.py +3 -7
  127. investing_algorithm_framework/services/metrics/trades.py +500 -0
  128. investing_algorithm_framework/services/metrics/volatility.py +97 -0
  129. investing_algorithm_framework/{app/reporting → services}/metrics/win_rate.py +70 -3
  130. investing_algorithm_framework/services/order_service/order_backtest_service.py +21 -31
  131. investing_algorithm_framework/services/order_service/order_service.py +9 -71
  132. investing_algorithm_framework/services/portfolios/portfolio_provider_lookup.py +0 -2
  133. investing_algorithm_framework/services/portfolios/portfolio_service.py +3 -13
  134. investing_algorithm_framework/services/portfolios/portfolio_snapshot_service.py +62 -96
  135. investing_algorithm_framework/services/portfolios/portfolio_sync_service.py +0 -3
  136. investing_algorithm_framework/services/repository_service.py +5 -2
  137. investing_algorithm_framework/services/trade_order_evaluator/__init__.py +9 -0
  138. investing_algorithm_framework/services/trade_order_evaluator/backtest_trade_oder_evaluator.py +113 -0
  139. investing_algorithm_framework/services/trade_order_evaluator/default_trade_order_evaluator.py +51 -0
  140. investing_algorithm_framework/services/trade_order_evaluator/trade_order_evaluator.py +80 -0
  141. investing_algorithm_framework/services/trade_service/__init__.py +7 -1
  142. investing_algorithm_framework/services/trade_service/trade_service.py +51 -29
  143. investing_algorithm_framework/services/trade_service/trade_stop_loss_service.py +39 -0
  144. investing_algorithm_framework/services/trade_service/trade_take_profit_service.py +41 -0
  145. investing_algorithm_framework-7.19.15.dist-info/METADATA +537 -0
  146. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/RECORD +159 -148
  147. investing_algorithm_framework/app/reporting/evaluation.py +0 -243
  148. investing_algorithm_framework/app/reporting/metrics/risk_free_rate.py +0 -8
  149. investing_algorithm_framework/app/reporting/metrics/volatility.py +0 -69
  150. investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template +0 -3
  151. investing_algorithm_framework/domain/models/backtesting/__init__.py +0 -9
  152. investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py +0 -47
  153. investing_algorithm_framework/domain/models/backtesting/backtest_position.py +0 -120
  154. investing_algorithm_framework/domain/models/backtesting/backtest_reports_evaluation.py +0 -0
  155. investing_algorithm_framework/domain/models/backtesting/backtest_results.py +0 -440
  156. investing_algorithm_framework/domain/models/data_source.py +0 -21
  157. investing_algorithm_framework/domain/models/date_range.py +0 -64
  158. investing_algorithm_framework/domain/models/trade/trade_risk_type.py +0 -34
  159. investing_algorithm_framework/domain/models/trading_data_types.py +0 -48
  160. investing_algorithm_framework/domain/models/trading_time_frame.py +0 -223
  161. investing_algorithm_framework/domain/services/market_data_sources.py +0 -543
  162. investing_algorithm_framework/domain/services/market_service.py +0 -153
  163. investing_algorithm_framework/domain/services/observable.py +0 -51
  164. investing_algorithm_framework/domain/services/observer.py +0 -19
  165. investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +0 -16
  166. investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +0 -746
  167. investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -270
  168. investing_algorithm_framework/infrastructure/models/market_data_sources/pandas.py +0 -312
  169. investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -5
  170. investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -471
  171. investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -7
  172. investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -2
  173. investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +0 -322
  174. investing_algorithm_framework/services/market_data_source_service/__init__.py +0 -10
  175. investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +0 -269
  176. investing_algorithm_framework/services/market_data_source_service/data_provider_service.py +0 -350
  177. investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -377
  178. investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -296
  179. investing_algorithm_framework-6.9.1.dist-info/METADATA +0 -440
  180. /investing_algorithm_framework/{app/reporting → services}/metrics/alpha.py +0 -0
  181. /investing_algorithm_framework/{app/reporting → services}/metrics/beta.py +0 -0
  182. /investing_algorithm_framework/{app/reporting → services}/metrics/cagr.py +0 -0
  183. /investing_algorithm_framework/{app/reporting → services}/metrics/calmar_ratio.py +0 -0
  184. /investing_algorithm_framework/{app/reporting → services}/metrics/mean_daily_return.py +0 -0
  185. /investing_algorithm_framework/{app/reporting → services}/metrics/price_efficiency.py +0 -0
  186. /investing_algorithm_framework/{app/reporting → services}/metrics/standard_deviation.py +0 -0
  187. /investing_algorithm_framework/{app/reporting → services}/metrics/treynor_ratio.py +0 -0
  188. /investing_algorithm_framework/{app/reporting → services}/metrics/ulcer.py +0 -0
  189. /investing_algorithm_framework/{app/reporting → services}/metrics/value_at_risk.py +0 -0
  190. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/LICENSE +0 -0
  191. {investing_algorithm_framework-6.9.1.dist-info → investing_algorithm_framework-7.19.15.dist-info}/WHEEL +0 -0
  192. {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
- lambda_handler=lambda_handler,
202
- project_dir=project_dir
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
- zip_file_path,
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
- handler_name: str, the name of the handler function
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
- ZipFile=zipped_code
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
- Handler=handler_name,
171
- Code={'ZipFile': zipped_code},
181
+ PackageType="Image",
182
+ Code={"ImageUri": image_uri},
172
183
  Timeout=900,
173
- MemorySize=256,
174
- Environment={'Variables': env_vars or {}}
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
- lambda_handler: str, the name of the handler function in the code
360
- (default is "aws_function.lambda_handler").
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(f"Deploying to AWS Lambda "
372
- f"function: {lambda_function_name} in region: {region}")
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
- env_vars = read_env_file(
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
- for key, value in env_vars.items():
389
- click.echo(f"{key}={value}")
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
- zip_file_path=zip_file_path,
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 investing_algorithm_framework import create_app, \
3
- AWSS3StorageStateHandler, AWS_S3_STATE_BUCKET_NAME
3
+ from logging import getLogger
4
4
 
5
- from .strategies.strategy import MyTradingStrategy
6
- app = create_app()
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", initial_balance=1000)
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(number_of_iterations=1)
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
- investing-algorithm-framework>=6.8.2
2
- pyindicators>=0.5.4
1
+ investing_algorithm_framework>=6.9.4
2
+ pyindicators>=0.5.4
@@ -1,3 +1,3 @@
1
- investing-algorithm-framework>=6.8.2
2
1
  azure-functions==1.17.0
2
+ investing_algorithm_framework>=6.9.4
3
3
  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("APP_MODE", AppMode.WEB.value)
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 algoritm framework app created")
53
+ logger.info("Investing algorithm framework app created")
56
54
  return app