investing-algorithm-framework 2.3.2__tar.gz → 2.5.0__tar.gz

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.
Files changed (142) hide show
  1. {investing_algorithm_framework-2.3.2/investing_algorithm_framework.egg-info → investing_algorithm_framework-2.5.0}/PKG-INFO +25 -35
  2. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/README.md +1 -1
  3. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/__init__.py +1 -1
  4. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/algorithm.py +70 -223
  5. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/app.py +68 -22
  6. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/stateless/action_handlers/__init__.py +2 -1
  7. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/strategy.py +24 -4
  8. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/create_app.py +2 -1
  9. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/dependency_container.py +14 -3
  10. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/__init__.py +5 -5
  11. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/config.py +6 -5
  12. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/__init__.py +2 -2
  13. investing_algorithm_framework-2.5.0/investing_algorithm_framework/domain/models/backtesting/__init__.py +6 -0
  14. investing_algorithm_framework-2.3.2/investing_algorithm_framework/domain/models/backtest_profile.py → investing_algorithm_framework-2.5.0/investing_algorithm_framework/domain/models/backtesting/backtest_report.py +53 -4
  15. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/order/order.py +45 -4
  16. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/portfolio/portfolio_configuration.py +1 -1
  17. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/position/position_snapshot.py +0 -2
  18. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/trade.py +7 -0
  19. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/trading_time_frame.py +1 -1
  20. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/services/market_data_sources.py +4 -3
  21. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/services/market_service.py +4 -0
  22. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/utils/backtesting.py +78 -20
  23. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py +1 -1
  24. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/market_data_sources/ccxt.py +28 -12
  25. investing_algorithm_framework-2.5.0/investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +177 -0
  26. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/portfolio/portfolio.py +0 -1
  27. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py +0 -2
  28. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/position/position_snapshot.py +3 -1
  29. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py +1 -1
  30. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/services/performance_service/performance_service.py +11 -6
  31. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/__init__.py +6 -3
  32. investing_algorithm_framework-2.5.0/investing_algorithm_framework/services/backtesting/__init__.py +8 -0
  33. investing_algorithm_framework-2.5.0/investing_algorithm_framework/services/backtesting/backtest_report_writer_service.py +37 -0
  34. {investing_algorithm_framework-2.3.2/investing_algorithm_framework/services → investing_algorithm_framework-2.5.0/investing_algorithm_framework/services/backtesting}/backtest_service.py +77 -45
  35. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/market_data_source_service/__init__.py +1 -1
  36. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/market_data_source_service/backtest_market_data_source_service.py +16 -14
  37. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/market_data_source_service/market_data_source_service.py +0 -1
  38. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/order_service/order_backtest_service.py +2 -1
  39. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/order_service/order_service.py +36 -17
  40. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/portfolio_service/portfolio_service.py +136 -96
  41. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/portfolio_snapshot_service.py +0 -1
  42. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/position_service.py +2 -1
  43. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/strategy_orchestrator_service.py +0 -2
  44. investing_algorithm_framework-2.5.0/investing_algorithm_framework/services/trade_service/__init__.py +3 -0
  45. investing_algorithm_framework-2.5.0/investing_algorithm_framework/services/trade_service/trade_service.py +283 -0
  46. investing_algorithm_framework-2.5.0/pyproject.toml +32 -0
  47. investing_algorithm_framework-2.3.2/AUTHORS.md +0 -8
  48. investing_algorithm_framework-2.3.2/PKG-INFO +0 -353
  49. investing_algorithm_framework-2.3.2/investing_algorithm_framework/infrastructure/models/market_data_sources/csv.py +0 -177
  50. investing_algorithm_framework-2.3.2/investing_algorithm_framework.egg-info/SOURCES.txt +0 -134
  51. investing_algorithm_framework-2.3.2/investing_algorithm_framework.egg-info/dependency_links.txt +0 -1
  52. investing_algorithm_framework-2.3.2/investing_algorithm_framework.egg-info/requires.txt +0 -16
  53. investing_algorithm_framework-2.3.2/investing_algorithm_framework.egg-info/top_level.txt +0 -1
  54. investing_algorithm_framework-2.3.2/setup.cfg +0 -7
  55. investing_algorithm_framework-2.3.2/setup.py +0 -39
  56. investing_algorithm_framework-2.3.2/tests/test_create_app.py +0 -68
  57. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/LICENSE +0 -0
  58. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/__init__.py +0 -0
  59. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/stateless/__init__.py +0 -0
  60. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py +0 -0
  61. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py +0 -0
  62. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py +0 -0
  63. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/stateless/exception_handler.py +0 -0
  64. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/task.py +0 -0
  65. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/__init__.py +0 -0
  66. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/controllers/__init__.py +0 -0
  67. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/controllers/orders.py +0 -0
  68. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/controllers/portfolio.py +0 -0
  69. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/controllers/positions.py +0 -0
  70. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/create_app.py +0 -0
  71. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/error_handler.py +0 -0
  72. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/responses.py +0 -0
  73. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/run_strategies.py +0 -0
  74. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/schemas/__init__.py +0 -0
  75. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/schemas/order.py +0 -0
  76. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/schemas/portfolio.py +0 -0
  77. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/schemas/position.py +0 -0
  78. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/app/web/setup_cors.py +0 -0
  79. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/constants.py +0 -0
  80. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/data_structures.py +0 -0
  81. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/decimal_parsing.py +0 -0
  82. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/exceptions.py +0 -0
  83. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/base_model.py +0 -0
  84. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/market/__init__.py +0 -0
  85. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/market/market_credential.py +0 -0
  86. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/order/__init__.py +0 -0
  87. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/order/order_fee.py +0 -0
  88. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/order/order_side.py +0 -0
  89. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/order/order_status.py +0 -0
  90. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/order/order_type.py +0 -0
  91. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/portfolio/__init__.py +0 -0
  92. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/portfolio/portfolio.py +0 -0
  93. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/portfolio/portfolio_snapshot.py +0 -0
  94. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/position/__init__.py +0 -0
  95. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/position/position.py +0 -0
  96. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/strategy_profile.py +0 -0
  97. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/time_frame.py +0 -0
  98. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/time_interval.py +0 -0
  99. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/time_unit.py +0 -0
  100. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/models/trading_data_types.py +0 -0
  101. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/services/__init__.py +0 -0
  102. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/singleton.py +0 -0
  103. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/stateless_actions.py +0 -0
  104. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/strategy.py +0 -0
  105. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/utils/__init__.py +0 -0
  106. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/utils/csv.py +0 -0
  107. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/utils/random.py +0 -0
  108. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/utils/signatures.py +0 -0
  109. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/utils/stoppable_thread.py +0 -0
  110. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/domain/utils/synchronized.py +0 -0
  111. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/__init__.py +0 -0
  112. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/database/__init__.py +0 -0
  113. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/database/sql_alchemy.py +0 -0
  114. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/__init__.py +0 -0
  115. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/decimal_parser.py +0 -0
  116. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/model_extension.py +0 -0
  117. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/order/__init__.py +0 -0
  118. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/order/order.py +0 -0
  119. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/order/order_fee.py +0 -0
  120. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/portfolio/__init__.py +0 -0
  121. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/position/__init__.py +0 -0
  122. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/models/position/position.py +0 -0
  123. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/__init__.py +0 -0
  124. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/order_fee_repository.py +0 -0
  125. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/order_repository.py +0 -0
  126. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/portfolio_snapshot_repository.py +0 -0
  127. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/position_repository.py +0 -0
  128. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py +0 -0
  129. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/repositories/repository.py +0 -0
  130. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/services/__init__.py +0 -0
  131. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/services/market_service/__init__.py +0 -0
  132. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/services/market_service/ccxt_market_service.py +0 -0
  133. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/services/performance_service/__init__.py +0 -0
  134. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py +0 -0
  135. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/configuration_service.py +0 -0
  136. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/market_credential_service.py +0 -0
  137. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/order_service/__init__.py +0 -0
  138. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/portfolio_configuration_service.py +0 -0
  139. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/portfolio_service/__init__.py +0 -0
  140. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/portfolio_service/backtest_portfolio_service.py +0 -0
  141. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/position_snapshot_service.py +0 -0
  142. {investing_algorithm_framework-2.3.2 → investing_algorithm_framework-2.5.0}/investing_algorithm_framework/services/repository_service.py +0 -0
@@ -1,40 +1,29 @@
1
1
  Metadata-Version: 2.1
2
- Name: investing_algorithm_framework
3
- Version: 2.3.2
4
- Summary: A framework for creating an investment algorithm
5
- Home-page: https://github.com/coding-kitties/investing-algorithm-framework.git
6
- Download-URL: https://github.com/coding-kitties/investing-algorithm-framework/archive/v0.1.1.tar.gz
7
- Author: coding kitties
8
- License: Apache License 2.0
9
- Keywords: TRADING,INVESTING,BOT,ALGORITHM,FRAMEWORK
10
- Classifier: Intended Audience :: Developers
2
+ Name: investing-algorithm-framework
3
+ Version: 2.5.0
4
+ Summary: A framework for creating trading bots
5
+ Author: MDUYN
6
+ Requires-Python: >=3.8.1,<4.0.0
11
7
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.4
13
- Classifier: Programming Language :: Python :: 3.5
14
- Classifier: Programming Language :: Python :: 3.6
15
- Classifier: Topic :: Software Development
16
- Classifier: License :: OSI Approved :: Apache Software License
17
- Classifier: Operating System :: OS Independent
18
- Requires-Python: >=3
8
+ Classifier: Programming Language :: Python :: 3.9
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: Flask (>=2.3.2,<3.0.0)
13
+ Requires-Dist: Flask-Cors (>=3.0.9,<4.0.0)
14
+ Requires-Dist: Flask-Migrate (>=2.6.0,<3.0.0)
15
+ Requires-Dist: MarkupSafe (>=2.1.2,<3.0.0)
16
+ Requires-Dist: SQLAlchemy (>=2.0.18,<3.0.0)
17
+ Requires-Dist: ccxt (>=4.2.48,<5.0.0)
18
+ Requires-Dist: dependency-injector (>=4.40.0,<5.0.0)
19
+ Requires-Dist: marshmallow (>=3.5.0,<4.0.0)
20
+ Requires-Dist: polars[numpy,pandas] (>=0.20.10,<0.21.0)
21
+ Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
22
+ Requires-Dist: schedule (>=1.1.0,<2.0.0)
23
+ Requires-Dist: tabulate (>=0.9.0,<0.10.0)
24
+ Requires-Dist: tqdm (>=4.66.1,<5.0.0)
25
+ Requires-Dist: wrapt (>=1.16.0,<2.0.0)
19
26
  Description-Content-Type: text/markdown
20
- License-File: LICENSE
21
- License-File: AUTHORS.md
22
- Requires-Dist: wrapt>=1.11.2
23
- Requires-Dist: Flask>=2.3.2
24
- Requires-Dist: Flask-Migrate>=2.6.0
25
- Requires-Dist: Flask-Cors>=3.0.9
26
- Requires-Dist: SQLAlchemy>=2.0.18
27
- Requires-Dist: marshmallow>=3.5.0
28
- Requires-Dist: setuptools>=60.9.0
29
- Requires-Dist: ccxt>=3.0.57
30
- Requires-Dist: python-dateutil>=2.8.2
31
- Requires-Dist: MarkupSafe>=2.1.2
32
- Requires-Dist: dependency-injector>=4.40.0
33
- Requires-Dist: schedule>=1.1.0
34
- Requires-Dist: pandas>=2.0.0
35
- Requires-Dist: tqdm>=4.66.1
36
- Requires-Dist: tabulate>=0.9.0
37
- Requires-Dist: polars[numpy,pandas]==0.20.5
38
27
 
39
28
  <a href=https://investing-algorithm-framework.com><img src="https://img.shields.io/badge/docs-website-brightgreen"></a>
40
29
  [![Build](https://github.com/coding-kitties/investing-algorithm-framework/actions/workflows/build.yml/badge.svg)](https://github.com/coding-kitties/investing-algorithm-framework/actions/workflows/build.yml)
@@ -47,7 +36,7 @@ Requires-Dist: polars[numpy,pandas]==0.20.5
47
36
  ###### Sponsors
48
37
  <p align="left">
49
38
  <a href="https://finterion.com">
50
- <img alt="Finterion" src="https://logicfunds-web-app-images.s3.eu-central-1.amazonaws.com/finterion.png" width="200px" />
39
+ <img alt="Finterion" src="static/sponsors/finterion.png" width="200px" />
51
40
  </a>
52
41
  </p>
53
42
 
@@ -351,3 +340,4 @@ You can pick up a task by assigning yourself to it.
351
340
  This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
352
341
 
353
342
  **Important:** Always create your feature or hotfix against the `develop` branch, not `master`.
343
+
@@ -9,7 +9,7 @@
9
9
  ###### Sponsors
10
10
  <p align="left">
11
11
  <a href="https://finterion.com">
12
- <img alt="Finterion" src="https://logicfunds-web-app-images.s3.eu-central-1.amazonaws.com/finterion.png" width="200px" />
12
+ <img alt="Finterion" src="static/sponsors/finterion.png" width="200px" />
13
13
  </a>
14
14
  </p>
15
15
 
@@ -1,7 +1,7 @@
1
1
  from investing_algorithm_framework.app import App, Algorithm
2
2
  from .create_app import create_app
3
3
  from investing_algorithm_framework.domain import ApiException, \
4
- TradingDataType, TradingTimeFrame, OrderType,\
4
+ TradingDataType, TradingTimeFrame, OrderType, \
5
5
  OrderStatus, OrderSide, Config, TimeUnit, TimeInterval, Order, Portfolio, \
6
6
  Position, TimeFrame, BACKTESTING_INDEX_DATETIME, MarketCredential, \
7
7
  PortfolioConfiguration, RESOURCE_DIRECTORY, pretty_print_backtest, \
@@ -1,13 +1,14 @@
1
- import logging
2
1
  import decimal
2
+ import logging
3
3
  from typing import List
4
4
 
5
5
  from investing_algorithm_framework.domain import OrderStatus, OrderFee, \
6
6
  Position, Order, Portfolio, OrderType, OrderSide, ApiException, \
7
- BACKTESTING_FLAG, BACKTESTING_INDEX_DATETIME, Trade, PeekableQueue, \
8
- MarketService
7
+ BACKTESTING_FLAG, BACKTESTING_INDEX_DATETIME, MarketService
9
8
  from investing_algorithm_framework.services import MarketCredentialService, \
10
- MarketDataSourceService
9
+ MarketDataSourceService, PortfolioService, PositionService, TradeService, \
10
+ OrderService, ConfigurationService, StrategyOrchestratorService, \
11
+ PortfolioConfigurationService
11
12
 
12
13
  logger = logging.getLogger("investing_algorithm_framework")
13
14
 
@@ -24,21 +25,26 @@ class Algorithm:
24
25
  market_service,
25
26
  strategy_orchestrator_service,
26
27
  market_credential_service,
27
- market_data_source_service
28
+ market_data_source_service,
29
+ trade_service
28
30
  ):
29
- self.portfolio_service = portfolio_service
30
- self.position_service = position_service
31
- self.order_service = order_service
32
- self._market_service: MarketService = market_service
33
- self.configuration_service = configuration_service
34
- self.portfolio_configuration_service = portfolio_configuration_service
35
- self.strategy_orchestrator_service = strategy_orchestrator_service
31
+ self.portfolio_service: PortfolioService = portfolio_service
32
+ self.position_service: PositionService = position_service
33
+ self.order_service: OrderService = order_service
34
+ self.market_service: MarketService = market_service
35
+ self.configuration_service: ConfigurationService \
36
+ = configuration_service
37
+ self.portfolio_configuration_service: PortfolioConfigurationService \
38
+ = portfolio_configuration_service
39
+ self.strategy_orchestrator_service: StrategyOrchestratorService \
40
+ = strategy_orchestrator_service
36
41
  self._market_data_sources = {}
37
42
  self._strategies = []
38
43
  self._market_credential_service: MarketCredentialService \
39
44
  = market_credential_service
40
45
  self._market_data_source_service: MarketDataSourceService \
41
46
  = market_data_source_service
47
+ self.trade_service: TradeService = trade_service
42
48
 
43
49
  def start(self, number_of_iterations=None, stateless=False):
44
50
 
@@ -158,14 +164,14 @@ class Algorithm:
158
164
  )
159
165
 
160
166
  def create_market_order(
161
- self,
162
- target_symbol,
163
- order_side,
164
- amount,
165
- market=None,
166
- execute=False,
167
- validate=False,
168
- sync=True
167
+ self,
168
+ target_symbol,
169
+ order_side,
170
+ amount,
171
+ market=None,
172
+ execute=False,
173
+ validate=False,
174
+ sync=True
169
175
  ):
170
176
 
171
177
  if market is None:
@@ -210,18 +216,24 @@ class Algorithm:
210
216
  {"portfolio": portfolio.id, "symbol": trading_symbol}
211
217
  ).get_amount()
212
218
 
219
+ def get_total_size(self):
220
+ """
221
+ Returns the total size of the portfolio.
222
+ """
223
+ return self.get_unallocated() + self.get_allocated()
224
+
213
225
  def reset(self):
214
226
  self._workers = []
215
227
  self._running_workers = []
216
228
 
217
229
  def get_order(
218
- self,
219
- reference_id=None,
220
- market=None,
221
- target_symbol=None,
222
- trading_symbol=None,
223
- order_side=None,
224
- order_type=None
230
+ self,
231
+ reference_id=None,
232
+ market=None,
233
+ target_symbol=None,
234
+ trading_symbol=None,
235
+ order_side=None,
236
+ order_type=None
225
237
  ) -> Order:
226
238
  query_params = {}
227
239
 
@@ -250,12 +262,12 @@ class Algorithm:
250
262
  return self.order_service.find(query_params)
251
263
 
252
264
  def get_orders(
253
- self,
254
- target_symbol=None,
255
- status=None,
256
- order_type=None,
257
- order_side=None,
258
- market=None
265
+ self,
266
+ target_symbol=None,
267
+ status=None,
268
+ order_type=None,
269
+ order_side=None,
270
+ market=None
259
271
  ) -> List[Order]:
260
272
 
261
273
  if market is None:
@@ -278,13 +290,13 @@ class Algorithm:
278
290
  return self.order_service.get_order_fee(order_id)
279
291
 
280
292
  def get_positions(
281
- self,
282
- market=None,
283
- identifier=None,
284
- amount_gt=None,
285
- amount_gte=None,
286
- amount_lt=None,
287
- amount_lte=None
293
+ self,
294
+ market=None,
295
+ identifier=None,
296
+ amount_gt=None,
297
+ amount_gte=None,
298
+ amount_lt=None,
299
+ amount_lte=None
288
300
  ) -> List[Position]:
289
301
  query_params = {}
290
302
 
@@ -340,14 +352,14 @@ class Algorithm:
340
352
  return None
341
353
 
342
354
  def has_position(
343
- self,
344
- symbol,
345
- market=None,
346
- identifier=None,
347
- amount_gt=0,
348
- amount_gte=None,
349
- amount_lt=None,
350
- amount_lte=None
355
+ self,
356
+ symbol,
357
+ market=None,
358
+ identifier=None,
359
+ amount_gt=0,
360
+ amount_gte=None,
361
+ amount_lt=None,
362
+ amount_lte=None
351
363
  ):
352
364
  return self.position_exists(
353
365
  symbol,
@@ -592,9 +604,9 @@ class Algorithm:
592
604
  orders = self.order_service.get_all(
593
605
  {"status": OrderStatus.OPEN.value, "portfolio": portfolio.id}
594
606
  )
595
- unfilled = unfilled \
596
- + sum(
597
- [order.get_amount() * order.get_price() for order in orders])
607
+ unfilled = unfilled + sum(
608
+ [order.get_amount() * order.get_price() for order in orders]
609
+ )
598
610
 
599
611
  return unfilled
600
612
 
@@ -642,7 +654,9 @@ class Algorithm:
642
654
  query_params["status"] = OrderStatus.OPEN.value
643
655
  return self.order_service.exists(query_params)
644
656
 
645
- def has_open_orders(self, target_symbol=None, identifier=None, market=None):
657
+ def has_open_orders(
658
+ self, target_symbol=None, identifier=None, market=None
659
+ ):
646
660
  query_params = {}
647
661
 
648
662
  if identifier is not None:
@@ -667,55 +681,10 @@ class Algorithm:
667
681
  self.order_service.check_pending_orders()
668
682
 
669
683
  def get_trades(self, market=None):
670
- portfolios = self.portfolio_service.get_all()
671
- trades = []
672
-
673
- for portfolio in portfolios:
674
- buy_orders = self.order_service.get_all({
675
- "status": OrderStatus.CLOSED.value,
676
- "order_side": OrderSide.BUY.value,
677
- "portfolio_id": portfolio.id
678
- })
679
-
680
- for buy_order in buy_orders:
681
- symbol = buy_order.get_symbol()
682
- ticker = self._market_data_source_service.get_ticker(
683
- symbol=symbol, market=market
684
- )
685
- trades.append(
686
- Trade(
687
- buy_order_id=buy_order.id,
688
- target_symbol=buy_order.get_target_symbol(),
689
- trading_symbol=buy_order.get_trading_symbol(),
690
- amount=buy_order.get_amount(),
691
- open_price=buy_order.get_price(),
692
- closed_price=buy_order.get_trade_closed_price(),
693
- closed_at=buy_order.get_trade_closed_at(),
694
- opened_at=buy_order.get_created_at(),
695
- current_price=ticker["bid"]
696
- )
697
- )
698
-
699
- return trades
684
+ return self.trade_service.get_trades(market)
700
685
 
701
686
  def get_closed_trades(self):
702
- buy_orders = self.order_service.get_all({
703
- "status": OrderStatus.CLOSED.value,
704
- "order_side": OrderSide.BUY.value
705
- })
706
- return [
707
- Trade(
708
- buy_order_id=order.id,
709
- target_symbol=order.get_target_symbol(),
710
- trading_symbol=order.get_trading_symbol(),
711
- amount=order.get_amount(),
712
- open_price=order.get_price(),
713
- closed_price=order.get_trade_closed_price(),
714
- closed_at=order.get_trade_closed_at(),
715
- opened_at=order.get_created_at()
716
- ) for order in buy_orders
717
- if order.get_trade_closed_at() is not None
718
- ]
687
+ return self.trade_service.get_closed_trades()
719
688
 
720
689
  def round_down(self, value, amount_of_decimals):
721
690
 
@@ -735,132 +704,10 @@ class Algorithm:
735
704
  return 0
736
705
 
737
706
  def get_open_trades(self, target_symbol=None, market=None):
738
- portfolios = self.portfolio_service.get_all()
739
- trades = []
740
-
741
- for portfolio in portfolios:
742
-
743
- if target_symbol is not None:
744
- buy_orders = self.order_service.get_all({
745
- "status": OrderStatus.CLOSED.value,
746
- "order_side": OrderSide.BUY.value,
747
- "portfolio_id": portfolio.id,
748
- "target_symbol": target_symbol
749
- })
750
- sell_orders = self.order_service.get_all({
751
- "status": OrderStatus.OPEN.value,
752
- "order_side": OrderSide.SELL.value,
753
- "portfolio_id": portfolio.id,
754
- "target_symbol": target_symbol
755
- })
756
- else:
757
- buy_orders = self.order_service.get_all({
758
- "status": OrderStatus.CLOSED.value,
759
- "order_side": OrderSide.BUY.value,
760
- "portfolio_id": portfolio.id
761
- })
762
- sell_orders = self.order_service.get_all({
763
- "status": OrderStatus.OPEN.value,
764
- "order_side": OrderSide.SELL.value,
765
- "portfolio_id": portfolio.id
766
- })
767
-
768
- buy_orders = [
769
- buy_order for buy_order in buy_orders
770
- if buy_order.get_trade_closed_at() is None
771
- ]
772
- sell_amount = sum([order.amount for order in sell_orders])
773
-
774
- # Subtract the amount of the open sell orders
775
- # from the amount of the buy orders
776
- buy_orders_queue = PeekableQueue()
777
-
778
- for buy_order in buy_orders:
779
- buy_orders_queue.enqueue(buy_order)
780
-
781
- while sell_amount > 0 and not buy_orders_queue.is_empty():
782
- first_buy_order = buy_orders_queue.peek()
783
- available = first_buy_order.get_filled() \
784
- - first_buy_order.get_trade_closed_amount()
785
-
786
- if available > sell_amount:
787
- remaining = available - sell_amount
788
- sell_amount = 0
789
- first_buy_order.set_filled(remaining)
790
- else:
791
- sell_amount = sell_amount - available
792
- buy_orders_queue.dequeue()
793
-
794
- for buy_order in buy_orders_queue:
795
- symbol = buy_order.get_symbol()
796
-
797
- try:
798
- ticker = self._market_data_source_service.get_ticker(
799
- symbol=symbol, market=market
800
- )
801
- except Exception as e:
802
- logger.error(e)
803
- raise ApiException(
804
- f"Error getting ticker data for "
805
- f"trade {buy_order.get_target_symbol()}"
806
- f"-{buy_order.get_trading_symbol()}. Make sure you "
807
- f"have registered a ticker market data source for "
808
- f"{buy_order.get_target_symbol()}"
809
- f"-{buy_order.get_trading_symbol()} "
810
- f"for market {portfolio.market}"
811
- )
812
- trades.append(
813
- Trade(
814
- buy_order_id=buy_order.id,
815
- target_symbol=buy_order.get_target_symbol(),
816
- trading_symbol=buy_order.get_trading_symbol(),
817
- amount=buy_order.get_filled()
818
- - buy_order.get_trade_closed_amount(),
819
- open_price=buy_order.get_price(),
820
- opened_at=buy_order.get_created_at(),
821
- current_price=ticker["bid"]
822
- )
823
- )
824
-
825
- return trades
707
+ return self.trade_service.get_open_trades(target_symbol, market)
826
708
 
827
709
  def close_trade(self, trade, market=None):
828
-
829
- if trade.closed_at is not None:
830
- raise ApiException("Trade already closed.")
831
-
832
- order = self.order_service.get(trade.buy_order_id)
833
-
834
- if order.get_filled() <= 0:
835
- raise ApiException(
836
- "Buy order belonging to the trade has no amount."
837
- )
838
-
839
- portfolio = self.portfolio_service\
840
- .find({"position": order.position_id})
841
- position = self.get_position(order.get_target_symbol())
842
- amount = order.get_amount()
843
-
844
- if position.get_amount() < amount:
845
- logger.warning(
846
- f"Order amount {amount} is larger then amount "
847
- f"of available {position.symbol} "
848
- f"position: {position.get_amount()}, "
849
- f"changing order amount to size of position"
850
- )
851
- amount = position.get_amount()
852
-
853
- symbol = f"{order.get_target_symbol().upper()}" \
854
- f"/{order.get_trading_symbol().upper()}"
855
- ticker = self._market_data_source_service.get_ticker(
856
- symbol=symbol, market=market
857
- )
858
- self.create_limit_order(
859
- target_symbol=order.target_symbol,
860
- amount=amount,
861
- order_side=OrderSide.SELL.value,
862
- price=ticker["bid"],
863
- )
710
+ self.trade_service.close_trade(trade, market)
864
711
 
865
712
  def get_number_of_positions(self):
866
713
  """
@@ -17,7 +17,7 @@ from investing_algorithm_framework.app.web import create_flask_app
17
17
  from investing_algorithm_framework.domain import DATABASE_NAME, TimeUnit, \
18
18
  DATABASE_DIRECTORY_PATH, RESOURCE_DIRECTORY, ENVIRONMENT, Environment, \
19
19
  SQLALCHEMY_DATABASE_URI, OperationalException, BACKTESTING_FLAG, \
20
- BACKTESTING_START_DATE, MarketService, BACKTESTING_END_DATE, \
20
+ BACKTESTING_START_DATE, BACKTESTING_END_DATE, \
21
21
  BACKTESTING_PENDING_ORDER_CHECK_INTERVAL
22
22
  from investing_algorithm_framework.infrastructure import setup_sqlalchemy, \
23
23
  create_all_tables
@@ -40,7 +40,6 @@ class App:
40
40
  self._strategies = []
41
41
  self._tasks = []
42
42
  self._configuration_service = None
43
- self._market_service: MarketService = None
44
43
  self._market_data_source_service: MarketDataSourceService = None
45
44
  self._market_credential_service: MarketCredentialService = None
46
45
 
@@ -50,13 +49,19 @@ class App:
50
49
 
51
50
  def initialize_services(self):
52
51
  self._configuration_service = self.container.configuration_service()
53
- self._market_service = self.container.market_service()
54
52
  self._market_data_source_service = \
55
53
  self.container.market_data_source_service()
56
54
  self._market_credential_service = \
57
55
  self.container.market_credential_service()
58
56
 
59
57
  def initialize(self):
58
+ """
59
+ Method to initialize the app. This method should be called before
60
+ running the algorithm. It initializes the services and the algorithm
61
+ and sets up the database if it does not exist.
62
+
63
+ :return: None
64
+ """
60
65
 
61
66
  if self._web:
62
67
  self._initialize_web()
@@ -70,13 +75,21 @@ class App:
70
75
  self._initialize_standard()
71
76
  setup_sqlalchemy(self)
72
77
  create_all_tables()
78
+
79
+ # Initialize the algorithm
73
80
  self.algorithm = self.container.algorithm()
81
+
82
+ # Add strategies
74
83
  self.algorithm.add_strategies(self.strategies)
84
+
85
+ # Add tasks
75
86
  self.algorithm.add_tasks(self.tasks)
76
87
 
88
+ # Initialize all portfolios that are registered
77
89
  portfolio_configuration_service = self.container\
78
90
  .portfolio_configuration_service()
79
91
 
92
+ # Throw an error if no portfolios are configured
80
93
  if portfolio_configuration_service.count() == 0:
81
94
  raise OperationalException("No portfolios configured")
82
95
 
@@ -85,15 +98,42 @@ class App:
85
98
  portfolio_service = self.container.portfolio_service()
86
99
 
87
100
  for portfolio_configuration in portfolio_configurations:
88
- portfolio_service.create_portfolio_from_configuration(
101
+ # Create portfolio if not exists
102
+ portfolio = portfolio_service.create_portfolio_from_configuration(
89
103
  portfolio_configuration
90
104
  )
105
+ # Sync all orders from exchange with current order history
106
+ portfolio_service.sync_portfolio_orders(portfolio)
91
107
 
92
108
  def _initialize_stateless(self):
109
+ """
110
+ Initialize the app for stateless mode by setting the configuration
111
+ parameters for stateless mode and overriding the services with the
112
+ stateless services equivalents.
113
+
114
+ In stateless mode, sqlalchemy is-setup with an in-memory database.
115
+
116
+ Stateless has the following implications:
117
+ db: in-memory
118
+ web: False
119
+ app: Run with stateless action objects
120
+ algorithm: Run with stateless action objects
121
+ """
93
122
  configuration_service = self.container.configuration_service()
94
123
  configuration_service.config[SQLALCHEMY_DATABASE_URI] = "sqlite://"
95
124
 
96
125
  def _initialize_standard(self):
126
+ """
127
+ Initialize the app for standard mode by setting the configuration
128
+ parameters for standard mode and overriding the services with the
129
+ standard services equivalents.
130
+
131
+ Standard has the following implications:
132
+ db: sqlite
133
+ web: False
134
+ app: Standard
135
+ algorithm: Standard
136
+ """
97
137
  configuration_service = self.container.configuration_service()
98
138
  resource_dir = configuration_service.config[RESOURCE_DIRECTORY]
99
139
 
@@ -197,6 +237,9 @@ class App:
197
237
  portfolio_snapshot_service=self.container
198
238
  .portfolio_snapshot_service(),
199
239
  ))
240
+ market_data_source_service = \
241
+ self.container.market_data_source_service()
242
+
200
243
  # Override the order service with the backtest order service
201
244
  self.container.order_service.override(
202
245
  OrderBacktestService(
@@ -209,7 +252,7 @@ class App:
209
252
  portfolio_snapshot_service=self.container
210
253
  .portfolio_snapshot_service(),
211
254
  configuration_service=self.container.configuration_service(),
212
- market_data_source_service=self.container.market_data_source_service()
255
+ market_data_source_service=market_data_source_service
213
256
  )
214
257
  )
215
258
 
@@ -256,14 +299,8 @@ class App:
256
299
  self,
257
300
  payload: dict = None,
258
301
  number_of_iterations: int = None,
259
- sync=True
260
302
  ):
261
303
  self.initialize()
262
- portfolio_service = self.container.portfolio_service()
263
-
264
- if sync:
265
- portfolio_service.sync_portfolios()
266
-
267
304
  self.algorithm.start(
268
305
  number_of_iterations=number_of_iterations,
269
306
  stateless=self.stateless
@@ -438,13 +475,6 @@ class App:
438
475
  def tasks(self):
439
476
  return self._tasks
440
477
 
441
- def sync_portfolios(self):
442
- portfolio_configuration_service = self.container\
443
- .portfolio_configuration_service()
444
- portfolio_configuration_service.create_portfolios()
445
- portfolio_service = self.container.portfolio_service()
446
- portfolio_service.sync_portfolios()
447
-
448
478
  def _initialize_web(self):
449
479
  configuration_service = self.container.configuration_service()
450
480
  resource_dir = configuration_service.config[RESOURCE_DIRECTORY]
@@ -453,9 +483,8 @@ class App:
453
483
  configuration_service.config[SQLALCHEMY_DATABASE_URI] = "sqlite://"
454
484
  else:
455
485
  resource_dir = self._create_resource_directory_if_not_exists()
456
- configuration_service.config[DATABASE_DIRECTORY_PATH] = os.path.join(
457
- resource_dir, "databases"
458
- )
486
+ configuration_service.config[DATABASE_DIRECTORY_PATH] = \
487
+ os.path.join(resource_dir, "databases")
459
488
  configuration_service.config[DATABASE_NAME] \
460
489
  = "prod-database.sqlite3"
461
490
  configuration_service.config[SQLALCHEMY_DATABASE_URI] = \
@@ -528,7 +557,11 @@ class App:
528
557
  return self.algorithm.get_portfolio_configurations()
529
558
 
530
559
  def backtest(
531
- self, start_date, end_date, pending_order_check_interval='1h'
560
+ self,
561
+ start_date,
562
+ end_date,
563
+ pending_order_check_interval='1h',
564
+ output_directory=None
532
565
  ):
533
566
  logger.info("Initializing backtest")
534
567
 
@@ -547,6 +580,19 @@ class App:
547
580
  report = backtest_service.backtest(
548
581
  self.algorithm, start_date, end_date
549
582
  )
583
+ backtest_report_writer_service = self.container\
584
+ .backtest_report_writer_service()
585
+
586
+ if output_directory is None:
587
+ output_directory = os.path.join(
588
+ self.config.get(RESOURCE_DIRECTORY),
589
+ "backtest_reports"
590
+ )
591
+
592
+ backtest_report_writer_service.write_report_to_csv(
593
+ report=report, output_directory=output_directory
594
+ )
595
+
550
596
  return report
551
597
 
552
598
  def add_market_data_source(self, market_data_source):