├── .flake8 ├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── AUTHORS.md ├── LICENSE ├── README.md ├── __init__.py ├── docs ├── 404.html ├── Contributing Guide │ ├── contributing │ │ └── index.html │ └── style-guide │ │ └── index.html ├── Data │ ├── download │ │ └── index.html │ ├── market-data-sources │ │ └── index.html │ └── multiple-market-data-sources │ │ └── index.html ├── Getting Started │ ├── application-setup │ │ └── index.html │ ├── backtesting │ │ └── index.html │ ├── deployment │ │ └── index.html │ ├── installation │ │ └── index.html │ ├── orders │ │ └── index.html │ ├── performance │ │ └── index.html │ ├── portfolio-configuration │ │ └── index.html │ ├── positions │ │ └── index.html │ ├── strategies │ │ └── index.html │ ├── tasks │ │ └── index.html │ └── trades │ │ └── index.html ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── assets │ ├── css │ │ └── styles.b0373047.css │ └── js │ │ ├── 01a85c17.db8dfd4d.js │ │ ├── 05a12035.eaa8724b.js │ │ ├── 08d10a9f.d2b6c808.js │ │ ├── 128bc220.cbc321b4.js │ │ ├── 14b4c6b6.25a28fc0.js │ │ ├── 14b4c6b6.4f9ce434.js │ │ ├── 154b9f02.f6489b54.js │ │ ├── 15d1879c.8d4bc9ae.js │ │ ├── 17896441.adbc382f.js │ │ ├── 1be78505.95e24b02.js │ │ ├── 1d5bcf78.95df0c7f.js │ │ ├── 1d7549f1.a2d4e371.js │ │ ├── 20272bd0.932f96ba.js │ │ ├── 2892d01f.727967c7.js │ │ ├── 3606c9ce.ad77712a.js │ │ ├── 3befab5d.308b66c0.js │ │ ├── 3fa5d83a.7a500c17.js │ │ ├── 3fa5d83a.e438aeec.js │ │ ├── 45b0522a.2c69fa68.js │ │ ├── 4972.eef817e7.js │ │ ├── 49789df1.d3cef4fc.js │ │ ├── 4a16d40d.1a90fd5b.js │ │ ├── 4d54d076.2343e355.js │ │ ├── 4e20fc8c.312a51cf.js │ │ ├── 5447f96c.a3ec9a81.js │ │ ├── 6048.ffb515f9.js │ │ ├── 6232ef27.d9700317.js │ │ ├── 64eeacce.b0a450e5.js │ │ ├── 6611844f.827b5797.js │ │ ├── 6875c492.292c623d.js │ │ ├── 6d4ff5c0.8f549863.js │ │ ├── 6db38527.15e93c39.js │ │ ├── 6db38527.4af26881.js │ │ ├── 6db38527.b4c65fee.js │ │ ├── 7d49d76a.0f3690d5.js │ │ ├── 7df9d7eb.32b11417.js │ │ ├── 80c72890.eb4b5cd0.js │ │ ├── 814f3328.8eafdee4.js │ │ ├── 865a984c.62e3815d.js │ │ ├── 876ceb6c.0bfe7662.js │ │ ├── 8d2d9807.08afc5bb.js │ │ ├── 935f2afb.082074d8.js │ │ ├── 935f2afb.208702f1.js │ │ ├── 935f2afb.aae61b89.js │ │ ├── 935f2afb.ae737b0a.js │ │ ├── 96d18e55.e2da4aa0.js │ │ ├── 9785.4be12d50.js │ │ ├── 97a33824.a3c355f6.js │ │ ├── 97a33824.ad303550.js │ │ ├── 9c0f94cd.9883d7bd.js │ │ ├── 9c8714d7.6d2a48bf.js │ │ ├── 9d73a88a.8cf30b50.js │ │ ├── 9e4087bc.a441a013.js │ │ ├── a062c17f.358cde46.js │ │ ├── a09c2993.7e53aa7e.js │ │ ├── a09c2993.9f3b79e5.js │ │ ├── a6aa9e1f.f0482a67.js │ │ ├── a888f091.20625ec2.js │ │ ├── a88f1ba7.678a7056.js │ │ ├── ab1a3d19.3fcd260a.js │ │ ├── b07ff907.d5eb985e.js │ │ ├── ccc49370.c5e99874.js │ │ ├── cd98809c.b6011a28.js │ │ ├── de394c98.b6d537d1.js │ │ ├── e37ff64a.393d56a4.js │ │ ├── f2ba5c43.04dda43b.js │ │ ├── f7a3af2e.23cef503.js │ │ ├── f7a3af2e.2684275b.js │ │ ├── f7a3af2e.bb2545e2.js │ │ ├── f9d42936.17aee861.js │ │ ├── fa34fb01.288d332a.js │ │ ├── fa34fb01.3d44bf38.js │ │ ├── fa34fb01.ea989666.js │ │ ├── fb0f75cc.58600423.js │ │ ├── fcf305ca.879c279a.js │ │ ├── main.628f15e7.js │ │ ├── main.628f15e7.js.LICENSE.txt │ │ ├── main.c4a7ff16.js │ │ ├── main.c4a7ff16.js.LICENSE.txt │ │ ├── runtime~main.3f0f609b.js │ │ ├── runtime~main.8fd9f1c0.js │ │ ├── runtime~main.b4397bf8.js │ │ └── runtime~main.f350e730.js ├── contributing │ └── index.html ├── img │ ├── code-sample.png │ ├── coding-kitties.svg │ ├── docusaurus-social-card.jpg │ ├── docusaurus.png │ ├── favicon.ico │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg ├── index.html └── sitemap.xml ├── docusaurus ├── .gitignore ├── README.md ├── babel.config.js ├── blog │ ├── 2023-08-01-how-to-create-a-trading-bot-in-5-steps.md │ ├── 2023-08-03-how-to-create-a-trading-bot-for-binance.md │ ├── 2023-08-04-how-to-create-a-trading-bot-for-bitvavo.md │ ├── 2023-08-05-how-to-deploy-a-trading-bot.md │ └── authors.yml ├── docs │ ├── Advanced Concepts │ │ ├── _category_.json │ │ └── logging-configuration.md │ ├── Contributing Guide │ │ ├── _category_.json │ │ ├── contributing.md │ │ └── style_guide.md │ ├── Cook Book │ │ ├── how-to-check-open-orders.md.js │ │ ├── how-to-structure-your-code.js │ │ └── using-take-profit-and-stop-loss.js │ ├── Data │ │ ├── __category__.json │ │ ├── download.md │ │ ├── market-data-sources.md │ │ └── multiple-market-data-sources.md │ ├── Getting Started │ │ ├── __category__.json │ │ ├── application-setup.md │ │ ├── backtesting.md │ │ ├── deployment.md │ │ ├── installation.md │ │ ├── orders.md │ │ ├── performance.md │ │ ├── portfolio-configuration.md │ │ ├── positions.md │ │ ├── strategies.md │ │ ├── tasks.md │ │ └── trades.md │ └── introduction.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebar.js ├── sidebars.js ├── src │ ├── components │ │ ├── buttons.js │ │ └── index.js │ └── css │ │ └── custom.css └── static │ ├── .nojekyll │ └── img │ ├── code-sample.png │ ├── coding-kitties.svg │ ├── docusaurus-social-card.jpg │ ├── docusaurus.png │ ├── favicon.ico │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ └── undraw_docusaurus_tree.svg ├── examples ├── backtest_example │ ├── resources │ │ └── backtest_data │ │ │ ├── OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-06-00_2023-12-02-00-00.csv │ │ │ └── TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv │ ├── run_backtest.ipynb │ ├── run_multiple_backtests.ipynb │ └── strategies │ │ ├── __init__.py │ │ ├── strategy_v1 │ │ ├── __init__.py │ │ ├── data_sources.py │ │ └── strategy_v1.py │ │ ├── strategy_v2 │ │ ├── __init__.py │ │ ├── data_sources.py │ │ └── strategy_v2.py │ │ └── strategy_v3 │ │ ├── __init__.py │ │ ├── data_sources.py │ │ └── strategy_v3.py ├── bitvavo_trading_bot.py ├── coinbase_trading_bot.py ├── download_data.py ├── example_strategies │ ├── README.md │ ├── adx_rsi_divergence │ │ ├── __init__.py │ │ ├── adx_rsi.ipynb │ │ ├── alternative.py │ │ ├── challenger.py │ │ ├── challenger_three.py │ │ ├── challenger_three_alternative.py │ │ ├── challenger_two.py │ │ ├── crypto_catching_bottom.py │ │ └── primary.py │ └── macd_wr │ │ ├── macd_wr.ipynb │ │ └── strategy.py ├── resources │ └── backtest_report │ │ └── report.json ├── test.ipynb └── test.py ├── investing_algorithm_framework ├── __init__.py ├── app │ ├── __init__.py │ ├── algorithm │ │ ├── __init__.py │ │ ├── algorithm.py │ │ └── algorithm_factory.py │ ├── app.py │ ├── app_hook.py │ ├── context.py │ ├── metrics │ │ ├── __init__.py │ │ ├── equity_curve.py │ │ ├── price_efficiency.py │ │ ├── profit_factor.py │ │ └── sharp_ratio.py │ ├── stateless │ │ ├── __init__.py │ │ ├── action_handlers │ │ │ ├── __init__.py │ │ │ ├── action_handler_strategy.py │ │ │ ├── check_online_handler.py │ │ │ └── run_strategy_handler.py │ │ └── exception_handler.py │ ├── strategy.py │ ├── task.py │ └── web │ │ ├── __init__.py │ │ ├── controllers │ │ ├── __init__.py │ │ ├── orders.py │ │ ├── portfolio.py │ │ └── positions.py │ │ ├── create_app.py │ │ ├── error_handler.py │ │ ├── responses.py │ │ ├── run_strategies.py │ │ ├── schemas │ │ ├── __init__.py │ │ ├── order.py │ │ ├── portfolio.py │ │ └── position.py │ │ └── setup_cors.py ├── cli │ ├── __init__.py │ ├── cli.py │ ├── deploy_to_azure_function.py │ ├── initialize_app.py │ └── templates │ │ ├── .gitignore.template │ │ ├── app-web.py.template │ │ ├── app.py.template │ │ ├── app_azure_function.py.template │ │ ├── app_web.py.template │ │ ├── azure_function_function_app.py.template │ │ ├── azure_function_host.json.template │ │ ├── azure_function_local.settings.json.template │ │ ├── data_providers.py.template │ │ ├── env.example.template │ │ ├── env_azure_function.example.template │ │ ├── market_data_providers.py.template │ │ ├── readme.md.template │ │ ├── requirements.txt.template │ │ ├── requirements_azure_function.txt.template │ │ ├── run_backtest.py.template │ │ └── strategy.py.template ├── create_app.py ├── dependency_container.py ├── domain │ ├── __init__.py │ ├── config.py │ ├── constants.py │ ├── data_provider.py │ ├── data_structures.py │ ├── decimal_parsing.py │ ├── exceptions.py │ ├── models │ │ ├── __init__.py │ │ ├── app_mode.py │ │ ├── backtesting │ │ │ ├── __init__.py │ │ │ ├── backtest_date_range.py │ │ │ ├── backtest_position.py │ │ │ ├── backtest_report.py │ │ │ └── backtest_reports_evaluation.py │ │ ├── base_model.py │ │ ├── data_source.py │ │ ├── date_range.py │ │ ├── market │ │ │ ├── __init__.py │ │ │ └── market_credential.py │ │ ├── market_data_type.py │ │ ├── order │ │ │ ├── __init__.py │ │ │ ├── order.py │ │ │ ├── order_side.py │ │ │ ├── order_status.py │ │ │ └── order_type.py │ │ ├── portfolio │ │ │ ├── __init__.py │ │ │ ├── portfolio.py │ │ │ ├── portfolio_configuration.py │ │ │ └── portfolio_snapshot.py │ │ ├── position │ │ │ ├── __init__.py │ │ │ ├── position.py │ │ │ └── position_snapshot.py │ │ ├── strategy_profile.py │ │ ├── time_frame.py │ │ ├── time_interval.py │ │ ├── time_unit.py │ │ ├── tracing │ │ │ ├── __init__.py │ │ │ └── trace.py │ │ ├── trade │ │ │ ├── __init__.py │ │ │ ├── trade.py │ │ │ ├── trade_risk_type.py │ │ │ ├── trade_status.py │ │ │ ├── trade_stop_loss.py │ │ │ └── trade_take_profit.py │ │ ├── trading_data_types.py │ │ └── trading_time_frame.py │ ├── order_executor.py │ ├── portfolio_provider.py │ ├── services │ │ ├── __init__.py │ │ ├── market_credential_service.py │ │ ├── market_data_sources.py │ │ ├── market_service.py │ │ ├── portfolios │ │ │ ├── __init__.py │ │ │ └── portfolio_sync_service.py │ │ ├── rounding_service.py │ │ └── state_handler.py │ ├── stateless_actions.py │ ├── strategy.py │ └── utils │ │ ├── __init__.py │ │ ├── backtesting.py │ │ ├── csv.py │ │ ├── dates.py │ │ ├── polars.py │ │ ├── random.py │ │ ├── signatures.py │ │ ├── stoppable_thread.py │ │ └── synchronized.py ├── download_data.py ├── infrastructure │ ├── __init__.py │ ├── data_providers │ │ ├── __init__.py │ │ ├── ccxt.py │ │ └── csv.py │ ├── database │ │ ├── __init__.py │ │ └── sql_alchemy.py │ ├── models │ │ ├── __init__.py │ │ ├── decimal_parser.py │ │ ├── market_data_sources │ │ │ ├── __init__.py │ │ │ ├── ccxt.py │ │ │ ├── csv.py │ │ │ └── pandas.py │ │ ├── model_extension.py │ │ ├── order │ │ │ ├── __init__.py │ │ │ ├── order.py │ │ │ └── order_metadata.py │ │ ├── order_trade_association.py │ │ ├── portfolio │ │ │ ├── __init__.py │ │ │ ├── portfolio_snapshot.py │ │ │ └── sql_portfolio.py │ │ ├── position │ │ │ ├── __init__.py │ │ │ ├── position.py │ │ │ └── position_snapshot.py │ │ └── trades │ │ │ ├── __init__.py │ │ │ ├── trade.py │ │ │ ├── trade_stop_loss.py │ │ │ └── trade_take_profit.py │ ├── order_executors │ │ ├── __init__.py │ │ └── ccxt_order_executor.py │ ├── portfolio_providers │ │ ├── __init__.py │ │ └── ccxt_portfolio_provider.py │ ├── repositories │ │ ├── __init__.py │ │ ├── order_metadata_repository.py │ │ ├── order_repository.py │ │ ├── portfolio_repository.py │ │ ├── portfolio_snapshot_repository.py │ │ ├── position_repository.py │ │ ├── position_snapshot_repository.py │ │ ├── repository.py │ │ ├── trade_repository.py │ │ ├── trade_stop_loss_repository.py │ │ └── trade_take_profit_repository.py │ └── services │ │ ├── __init__.py │ │ ├── azure │ │ ├── __init__.py │ │ └── state_handler.py │ │ ├── market_service │ │ ├── __init__.py │ │ └── ccxt_market_service.py │ │ └── performance_service │ │ ├── __init__.py │ │ ├── backtest_performance_service.py │ │ └── performance_service.py └── services │ ├── __init__.py │ ├── backtesting │ ├── __init__.py │ └── backtest_service.py │ ├── configuration_service.py │ ├── market_credential_service.py │ ├── market_data_source_service │ ├── __init__.py │ ├── backtest_market_data_source_service.py │ ├── data_provider_service.py │ └── market_data_source_service.py │ ├── order_service │ ├── __init__.py │ ├── order_backtest_service.py │ ├── order_executor_lookup.py │ └── order_service.py │ ├── portfolios │ ├── __init__.py │ ├── backtest_portfolio_service.py │ ├── portfolio_configuration_service.py │ ├── portfolio_provider_lookup.py │ ├── portfolio_service.py │ ├── portfolio_snapshot_service.py │ └── portfolio_sync_service.py │ ├── positions │ ├── __init__.py │ ├── position_service.py │ └── position_snapshot_service.py │ ├── repository_service.py │ ├── strategy_orchestrator_service.py │ └── trade_service │ ├── __init__.py │ └── trade_service.py ├── poetry.lock ├── pyproject.toml └── tests ├── __init__.py ├── app ├── __init__.py ├── algorithm │ ├── __init__.py │ ├── test_algorithm_factory.py │ ├── test_check_order_status.py │ ├── test_close_position.py │ ├── test_close_trade.py │ ├── test_create_limit_buy_order.py │ ├── test_create_limit_sell_order.py │ ├── test_get_allocated.py │ ├── test_get_closed_trades.py │ ├── test_get_number_of_positions.py │ ├── test_get_open_trades.py │ ├── test_get_order.py │ ├── test_get_pending_orders.py │ ├── test_get_portfolio.py │ ├── test_get_position.py │ ├── test_get_trades.py │ ├── test_get_unallocated.py │ ├── test_get_unfilled_buy_value.py │ ├── test_get_unfilled_sell_value.py │ ├── test_has_open_buy_orders.py │ ├── test_has_open_sell_orders.py │ ├── test_has_position.py │ ├── test_has_trading_symbol_position_available.py │ ├── test_name.py │ ├── test_round_down.py │ ├── test_run_strategy.py │ └── test_trade_price_update.py ├── backtesting │ ├── __init__.py │ ├── test_backtest_report.py │ ├── test_run_backtest.py │ ├── test_run_backtests.py │ └── test_strategy_saving.py ├── metrics │ ├── __init__.py │ └── test_price_efficiency.py ├── test_add_config.py ├── test_add_market.py ├── test_add_portfolio_configuration.py ├── test_app_initialize.py ├── test_backtesting.py ├── test_config.py ├── test_start.py └── web │ ├── __init__.py │ └── controllers │ ├── __init__.py │ ├── order_controller │ ├── __init__.py │ └── test_list_orders.py │ ├── portfolio_controller │ ├── __init__.py │ └── test_list_portfolio.py │ └── position_controller │ ├── __init__.py │ └── test_list_positions.py ├── cli ├── __init__.py └── test_initialize.py ├── domain ├── __init__.py ├── backtesting │ ├── __init__.py │ ├── test_pretty_print_backtest.py │ ├── test_pretty_print_backtest_orders.py │ ├── test_pretty_print_backtest_positions.py │ ├── test_pretty_print_backtest_trades.py │ └── test_pretty_print_backtests_evaluation.py ├── metrics │ ├── __init__.py │ └── test_get_price_efficiency_ratio.py ├── models │ ├── __init__.py │ ├── test_backtest_date_range.py │ ├── test_backtest_report.py │ ├── test_backtest_reports_evaluation.py │ ├── test_order.py │ ├── test_portfolio.py │ ├── test_portfolio_configuration.py │ ├── test_position.py │ ├── test_trade.py │ └── trades │ │ ├── __init__.py │ │ ├── test_trade_stop_loss.py │ │ └── test_trade_take_profit.py ├── test_csv.py ├── test_decimal_parsing.py ├── test_load_backtest_reports.py └── utils │ ├── __init__.py │ └── test_polars.py ├── infrastructure ├── __init__.py ├── market_data_sources │ ├── __init__.py │ ├── test_ccxt_ohlcv_backtest_market_data_source.py │ ├── test_ccxt_ohlcv_market_data_source.py │ └── test_csv_ohlcv_market_data_source.py ├── models │ ├── __init__.py │ ├── market_data_sources │ │ └── test_ccxt_ohlcv_backtest_data_source.py │ ├── test_order.py │ ├── test_portfolio.py │ └── test_position.py ├── order_validators │ ├── __init__.py │ └── test_default_order_validator.py ├── repositories │ ├── __init__.py │ ├── test_order_repository.py │ ├── test_position_repository.py │ └── test_trade_repository.py └── services │ ├── __init__.py │ └── test_ccxt_market_service.py ├── resources ├── __init__.py ├── backtest_data │ ├── OHLCV_BTC-EUR_BINANCE_2h_2020-12-15-06-00_2021-01-01-00-30.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2020-12-15-06-00_2021-02-01-00-00.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-06-00_2023-12-02-00-00.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-15-37_2025-05-24-09-37.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-15-40_2025-05-24-09-40.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-15-42_2025-05-24-09-42.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-15-44_2025-05-24-09-44.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-15-49_2025-05-24-09-49.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-15-55_2025-05-24-09-55.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-15-58_2025-05-24-09-58.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-16-01_2025-05-24-10-01.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-16-11_2025-05-24-10-11.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-16-13_2025-05-24-10-13.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-16-15_2025-05-24-10-15.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-16-21_2025-05-24-10-21.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-06-16-22_2025-05-24-10-22.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-07_2025-05-25-08-07.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-21_2025-05-25-08-21.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-29_2025-05-25-08-29.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-31_2025-05-25-08-31.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-33_2025-05-25-08-33.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-40_2025-05-25-08-40.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-42_2025-05-25-08-42.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-43_2025-05-25-08-43.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-44_2025-05-25-08-44.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-14-53_2025-05-25-08-53.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-15-53_2025-05-25-09-53.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-15-55_2025-05-25-09-55.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-17-10_2025-05-25-11-10.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-17-11_2025-05-25-11-11.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-17-13_2025-05-25-11-13.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2025-05-07-17-22_2025-05-25-11-22.csv │ ├── TICKER_BTC-EUR_BINANCE_2020-12-31-22-00_2021-01-01-00-30.csv │ ├── TICKER_BTC-EUR_BINANCE_2020-12-31-22-00_2021-02-01-00-00.csv │ ├── TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-07-37_2025-05-24-09-37.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-07-40_2025-05-24-09-40.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-07-42_2025-05-24-09-42.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-07-44_2025-05-24-09-44.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-07-49_2025-05-24-09-49.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-07-55_2025-05-24-09-55.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-08-11_2025-05-24-10-11.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-08-13_2025-05-24-10-13.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-23-08-22_2025-05-24-10-22.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-06-07_2025-05-25-08-07.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-06-29_2025-05-25-08-29.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-06-43_2025-05-25-08-43.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-06-53_2025-05-25-08-53.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-07-53_2025-05-25-09-53.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-07-55_2025-05-25-09-55.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-09-11_2025-05-25-11-11.csv │ ├── TICKER_BTC-EUR_BINANCE_2025-05-24-09-13_2025-05-25-11-13.csv │ └── TICKER_BTC-EUR_BINANCE_2025-05-24-09-22_2025-05-25-11-22.csv ├── backtest_reports_for_testing │ ├── report_950100_backtest-start-date_2021-12-21-00-00_backtest-end-date_2022-06-20-00-00_created-at_2024-04-25-13-52.json │ ├── report_GoldenCrossStrategy_backtest-start-date_2023-08-24-00-00_backtest-end-date_2023-12-02-00-00_created-at_2025-01-27-08-21.json │ ├── report_test_backtest-start-date_2021-12-21-00-00_backtest-end-date_2022-06-20-00-00_created-at_2024-04-25-13-52.json │ └── test_algorithm_backtest_created-at_2025-04-21-21-21 │ │ └── report.json ├── market_data_sources │ ├── OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv │ ├── OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-22-00_2023-12-25-00-00.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv │ ├── OHLCV_DOT-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv │ ├── TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv │ └── TICKER_DOT-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv ├── market_data_sources_for_testing │ ├── OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-21-45_2023-12-25-00-00.csv │ ├── OHLCV_BTC-EUR_BINANCE_15m_2023-12-14-22-00_2023-12-25-00-00.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv │ ├── OHLCV_BTC-EUR_BINANCE_2h_NO_COLUMNS_2023-08-07-07-59_2023-12-02-00-00.csv │ ├── OHLCV_BTC-EUR_BITVAVO_2h_2020-12-15-06-00_2022-01-01-00-00.csv │ ├── OHLCV_BTC-EUR_BITVAVO_2h_2020-12-15-06-00_2025-01-01-00-00.csv │ ├── OHLCV_BTC-EUR_BITVAVO_2h_2023-07-21-14-00_2024-06-07-10-00.csv │ ├── OHLCV_DOT-EUR_BINANCE_2h_2023-08-07-07-59_2023-12-02-00-00.csv │ ├── TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv │ └── TICKER_DOT-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv ├── serialization_dicts.py ├── settings.py ├── strategies_for_testing │ ├── __init__.py │ ├── strategy_one.py │ ├── strategy_two.py │ ├── strategy_v1 │ │ ├── __init__.py │ │ ├── data_sources.py │ │ └── strategy_v1.py │ ├── strategy_v2 │ │ ├── __init__.py │ │ ├── data_sources.py │ │ └── strategy_v2.py │ └── strategy_v3 │ │ ├── __init__.py │ │ ├── data_sources.py │ │ └── strategy_v3.py ├── stubs │ ├── __init__.py │ ├── market_data_source_service_stub.py │ ├── market_service_stub.py │ ├── order_executor.py │ ├── portfolio_provider.py │ └── portfolio_sync_service.py ├── test_base.py ├── test_order_objects.py └── utils.py ├── scenarios ├── __init__.py ├── resources │ └── backtest_data │ │ ├── OHLCV_BTC-EUR_BINANCE_2h_2023-08-07-06-00_2023-12-02-00-00.csv │ │ └── TICKER_BTC-EUR_BINANCE_2023-08-23-22-00_2023-12-02-00-00.csv ├── test_run_backtest_algorithm_param.py ├── test_run_backtest_multiple_strategies_param.py ├── test_run_backtest_single_strategy_param.py ├── test_run_backtest_strategies_attribute.py ├── test_run_backtests_algorithms_param.py └── test_run_backtests_strategies_param.py ├── services ├── __init__.py ├── test_backtest_service.py ├── test_market_data_source_service.py ├── test_order_backtest_service.py ├── test_order_service.py ├── test_portfolio_configuration_service.py ├── test_portfolio_service.py ├── test_portfolio_sync_service.py ├── test_position_service.py └── test_trade_service.py └── test_create_app.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = 3 | investing_algorithm_framework/domain/utils/backtesting.py 4 | investing_algorithm_framework/infrastructure/database/sql_alchemy.py 5 | examples -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and publish python package 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | publish-service-client-package: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - name: Publish PyPi package 14 | uses: code-specialist/pypi-poetry-publish@v1 15 | with: 16 | ACCESS_TOKEN: ${{ secrets.REPOSITORY_ACCESS_TOKEN }} 17 | PUBLISH_REGISTRY_PASSWORD: ${{ secrets.PYPI_TOKEN }} 18 | BRANCH: "main" 19 | POETRY_VERSION: "1.7.1" 20 | POETRY_CORE_VERSION: "1.8.1" -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Marc van Duyn | Mod and Contributor 2 | 3 | 4 | 5 |
6 | 7 | 8 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | __version__='v6.6.1' 2 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug in the project 4 | title: "[Bug]: Brief description" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the Bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Steps to Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Run '...' 17 | 3. See error 18 | 19 | **Expected Behavior** 20 | What you expected to happen. 21 | 22 | **Screenshots (if applicable)** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Environment (please complete the following information):** 26 | - OS: [e.g., macOS 12.3, Windows 11] 27 | - Python version: [e.g., 3.9.1] 28 | - Package version: [e.g., 0.1.0] 29 | 30 | **Additional Context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /docs/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new feature or improvement 4 | title: "[Feature]: Brief description" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem?** 11 | A clear and concise description of the problem. Example: "I find it difficult to ..." 12 | 13 | **Describe the Solution You'd Like** 14 | What you want the feature to do. 15 | 16 | **Describe Alternatives You've Considered** 17 | Any alternative solutions or features you've considered. 18 | 19 | **Additional Context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /docs/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request Title: [Brief Description] 2 | 3 | ## Description 4 | 5 | A clear and concise description of the changes made. Include: 6 | - What issue or feature this PR addresses. 7 | - Why these changes are necessary. 8 | 9 | Fixes #[issue-number] (if applicable). 10 | 11 | ## Type of Change 12 | - [ ] Bugfix 13 | - [ ] New feature 14 | - [ ] Documentation update 15 | - [ ] Refactor/optimization 16 | 17 | ## Checklist 18 | - [ ] Code is formatted with `black` or a similar linter. 19 | - [ ] Tests have been added or updated. 20 | - [ ] Documentation has been updated (if needed). 21 | 22 | ## Additional Notes 23 | Include any additional notes or context here. 24 | -------------------------------------------------------------------------------- /docs/assets/js/128bc220.cbc321b4.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[6714],{8602:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/crypto","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/154b9f02.f6489b54.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[1131],{3874:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/aws-lambda","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/1d5bcf78.95df0c7f.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[8338],{7332:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/investing-algorithm-framework","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/1d7549f1.a2d4e371.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[1430],{3208:e=>{e.exports=JSON.parse('{"label":"deployment","permalink":"/investing-algorithm-framework/blog/tags/deployment","allTagsPath":"/investing-algorithm-framework/blog/tags","count":2}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/20272bd0.932f96ba.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[3171],{7880:a=>{a.exports=JSON.parse('[{"label":"trading bot","permalink":"/investing-algorithm-framework/blog/tags/trading-bot","count":4},{"label":"deployment","permalink":"/investing-algorithm-framework/blog/tags/deployment","count":2},{"label":"azure functions","permalink":"/investing-algorithm-framework/blog/tags/azure-functions","count":2},{"label":"aws lambda","permalink":"/investing-algorithm-framework/blog/tags/aws-lambda","count":2},{"label":"crypto","permalink":"/investing-algorithm-framework/blog/tags/crypto","count":4},{"label":"investing algorithm","permalink":"/investing-algorithm-framework/blog/tags/investing-algorithm","count":4},{"label":"investing algorithm framework","permalink":"/investing-algorithm-framework/blog/tags/investing-algorithm-framework","count":4},{"label":"bitvavo","permalink":"/investing-algorithm-framework/blog/tags/bitvavo","count":1},{"label":"binance","permalink":"/investing-algorithm-framework/blog/tags/binance","count":1}]')}}]); -------------------------------------------------------------------------------- /docs/assets/js/3befab5d.308b66c0.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[9598],{6396:a=>{a.exports=JSON.parse('{"label":"binance","permalink":"/investing-algorithm-framework/blog/tags/binance","allTagsPath":"/investing-algorithm-framework/blog/tags","count":1}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/4972.eef817e7.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[4972],{4972:(e,t,n)=>{n.r(t),n.d(t,{default:()=>i});var a=n(7294),o=n(5999),l=n(1944),r=n(7961);function i(){return a.createElement(a.Fragment,null,a.createElement(l.d,{title:(0,o.I)({id:"theme.NotFound.title",message:"Page Not Found"})}),a.createElement(r.Z,null,a.createElement("main",{className:"container margin-vert--xl"},a.createElement("div",{className:"row"},a.createElement("div",{className:"col col--6 col--offset-3"},a.createElement("h1",{className:"hero__title"},a.createElement(o.Z,{id:"theme.NotFound.title",description:"The title of the 404 page"},"Page Not Found")),a.createElement("p",null,a.createElement(o.Z,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page"},"We could not find what you were looking for.")),a.createElement("p",null,a.createElement(o.Z,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page"},"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.")))))))}}}]); -------------------------------------------------------------------------------- /docs/assets/js/49789df1.d3cef4fc.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[9119],{4469:e=>{e.exports=JSON.parse('{"name":"docusaurus-plugin-content-blog","id":"default"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/4e20fc8c.312a51cf.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[9425],{7171:a=>{a.exports=JSON.parse('{"label":"bitvavo","permalink":"/investing-algorithm-framework/blog/tags/bitvavo","allTagsPath":"/investing-algorithm-framework/blog/tags","count":1}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/6232ef27.d9700317.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[5287],{1779:a=>{a.exports=JSON.parse('{"label":"investing algorithm","permalink":"/investing-algorithm-framework/blog/tags/investing-algorithm","allTagsPath":"/investing-algorithm-framework/blog/tags","count":4}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/64eeacce.b0a450e5.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[9194],{5571:a=>{a.exports=JSON.parse('{"label":"crypto","permalink":"/investing-algorithm-framework/blog/tags/crypto","allTagsPath":"/investing-algorithm-framework/blog/tags","count":4}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/6d4ff5c0.8f549863.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[5657],{2913:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/deployment","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/7d49d76a.0f3690d5.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[8023],{3769:e=>{e.exports=JSON.parse('{"name":"docusaurus-plugin-content-docs","id":"default"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/80c72890.eb4b5cd0.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[3657],{8170:a=>{a.exports=JSON.parse('{"label":"investing algorithm framework","permalink":"/investing-algorithm-framework/blog/tags/investing-algorithm-framework","allTagsPath":"/investing-algorithm-framework/blog/tags","count":4}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/814f3328.8eafdee4.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[2535],{5641:t=>{t.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"How to deploy a trading bot","permalink":"/investing-algorithm-framework/blog/how-to-deploy-a-trading-bot"},{"title":"How to create a trading bot for bitvavo","permalink":"/investing-algorithm-framework/blog/how-to-create-a-trading-bot-for-bitvavo"},{"title":"How to create a trading bot for binance","permalink":"/investing-algorithm-framework/blog/hot-to-create-a-trading-bot-for-binance"},{"title":"How to build a trading bot in 5 steps","permalink":"/investing-algorithm-framework/blog/how-to-create-a-trading-bot-in-5-steps"}]}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/9c0f94cd.9883d7bd.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[6101],{5385:a=>{a.exports=JSON.parse('{"label":"trading bot","permalink":"/investing-algorithm-framework/blog/tags/trading-bot","allTagsPath":"/investing-algorithm-framework/blog/tags","count":4}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/9d73a88a.8cf30b50.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[9674],{1988:a=>{a.exports=JSON.parse('{"label":"aws lambda","permalink":"/investing-algorithm-framework/blog/tags/aws-lambda","allTagsPath":"/investing-algorithm-framework/blog/tags","count":2}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/9e4087bc.a441a013.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[3608],{3169:(e,t,a)=>{a.r(t),a.d(t,{default:()=>o});var r=a(7294),l=a(9960),n=a(5999),c=a(1944),i=a(7961);function m(e){let{year:t,posts:a}=e;return r.createElement(r.Fragment,null,r.createElement("h3",null,t),r.createElement("ul",null,a.map((e=>r.createElement("li",{key:e.metadata.date},r.createElement(l.Z,{to:e.metadata.permalink},e.metadata.formattedDate," - ",e.metadata.title))))))}function s(e){let{years:t}=e;return r.createElement("section",{className:"margin-vert--lg"},r.createElement("div",{className:"container"},r.createElement("div",{className:"row"},t.map(((e,t)=>r.createElement("div",{key:t,className:"col col--4 margin-vert--lg"},r.createElement(m,e)))))))}function o(e){let{archive:t}=e;const a=(0,n.I)({id:"theme.blog.archive.title",message:"Archive",description:"The page & hero title of the blog archive page"}),l=(0,n.I)({id:"theme.blog.archive.description",message:"Archive",description:"The page & hero description of the blog archive page"}),m=function(e){const t=e.reduceRight(((e,t)=>{const a=t.metadata.date.split("-")[0],r=e.get(a)??[];return e.set(a,[t,...r])}),new Map);return Array.from(t,(e=>{let[t,a]=e;return{year:t,posts:a}}))}(t.blogPosts);return r.createElement(r.Fragment,null,r.createElement(c.d,{title:a,description:l}),r.createElement(i.Z,null,r.createElement("header",{className:"hero hero--primary"},r.createElement("div",{className:"container"},r.createElement("h1",{className:"hero__title"},a),r.createElement("p",{className:"hero__subtitle"},l))),r.createElement("main",null,m.length>0&&r.createElement(s,{years:m}))))}}}]); -------------------------------------------------------------------------------- /docs/assets/js/a888f091.20625ec2.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[2164],{4251:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/bitvavo","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/a88f1ba7.678a7056.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[5663],{5987:a=>{a.exports=JSON.parse('{"label":"azure functions","permalink":"/investing-algorithm-framework/blog/tags/azure-functions","allTagsPath":"/investing-algorithm-framework/blog/tags","count":2}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/ab1a3d19.3fcd260a.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[2701],{7425:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/binance","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/b07ff907.d5eb985e.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[3606],{7925:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/azure-functions","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/cd98809c.b6011a28.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[8378],{6609:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/investing-algorithm","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/de394c98.b6d537d1.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[9324],{1884:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog/tags/trading-bot","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/assets/js/f9d42936.17aee861.js: -------------------------------------------------------------------------------- 1 | "use strict";(self.webpackChunkinvesting_algorithm_framework_docs=self.webpackChunkinvesting_algorithm_framework_docs||[]).push([[113],{9031:e=>{e.exports=JSON.parse('{"permalink":"/investing-algorithm-framework/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":4,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); -------------------------------------------------------------------------------- /docs/img/code-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docs/img/code-sample.png -------------------------------------------------------------------------------- /docs/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docs/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docs/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docs/img/docusaurus.png -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docs/img/favicon.ico -------------------------------------------------------------------------------- /docusaurus/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docusaurus/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docusaurus/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docusaurus/blog/2023-08-03-how-to-create-a-trading-bot-for-binance.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hot-to-create-a-trading-bot-for-binance 3 | title: How to create a trading bot for binance 4 | authors: 5 | name: Marc van Duyn 6 | title: How to create a trading bot for Binance 7 | url: https://github.com/mduyn 8 | image_url: https://github.com/mduyn.png 9 | tags: [trading bot, binance, crypto, investing algorithm, investing algorithm framework] 10 | --- 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | 14 | ## How to create a trading bot for binance 15 | 16 | ## Connect to Binance 17 | 18 | ## Create a strategy 19 | 20 | ## Backtest your strategy 21 | 22 | ## Run your strategy 23 | -------------------------------------------------------------------------------- /docusaurus/blog/2023-08-04-how-to-create-a-trading-bot-for-bitvavo.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: how-to-create-a-trading-bot-for-bitvavo 3 | title: How to create a trading bot for bitvavo 4 | authors: 5 | name: Marc van Duyn 6 | title: How to create a trading bot for bitvavo 7 | url: https://github.com/mduyn 8 | image_url: https://github.com/mduyn.png 9 | tags: [trading bot, bitvavo, crypto, investing algorithm, investing algorithm framework] 10 | --- 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | 14 | ## How to create a trading bot for binance 15 | 16 | ## Connect to Binance 17 | 18 | ## Create a strategy 19 | 20 | ## Backtest your strategy 21 | 22 | ## Run your strategy -------------------------------------------------------------------------------- /docusaurus/blog/2023-08-05-how-to-deploy-a-trading-bot.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: how-to-deploy-a-trading-bot 3 | title: How to deploy a trading bot 4 | authors: 5 | name: Marc van Duyn 6 | title: How to deploy a trading bot 7 | url: https://github.com/mduyn 8 | image_url: https://github.com/mduyn.png 9 | tags: [trading bot, deployment, azure functions, aws lambda, crypto, investing algorithm, investing algorithm framework] 10 | --- 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | 14 | ## How to create a trading bot for binance 15 | 16 | ## Connect to Binance 17 | 18 | ## Create a strategy 19 | 20 | ## Backtest your strategy 21 | 22 | ## Run your strategy -------------------------------------------------------------------------------- /docusaurus/blog/authors.yml: -------------------------------------------------------------------------------- 1 | mduyn: 2 | name: Marc van Duyn 3 | title: Investing algorithm framework maintainer 4 | url: https://github.com/mduyn 5 | image_url: https://github.com/mduyn.png 6 | bio: | 7 | Marc is a software engineer with a passion for investing. He is the maintainer of the 8 | investing algorithm framework. [Twitter](https://twitter.com/marcvanduyn), [Medium](https://medium.com/@marcvanduyn) 9 | -------------------------------------------------------------------------------- /docusaurus/docs/Advanced Concepts/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Advanced concepts", 3 | "position": 1, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Advanced concepts and features of the framework." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docusaurus/docs/Contributing Guide/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Contributing", 3 | "position": 2, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Contributing to the Investing Algorithm Framework." 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docusaurus/docs/Cook Book/how-to-check-open-orders.md.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/docs/Cook Book/how-to-check-open-orders.md.js -------------------------------------------------------------------------------- /docusaurus/docs/Cook Book/how-to-structure-your-code.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/docs/Cook Book/how-to-structure-your-code.js -------------------------------------------------------------------------------- /docusaurus/docs/Cook Book/using-take-profit-and-stop-loss.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/docs/Cook Book/using-take-profit-and-stop-loss.js -------------------------------------------------------------------------------- /docusaurus/docs/Data/__category__.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Data", 3 | "position": 0, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Learn the basic concepts of using data sources in the investing algorithm framework" 7 | } 8 | } -------------------------------------------------------------------------------- /docusaurus/docs/Getting Started/__category__.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Getting Started", 3 | "position": 0, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Learn the basic concepts of the investing algorithm framework" 7 | } 8 | } -------------------------------------------------------------------------------- /docusaurus/docs/Getting Started/deployment.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | # Deployment 4 | -------------------------------------------------------------------------------- /docusaurus/docs/Getting Started/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Installation 6 | You can install the framework using the following command: 7 | 8 | :::info 9 | Python 3.10 or higher is required. 10 | ::: 11 | 12 | ```bash 13 | pip install investing-algorithm-framework 14 | ``` 15 | -------------------------------------------------------------------------------- /docusaurus/docs/Getting Started/performance.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/docs/Getting Started/performance.md -------------------------------------------------------------------------------- /docusaurus/docs/Getting Started/strategies.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | # Strategies 5 | 6 | Strategies are the core of the framework. They are the main entry point for the framework. 7 | Strategies are used to define the trading logic of your algorithm. In your strategy you can use the algorithm object to 8 | place orders, get orders, get the current balance and more. 9 | 10 | When defining a strategy you need to define the following things: 11 | 12 | - The time unit of the strategy (second, minute, hour, day, week, month) 13 | - The interval of the strategy (how often the strategy should run within the time unit) 14 | 15 | The framework comes with two ways to define a strategy. 16 | 17 | - Class based strategies 18 | - Decorator strategies 19 | 20 | ## Class based strategy 21 | 22 | 23 | ```python 24 | from investing_algorithm_framework import TimeUnit, TradingStrategy, Algorithm 25 | 26 | app = create_app() 27 | 28 | class MyTradingStrategy(TradingStrategy): 29 | time_unit = TimeUnit.SECOND # The time unit of the strategy 30 | interval = 5 # The interval of the strategy, runs every 5 seconds 31 | 32 | def apply_strategy(self, algorithm: Algorithm, market_data: Dict[str, Any]): 33 | pass 34 | 35 | app.register_strategy(MyTradingStrategy) 36 | ``` 37 | 38 | ## Decorator based strategy 39 | 40 | ```python 41 | from investing_algorithm_framework import create_app, TimeUnit, Algorithm 42 | 43 | # Runs every 5 seconds 44 | @app.strategy(time_unit=TimeUnit.SECOND, interval=5) 45 | def perform_strategy(algorithm: Algorithm, market_data: Dict[str, Any]): 46 | pass 47 | ``` 48 | -------------------------------------------------------------------------------- /docusaurus/docs/Getting Started/tasks.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | # Tasks 4 | -------------------------------------------------------------------------------- /docusaurus/docs/Getting Started/trades.md: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | # Trades 4 | -------------------------------------------------------------------------------- /docusaurus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "investing-algorithm-framework-docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.4.1", 18 | "@docusaurus/preset-classic": "2.4.1", 19 | "@emotion/react": "^11.11.1", 20 | "@emotion/styled": "^11.11.0", 21 | "@mdx-js/react": "^1.6.22", 22 | "@mui/icons-material": "^5.14.3", 23 | "@mui/material": "^5.14.3", 24 | "clsx": "^1.2.1", 25 | "prism-react-renderer": "^1.3.5", 26 | "react": "^17.0.2", 27 | "react-dom": "^17.0.2" 28 | }, 29 | "devDependencies": { 30 | "@docusaurus/module-type-aliases": "2.4.1" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.5%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "engines": { 45 | "node": ">=16.14" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docusaurus/src/components/buttons.js: -------------------------------------------------------------------------------- 1 | import {styled} from "@mui/material/styles"; 2 | import {Button} from "@mui/material"; 3 | 4 | export const LowercaseButton = styled(Button)( 5 | ({}) => ({ 6 | textTransform: 'none !important', 7 | fontSize: 12 8 | }) 9 | ) 10 | -------------------------------------------------------------------------------- /docusaurus/src/components/index.js: -------------------------------------------------------------------------------- 1 | export * from "./buttons"; -------------------------------------------------------------------------------- /docusaurus/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 95%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme='dark'] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /docusaurus/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/static/.nojekyll -------------------------------------------------------------------------------- /docusaurus/static/img/code-sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/static/img/code-sample.png -------------------------------------------------------------------------------- /docusaurus/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /docusaurus/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/static/img/docusaurus.png -------------------------------------------------------------------------------- /docusaurus/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/docusaurus/static/img/favicon.ico -------------------------------------------------------------------------------- /examples/backtest_example/strategies/__init__.py: -------------------------------------------------------------------------------- 1 | from strategies.strategy_v1 import CrossOverStrategyV1 2 | 3 | __all__ = [ 4 | "CrossOverStrategyV1", 5 | ] 6 | -------------------------------------------------------------------------------- /examples/backtest_example/strategies/strategy_v1/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy_v1 import CrossOverStrategyV1 2 | 3 | __all__ = [ 4 | "CrossOverStrategyV1", 5 | ] 6 | -------------------------------------------------------------------------------- /examples/backtest_example/strategies/strategy_v1/data_sources.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import ( 2 | CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource 3 | ) 4 | 5 | bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 6 | identifier="BTC/EUR-ohlcv-2h", 7 | market="BINANCE", 8 | symbol="BTC/EUR", 9 | time_frame="2h", 10 | window_size=200 11 | ) 12 | bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 13 | identifier="DOT/EUR-ohlch-2h", 14 | market="BINANCE", 15 | symbol="DOT/EUR", 16 | time_frame="2h", 17 | window_size=200 18 | ) 19 | bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( 20 | identifier="DOT/EUR-ticker", 21 | market="BINANCE", 22 | symbol="DOT/EUR", 23 | backtest_time_frame="2h", 24 | ) 25 | bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( 26 | identifier="BTC/EUR-ticker", 27 | market="BINANCE", 28 | symbol="BTC/EUR", 29 | backtest_time_frame="2h", 30 | ) 31 | -------------------------------------------------------------------------------- /examples/backtest_example/strategies/strategy_v2/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy_v2 import CrossOverStrategyV2 2 | 3 | __all__ = [ 4 | "CrossOverStrategyV2", 5 | ] 6 | -------------------------------------------------------------------------------- /examples/backtest_example/strategies/strategy_v2/data_sources.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import ( 2 | CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource 3 | ) 4 | 5 | bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 6 | identifier="BTC/EUR-ohlcv-2h", 7 | market="BINANCE", 8 | symbol="BTC/EUR", 9 | time_frame="2h", 10 | window_size=200 11 | ) 12 | bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 13 | identifier="DOT/EUR-ohlch-2h", 14 | market="BINANCE", 15 | symbol="DOT/EUR", 16 | time_frame="2h", 17 | window_size=200 18 | ) 19 | bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( 20 | identifier="DOT/EUR-ticker", 21 | market="BINANCE", 22 | symbol="DOT/EUR", 23 | backtest_time_frame="2h", 24 | ) 25 | bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( 26 | identifier="BTC/EUR-ticker", 27 | market="BINANCE", 28 | symbol="BTC/EUR", 29 | backtest_time_frame="2h", 30 | ) 31 | -------------------------------------------------------------------------------- /examples/backtest_example/strategies/strategy_v3/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy_v3 import CrossOverStrategyV3 2 | 3 | __all__ = [ 4 | "CrossOverStrategyV3", 5 | ] 6 | -------------------------------------------------------------------------------- /examples/backtest_example/strategies/strategy_v3/data_sources.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import ( 2 | CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource 3 | ) 4 | 5 | bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 6 | identifier="BTC/EUR-ohlcv-2h", 7 | market="BINANCE", 8 | symbol="BTC/EUR", 9 | time_frame="2h", 10 | window_size=200 11 | ) 12 | bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 13 | identifier="DOT/EUR-ohlch-2h", 14 | market="BINANCE", 15 | symbol="DOT/EUR", 16 | time_frame="2h", 17 | window_size=200 18 | ) 19 | bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( 20 | identifier="DOT/EUR-ticker", 21 | market="BINANCE", 22 | symbol="DOT/EUR", 23 | backtest_time_frame="2h", 24 | ) 25 | bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( 26 | identifier="BTC/EUR-ticker", 27 | market="BINANCE", 28 | symbol="BTC/EUR", 29 | backtest_time_frame="2h", 30 | ) 31 | -------------------------------------------------------------------------------- /examples/download_data.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import download 2 | 3 | if __name__ == "__main__": 4 | btceur_ohlcv = download( 5 | symbol="btc/eur", 6 | market="bitvavo", 7 | time_frame="1d", 8 | start_date="2023-01-01", 9 | end_date="2023-12-31", 10 | pandas=False, 11 | save=True, 12 | storage_path="./data" 13 | ) 14 | -------------------------------------------------------------------------------- /examples/example_strategies/README.md: -------------------------------------------------------------------------------- 1 | # Trading bot strategies series 2 | This directory forms a list of example quantitative algorithms for 3 | implemented in Python with the Investing Algorithm Framework. 4 | The goal is to create a set of jupyter notebooks that can used as a reference 5 | for developing your own strategies. 6 | 7 | 8 | ## Strategies 9 | 1. [SMA_CROSSOVER: A strategy that uses Simple Moving Average Crossover](simple_moving_average_crossover.ipynb) 10 | 2. ADX_RSI.ipynb: A strategy that uses Average Directional Index and RSI (adx_rsi.ipynb) -------------------------------------------------------------------------------- /examples/example_strategies/adx_rsi_divergence/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/examples/example_strategies/adx_rsi_divergence/__init__.py -------------------------------------------------------------------------------- /examples/test.py: -------------------------------------------------------------------------------- 1 | #%% 2 | import numpy as np 3 | import pandas as pd 4 | import plotly.graph_objects as go 5 | 6 | from investing_algorithm_framework import get_backtest_report, \ 7 | get_cumulative_profit_factor_series, download, get_profit_factor, \ 8 | get_sharpe_ratio 9 | 10 | 11 | 12 | if __name__ == "__main__": 13 | report = get_backtest_report(directory="./resources/backtest_report") 14 | btceur_ohlcv = download( 15 | symbol="btc/eur", 16 | market="bitvavo", 17 | time_frame="1d", 18 | start_date=report.backtest_date_range.start_date, 19 | end_date=report.backtest_date_range.end_date, 20 | pandas=True, 21 | save=True, 22 | storage_path="./data" 23 | ) 24 | 25 | # Example usage of get_backtest_report 26 | profit_factor_series = get_cumulative_profit_factor_series( 27 | backtest_report=report 28 | ) 29 | 30 | profit_factor = get_profit_factor( 31 | backtest_report=report 32 | ) 33 | print(f"Profit Factor: {profit_factor}") 34 | 35 | sharp_ratio = get_sharpe_ratio(backtest_report=report, frequency="weekly", risk_free_rate=0.025) 36 | print(f"Sharp Ratio: {sharp_ratio}") 37 | print(report.number_of_days) 38 | print(report.total_net_gain_percentage) 39 | 40 | # Simulated price data 41 | dates = pd.date_range(start="2016-01-01", periods=1000, freq='D') 42 | prices = pd.Series(np.random.lognormal(mean=0.0005, sigma=0.01, size=len(dates)), index=dates).cumprod() 43 | prices_btceur = btceur_ohlcv['Close'] 44 | 45 | # # Ensure the index is a datetime index 46 | dates_btceur = btceur_ohlcv.index 47 | 48 | # Calculate log close for BTC/EUR 49 | log_close_btceur = np.log(btceur_ohlcv['Close']) 50 | 51 | 52 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/__init__.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.app.app import App, AppHook 2 | from investing_algorithm_framework.app.stateless import StatelessAction 3 | from investing_algorithm_framework.app.strategy import TradingStrategy 4 | from investing_algorithm_framework.app.task import Task 5 | from investing_algorithm_framework.app.web import create_flask_app 6 | from .algorithm import Algorithm 7 | from .context import Context 8 | from .metrics import get_cumulative_profit_factor_series, \ 9 | get_rolling_profit_factor_series, get_price_efficiency_ratio 10 | 11 | __all__ = [ 12 | "Algorithm", 13 | "App", 14 | "create_flask_app", 15 | "TradingStrategy", 16 | "StatelessAction", 17 | "Task", 18 | "AppHook", 19 | "Context", 20 | "get_cumulative_profit_factor_series", 21 | "get_rolling_profit_factor_series", 22 | "get_price_efficiency_ratio" 23 | ] 24 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/algorithm/__init__.py: -------------------------------------------------------------------------------- 1 | from .algorithm_factory import AlgorithmFactory 2 | from .algorithm import Algorithm 3 | 4 | __all__ = [ 5 | "AlgorithmFactory", 6 | "Algorithm" 7 | ] 8 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/app_hook.py: -------------------------------------------------------------------------------- 1 | from abc import abstractmethod 2 | 3 | 4 | class AppHook: 5 | """ 6 | Abstract class for app hooks. App hooks are used to run code before 7 | actions of the framework are executed. This is useful for running code 8 | that needs to be run before the following events: 9 | - App initialization 10 | - Strategy run 11 | """ 12 | 13 | @abstractmethod 14 | def on_run(self, context) -> None: 15 | """ 16 | Method to run the app hook. This method should be implemented 17 | by the user. This method will be called when the app is performing 18 | a specific action. 19 | 20 | Args: 21 | context: The context of the app. This can be used to get the 22 | current state of the trading bot, such as portfolios, 23 | orders, positions, etc. 24 | 25 | Returns: 26 | None 27 | """ 28 | raise NotImplementedError() 29 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/metrics/__init__.py: -------------------------------------------------------------------------------- 1 | from .price_efficiency import get_price_efficiency_ratio 2 | from .profit_factor import get_cumulative_profit_factor_series, \ 3 | get_rolling_profit_factor_series, get_profit_factor 4 | from .sharp_ratio import get_sharpe_ratio 5 | from .equity_curve import get_equity_curve 6 | 7 | __all__ = [ 8 | "get_price_efficiency_ratio", 9 | "get_rolling_profit_factor_series", 10 | "get_cumulative_profit_factor_series", 11 | "get_profit_factor", 12 | "get_sharpe_ratio", 13 | "get_equity_curve" 14 | ] 15 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/metrics/equity_curve.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from investing_algorithm_framework.domain import BacktestReport 3 | 4 | 5 | def get_equity_curve( 6 | backtest_report: BacktestReport 7 | ) -> list[tuple[datetime, float]]: 8 | """ 9 | Calculate the total size of the portfolio at each snapshot timestamp. 10 | 11 | Args: 12 | backtest_report (BacktestReport): The backtest report 13 | containing history of the portfolio. 14 | 15 | Returns: 16 | list[tuple[datetime, float]]: A list of tuples with 17 | timestamps and total sizes. 18 | """ 19 | series = [] 20 | for snapshot in backtest_report.get_snapshots(): 21 | timestamp = snapshot.created_at 22 | total_size = snapshot.net_size 23 | series.append((timestamp, total_size)) 24 | 25 | return series 26 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/stateless/__init__.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.app.stateless.action_handlers \ 2 | import ActionHandler 3 | from investing_algorithm_framework.app.stateless.action_handlers import \ 4 | StatelessAction 5 | from investing_algorithm_framework.app.stateless.exception_handler import \ 6 | handle_exception 7 | from investing_algorithm_framework.domain.exceptions import \ 8 | OperationalException 9 | 10 | 11 | class StatelessHandler: 12 | 13 | def handler(self, payload, algorithm): 14 | action = StatelessHandler.get_action_type(payload) 15 | 16 | try: 17 | # Handle the action 18 | action_handler = ActionHandler.of(StatelessAction 19 | .from_string(action)) 20 | return action_handler.handle(payload) 21 | except Exception as e: 22 | return handle_exception(e) 23 | 24 | @staticmethod 25 | def get_action_type(payload): 26 | 27 | if "action" in payload: 28 | action = payload["action"] 29 | else: 30 | action = payload["ACTION"] 31 | 32 | if action is None: 33 | raise OperationalException("Action type not supported") 34 | 35 | return action 36 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/stateless/action_handlers/action_handler_strategy.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class ActionHandlerStrategy(ABC): 5 | 6 | @abstractmethod 7 | def handle_event(self, payload, context, strategy_orchestrator_service): 8 | pass 9 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/stateless/action_handlers/check_online_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from investing_algorithm_framework.app.stateless.action_handlers \ 4 | .action_handler_strategy import ActionHandlerStrategy 5 | 6 | 7 | class CheckOnlineHandler(ActionHandlerStrategy): 8 | MESSAGE = {"message": "online"} 9 | 10 | def handle_event(self, payload, context, strategy_orchestrator_service): 11 | return { 12 | "statusCode": 200, 13 | "headers": {"Content-Type": "application/json"}, 14 | "body": json.dumps(CheckOnlineHandler.MESSAGE) 15 | } 16 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/stateless/action_handlers/run_strategy_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from investing_algorithm_framework.app.stateless.action_handlers \ 4 | .action_handler_strategy import ActionHandlerStrategy 5 | 6 | 7 | class RunStrategyHandler(ActionHandlerStrategy): 8 | """ 9 | RunStrategyHandler is an action handler that runs a strategy and its tasks 10 | synchronously. 11 | 12 | If the run was successful, it returns a 200 OK response with a message 13 | "OK". 14 | """ 15 | MESSAGE = {"message": "Ok"} 16 | 17 | def handle_event(self, payload, context, strategy_orchestrator_service): 18 | strategies = strategy_orchestrator_service\ 19 | .get_strategies(payload.get("strategies", None)) 20 | tasks = strategy_orchestrator_service.get_tasks() 21 | 22 | for strategy in strategies: 23 | strategy_orchestrator_service.run_strategy( 24 | strategy=strategy, 25 | context=context, 26 | sync=True 27 | ) 28 | 29 | for task in tasks: 30 | strategy_orchestrator_service.run_task( 31 | task=task, 32 | context=context, 33 | sync=True 34 | ) 35 | 36 | return { 37 | "statusCode": 200, 38 | "headers": {"Content-Type": "application/json"}, 39 | "body": json.dumps({"message": RunStrategyHandler.MESSAGE}) 40 | } 41 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/stateless/exception_handler.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | from typing import Dict, List 4 | 5 | from investing_algorithm_framework.domain import OperationalException 6 | 7 | logger = logging.getLogger("investing_algorithm_framework") 8 | 9 | 10 | def create_error_response(error_message, status_code: int = 400): 11 | response = json.dumps({"error_message": error_message}) 12 | return response, status_code 13 | 14 | 15 | def format_marshmallow_validation_error(errors): 16 | errors_message = {} 17 | 18 | for key in errors: 19 | 20 | if isinstance(errors[key], Dict): 21 | errors_message[key] = \ 22 | format_marshmallow_validation_error(errors[key]) 23 | 24 | if isinstance(errors[key], List): 25 | errors_message[key] = errors[key][0].lower() 26 | return errors_message 27 | 28 | 29 | def handle_exception(error): 30 | logger.error("exception of type {} occurred".format(type(error))) 31 | logger.exception(error) 32 | 33 | if isinstance(error, OperationalException): 34 | return error.to_response() 35 | else: 36 | # Internal error happened that was unknown 37 | return { 38 | "status": "error", 39 | "message": str(error) 40 | } 41 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/task.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.domain import \ 2 | TimeUnit 3 | 4 | 5 | class Task: 6 | time_unit: str = None 7 | interval: int = None 8 | worker_id: str = None 9 | decorated = None 10 | 11 | def __init__( 12 | self, 13 | time_unit=None, 14 | interval=None, 15 | worker_id=None, 16 | decorated=None 17 | ): 18 | if time_unit is not None: 19 | self.time_unit = TimeUnit.from_value(time_unit) 20 | 21 | if interval is not None: 22 | self.interval = interval 23 | 24 | if decorated is not None: 25 | self.decorated = decorated 26 | 27 | if worker_id is not None: 28 | self.worker_id = worker_id 29 | elif self.decorated: 30 | self.worker_id = decorated.__name__ 31 | else: 32 | self.worker_id = self.__class__.__name__ 33 | 34 | def run(self, algorithm): 35 | 36 | if self.decorated: 37 | self.decorated(algorithm=algorithm) 38 | else: 39 | raise NotImplementedError("Apply strategy is not implemented") 40 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/__init__.py: -------------------------------------------------------------------------------- 1 | from .create_app import create_flask_app 2 | from .run_strategies import run_strategies 3 | from .schemas import OrderSerializer 4 | 5 | __all__ = ["create_flask_app", "run_strategies", 'OrderSerializer'] 6 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/controllers/__init__.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.app.web.controllers.orders import \ 2 | blueprint as orders_blueprint 3 | from investing_algorithm_framework.app.web.controllers.portfolio \ 4 | import blueprint as portfolio_blueprint 5 | from investing_algorithm_framework.app.web.controllers.positions import \ 6 | blueprint as positions_blueprint 7 | 8 | 9 | def setup_blueprints(flask_app): 10 | flask_app.register_blueprint(portfolio_blueprint, prefix="/api") 11 | flask_app.register_blueprint(orders_blueprint, prefix="/api") 12 | flask_app.register_blueprint(positions_blueprint, prefix="/api") 13 | return flask_app 14 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/controllers/orders.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from dependency_injector.wiring import inject, Provide 4 | from flask import Blueprint, request 5 | 6 | from investing_algorithm_framework.app.web.responses import create_response 7 | from investing_algorithm_framework.app.web.schemas import OrderSerializer 8 | from investing_algorithm_framework.dependency_container import \ 9 | DependencyContainer 10 | 11 | logger = logging.getLogger("investing_algorithm_framework") 12 | 13 | blueprint = Blueprint("order-views", __name__) 14 | 15 | 16 | @blueprint.route("/api/orders", methods=["GET"]) 17 | @inject 18 | def list_orders(order_service=Provide[DependencyContainer.order_service]): 19 | orders = order_service.get_all(request.args) 20 | return create_response(orders, OrderSerializer()) 21 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/controllers/portfolio.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from dependency_injector.wiring import inject, Provide 4 | from flask import Blueprint, request 5 | 6 | from investing_algorithm_framework.app.web.responses import create_response 7 | from investing_algorithm_framework.app.web.schemas import PortfolioSerializer 8 | from investing_algorithm_framework.dependency_container import \ 9 | DependencyContainer 10 | 11 | logger = logging.getLogger("investing_algorithm_framework") 12 | 13 | blueprint = Blueprint("portfolio-views", __name__) 14 | 15 | 16 | @blueprint.route("/api/portfolios", methods=["GET"]) 17 | @inject 18 | def retrieve(portfolio_service=Provide[DependencyContainer.portfolio_service]): 19 | portfolios = portfolio_service.get_all(request.args) 20 | return create_response(portfolios, PortfolioSerializer()) 21 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/controllers/positions.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from dependency_injector.wiring import inject, Provide 4 | from flask import Blueprint, request 5 | 6 | from investing_algorithm_framework.app.web.responses import create_response 7 | from investing_algorithm_framework.app.web.schemas import PositionSerializer 8 | 9 | logger = logging.getLogger("investing_algorithm_framework") 10 | 11 | blueprint = Blueprint("position-views", __name__) 12 | 13 | 14 | @blueprint.route("/api/positions", methods=["GET"]) 15 | @inject 16 | def list_positions(position_service=Provide["position_service"]): 17 | positions = position_service.get_all(request.args) 18 | return create_response(positions, PositionSerializer()) 19 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/create_app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | 3 | from investing_algorithm_framework.app.web.controllers import setup_blueprints 4 | from investing_algorithm_framework.app.web.setup_cors import setup_cors 5 | from .error_handler import setup_error_handler 6 | 7 | 8 | def create_flask_app(configuration_service): 9 | app = Flask(__name__.split('.')[0]) 10 | 11 | flask_config = configuration_service.get_flask_config() 12 | 13 | for key, value in flask_config.items(): 14 | app.config[key] = value 15 | 16 | app = setup_cors(app) 17 | app.strict_slashes = False 18 | app = setup_blueprints(app) 19 | app = setup_error_handler(app) 20 | return app 21 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/responses.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from flask import jsonify 4 | 5 | 6 | def create_response(data, serializer, status_code=200): 7 | 8 | if inspect.isclass(serializer): 9 | serializer = serializer() 10 | 11 | if isinstance(data, dict): 12 | item_selection = data["items"] 13 | data["items"] = serializer.dump(item_selection, many=True) 14 | return data, status_code 15 | elif isinstance(data, list): 16 | data = serializer.dump(data, many=True) 17 | return jsonify({"items": data, "total": len(data)}), status_code 18 | else: 19 | data = serializer.dump(data) 20 | return jsonify(data), status_code 21 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/run_strategies.py: -------------------------------------------------------------------------------- 1 | def run_strategies(strategy_orchestration_service): 2 | 3 | if strategy_orchestration_service.running: 4 | strategy_orchestration_service.run_pending_jobs() 5 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.app.web.schemas.order import \ 2 | OrderSerializer 3 | from investing_algorithm_framework.app.web.schemas.portfolio import\ 4 | PortfolioSerializer 5 | from investing_algorithm_framework.app.web.schemas.position import \ 6 | PositionSerializer 7 | 8 | __all__ = [ 9 | "OrderSerializer", 10 | "PositionSerializer", 11 | "PortfolioSerializer" 12 | ] 13 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/schemas/order.py: -------------------------------------------------------------------------------- 1 | from marshmallow import Schema, fields 2 | 3 | 4 | class OrderSerializer(Schema): 5 | reference_id = fields.String(dump_only=True) 6 | target_symbol = fields.String(dump_only=True) 7 | trading_symbol = fields.String(dump_only=True) 8 | price = fields.Float(dump_only=True) 9 | amount = fields.Float(dump_only=True) 10 | status = fields.String(dump_only=True) 11 | order_type = fields.String(dump_only=True) 12 | order_side = fields.String(dump_only=True) 13 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/schemas/portfolio.py: -------------------------------------------------------------------------------- 1 | from marshmallow import Schema, fields 2 | 3 | from investing_algorithm_framework.dependency_container \ 4 | import DependencyContainer 5 | 6 | 7 | class PortfolioSerializer(Schema): 8 | identifier = fields.String(dump_only=True) 9 | trading_symbol = fields.String(dump_only=True) 10 | unallocated = fields.Float(dump_only=True) 11 | orders = fields.Method("get_orders") 12 | positions = fields.Method("get_positions") 13 | 14 | @staticmethod 15 | def get_orders(obj): 16 | order_service = DependencyContainer.order_service() 17 | return order_service.count({"portfolio": obj.identifier}) 18 | 19 | @staticmethod 20 | def get_positions(obj): 21 | position_service = DependencyContainer.position_service() 22 | return position_service.count({"portfolio": obj.id}) 23 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/schemas/position.py: -------------------------------------------------------------------------------- 1 | from marshmallow import Schema, fields 2 | 3 | from investing_algorithm_framework.dependency_container import \ 4 | DependencyContainer 5 | 6 | 7 | class PositionSerializer(Schema): 8 | symbol = fields.String() 9 | amount = fields.Float(dump_only=True) 10 | orders = fields.Method("get_orders") 11 | 12 | @staticmethod 13 | def get_orders(obj): 14 | order_service = DependencyContainer.order_service() 15 | return order_service.count({"position": obj.id}) 16 | -------------------------------------------------------------------------------- /investing_algorithm_framework/app/web/setup_cors.py: -------------------------------------------------------------------------------- 1 | from flask_cors import CORS 2 | 3 | 4 | def setup_cors(app): 5 | CORS(app) 6 | return app 7 | -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/investing_algorithm_framework/cli/__init__.py -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/app-web.py.template: -------------------------------------------------------------------------------- 1 | import logging.config 2 | from dotenv import load_dotenv 3 | 4 | from investing_algorithm_framework import create_app, \ 5 | DEFAULT_LOGGING_CONFIG, Algorithm 6 | from strategies.strategy import MyTradingStrategy 7 | 8 | load_dotenv() 9 | logging.config.dictConfig(DEFAULT_LOGGING_CONFIG) 10 | 11 | app = create_app(web=True) 12 | app.add_market(market="binance", initial_balance=1000, trading_symbol="EUR") 13 | algorithm = Algorithm(name="MyTradingBot") 14 | algorithm.add_strategy(MyTradingStrategy) 15 | app.add_algorithm(algorithm) 16 | 17 | if __name__ == "__main__": 18 | app.run() 19 | -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/app.py.template: -------------------------------------------------------------------------------- 1 | import logging.config 2 | from dotenv import load_dotenv 3 | 4 | from investing_algorithm_framework import create_app, \ 5 | DEFAULT_LOGGING_CONFIG, Algorithm, PortfolioConfiguration 6 | from strategies.strategy import MyTradingStrategy 7 | 8 | load_dotenv() 9 | logging.config.dictConfig(DEFAULT_LOGGING_CONFIG) 10 | 11 | app = create_app() 12 | app.add_market(market="binance", initial_balance=1000, trading_symbol="EUR") 13 | algorithm = Algorithm(name="MyTradingBot") 14 | algorithm.add_strategy(MyTradingStrategy) 15 | app.add_algorithm(algorithm) 16 | 17 | if __name__ == "__main__": 18 | app.run() 19 | -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/app_azure_function.py.template: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | 3 | from investing_algorithm_framework import create_app, PortfolioConfiguration, \ 4 | TimeUnit, CCXTOHLCVMarketDataSource, Algorithm, \ 5 | CCXTTickerMarketDataSource, MarketCredential, AzureBlobStorageStateHandler 6 | from strategies.strategy import MyTradingStrategy 7 | 8 | load_dotenv() 9 | 10 | app = create_app() 11 | app.add_market(market="binance", initial_balance=1000, trading_symbol="EUR") 12 | algorithm = Algorithm(name="MyTradingBot") 13 | algorithm.add_strategy(MyTradingStrategy) 14 | app.add_algorithm(algorithm) 15 | -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/app_web.py.template: -------------------------------------------------------------------------------- 1 | import logging.config 2 | from dotenv import load_dotenv 3 | 4 | from investing_algorithm_framework import create_app, \ 5 | DEFAULT_LOGGING_CONFIG, Algorithm 6 | from strategies.strategy import MyTradingStrategy 7 | 8 | load_dotenv() 9 | logging.config.dictConfig(DEFAULT_LOGGING_CONFIG) 10 | 11 | app = create_app(web=True) 12 | app.add_market(market="binance", initial_balance=1000, trading_symbol="EUR") 13 | algorithm = Algorithm(name="MyTradingBot") 14 | algorithm.add_strategy(MyTradingStrategy) 15 | app.add_algorithm(algorithm) 16 | 17 | if __name__ == "__main__": 18 | app.run() -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/azure_function_host.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | }, 11 | "extensionBundle": { 12 | "id": "Microsoft.Azure.Functions.ExtensionBundle", 13 | "version": "[4.*, 5.0.0)" 14 | } 15 | } -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/azure_function_local.settings.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "FUNCTIONS_WORKER_RUNTIME": "python", 5 | "AzureWebJobsFeatureFlags": "EnableWorkerIndexing", 6 | "AzureWebJobsStorage": "" 7 | } 8 | } -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/data_providers.py.template: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import CCXTOHLCVMarketDataSource, \ 2 | CCXTTickerMarketDataSource 3 | 4 | btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 5 | identifier="BTC/EUR-ohlcv-2h", 6 | market="BITVAVO", 7 | symbol="BTC/EUR", 8 | time_frame="2h", 9 | window_size=200 10 | ) 11 | 12 | btc_eur_ticker = CCXTTickerMarketDataSource( 13 | identifier="BTC/EUR-ticker", 14 | market="BITVAVO", 15 | symbol="BTC/EUR", 16 | backtest_time_frame="2h", 17 | ) 18 | -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/env.example.template: -------------------------------------------------------------------------------- 1 | BITVAVO_API_KEY= 2 | BITVAVO_SECRET_KEY= -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/env_azure_function.example.template: -------------------------------------------------------------------------------- 1 | BITVAVO_API_KEY= 2 | BITVAVO_SECRET_KEY= 3 | AZURE_STORAGE_CONNECTION_STRING= 4 | AZURE_STORAGE_CONTAINER_NAME= -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/market_data_providers.py.template: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import CCXTOHLCVMarketDataSource 2 | 3 | btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 4 | identifier="BTC/EUR-ohlcv", 5 | market="BINANCE", 6 | symbol="BTC/EUR", 7 | time_frame="2h", 8 | window_size=200 9 | ) 10 | -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/requirements.txt.template: -------------------------------------------------------------------------------- 1 | investing-algorithm-framework>=6.2.1 2 | pyindicators>=0.5.4 3 | -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/requirements_azure_function.txt.template: -------------------------------------------------------------------------------- 1 | investing-algorithm-framework>=6.2.0 2 | azure-functions==1.17.0 3 | pyindicators>=0.5.4 -------------------------------------------------------------------------------- /investing_algorithm_framework/cli/templates/run_backtest.py.template: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from investing_algorithm_framework import BacktestDateRange, \ 3 | pretty_print_backtest 4 | 5 | from app import app 6 | 7 | if __name__ == "__main__": 8 | backtest_date_range = BacktestDateRange( 9 | start_date=datetime(2023, 1, 1), 10 | end_date=datetime(2023, 12, 31), 11 | ) 12 | report = app.run_backtest(backtest_date_range=backtest_date_range) 13 | pretty_print_backtest(report) 14 | -------------------------------------------------------------------------------- /investing_algorithm_framework/create_app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import inspect 4 | from dotenv import load_dotenv 5 | 6 | from .app import App 7 | from .dependency_container import setup_dependency_container 8 | from .domain import AppMode, APPLICATION_DIRECTORY 9 | 10 | logger = logging.getLogger("investing_algorithm_framework") 11 | 12 | 13 | def create_app( 14 | config: dict = None, 15 | state_handler=None, 16 | web: bool = False, 17 | name=None 18 | ) -> App: 19 | """ 20 | Factory method to create an app instance. 21 | 22 | Args: 23 | config (dict): Configuration dictionary 24 | web (bool): Whether to create a web app 25 | state_handler (StateHandler): State handler for the app 26 | name (str): Name of the app 27 | 28 | Returns: 29 | App: App instance 30 | """ 31 | # Load the environment variables 32 | load_dotenv() 33 | 34 | app = App(state_handler=state_handler) 35 | app = setup_dependency_container( 36 | app, 37 | ["investing_algorithm_framework"], 38 | ["investing_algorithm_framework"] 39 | ) 40 | # After the container is setup, initialize the services 41 | app.initialize_services() 42 | app.name = name 43 | 44 | if config is not None: 45 | app.set_config_with_dict(config) 46 | 47 | if web: 48 | app.set_config("APP_MODE", AppMode.WEB.value) 49 | 50 | # Add the application directory to the config 51 | caller_frame = inspect.stack()[1] 52 | caller_path = os.path.abspath(caller_frame.filename) 53 | app.set_config(APPLICATION_DIRECTORY, caller_path) 54 | 55 | logger.info("Investing algoritm framework app created") 56 | return app 57 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/data_structures.py: -------------------------------------------------------------------------------- 1 | class PeekableQueue: 2 | def __init__(self, items=[]): 3 | self.queue = items 4 | self.index = 0 5 | 6 | def enqueue(self, item): 7 | self.queue.append(item) 8 | 9 | def dequeue(self): 10 | if not self.is_empty(): 11 | return self.queue.pop(0) 12 | else: 13 | raise IndexError("Queue is empty") 14 | 15 | def peek(self): 16 | if not self.is_empty(): 17 | return self.queue[0] 18 | else: 19 | raise IndexError("Queue is empty") 20 | 21 | def is_empty(self): 22 | return len(self.queue) == 0 23 | 24 | def __len__(self): 25 | return len(self.queue) 26 | 27 | @property 28 | def size(self): 29 | return len(self.queue) 30 | 31 | def __iter__(self): 32 | self.index = 0 33 | return self 34 | 35 | def __next__(self): 36 | if self.index < len(self.queue): 37 | result = self.queue[self.index] 38 | self.index += 1 39 | return result 40 | else: 41 | self.index = 0 # Reset index for next iteration 42 | raise StopIteration 43 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/decimal_parsing.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal, getcontext 2 | 3 | 4 | def count_number_of_decimals(value) -> int: 5 | value = str(value) 6 | if "." in value: 7 | return len(value) - value.index(".") - 1 8 | else: 9 | return 0 10 | 11 | 12 | def parse_decimal(value) -> Decimal: 13 | getcontext().prec = count_number_of_decimals(value) 14 | return Decimal(value) 15 | 16 | 17 | def parse_decimal_to_string(decimal, precision=None): 18 | 19 | if decimal is None: 20 | return None 21 | 22 | if isinstance(decimal, str): 23 | return decimal 24 | 25 | value_str = str(Decimal(decimal)) 26 | 27 | if precision is None: 28 | return value_str 29 | 30 | value_decimal = Decimal(value_str) 31 | value_with_precision = format(value_decimal, f'.{precision}f') 32 | return value_with_precision 33 | 34 | 35 | def parse_string_to_decimal(value): 36 | 37 | if value is None: 38 | return None 39 | 40 | return Decimal(value) 41 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .app_mode import AppMode 2 | from .backtesting import BacktestReport, BacktestPosition, \ 3 | BacktestReportsEvaluation, BacktestDateRange 4 | from .market import MarketCredential 5 | from .order import OrderStatus, OrderSide, OrderType, Order 6 | from .portfolio import PortfolioConfiguration, Portfolio, PortfolioSnapshot 7 | from .position import Position, PositionSnapshot 8 | from .strategy_profile import StrategyProfile 9 | from .time_frame import TimeFrame 10 | from .time_interval import TimeInterval 11 | from .time_unit import TimeUnit 12 | from .trade import Trade, TradeStatus, TradeStopLoss, TradeTakeProfit, \ 13 | TradeRiskType 14 | from .trading_data_types import TradingDataType 15 | from .trading_time_frame import TradingTimeFrame 16 | from .date_range import DateRange 17 | from .market_data_type import MarketDataType 18 | from .data_source import DataSource 19 | 20 | __all__ = [ 21 | "OrderStatus", 22 | "OrderSide", 23 | "OrderType", 24 | "Order", 25 | "TimeFrame", 26 | "TimeInterval", 27 | "TimeUnit", 28 | "TradingTimeFrame", 29 | "TradingDataType", 30 | "PortfolioConfiguration", 31 | "Position", 32 | "Portfolio", 33 | "BacktestReport", 34 | "PositionSnapshot", 35 | "PortfolioSnapshot", 36 | "StrategyProfile", 37 | "BacktestPosition", 38 | "Trade", 39 | "MarketCredential", 40 | "TradeStatus", 41 | "BacktestReportsEvaluation", 42 | "AppMode", 43 | "BacktestDateRange", 44 | "DateRange", 45 | "MarketDataType", 46 | "TradeStopLoss", 47 | "TradeTakeProfit", 48 | "TradeRiskType", 49 | "DataSource" 50 | ] 51 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/app_mode.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class AppMode(Enum): 5 | STATELESS = "STATELESS" 6 | DEFAULT = "DEFAULT" 7 | WEB = "WEB" 8 | 9 | @staticmethod 10 | def from_string(value: str): 11 | 12 | if isinstance(value, str): 13 | for status in AppMode: 14 | 15 | if value.upper() == status.value: 16 | return status 17 | 18 | raise ValueError("Could not convert value to AppMode") 19 | 20 | @staticmethod 21 | def from_value(value): 22 | 23 | if isinstance(value, AppMode): 24 | for status in AppMode: 25 | 26 | if value == status: 27 | return status 28 | elif isinstance(value, str): 29 | return AppMode.from_string(value) 30 | 31 | raise ValueError(f"Could not convert value {value} to AppMode") 32 | 33 | def equals(self, other): 34 | return AppMode.from_value(other) == self 35 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/backtesting/__init__.py: -------------------------------------------------------------------------------- 1 | from .backtest_position import BacktestPosition 2 | from .backtest_report import BacktestReport 3 | from .backtest_reports_evaluation import BacktestReportsEvaluation 4 | from .backtest_date_range import BacktestDateRange 5 | 6 | __all__ = [ 7 | "BacktestReport", 8 | "BacktestPosition", 9 | "BacktestReportsEvaluation", 10 | "BacktestDateRange" 11 | ] 12 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/backtesting/backtest_date_range.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from dateutil.parser import parse 3 | 4 | 5 | class BacktestDateRange: 6 | """ 7 | Represents a date range for a backtest 8 | """ 9 | def __init__(self, start_date, end_date=None, name=None): 10 | 11 | if isinstance(start_date, str): 12 | start_date = parse(start_date) 13 | 14 | if end_date is not None and isinstance(end_date, str): 15 | end_date = parse(end_date) 16 | 17 | self._start_date = start_date 18 | self._end_date = end_date 19 | self._name = name 20 | 21 | if end_date is None: 22 | self._end_date = datetime.now() 23 | 24 | if end_date < start_date: 25 | raise ValueError( 26 | "End date cannot be before start date for a backtest " 27 | "date range. " + 28 | f"(start_date: {start_date}, end_date: {end_date})" 29 | ) 30 | 31 | @property 32 | def start_date(self): 33 | return self._start_date 34 | 35 | @property 36 | def end_date(self): 37 | return self._end_date 38 | 39 | @property 40 | def name(self): 41 | return self._name 42 | 43 | def __repr__(self): 44 | return f"{self.name}: {self._start_date} - {self._end_date}" 45 | 46 | def __str__(self): 47 | return self.__repr__() 48 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/base_model.py: -------------------------------------------------------------------------------- 1 | class BaseModel: 2 | 3 | def repr(self, **fields) -> str: 4 | """ 5 | Helper for __repr__ 6 | """ 7 | 8 | field_strings = [] 9 | at_least_one_attached_attribute = False 10 | 11 | for key, field in fields.items(): 12 | field_strings.append(f'{key}={field!r}') 13 | at_least_one_attached_attribute = True 14 | 15 | if at_least_one_attached_attribute: 16 | return f"<{self.__class__.__name__}({','.join(field_strings)})>" 17 | 18 | return f"<{self.__class__.__name__} {id(self)}>" 19 | 20 | def update(self, data): 21 | 22 | for attr, value in data.items(): 23 | 24 | if value is not None: 25 | setattr(self, attr, value) 26 | 27 | @staticmethod 28 | def from_dict(data): 29 | instance = BaseModel() 30 | instance.update(data) 31 | return instance 32 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/data_source.py: -------------------------------------------------------------------------------- 1 | class DataSource: 2 | """ 3 | Base class for data sources. 4 | """ 5 | 6 | def __init__( 7 | self, 8 | data_type: str, 9 | symbol: str, 10 | market: str, 11 | time_frame: str, 12 | window_size: int, 13 | key: str, name: str 14 | ): 15 | self.name = name 16 | self.key = key 17 | self.data_type = data_type 18 | self.symbol = symbol 19 | self.market = market 20 | self.time_frame = time_frame 21 | self.window_size = window_size 22 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/market/__init__.py: -------------------------------------------------------------------------------- 1 | from .market_credential import MarketCredential 2 | 3 | __all__ = [ 4 | "MarketCredential", 5 | ] 6 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/market_data_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MarketDataType(Enum): 5 | OHLCV = "OHLCV" 6 | TICKER = "TICKER" 7 | ORDER_BOOK = "ORDER_BOOK" 8 | CUSTOM = "CUSTOM" 9 | 10 | @staticmethod 11 | def from_string(value: str): 12 | 13 | if isinstance(value, str): 14 | 15 | for entry in MarketDataType: 16 | 17 | if value.upper() == entry.value: 18 | return entry 19 | 20 | raise ValueError( 21 | f"Could not convert {value} to MarketDataType" 22 | ) 23 | 24 | @staticmethod 25 | def from_value(value): 26 | 27 | if isinstance(value, str): 28 | return MarketDataType.from_string(value) 29 | 30 | if isinstance(value, MarketDataType): 31 | 32 | for entry in MarketDataType: 33 | 34 | if value == entry: 35 | return entry 36 | 37 | raise ValueError( 38 | f"Could not convert {value} to TimeFrame" 39 | ) 40 | 41 | def equals(self, other): 42 | 43 | if isinstance(other, Enum): 44 | return self.value == other.value 45 | else: 46 | return MarketDataType.from_string(other) == self 47 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/order/__init__.py: -------------------------------------------------------------------------------- 1 | from .order import Order 2 | from .order_side import OrderSide 3 | from .order_status import OrderStatus 4 | from .order_type import OrderType 5 | 6 | __all__ = ["OrderType", "OrderStatus", "OrderSide", "Order"] 7 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/order/order_side.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class OrderSide(Enum): 5 | SELL = 'SELL' 6 | BUY = 'BUY' 7 | 8 | @staticmethod 9 | def from_string(value: str): 10 | 11 | if isinstance(value, str): 12 | for order_type in OrderSide: 13 | 14 | if value.upper() == order_type.value: 15 | return order_type 16 | 17 | raise ValueError(f"Could not convert value {value} to OrderSide") 18 | 19 | @staticmethod 20 | def from_value(value): 21 | 22 | if isinstance(value, OrderSide): 23 | for order_side in OrderSide: 24 | 25 | if value == order_side: 26 | return order_side 27 | 28 | return OrderSide.from_string(value) 29 | 30 | def equals(self, other): 31 | 32 | if isinstance(other, Enum): 33 | return self.value == other.value 34 | 35 | else: 36 | return OrderSide.from_string(other) == self 37 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/order/order_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class OrderStatus(Enum): 5 | CREATED = 'CREATED' 6 | OPEN = "OPEN" 7 | CLOSED = "CLOSED" 8 | CANCELED = "CANCELED" 9 | EXPIRED = "EXPIRED" 10 | REJECTED = "REJECTED" 11 | 12 | @staticmethod 13 | def from_string(value: str): 14 | 15 | if isinstance(value, str): 16 | for order_type in OrderStatus: 17 | 18 | if value.upper() == order_type.value: 19 | return order_type 20 | 21 | raise ValueError(f"Could not convert value {value} to OrderStatus") 22 | 23 | @staticmethod 24 | def from_value(value): 25 | 26 | if isinstance(value, OrderStatus): 27 | for order_status in OrderStatus: 28 | 29 | if value == order_status: 30 | return order_status 31 | elif isinstance(value, str): 32 | return OrderStatus.from_string(value) 33 | 34 | raise ValueError("Could not convert value to OrderStatus") 35 | 36 | def equals(self, other): 37 | return OrderStatus.from_value(other) == self 38 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/order/order_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class OrderType(Enum): 5 | LIMIT = 'LIMIT' 6 | 7 | @staticmethod 8 | def from_string(value: str): 9 | 10 | if isinstance(value, str): 11 | for order_type in OrderType: 12 | 13 | if value.upper() == order_type.value: 14 | return order_type 15 | 16 | raise ValueError("Could not convert value to OrderType") 17 | 18 | @staticmethod 19 | def from_value(value): 20 | 21 | if isinstance(value, OrderType): 22 | for order_type in OrderType: 23 | 24 | if value == order_type: 25 | return order_type 26 | 27 | return OrderType.from_string(value) 28 | 29 | def equals(self, other): 30 | return OrderType.from_value(other) == self 31 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/portfolio/__init__.py: -------------------------------------------------------------------------------- 1 | from .portfolio import Portfolio 2 | from .portfolio_configuration import PortfolioConfiguration 3 | from .portfolio_snapshot import PortfolioSnapshot 4 | 5 | __all__ = [ 6 | "PortfolioConfiguration", 7 | "Portfolio", 8 | "PortfolioSnapshot", 9 | ] 10 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/position/__init__.py: -------------------------------------------------------------------------------- 1 | from .position import Position 2 | from .position_snapshot import PositionSnapshot 3 | 4 | __all__ = ["Position", "PositionSnapshot"] 5 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/position/position.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.domain.models.base_model import BaseModel 2 | 3 | 4 | class Position(BaseModel): 5 | """ 6 | This class represents a position in a portfolio. 7 | """ 8 | 9 | def __init__( 10 | self, 11 | symbol=None, 12 | amount=0, 13 | cost=0, 14 | portfolio_id=None 15 | ): 16 | self.symbol = symbol 17 | self.amount = amount 18 | self.cost = cost 19 | self.portfolio_id = portfolio_id 20 | 21 | def get_symbol(self): 22 | return self.symbol 23 | 24 | def set_symbol(self, symbol): 25 | self.symbol = symbol.upper() 26 | 27 | def get_amount(self): 28 | return self.amount 29 | 30 | def get_cost(self): 31 | return self.cost 32 | 33 | def set_cost(self, cost): 34 | self.cost = cost 35 | 36 | def set_amount(self, amount): 37 | self.amount = amount 38 | 39 | def get_portfolio_id(self): 40 | return self.portfolio_id 41 | 42 | def set_portfolio_id(self, portfolio_id): 43 | self.portfolio_id = portfolio_id 44 | 45 | def to_dict(self): 46 | return { 47 | "symbol": self.symbol, 48 | "amount": self.amount, 49 | "cost": self.cost, 50 | "portfolio_id": self.portfolio_id, 51 | } 52 | 53 | def __repr__(self): 54 | return self.repr( 55 | symbol=self.symbol, 56 | amount=self.amount, 57 | cost=self.cost, 58 | portfolio_id=self.portfolio_id, 59 | ) 60 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/position/position_snapshot.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.domain.models.base_model import BaseModel 2 | 3 | 4 | class PositionSnapshot(BaseModel): 5 | 6 | def __init__( 7 | self, 8 | symbol=None, 9 | amount=0, 10 | cost=0, 11 | portfolio_snapshot_id=None 12 | ): 13 | self.symbol = symbol 14 | self.amount = amount 15 | self.cost = cost 16 | self.portfolio_snapshot_id = portfolio_snapshot_id 17 | 18 | def get_symbol(self): 19 | return self.symbol 20 | 21 | def set_symbol(self, symbol): 22 | self.symbol = symbol.upper() 23 | 24 | def get_amount(self): 25 | return self.amount 26 | 27 | def get_cost(self): 28 | return self.cost 29 | 30 | def set_cost(self, cost): 31 | self.cost = cost 32 | 33 | def set_amount(self, amount): 34 | self.amount = amount 35 | 36 | def get_portfolio_snapshot_id(self): 37 | return self.portfolio_snapshot_id 38 | 39 | def set_portfolio_snapshot_id(self, portfolio_snapshot_id): 40 | self.portfolio_snapshot_id = portfolio_snapshot_id 41 | 42 | def __repr__(self): 43 | return self.repr( 44 | symbol=self.symbol, 45 | amount=self.amount, 46 | portfolio_snapshot_id=self.portfolio_snapshot_id, 47 | ) 48 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/tracing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/investing_algorithm_framework/domain/models/tracing/__init__.py -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/tracing/trace.py: -------------------------------------------------------------------------------- 1 | class Trace: 2 | """ 3 | Represents a trace of a trading strategy. A trace contains 4 | data that has been generated by a trading strategy during its 5 | execution. 6 | 7 | The data can be used to analyze the performance of the trading after 8 | it has been executed. Usually, the data contains metrics that 9 | have been generated by the trading strategy during its execution, 10 | and the signals that have been generated. 11 | """ 12 | 13 | def __init__( 14 | self, 15 | strategy_id: str, 16 | symbol: str, 17 | data, 18 | drop_duplicates=True 19 | ): 20 | self.strategy_id = strategy_id 21 | self.symbol = symbol 22 | self.data = data 23 | self.drop_duplicates = drop_duplicates 24 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/trade/__init__.py: -------------------------------------------------------------------------------- 1 | from .trade import Trade 2 | from .trade_status import TradeStatus 3 | from .trade_stop_loss import TradeStopLoss 4 | from .trade_take_profit import TradeTakeProfit 5 | from .trade_risk_type import TradeRiskType 6 | 7 | __all__ = [ 8 | "Trade", 9 | "TradeStatus", 10 | "TradeStopLoss", 11 | "TradeTakeProfit", 12 | "TradeRiskType", 13 | ] 14 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/trade/trade_risk_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class TradeRiskType(Enum): 5 | FIXED = "FIXED" 6 | TRAILING = "TRAILING" 7 | 8 | @staticmethod 9 | def from_string(value: str): 10 | 11 | if isinstance(value, str): 12 | for status in TradeRiskType: 13 | 14 | if value.upper() == status.value: 15 | return status 16 | 17 | raise ValueError("Could not convert value to TradeRiskType") 18 | 19 | @staticmethod 20 | def from_value(value): 21 | 22 | if isinstance(value, TradeRiskType): 23 | for risk_type in TradeRiskType: 24 | 25 | if value == risk_type: 26 | return risk_type 27 | 28 | elif isinstance(value, str): 29 | return TradeRiskType.from_string(value) 30 | 31 | raise ValueError("Could not convert value to TradeRiskType") 32 | 33 | def equals(self, other): 34 | return TradeRiskType.from_value(other) == self 35 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/trade/trade_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class TradeStatus(Enum): 5 | CREATED = "CREATED" 6 | OPEN = "OPEN" 7 | CLOSED = "CLOSED" 8 | 9 | @staticmethod 10 | def from_string(value: str): 11 | 12 | if isinstance(value, str): 13 | for status in TradeStatus: 14 | 15 | if value.upper() == status.value: 16 | return status 17 | 18 | raise ValueError("Could not convert value to TradeStatus") 19 | 20 | @staticmethod 21 | def from_value(value): 22 | 23 | if isinstance(value, TradeStatus): 24 | for status in TradeStatus: 25 | 26 | if value == status: 27 | return status 28 | elif isinstance(value, str): 29 | return TradeStatus.from_string(value) 30 | 31 | raise ValueError("Could not convert value to TradeStatus") 32 | 33 | def equals(self, other): 34 | return TradeStatus.from_value(other) == self 35 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/models/trading_data_types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class TradingDataType(Enum): 5 | TICKER = 'TICKER' 6 | ORDER_BOOK = 'ORDER_BOOK' 7 | OHLCV = "OHLCV" 8 | CUSTOM = "CUSTOM" 9 | 10 | @staticmethod 11 | def from_value(value): 12 | 13 | if isinstance(value, TradingDataType): 14 | for trading_data_type in TradingDataType: 15 | 16 | if value == trading_data_type: 17 | return trading_data_type 18 | 19 | elif isinstance(value, str): 20 | return TradingDataType.from_string(value) 21 | 22 | raise ValueError( 23 | "Could not convert value to trading data type" 24 | ) 25 | 26 | @staticmethod 27 | def from_string(value: str): 28 | 29 | if isinstance(value, str): 30 | for order_type in TradingDataType: 31 | 32 | if value.upper() == order_type.value: 33 | return order_type 34 | 35 | raise ValueError( 36 | "Could not convert value to trading data type" 37 | ) 38 | 39 | def equals(self, other): 40 | 41 | if other is None: 42 | return False 43 | 44 | if isinstance(other, Enum): 45 | return self.value == other.value 46 | 47 | else: 48 | return TradingDataType.from_string(other) == self 49 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/services/__init__.py: -------------------------------------------------------------------------------- 1 | from .market_credential_service import MarketCredentialService 2 | from .market_data_sources import MarketDataSource, TickerMarketDataSource, \ 3 | OHLCVMarketDataSource, OrderBookMarketDataSource, BacktestMarketDataSource 4 | from .market_service import MarketService 5 | from .portfolios import AbstractPortfolioSyncService 6 | from .rounding_service import RoundingService 7 | from .state_handler import StateHandler 8 | 9 | __all__ = [ 10 | "MarketDataSource", 11 | "TickerMarketDataSource", 12 | "OHLCVMarketDataSource", 13 | "OrderBookMarketDataSource", 14 | "BacktestMarketDataSource", 15 | "MarketService", 16 | "MarketCredentialService", 17 | "AbstractPortfolioSyncService", 18 | "RoundingService", 19 | "StateHandler" 20 | ] 21 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/services/market_credential_service.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import List, Optional 3 | 4 | from investing_algorithm_framework.domain import MarketCredential 5 | 6 | 7 | class MarketCredentialService(ABC): 8 | """ 9 | This class is responsible for managing the market credentials of the app. 10 | """ 11 | @abstractmethod 12 | def add(self, market_data_credential: MarketCredential): 13 | """ 14 | Add a market credential in the repository 15 | """ 16 | raise NotImplementedError() 17 | 18 | @abstractmethod 19 | def add_all(self, market_data_credentials: List[MarketCredential]): 20 | """ 21 | Add a list of market credentials in the repository 22 | """ 23 | raise NotImplementedError() 24 | 25 | @abstractmethod 26 | def get(self, market) -> Optional[MarketCredential]: 27 | """ 28 | Get a market credential from the repository 29 | """ 30 | raise NotImplementedError() 31 | 32 | @abstractmethod 33 | def get_all(self) -> List[MarketCredential]: 34 | """ 35 | Get all market credentials from the repository 36 | """ 37 | raise NotImplementedError() 38 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/services/portfolios/__init__.py: -------------------------------------------------------------------------------- 1 | from .portfolio_sync_service import AbstractPortfolioSyncService 2 | 3 | __all__ = [ 4 | "AbstractPortfolioSyncService" 5 | ] 6 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/services/portfolios/portfolio_sync_service.py: -------------------------------------------------------------------------------- 1 | class AbstractPortfolioSyncService: 2 | """ 3 | Service to sync the portfolio with the exchange. 4 | """ 5 | def sync_unallocated(self, portfolio): 6 | pass 7 | 8 | def sync_orders(self, portfolio): 9 | pass 10 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/services/rounding_service.py: -------------------------------------------------------------------------------- 1 | import decimal 2 | 3 | 4 | class RoundingService: 5 | """ 6 | Service to round numbers to a certain amount of decimals. 7 | It will always round down. 8 | """ 9 | 10 | @staticmethod 11 | def round_down(value, amount_of_decimals): 12 | 13 | if RoundingService.count_decimals(value) <= amount_of_decimals: 14 | return value 15 | 16 | with decimal.localcontext() as ctx: 17 | d = decimal.Decimal(value) 18 | ctx.rounding = decimal.ROUND_DOWN 19 | return float(round(d, amount_of_decimals)) 20 | 21 | @staticmethod 22 | def count_decimals(number): 23 | decimal_str = str(number) 24 | if '.' in decimal_str: 25 | return len(decimal_str.split('.')[1]) 26 | else: 27 | return 0 28 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/services/state_handler.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class StateHandler(ABC): 5 | """ 6 | Abstract base class for state handlers. 7 | 8 | This class defines the 9 | interface for state handlers, which are responsible for 10 | saving and loading state information. 11 | """ 12 | 13 | @abstractmethod 14 | def initialize(self): 15 | """ 16 | Initialize the state handler. 17 | """ 18 | pass 19 | 20 | @abstractmethod 21 | def save(self, target_directory: str): 22 | """ 23 | Save the state to the specified directory. 24 | 25 | Args: 26 | target_directory (str): Directory to save the state 27 | """ 28 | pass 29 | 30 | @abstractmethod 31 | def load(self, target_directory: str): 32 | """ 33 | Load the state from the specified directory. 34 | 35 | Args: 36 | target_directory (str): Directory to load the state 37 | """ 38 | pass 39 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/stateless_actions.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class StatelessActions(Enum): 5 | RUN_STRATEGY = 'RUN_STRATEGY' 6 | CHECK_PENDING_ORDERS = 'CHECK_PENDING_ORDERS' 7 | PING = 'PING' 8 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .backtesting import pretty_print_backtest, load_backtest_report, \ 2 | pretty_print_backtest_reports_evaluation, load_backtest_reports, \ 3 | get_backtest_report, pretty_print_positions, pretty_print_trades, \ 4 | pretty_print_orders 5 | from .csv import get_total_amount_of_rows, append_dict_as_row_to_csv, \ 6 | add_column_headers_to_csv, csv_to_list, load_csv_into_dict 7 | from .random import random_string, random_number 8 | from .stoppable_thread import StoppableThread 9 | from .synchronized import synchronized 10 | from .polars import convert_polars_to_pandas 11 | from .dates import is_timezone_aware, sync_timezones, get_timezone 12 | 13 | __all__ = [ 14 | 'synchronized', 15 | 'StoppableThread', 16 | 'random_string', 17 | 'random_number', 18 | 'get_total_amount_of_rows', 19 | 'append_dict_as_row_to_csv', 20 | 'add_column_headers_to_csv', 21 | 'csv_to_list', 22 | 'pretty_print_backtest', 23 | 'pretty_print_backtest_reports_evaluation', 24 | 'load_csv_into_dict', 25 | 'load_backtest_report', 26 | 'load_backtest_reports', 27 | 'convert_polars_to_pandas', 28 | 'get_backtest_report', 29 | 'pretty_print_positions', 30 | 'pretty_print_trades', 31 | 'pretty_print_orders', 32 | 'is_timezone_aware', 33 | 'sync_timezones', 34 | 'get_timezone' 35 | ] 36 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/utils/random.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | 5 | def random_string(n, spaces: bool = False): 6 | """ 7 | Function to generate a random string of n characters. 8 | 9 | Args: 10 | n: number of characters 11 | spaces: if True, include spaces in the string 12 | 13 | Returns: 14 | str: Random string of n characters 15 | """ 16 | 17 | if spaces: 18 | return ''.join( 19 | random.choice(string.ascii_lowercase + ' ') for _ in range(n) 20 | ) 21 | 22 | return ''.join(random.choice(string.ascii_lowercase) for _ in range(n)) 23 | 24 | 25 | def random_number(n, variable_size: bool = False): 26 | """ 27 | Function to generate a random number of n digits. 28 | 29 | Args: 30 | n: number of digits 31 | variable_size: if True, the number of digits will be variable 32 | between 1 and n 33 | 34 | Returns: 35 | int: Random number of n digits 36 | """ 37 | 38 | if variable_size: 39 | n = random.randint(1, n) 40 | 41 | return int(''.join(random.choice(string.digits) for _ in range(n))) 42 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/utils/signatures.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | 4 | 5 | # Function to create a sha256 signature 6 | def create_sha256_signature(secret_key: str, params: str): 7 | 8 | # Encode the secret key 9 | byte_secret_key = bytearray() 10 | byte_secret_key.extend(map(ord, secret_key)) 11 | 12 | # Encode the params 13 | encoded_params = params.encode() 14 | 15 | # Create signature 16 | return hmac.new(byte_secret_key, encoded_params, hashlib.sha256)\ 17 | .hexdigest() 18 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/utils/stoppable_thread.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class StoppableThread(threading.Thread): 5 | """Thread class with a stop() method. The thread itself has to check 6 | regularly for the stopped() condition.""" 7 | 8 | def __init__(self, *args, **kwargs): 9 | super().__init__(*args, **kwargs) 10 | self._stop_event = threading.Event() 11 | self.done = False 12 | self._name = None 13 | 14 | @property 15 | def name(self): 16 | return self._name 17 | 18 | @name.setter 19 | def name(self, name): 20 | self._name = name 21 | 22 | def stop(self): 23 | self._stop_event.set() 24 | 25 | def stopped(self): 26 | return self._stop_event.is_set() 27 | -------------------------------------------------------------------------------- /investing_algorithm_framework/domain/utils/synchronized.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import threading 3 | 4 | 5 | def synchronized(function): 6 | lock = threading.Lock() 7 | 8 | @functools.wraps(function) 9 | def wrapper(self, *args, **kwargs): 10 | with lock: 11 | return function(self, *args, **kwargs) 12 | return wrapper 13 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/data_providers/__init__.py: -------------------------------------------------------------------------------- 1 | from .ccxt import CCXTDataProvider, CCXTOHLCVDataProvider 2 | 3 | 4 | def get_default_data_providers(): 5 | """ 6 | Function to get the default data providers. 7 | 8 | Returns: 9 | list: List of default data providers. 10 | """ 11 | return [ 12 | CCXTDataProvider(), 13 | ] 14 | 15 | 16 | def get_default_ohlcv_data_providers(): 17 | """ 18 | Function to get the default OHLCV data providers. 19 | 20 | Returns: 21 | list: List of default OHLCV data providers. 22 | """ 23 | return [ 24 | CCXTOHLCVDataProvider(), 25 | ] 26 | 27 | 28 | __all__ = [ 29 | 'CCXTDataProvider', 30 | 'CCXTOHLCVDataProvider', 31 | 'get_default_data_providers', 32 | 'get_default_ohlcv_data_providers' 33 | ] 34 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/database/__init__.py: -------------------------------------------------------------------------------- 1 | from .sql_alchemy import Session, setup_sqlalchemy, SQLBaseModel, \ 2 | create_all_tables 3 | 4 | __all__ = [ 5 | "Session", "setup_sqlalchemy", "SQLBaseModel", "create_all_tables" 6 | ] 7 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/database/sql_alchemy.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sqlalchemy import create_engine, StaticPool 4 | from sqlalchemy.orm import DeclarativeBase, sessionmaker 5 | 6 | from investing_algorithm_framework.domain import SQLALCHEMY_DATABASE_URI, \ 7 | OperationalException 8 | 9 | Session = sessionmaker() 10 | logger = logging.getLogger("investing_algorithm_framework") 11 | 12 | 13 | class SQLAlchemyAdapter: 14 | 15 | def __init__(self, app): 16 | self._app = app 17 | if SQLALCHEMY_DATABASE_URI not in app.config \ 18 | or app.config[SQLALCHEMY_DATABASE_URI] is None: 19 | raise OperationalException("SQLALCHEMY_DATABASE_URI not set") 20 | 21 | global Session 22 | engine = create_engine( 23 | app.config[SQLALCHEMY_DATABASE_URI], 24 | connect_args={'check_same_thread': False}, 25 | poolclass=StaticPool 26 | ) 27 | Session.configure(bind=engine) 28 | 29 | 30 | def setup_sqlalchemy(app, throw_exception_if_not_set=True): 31 | 32 | try: 33 | SQLAlchemyAdapter(app) 34 | except OperationalException as e: 35 | if throw_exception_if_not_set: 36 | raise e 37 | 38 | return app 39 | 40 | 41 | class SQLBaseModel(DeclarativeBase): 42 | pass 43 | 44 | 45 | def create_all_tables(): 46 | SQLBaseModel.metadata.create_all(bind=Session().bind) 47 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .market_data_sources import CCXTOrderBookMarketDataSource, \ 2 | CCXTTickerMarketDataSource, CCXTOHLCVMarketDataSource, \ 3 | CCXTOHLCVBacktestMarketDataSource, CSVOHLCVMarketDataSource, \ 4 | CSVTickerMarketDataSource 5 | from .order import SQLOrder, SQLOrderMetadata 6 | from .portfolio import SQLPortfolio, SQLPortfolioSnapshot 7 | from .position import SQLPosition, SQLPositionSnapshot 8 | from .trades import SQLTrade, SQLTradeStopLoss, SQLTradeTakeProfit 9 | 10 | __all__ = [ 11 | "SQLOrder", 12 | "SQLPosition", 13 | "SQLPortfolio", 14 | "SQLPositionSnapshot", 15 | "SQLPortfolioSnapshot", 16 | "CCXTOHLCVBacktestMarketDataSource", 17 | "CCXTOrderBookMarketDataSource", 18 | "CCXTTickerMarketDataSource", 19 | "CCXTOHLCVMarketDataSource", 20 | "CSVTickerMarketDataSource", 21 | "CSVOHLCVMarketDataSource", 22 | "SQLTrade", 23 | "SQLTradeStopLoss", 24 | "SQLTradeTakeProfit", 25 | "SQLOrderMetadata", 26 | ] 27 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/decimal_parser.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal, getcontext 2 | 3 | 4 | def count_number_of_decimals(value) -> int: 5 | value = str(value) 6 | if "." in value: 7 | return len(value) - value.index(".") - 1 8 | else: 9 | return 0 10 | 11 | 12 | def parse_decimal(value) -> Decimal: 13 | getcontext().prec = count_number_of_decimals(value) 14 | return Decimal(value) 15 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/market_data_sources/__init__.py: -------------------------------------------------------------------------------- 1 | from .ccxt import CCXTOrderBookMarketDataSource, CCXTTickerMarketDataSource, \ 2 | CCXTOHLCVMarketDataSource, CCXTOHLCVBacktestMarketDataSource 3 | from .csv import CSVOHLCVMarketDataSource, CSVTickerMarketDataSource 4 | 5 | __all__ = [ 6 | 'CCXTOrderBookMarketDataSource', 7 | 'CCXTTickerMarketDataSource', 8 | 'CCXTOHLCVMarketDataSource', 9 | "CCXTOHLCVBacktestMarketDataSource", 10 | "CSVOHLCVMarketDataSource", 11 | "CSVTickerMarketDataSource" 12 | ] 13 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/model_extension.py: -------------------------------------------------------------------------------- 1 | class SQLAlchemyModelExtension: 2 | 3 | def update(self, data): 4 | 5 | for attr, value in data.items(): 6 | setattr(self, attr, value) 7 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/order/__init__.py: -------------------------------------------------------------------------------- 1 | from .order import SQLOrder 2 | from .order_metadata import SQLOrderMetadata 3 | 4 | __all__ = ["SQLOrder", "SQLOrderMetadata"] 5 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/order/order_metadata.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from sqlalchemy import Column, Integer, ForeignKey, Float 4 | from sqlalchemy.orm import relationship 5 | 6 | from investing_algorithm_framework.infrastructure.database import SQLBaseModel 7 | from investing_algorithm_framework.infrastructure.models.model_extension \ 8 | import SQLAlchemyModelExtension 9 | 10 | logger = logging.getLogger("investing_algorithm_framework") 11 | 12 | 13 | class SQLOrderMetadata(SQLBaseModel, SQLAlchemyModelExtension): 14 | __tablename__ = "sql_order_metadata" 15 | id = Column(Integer, primary_key=True, unique=True) 16 | order_id = Column(Integer, ForeignKey('orders.id')) 17 | order = relationship('SQLOrder', back_populates='order_metadata') 18 | trade_id = Column(Integer) 19 | stop_loss_id = Column(Integer) 20 | take_profit_id = Column(Integer) 21 | amount = Column(Float) 22 | amount_pending = Column(Float) 23 | 24 | def __init__( 25 | self, 26 | order_id, 27 | amount, 28 | amount_pending, 29 | trade_id=None, 30 | stop_loss_id=None, 31 | take_profit_id=None, 32 | ): 33 | self.order_id = order_id 34 | self.trade_id = trade_id 35 | self.stop_loss_id = stop_loss_id 36 | self.take_profit_id = take_profit_id 37 | self.amount = amount 38 | self.amount_pending = amount_pending 39 | 40 | def __repr__(self): 41 | return f"" 45 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/order_trade_association.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Table, Column, Integer, ForeignKey 2 | from investing_algorithm_framework.infrastructure.database import SQLBaseModel 3 | 4 | # Association table 5 | order_trade_association = Table( 6 | 'order_trade', # Table name 7 | SQLBaseModel.metadata, 8 | Column('order_id', Integer, ForeignKey('orders.id'), primary_key=True), 9 | Column('trade_id', Integer, ForeignKey('trades.id'), primary_key=True) 10 | ) 11 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/portfolio/__init__.py: -------------------------------------------------------------------------------- 1 | from .sql_portfolio import SQLPortfolio 2 | from .portfolio_snapshot import SQLPortfolioSnapshot 3 | 4 | __all__ = ['SQLPortfolio', "SQLPortfolioSnapshot"] 5 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/portfolio/portfolio_snapshot.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String, DateTime, Float 2 | from sqlalchemy.orm import relationship 3 | 4 | from investing_algorithm_framework.domain import PortfolioSnapshot 5 | from investing_algorithm_framework.infrastructure.database import SQLBaseModel 6 | from investing_algorithm_framework.infrastructure.models.model_extension \ 7 | import SQLAlchemyModelExtension 8 | 9 | 10 | class SQLPortfolioSnapshot( 11 | PortfolioSnapshot, SQLBaseModel, SQLAlchemyModelExtension 12 | ): 13 | """ 14 | SQLAlchemy model for portfolio snapshots. 15 | 16 | Portfolio snapshots represent the state of a portfolio at a specific 17 | point in time. 18 | """ 19 | __tablename__ = "portfolio_snapshots" 20 | id = Column(Integer, primary_key=True) 21 | portfolio_id = Column(String, nullable=False) 22 | trading_symbol = Column(String, nullable=False) 23 | pending_value = Column(Float, nullable=False, default=0) 24 | unallocated = Column(Float, nullable=False, default=0) 25 | net_size = Column(Float, nullable=False, default=0) 26 | total_net_gain = Column(Float, nullable=False, default=0) 27 | total_revenue = Column(Float, nullable=False, default=0) 28 | total_cost = Column(Float, nullable=False, default=0) 29 | cash_flow = Column(Float, nullable=False, default=0) 30 | created_at = Column(DateTime, nullable=False, default=0) 31 | position_snapshots = relationship( 32 | "SQLPositionSnapshot", 33 | back_populates="portfolio_snapshot", 34 | lazy="dynamic", 35 | cascade="all,delete", 36 | ) 37 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/position/__init__.py: -------------------------------------------------------------------------------- 1 | from .position import SQLPosition 2 | from .position_snapshot import SQLPositionSnapshot 3 | 4 | __all__ = ["SQLPosition", "SQLPositionSnapshot"] 5 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/position/position_snapshot.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String, ForeignKey, Float 2 | from sqlalchemy.orm import relationship 3 | 4 | from investing_algorithm_framework.domain import PositionSnapshot 5 | from investing_algorithm_framework.infrastructure.database import SQLBaseModel 6 | from investing_algorithm_framework.infrastructure.models.model_extension \ 7 | import SQLAlchemyModelExtension 8 | 9 | 10 | class SQLPositionSnapshot( 11 | SQLBaseModel, PositionSnapshot, SQLAlchemyModelExtension 12 | ): 13 | __tablename__ = "position_snapshots" 14 | id = Column(Integer, primary_key=True, unique=True) 15 | symbol = Column(String) 16 | amount = Column(Float) 17 | cost = Column(Float) 18 | portfolio_snapshot_id = Column( 19 | Integer, ForeignKey('portfolio_snapshots.id') 20 | ) 21 | portfolio_snapshot = relationship( 22 | "SQLPortfolioSnapshot", back_populates="position_snapshots" 23 | ) 24 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/trades/__init__.py: -------------------------------------------------------------------------------- 1 | from .trade import SQLTrade 2 | from .trade_stop_loss import SQLTradeStopLoss 3 | from .trade_take_profit import SQLTradeTakeProfit 4 | 5 | __all__ = [ 6 | "SQLTrade", 7 | "SQLTradeStopLoss", 8 | "SQLTradeTakeProfit", 9 | ] 10 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/trades/trade_stop_loss.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String, Float, ForeignKey, Boolean 2 | from sqlalchemy.orm import relationship 3 | 4 | from investing_algorithm_framework.domain import TradeStopLoss 5 | from investing_algorithm_framework.infrastructure.database import SQLBaseModel 6 | from investing_algorithm_framework.infrastructure.models.model_extension \ 7 | import SQLAlchemyModelExtension 8 | 9 | 10 | class SQLTradeStopLoss(TradeStopLoss, SQLBaseModel, SQLAlchemyModelExtension): 11 | """ 12 | SQLTradeStopLoss model 13 | 14 | A trade stop loss is a stop loss strategy for a trade. 15 | 16 | Attributes: 17 | * trade: Trade - the trade that the take profit is for 18 | * take_profit: float - the take profit percentage 19 | * trade_risk_type: TradeRiskType - the type of trade risk, either 20 | trailing or fixed 21 | * sell_percentage: float - the percentage of the trade to sell when the 22 | 23 | """ 24 | 25 | __tablename__ = "trade_stop_losses" 26 | id = Column(Integer, primary_key=True, unique=True) 27 | trade_id = Column(Integer, ForeignKey('trades.id')) 28 | trade = relationship('SQLTrade', back_populates='stop_losses') 29 | trade_risk_type = Column(String) 30 | percentage = Column(Float) 31 | sell_percentage = Column(Float) 32 | open_price = Column(Float) 33 | high_water_mark = Column(Float) 34 | high_water_mark_date = Column(String) 35 | stop_loss_price = Column(Float) 36 | sell_prices = Column(String) 37 | sell_dates = Column(String) 38 | sell_amount = Column(Float) 39 | sold_amount = Column(Float) 40 | active = Column(Boolean) 41 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/models/trades/trade_take_profit.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String, Float, ForeignKey, Boolean 2 | from sqlalchemy.orm import relationship 3 | 4 | from investing_algorithm_framework.domain import TradeTakeProfit 5 | from investing_algorithm_framework.infrastructure.database import SQLBaseModel 6 | from investing_algorithm_framework.infrastructure.models.model_extension \ 7 | import SQLAlchemyModelExtension 8 | 9 | 10 | class SQLTradeTakeProfit( 11 | TradeTakeProfit, SQLBaseModel, SQLAlchemyModelExtension 12 | ): 13 | """ 14 | SQLTradeTakeProfit model 15 | 16 | A trade take profit is a take profit strategy for a trade. 17 | 18 | Attributes: 19 | * trade: Trade - the trade that the take profit is for 20 | * take_profit: float - the take profit percentage 21 | * trade_risk_type: TradeRiskType - the type of trade risk, either 22 | trailing or fixed 23 | * sell_percentage: float - the percentage of the trade to sell when the 24 | """ 25 | 26 | __tablename__ = "trade_take_profits" 27 | id = Column(Integer, primary_key=True, unique=True) 28 | trade_id = Column(Integer, ForeignKey('trades.id')) 29 | trade = relationship('SQLTrade', back_populates='take_profits') 30 | trade_risk_type = Column(String) 31 | percentage = Column(Float) 32 | sell_percentage = Column(Float) 33 | open_price = Column(Float) 34 | high_water_mark = Column(Float) 35 | high_water_mark_date = Column(String) 36 | sell_prices = Column(String) 37 | take_profit_price = Column(Float) 38 | sell_amount = Column(Float) 39 | sell_dates = Column(String) 40 | sold_amount = Column(Float) 41 | active = Column(Boolean) 42 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/order_executors/__init__.py: -------------------------------------------------------------------------------- 1 | from .ccxt_order_executor import CCXTOrderExecutor 2 | 3 | 4 | def get_default_order_executors(): 5 | """ 6 | Function to get the default order executors. 7 | 8 | Returns: 9 | list: List of default order executors. 10 | """ 11 | return [ 12 | CCXTOrderExecutor(), 13 | ] 14 | 15 | 16 | __all__ = [ 17 | 'CCXTOrderExecutor', 18 | 'get_default_order_executors', 19 | ] 20 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/portfolio_providers/__init__.py: -------------------------------------------------------------------------------- 1 | from .ccxt_portfolio_provider import CCXTPortfolioProvider 2 | 3 | 4 | def get_default_portfolio_providers(): 5 | """ 6 | Function to get the default portfolio providers. 7 | 8 | Returns: 9 | list: List of default portfolio providers. 10 | """ 11 | return [ 12 | CCXTPortfolioProvider(), 13 | ] 14 | 15 | 16 | __all__ = [ 17 | "CCXTPortfolioProvider", 18 | "get_default_portfolio_providers", 19 | ] 20 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/repositories/__init__.py: -------------------------------------------------------------------------------- 1 | from .order_repository import SQLOrderRepository 2 | from .order_metadata_repository import SQLOrderMetadataRepository 3 | from .portfolio_repository import SQLPortfolioRepository 4 | from .portfolio_snapshot_repository import SQLPortfolioSnapshotRepository 5 | from .position_repository import SQLPositionRepository 6 | from .position_snapshot_repository import SQLPositionSnapshotRepository 7 | from .trade_repository import SQLTradeRepository 8 | from .trade_stop_loss_repository import SQLTradeStopLossRepository 9 | from .trade_take_profit_repository import SQLTradeTakeProfitRepository 10 | 11 | __all__ = [ 12 | "SQLOrderRepository", 13 | "SQLPositionRepository", 14 | "SQLPositionSnapshotRepository", 15 | "SQLPortfolioRepository", 16 | "SQLPortfolioSnapshotRepository", 17 | "SQLTradeRepository", 18 | "SQLTradeTakeProfitRepository", 19 | "SQLTradeStopLossRepository", 20 | "SQLOrderMetadataRepository" 21 | ] 22 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/repositories/order_metadata_repository.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.infrastructure.models import \ 2 | SQLOrderMetadata 3 | from .repository import Repository 4 | 5 | 6 | class SQLOrderMetadataRepository(Repository): 7 | base_class = SQLOrderMetadata 8 | DEFAULT_NOT_FOUND_MESSAGE = "The requested order metadata was not found" 9 | 10 | def _apply_query_params(self, db, query, query_params): 11 | 12 | if "order_id" in query_params: 13 | query = query.filter( 14 | SQLOrderMetadata.order_id == query_params["order_id"] 15 | ) 16 | 17 | return query 18 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/repositories/portfolio_repository.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.infrastructure.models import SQLPortfolio, \ 2 | SQLPosition 3 | from .repository import Repository 4 | 5 | 6 | class SQLPortfolioRepository(Repository): 7 | base_class = SQLPortfolio 8 | DEFAULT_NOT_FOUND_MESSAGE = "Portfolio not found" 9 | 10 | def _apply_query_params(self, db, query, query_params): 11 | id_query_param = query_params.get("id") 12 | market_query_param = query_params.get("market") 13 | identifier_query_param = query_params.get("identifier") 14 | position_query_param = query_params.get("position") 15 | 16 | if id_query_param: 17 | query = query.filter_by(id=id_query_param) 18 | 19 | if market_query_param: 20 | query = query.filter_by(market=market_query_param.upper()) 21 | 22 | if identifier_query_param: 23 | query = query.filter_by(identifier=identifier_query_param.upper()) 24 | 25 | if position_query_param: 26 | position = db.query(SQLPosition)\ 27 | .filter_by(id=position_query_param).first() 28 | query = query.filter_by(id=position.portfolio_id) 29 | 30 | return query 31 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/repositories/position_snapshot_repository.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.infrastructure.models import \ 2 | SQLPositionSnapshot 3 | from .repository import Repository 4 | 5 | 6 | class SQLPositionSnapshotRepository(Repository): 7 | base_class = SQLPositionSnapshot 8 | DEFAULT_NOT_FOUND_MESSAGE = "Position snapshot not found" 9 | 10 | def _apply_query_params(self, db, query, query_params): 11 | portfolio_snapshot_query_param = self.get_query_param( 12 | "portfolio_snapshot", query_params 13 | ) 14 | 15 | if portfolio_snapshot_query_param is not None: 16 | query = query\ 17 | .filter_by( 18 | portfolio_snapshot_id=portfolio_snapshot_query_param 19 | ) 20 | 21 | return query 22 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/repositories/trade_stop_loss_repository.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from investing_algorithm_framework.infrastructure.models import \ 4 | SQLTradeStopLoss 5 | 6 | from .repository import Repository 7 | 8 | logger = logging.getLogger("investing_algorithm_framework") 9 | 10 | 11 | class SQLTradeStopLossRepository(Repository): 12 | base_class = SQLTradeStopLoss 13 | DEFAULT_NOT_FOUND_MESSAGE = "The requested trade stop loss was not found" 14 | 15 | def _apply_query_params(self, db, query, query_params): 16 | trade_query_param = self.get_query_param("trade_id", query_params) 17 | 18 | if trade_query_param: 19 | query = query.filter( 20 | SQLTradeStopLoss.trade_id == trade_query_param 21 | ) 22 | 23 | return query 24 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/repositories/trade_take_profit_repository.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from investing_algorithm_framework.infrastructure.models import \ 4 | SQLTradeTakeProfit 5 | 6 | from .repository import Repository 7 | 8 | logger = logging.getLogger("investing_algorithm_framework") 9 | 10 | 11 | class SQLTradeTakeProfitRepository(Repository): 12 | base_class = SQLTradeTakeProfit 13 | DEFAULT_NOT_FOUND_MESSAGE = "The requested trade take profit was not found" 14 | 15 | def _apply_query_params(self, db, query, query_params): 16 | trade_query_param = self.get_query_param("trade_id", query_params) 17 | 18 | if trade_query_param: 19 | query = query.filter( 20 | SQLTradeTakeProfit.trade_id == trade_query_param 21 | ) 22 | 23 | return query 24 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/services/__init__.py: -------------------------------------------------------------------------------- 1 | from .market_service import CCXTMarketService 2 | from .performance_service import PerformanceService 3 | from .azure import AzureBlobStorageStateHandler 4 | 5 | __all__ = [ 6 | "PerformanceService", 7 | "CCXTMarketService", 8 | "AzureBlobStorageStateHandler" 9 | ] 10 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/services/azure/__init__.py: -------------------------------------------------------------------------------- 1 | from .state_handler import AzureBlobStorageStateHandler 2 | 3 | __all__ = [ 4 | "AzureBlobStorageStateHandler" 5 | ] 6 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/services/market_service/__init__.py: -------------------------------------------------------------------------------- 1 | from .ccxt_market_service import CCXTMarketService 2 | 3 | __all__ = [ 4 | "CCXTMarketService", 5 | ] 6 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/services/performance_service/__init__.py: -------------------------------------------------------------------------------- 1 | from .backtest_performance_service import BacktestPerformanceService 2 | from .performance_service import PerformanceService 3 | 4 | __all__ = [ 5 | "PerformanceService", 6 | "BacktestPerformanceService" 7 | ] 8 | -------------------------------------------------------------------------------- /investing_algorithm_framework/infrastructure/services/performance_service/backtest_performance_service.py: -------------------------------------------------------------------------------- 1 | class BacktestPerformanceService: 2 | pass 3 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/__init__.py: -------------------------------------------------------------------------------- 1 | from .backtesting import BacktestService 2 | from .configuration_service import ConfigurationService 3 | from .market_credential_service import MarketCredentialService 4 | from .market_data_source_service import MarketDataSourceService, \ 5 | BacktestMarketDataSourceService, DataProviderService 6 | from .order_service import OrderService, OrderBacktestService, \ 7 | OrderExecutorLookup 8 | from .portfolios import PortfolioService, BacktestPortfolioService, \ 9 | PortfolioConfigurationService, PortfolioSyncService, \ 10 | PortfolioSnapshotService, PortfolioProviderLookup 11 | from .positions import PositionService, PositionSnapshotService 12 | from .repository_service import RepositoryService 13 | from .strategy_orchestrator_service import StrategyOrchestratorService 14 | from .trade_service import TradeService 15 | 16 | __all__ = [ 17 | "StrategyOrchestratorService", 18 | "OrderService", 19 | "RepositoryService", 20 | "PortfolioService", 21 | "PositionService", 22 | "PortfolioConfigurationService", 23 | "MarketDataSourceService", 24 | "BacktestService", 25 | "OrderBacktestService", 26 | "ConfigurationService", 27 | "PortfolioSyncService", 28 | "PortfolioSnapshotService", 29 | "PositionSnapshotService", 30 | "MarketCredentialService", 31 | "BacktestMarketDataSourceService", 32 | "BacktestPortfolioService", 33 | "TradeService", 34 | "DataProviderService", 35 | "OrderExecutorLookup", 36 | "PortfolioServiceV2", 37 | "PortfolioProviderLookup", 38 | ] 39 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/backtesting/__init__.py: -------------------------------------------------------------------------------- 1 | from .backtest_service import BacktestService 2 | 3 | __all__ = [ 4 | "BacktestService", 5 | ] 6 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/market_credential_service.py: -------------------------------------------------------------------------------- 1 | from typing import Union, List 2 | 3 | from investing_algorithm_framework.domain import MarketCredential 4 | 5 | 6 | class MarketCredentialService: 7 | """ 8 | Service to manage market credentials. 9 | 10 | This service is responsible for adding, retrieving, and 11 | initializing market credentials. 12 | """ 13 | def __init__(self): 14 | self._market_credentials = {} 15 | 16 | def add(self, market_data_credential: MarketCredential): 17 | self._market_credentials[market_data_credential.market.upper()] \ 18 | = market_data_credential 19 | 20 | def add_all(self, market_data_credentials: List[MarketCredential]): 21 | for market_data_credential in market_data_credentials: 22 | self.add(market_data_credential) 23 | 24 | def get(self, market) -> Union[MarketCredential, None]: 25 | 26 | if market.upper() not in self._market_credentials: 27 | return None 28 | 29 | return self._market_credentials[market.upper()] 30 | 31 | def get_all(self) -> List[MarketCredential]: 32 | return list(self._market_credentials.values()) 33 | 34 | def initialize(self): 35 | """ 36 | Initialize all market credentials. 37 | """ 38 | 39 | for market_credential in self.get_all(): 40 | market_credential.initialize() 41 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/market_data_source_service/__init__.py: -------------------------------------------------------------------------------- 1 | from .backtest_market_data_source_service import \ 2 | BacktestMarketDataSourceService 3 | from .market_data_source_service import MarketDataSourceService 4 | from .data_provider_service import DataProviderService 5 | 6 | __all__ = [ 7 | "MarketDataSourceService", 8 | "BacktestMarketDataSourceService", 9 | "DataProviderService" 10 | ] 11 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/order_service/__init__.py: -------------------------------------------------------------------------------- 1 | from .order_backtest_service import OrderBacktestService 2 | from .order_service import OrderService 3 | from .order_executor_lookup import OrderExecutorLookup 4 | 5 | __all__ = [ 6 | "OrderService", 7 | "OrderBacktestService", 8 | "OrderExecutorLookup", 9 | ] 10 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/portfolios/__init__.py: -------------------------------------------------------------------------------- 1 | from .backtest_portfolio_service import BacktestPortfolioService 2 | from .portfolio_configuration_service import PortfolioConfigurationService 3 | from .portfolio_service import PortfolioService 4 | from .portfolio_snapshot_service import PortfolioSnapshotService 5 | from .portfolio_sync_service import PortfolioSyncService 6 | from .portfolio_provider_lookup import PortfolioProviderLookup 7 | 8 | __all__ = [ 9 | "PortfolioConfigurationService", 10 | "PortfolioSyncService", 11 | "PortfolioSnapshotService", 12 | "PortfolioService", 13 | "PortfolioSnapshotService", 14 | "BacktestPortfolioService", 15 | "PortfolioProviderLookup" 16 | ] 17 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/positions/__init__.py: -------------------------------------------------------------------------------- 1 | from .position_service import PositionService 2 | from .position_snapshot_service import PositionSnapshotService 3 | 4 | __all__ = [ 5 | "PositionService", 6 | "PositionSnapshotService" 7 | ] 8 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/positions/position_snapshot_service.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.services.repository_service \ 2 | import RepositoryService 3 | 4 | 5 | class PositionSnapshotService(RepositoryService): 6 | 7 | def create_snapshot(self, portfolio_snapshot_id, position): 8 | self.create( 9 | { 10 | "portfolio_snapshot_id": portfolio_snapshot_id, 11 | "symbol": position.symbol, 12 | "amount": position.amount, 13 | "cost": position.cost, 14 | } 15 | ) 16 | 17 | def get_snapshots(self, portfolio_snapshot_id): 18 | pass 19 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/repository_service.py: -------------------------------------------------------------------------------- 1 | class RepositoryService: 2 | 3 | def __init__(self, repository): 4 | self.repository = repository 5 | 6 | def create(self, data): 7 | return self.repository.create(data) 8 | 9 | def get(self, object_id): 10 | return self.repository.get(object_id) 11 | 12 | def get_all(self, query_params=None): 13 | return self.repository.get_all(query_params) 14 | 15 | def update(self, object_id, data): 16 | return self.repository.update(object_id, data) 17 | 18 | def update_all(self, query_params, data): 19 | return self.repository.update_all(query_params, data) 20 | 21 | def delete(self, object_id): 22 | return self.repository.delete(object_id) 23 | 24 | def delete_all(self, query_params): 25 | return self.repository.delete_all(query_params) 26 | 27 | def find(self, query_params): 28 | return self.repository.find(query_params) 29 | 30 | def count(self, query_params=None): 31 | return self.repository.count(query_params) 32 | 33 | def exists(self, query_params): 34 | return self.repository.exists(query_params) 35 | 36 | def save(self, object): 37 | return self.repository.save(object) 38 | -------------------------------------------------------------------------------- /investing_algorithm_framework/services/trade_service/__init__.py: -------------------------------------------------------------------------------- 1 | from .trade_service import TradeService 2 | 3 | __all__ = ["TradeService"] 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "investing-algorithm-framework" 3 | version = "v6.6.1" 4 | description = "A framework for creating trading bots" 5 | authors = ["MDUYN"] 6 | readme = "README.md" 7 | exclude = ["tests", "static", "examples", "docs"] 8 | 9 | 10 | [tool.poetry.dependencies] 11 | python = ">=3.10" 12 | wrapt = ">=1.16.0" 13 | Flask = ">=3.1.0" 14 | Flask-Migrate = ">=2.6.0" 15 | Flask-Cors = ">=3.0.9,<5.0.0" 16 | SQLAlchemy = ">=2.0.18" 17 | marshmallow = ">=3.5.0" 18 | ccxt = ">=4.2.48" 19 | python-dateutil = ">=2.8.2" 20 | dependency-injector= ">=4.40.0" 21 | schedule = ">=1.1.0" 22 | tqdm = ">=4.66.1" 23 | tabulate = ">=0.9.0" 24 | polars = { version = ">=0.20.10", extras = ["numpy", "pandas"] } 25 | jupyter = ">=1.0.0" 26 | azure-storage-blob = "^12.24.0" 27 | azure-identity = "^1.19.0" 28 | azure-mgmt-storage = "^21.2.1" 29 | azure-mgmt-web = "^7.3.1" 30 | azure-mgmt-resource = "^23.2.0" 31 | python-dotenv = "^1.0.1" 32 | pyarrow = ">=19.0.1" 33 | yfinance = "^0.2.61" 34 | 35 | [tool.poetry.group.test.dependencies] 36 | coverage= "7.4.2" 37 | flake8 = "7.0.0" 38 | Flask-Testing = "^0.8.1" 39 | pyindicators = "0.9.2" 40 | 41 | [tool.poetry.group.dev.dependencies] 42 | ipykernel = "^6.29.5" 43 | 44 | [build-system] 45 | requires = ["poetry-core"] 46 | build-backend = "poetry.core.masonry.api" 47 | 48 | [tool.poetry.scripts] 49 | investing-algorithm-framework = "investing_algorithm_framework.cli.cli:cli" 50 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/__init__.py -------------------------------------------------------------------------------- /tests/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/__init__.py -------------------------------------------------------------------------------- /tests/app/algorithm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/algorithm/__init__.py -------------------------------------------------------------------------------- /tests/app/algorithm/test_check_order_status.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | OrderStatus, MarketCredential 5 | from tests.resources import TestBase, MarketDataSourceServiceStub 6 | 7 | 8 | class Test(TestBase): 9 | portfolio_configurations = [ 10 | PortfolioConfiguration( 11 | market="BITVAVO", 12 | trading_symbol="EUR" 13 | ) 14 | ] 15 | market_credentials = [ 16 | MarketCredential( 17 | market="BITVAVO", 18 | api_key="api_key", 19 | secret_key="secret_key" 20 | ) 21 | ] 22 | external_balances = { 23 | "EUR": 1000, 24 | } 25 | 26 | def test_check_order_status(self): 27 | order_repository = self.app.container.order_repository() 28 | position_repository = self.app.container.position_repository() 29 | self.app.context.create_limit_order( 30 | target_symbol="BTC", 31 | amount=1, 32 | price=10, 33 | order_side="BUY", 34 | ) 35 | self.assertEqual(1, order_repository.count()) 36 | self.assertEqual(2, position_repository.count()) 37 | self.app.context.order_service.check_pending_orders() 38 | self.assertEqual(1, order_repository.count()) 39 | self.assertEqual(2, position_repository.count()) 40 | order = order_repository.find({"target_symbol": "BTC"}) 41 | self.assertEqual(OrderStatus.CLOSED.value, order.status) 42 | position = position_repository.find({"symbol": "BTC"}) 43 | self.assertEqual(1, position.get_amount()) 44 | -------------------------------------------------------------------------------- /tests/app/algorithm/test_get_order.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | OrderStatus, MarketCredential 5 | from tests.resources import TestBase, MarketDataSourceServiceStub 6 | 7 | 8 | class Test(TestBase): 9 | external_balances = { 10 | "EUR": 1000 11 | } 12 | external_available_symbols = ["BTC/EUR"] 13 | portfolio_configurations = [ 14 | PortfolioConfiguration( 15 | market="BITVAVO", 16 | trading_symbol="EUR" 17 | ) 18 | ] 19 | market_credentials = [ 20 | MarketCredential( 21 | market="bitvavo", 22 | api_key="api_key", 23 | secret_key="secret_key" 24 | ) 25 | ] 26 | 27 | def test_create_limit_buy_order_with_percentage_of_portfolio(self): 28 | order = self.app.context.create_limit_order( 29 | target_symbol="BTC", 30 | price=10, 31 | order_side="BUY", 32 | amount=20 33 | ) 34 | self.assertEqual(OrderStatus.OPEN.value, order.status) 35 | self.assertEqual(Decimal(10), order.get_price()) 36 | self.assertEqual(Decimal(20), order.get_amount()) 37 | -------------------------------------------------------------------------------- /tests/app/algorithm/test_get_portfolio.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | MarketCredential 5 | from tests.resources import TestBase, MarketDataSourceServiceStub 6 | 7 | 8 | class Test(TestBase): 9 | external_balances = { 10 | "EUR": 1000 11 | } 12 | external_available_symbols = ["BTC/EUR"] 13 | portfolio_configurations = [ 14 | PortfolioConfiguration( 15 | market="BITVAVO", 16 | trading_symbol="EUR" 17 | ) 18 | ] 19 | market_credentials = [ 20 | MarketCredential( 21 | market="bitvavo", 22 | api_key="api_key", 23 | secret_key="secret_key" 24 | ) 25 | ] 26 | 27 | def test_get_portfolio(self): 28 | portfolio = self.app.context.get_portfolio() 29 | self.assertEqual(Decimal(1000), portfolio.get_unallocated()) 30 | -------------------------------------------------------------------------------- /tests/app/algorithm/test_get_position.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | MarketCredential 5 | from tests.resources import TestBase, MarketDataSourceServiceStub 6 | 7 | 8 | class Test(TestBase): 9 | external_balances = { 10 | "EUR": 1000 11 | } 12 | external_available_symbols = ["BTC/EUR"] 13 | portfolio_configurations = [ 14 | PortfolioConfiguration( 15 | market="BITVAVO", 16 | trading_symbol="EUR" 17 | ) 18 | ] 19 | market_credentials = [ 20 | MarketCredential( 21 | market="bitvavo", 22 | api_key="api_key", 23 | secret_key="secret_key" 24 | ) 25 | ] 26 | 27 | def test_get_position(self): 28 | trading_symbol_position = self.app.context.get_position("EUR") 29 | self.assertEqual(Decimal(1000), trading_symbol_position.get_amount()) 30 | self.app.context.create_limit_order( 31 | target_symbol="BTC", 32 | amount=1, 33 | price=10, 34 | order_side="BUY", 35 | ) 36 | btc_position = self.app.context.get_position("BTC") 37 | self.assertIsNotNone(btc_position) 38 | self.assertEqual(Decimal(0), btc_position.get_amount()) 39 | order_service = self.app.container.order_service() 40 | order_service.check_pending_orders() 41 | btc_position = self.app.context.get_position("BTC") 42 | self.assertIsNotNone(btc_position.get_amount()) 43 | self.assertEqual(Decimal(1), btc_position.get_amount()) 44 | self.assertNotEqual(Decimal(990), trading_symbol_position.get_amount()) 45 | -------------------------------------------------------------------------------- /tests/app/algorithm/test_get_unallocated.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | MarketCredential 5 | from tests.resources import TestBase, MarketDataSourceServiceStub 6 | 7 | 8 | class Test(TestBase): 9 | external_balances = { 10 | "EUR": 1000 11 | } 12 | external_available_symbols = ["BTC/EUR"] 13 | portfolio_configurations = [ 14 | PortfolioConfiguration( 15 | market="BITVAVO", 16 | trading_symbol="EUR" 17 | ) 18 | ] 19 | market_credentials = [ 20 | MarketCredential( 21 | market="bitvavo", 22 | api_key="api_key", 23 | secret_key="secret_key" 24 | ) 25 | ] 26 | 27 | def test_create_limit_buy_order_with_percentage_of_portfolio(self): 28 | portfolio = self.app.context.get_portfolio() 29 | self.assertEqual(Decimal(1000), portfolio.get_unallocated()) 30 | -------------------------------------------------------------------------------- /tests/app/algorithm/test_has_open_buy_orders.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | MarketCredential 5 | from tests.resources import TestBase, MarketDataSourceServiceStub 6 | 7 | 8 | class Test(TestBase): 9 | portfolio_configurations = [ 10 | PortfolioConfiguration( 11 | market="binance", 12 | trading_symbol="EUR", 13 | initial_balance=1000, 14 | ) 15 | ] 16 | market_credentials = [ 17 | MarketCredential( 18 | market="binance", 19 | api_key="api_key", 20 | secret_key="secret_key", 21 | ) 22 | ] 23 | external_orders = [] 24 | external_balances = { 25 | "EUR": 1000, 26 | } 27 | 28 | def test_has_open_buy_orders(self): 29 | trading_symbol_position = self.app.context.get_position("EUR") 30 | self.assertEqual(Decimal(1000), trading_symbol_position.get_amount()) 31 | order = self.app.context.create_limit_order( 32 | target_symbol="BTC", 33 | amount=1, 34 | price=10, 35 | order_side="BUY", 36 | ) 37 | order_service = self.app.container.order_service() 38 | order = order_service.find({"symbol": "BTC/EUR"}) 39 | position_service = self.app.container.position_service() 40 | position = position_service.find({"symbol": "BTC"}) 41 | self.assertTrue(self.app.context.has_open_buy_orders("BTC")) 42 | order_service.check_pending_orders() 43 | self.assertFalse(self.app.context.has_open_buy_orders("BTC")) 44 | -------------------------------------------------------------------------------- /tests/app/algorithm/test_has_open_sell_orders.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import PortfolioConfiguration, \ 2 | MarketCredential 3 | from tests.resources import TestBase 4 | 5 | 6 | class Test(TestBase): 7 | portfolio_configurations = [ 8 | PortfolioConfiguration( 9 | market="binance", 10 | trading_symbol="EUR", 11 | initial_balance=1000, 12 | ) 13 | ] 14 | market_credentials = [ 15 | MarketCredential( 16 | market="binance", 17 | api_key="api_key", 18 | secret_key="secret_key", 19 | ) 20 | ] 21 | external_available_symbols = ["BTC/EUR", "DOT/EUR", "ADA/EUR", "ETH/EUR"] 22 | external_balances = { 23 | "EUR": 1000, 24 | } 25 | 26 | def test_has_open_sell_orders(self): 27 | trading_symbol_position = self.app.context.get_position("EUR") 28 | self.assertEqual(1000, trading_symbol_position.get_amount()) 29 | self.assertFalse(self.app.context.position_exists(symbol="BTC")) 30 | self.app.context.create_limit_order( 31 | target_symbol="BTC", 32 | amount=1, 33 | price=10, 34 | order_side="BUY", 35 | ) 36 | order_service = self.app.container.order_service() 37 | order_service.check_pending_orders() 38 | self.app.context.create_limit_order( 39 | target_symbol="BTC", 40 | amount=1, 41 | price=10, 42 | order_side="SELL", 43 | ) 44 | self.assertTrue(self.app.context.has_open_sell_orders("BTC")) 45 | order_service.check_pending_orders() 46 | self.assertFalse(self.app.context.has_open_sell_orders("BTC")) 47 | -------------------------------------------------------------------------------- /tests/app/algorithm/test_name.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import PortfolioConfiguration, \ 2 | MarketCredential 3 | 4 | from tests.resources import TestBase 5 | 6 | class Test(TestBase): 7 | portfolio_configurations = [ 8 | PortfolioConfiguration( 9 | market="binance", 10 | trading_symbol="EUR", 11 | initial_balance=1000, 12 | ) 13 | ] 14 | market_credentials = [ 15 | MarketCredential( 16 | market="binance", 17 | api_key="api_key", 18 | secret_key="secret_key", 19 | ) 20 | ] 21 | external_available_symbols = ["BTC/EUR", "DOT/EUR", "ADA/EUR", "ETH/EUR"] 22 | external_balances = { 23 | "EUR": 1000, 24 | } 25 | 26 | def test_name_containing_illegal_characters(self): 27 | with self.assertRaises(Exception) as context: 28 | self.app.algorithm.name = "v1/v2" 29 | 30 | self.assertTrue( 31 | "The name of the algorithm can only contain letters and numbers",str(context.exception) 32 | ) 33 | -------------------------------------------------------------------------------- /tests/app/backtesting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/backtesting/__init__.py -------------------------------------------------------------------------------- /tests/app/metrics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/metrics/__init__.py -------------------------------------------------------------------------------- /tests/app/metrics/test_price_efficiency.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from datetime import datetime 3 | import pandas as pd 4 | 5 | from investing_algorithm_framework.app.metrics.price_efficiency import \ 6 | get_price_efficiency_ratio 7 | 8 | class TestGetPriceEfficiencyRatio(TestCase): 9 | 10 | def test_get_price_efficiency_ratio(self): 11 | # Given 12 | data = { 13 | 'DateTime': [ 14 | datetime(2021, 1, 1), 15 | datetime(2021, 1, 2), 16 | datetime(2021, 1, 3), 17 | datetime(2021, 1, 4), 18 | datetime(2021, 1, 5) 19 | ], 20 | 'Close': [100, 102, 90, 105, 110] 21 | } 22 | df = pd.DataFrame(data) 23 | df.set_index('DateTime', inplace=True) 24 | 25 | # When 26 | result = get_price_efficiency_ratio(df) 27 | 28 | # Then 29 | self.assertEqual(result, 0.29411764705882354) 30 | 31 | # Given 32 | data = { 33 | 'DateTime': [ 34 | datetime(2021, 1, 1), 35 | datetime(2021, 1, 2), 36 | datetime(2021, 1, 3), 37 | datetime(2021, 1, 4), 38 | datetime(2021, 1, 5) 39 | ], 40 | 'Close': [100, 102, 101, 105, 110] 41 | } 42 | df = pd.DataFrame(data) 43 | df.set_index('DateTime', inplace=True) 44 | 45 | # When 46 | result = get_price_efficiency_ratio(df) 47 | 48 | # Then 49 | self.assertEqual(result, 0.8333333333333334) 50 | -------------------------------------------------------------------------------- /tests/app/test_add_config.py: -------------------------------------------------------------------------------- 1 | # import os 2 | # from unittest import TestCase 3 | 4 | # from investing_algorithm_framework import create_app, RESOURCE_DIRECTORY 5 | 6 | 7 | # class Test(TestCase): 8 | 9 | # def setUp(self) -> None: 10 | # self.resource_dir = os.path.abspath( 11 | # os.path.join( 12 | # os.path.join( 13 | # os.path.join( 14 | # os.path.realpath(__file__), 15 | # os.pardir 16 | # ), 17 | # os.pardir 18 | # ), 19 | # "resources" 20 | # ) 21 | # ) 22 | 23 | # def test_add(self): 24 | # app = create_app( 25 | # config={"test": "test", RESOURCE_DIRECTORY: self.resource_dir} 26 | # ) 27 | # self.assertIsNotNone(app.config) 28 | # self.assertIsNotNone(app.config.get("test")) 29 | # self.assertIsNotNone(app.config.get(RESOURCE_DIRECTORY)) 30 | -------------------------------------------------------------------------------- /tests/app/test_add_portfolio_configuration.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import PortfolioConfiguration, \ 2 | MarketCredential 3 | from tests.resources import TestBase 4 | 5 | 6 | class Test(TestBase): 7 | portfolio_configurations = [ 8 | PortfolioConfiguration( 9 | market="BITVAVO", 10 | trading_symbol="EUR" 11 | ) 12 | ] 13 | market_credentials = [ 14 | MarketCredential( 15 | market="BITVAVO", 16 | api_key="api_key", 17 | secret_key="secret_key" 18 | ) 19 | ] 20 | external_balances = { 21 | "EUR": 1000, 22 | } 23 | 24 | def test_add(self): 25 | self.assertEqual(1, self.app.context.portfolio_service.count()) 26 | self.assertEqual(1, self.app.context.position_service.count()) 27 | self.assertEqual(1000, self.app.context.get_unallocated()) 28 | 29 | # Make sure that the portfolio is initialized 30 | portfolio = self.app.context.get_portfolio() 31 | self.assertTrue(portfolio.initialized) 32 | -------------------------------------------------------------------------------- /tests/app/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/web/__init__.py -------------------------------------------------------------------------------- /tests/app/web/controllers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/web/controllers/__init__.py -------------------------------------------------------------------------------- /tests/app/web/controllers/order_controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/web/controllers/order_controller/__init__.py -------------------------------------------------------------------------------- /tests/app/web/controllers/order_controller/test_list_orders.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | MarketCredential 5 | from tests.resources import FlaskTestBase 6 | 7 | 8 | class Test(FlaskTestBase): 9 | portfolio_configurations = [ 10 | PortfolioConfiguration( 11 | market="BITVAVO", 12 | trading_symbol="EUR" 13 | ) 14 | ] 15 | market_credentials = [ 16 | MarketCredential( 17 | market="BITVAVO", 18 | api_key="", 19 | secret_key="" 20 | ) 21 | ] 22 | external_balances = { 23 | "EUR": 1000 24 | } 25 | 26 | def test_list_portfolios(self): 27 | self.iaf_app.run(number_of_iterations=1) 28 | response = self.client.get("/api/portfolios") 29 | data = json.loads(response.data.decode()) 30 | self.assertEqual(200, response.status_code) 31 | self.assertEqual(1, len(data["items"])) 32 | -------------------------------------------------------------------------------- /tests/app/web/controllers/portfolio_controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/web/controllers/portfolio_controller/__init__.py -------------------------------------------------------------------------------- /tests/app/web/controllers/portfolio_controller/test_list_portfolio.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from investing_algorithm_framework import MarketCredential, \ 4 | PortfolioConfiguration 5 | from tests.resources import FlaskTestBase 6 | 7 | 8 | class Test(FlaskTestBase): 9 | portfolio_configurations = [ 10 | PortfolioConfiguration( 11 | market="BITVAVO", 12 | trading_symbol="EUR" 13 | ) 14 | ] 15 | market_credentials = [ 16 | MarketCredential( 17 | market="BITVAVO", 18 | api_key="", 19 | secret_key="" 20 | ) 21 | ] 22 | external_balances = { 23 | "EUR": 1000 24 | } 25 | 26 | def test_list_portfolios(self): 27 | self.iaf_app.context.create_limit_order( 28 | target_symbol="KSM", 29 | price=10, 30 | amount=10, 31 | order_side="BUY" 32 | ) 33 | order_repository = self.iaf_app.container.order_repository() 34 | self.iaf_app.run(number_of_iterations=1) 35 | self.assertEqual(1, order_repository.count()) 36 | response = self.client.get("api/portfolios") 37 | data = json.loads(response.data.decode()) 38 | self.assertEqual(200, response.status_code) 39 | self.assertEqual(1, len(data["items"])) 40 | self.assertEqual(1, data["items"][0]["orders"]) 41 | self.assertEqual(2, data["items"][0]["positions"]) 42 | -------------------------------------------------------------------------------- /tests/app/web/controllers/position_controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/app/web/controllers/position_controller/__init__.py -------------------------------------------------------------------------------- /tests/app/web/controllers/position_controller/test_list_positions.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from investing_algorithm_framework import PortfolioConfiguration, \ 4 | MarketCredential 5 | from tests.resources import FlaskTestBase 6 | 7 | 8 | class Test(FlaskTestBase): 9 | portfolio_configurations = [ 10 | PortfolioConfiguration( 11 | market="BITVAVO", 12 | trading_symbol="EUR" 13 | ) 14 | ] 15 | market_credentials = [ 16 | MarketCredential( 17 | market="BITVAVO", 18 | api_key="", 19 | secret_key="" 20 | ) 21 | ] 22 | external_balances = { 23 | "EUR": 1000 24 | } 25 | 26 | def test_list_portfolios(self): 27 | self.iaf_app.context.create_limit_order( 28 | amount=10, 29 | target_symbol="KSM", 30 | price=10, 31 | order_side="BUY" 32 | ) 33 | order_repository = self.iaf_app.container.order_repository() 34 | self.iaf_app.run(number_of_iterations=1) 35 | self.assertEqual(1, order_repository.count()) 36 | response = self.client.get("/api/positions") 37 | data = json.loads(response.data.decode()) 38 | self.assertEqual(200, response.status_code) 39 | self.assertEqual(2, len(data["items"])) 40 | -------------------------------------------------------------------------------- /tests/cli/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/cli/__init__.py -------------------------------------------------------------------------------- /tests/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/__init__.py -------------------------------------------------------------------------------- /tests/domain/backtesting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/backtesting/__init__.py -------------------------------------------------------------------------------- /tests/domain/backtesting/test_pretty_print_backtest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework.domain import pretty_print_backtest, \ 5 | load_backtest_report 6 | 7 | 8 | class Test(TestCase): 9 | 10 | def setUp(self): 11 | self.resource_dir = os.path.abspath( 12 | os.path.join( 13 | os.path.join( 14 | os.path.join( 15 | os.path.join( 16 | os.path.realpath(__file__), 17 | os.pardir 18 | ), 19 | os.pardir 20 | ), 21 | os.pardir 22 | ), 23 | "resources" 24 | ) 25 | ) 26 | 27 | def test_pretty_print(self): 28 | path = os.path.join( 29 | self.resource_dir, 30 | "backtest_reports_for_testing/test_algorithm_backtest_created-at_2025-04-21-21-21" 31 | ) 32 | report = load_backtest_report(path) 33 | pretty_print_backtest(report) 34 | -------------------------------------------------------------------------------- /tests/domain/backtesting/test_pretty_print_backtest_orders.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework.domain import pretty_print_orders, \ 5 | load_backtest_report 6 | 7 | 8 | class Test(TestCase): 9 | 10 | def setUp(self): 11 | self.resource_dir = os.path.abspath( 12 | os.path.join( 13 | os.path.join( 14 | os.path.join( 15 | os.path.join( 16 | os.path.realpath(__file__), 17 | os.pardir 18 | ), 19 | os.pardir 20 | ), 21 | os.pardir 22 | ), 23 | "resources" 24 | ) 25 | ) 26 | 27 | def test_pretty_print(self): 28 | path = os.path.join( 29 | self.resource_dir, 30 | "backtest_reports_for_testing/test_algorithm_backtest_created-at_2025-04-21-21-21" 31 | ) 32 | report = load_backtest_report(path) 33 | pretty_print_orders(report) 34 | -------------------------------------------------------------------------------- /tests/domain/backtesting/test_pretty_print_backtest_trades.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework.domain import pretty_print_trades, \ 5 | load_backtest_report 6 | 7 | 8 | class Test(TestCase): 9 | 10 | def setUp(self): 11 | self.resource_dir = os.path.abspath( 12 | os.path.join( 13 | os.path.join( 14 | os.path.join( 15 | os.path.join( 16 | os.path.realpath(__file__), 17 | os.pardir 18 | ), 19 | os.pardir 20 | ), 21 | os.pardir 22 | ), 23 | "resources" 24 | ) 25 | ) 26 | 27 | def test_pretty_print(self): 28 | path = os.path.join( 29 | self.resource_dir, 30 | "backtest_reports_for_testing/test_algorithm_backtest_created-at_2025-04-21-21-21" 31 | ) 32 | report = load_backtest_report(path) 33 | pretty_print_trades(report) 34 | -------------------------------------------------------------------------------- /tests/domain/backtesting/test_pretty_print_backtests_evaluation.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework.domain import BacktestReportsEvaluation, \ 5 | pretty_print_backtest_reports_evaluation, load_backtest_reports 6 | 7 | 8 | class Test(TestCase): 9 | 10 | def setUp(self): 11 | self.resource_dir = os.path.abspath( 12 | os.path.join( 13 | os.path.join( 14 | os.path.join( 15 | os.path.join( 16 | os.path.realpath(__file__), 17 | os.pardir 18 | ), 19 | os.pardir 20 | ), 21 | os.pardir 22 | ), 23 | "resources" 24 | ) 25 | ) 26 | 27 | def test_pretty_print(self): 28 | path = os.path.join(self.resource_dir, "backtest_reports_for_testing") 29 | reports = load_backtest_reports(path) 30 | evaluation = BacktestReportsEvaluation(reports) 31 | pretty_print_backtest_reports_evaluation(evaluation) 32 | -------------------------------------------------------------------------------- /tests/domain/metrics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/metrics/__init__.py -------------------------------------------------------------------------------- /tests/domain/metrics/test_get_price_efficiency_ratio.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/metrics/test_get_price_efficiency_ratio.py -------------------------------------------------------------------------------- /tests/domain/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/models/__init__.py -------------------------------------------------------------------------------- /tests/domain/models/test_backtest_date_range.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from datetime import datetime, timedelta 3 | 4 | from investing_algorithm_framework.domain import BacktestDateRange 5 | 6 | 7 | class Test(TestCase): 8 | 9 | def test_with_datetime(self): 10 | end_date = datetime.utcnow() 11 | start_date = end_date - timedelta(days=10) 12 | date_range = BacktestDateRange( 13 | name="test", 14 | start_date=start_date, 15 | end_date=end_date 16 | ) 17 | self.assertEqual(start_date, date_range.start_date) 18 | self.assertEqual(end_date, date_range.end_date) 19 | self.assertEqual("test", date_range.name) 20 | 21 | def test_with_string(self): 22 | date_range = BacktestDateRange( 23 | name="string based", 24 | start_date="2022-01-01", 25 | end_date="2022-03-01" 26 | ) 27 | self.assertEqual( 28 | datetime(year=2022, day=1, month=1), date_range.start_date 29 | ) 30 | self.assertEqual( 31 | datetime(year=2022, day=1, month=3), date_range.end_date 32 | ) 33 | self.assertEqual("string based", date_range.name) 34 | -------------------------------------------------------------------------------- /tests/domain/models/test_backtest_reports_evaluation.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework import load_backtest_reports, \ 5 | BacktestReportsEvaluation 6 | 7 | 8 | class Test(TestCase): 9 | 10 | def setUp(self) -> None: 11 | self.resource_dir = os.path.abspath( 12 | os.path.join( 13 | os.path.join( 14 | os.path.join( 15 | os.path.join( 16 | os.path.realpath(__file__), 17 | os.pardir 18 | ), 19 | os.pardir 20 | ), 21 | os.pardir 22 | ), 23 | "resources" 24 | ) 25 | ) 26 | 27 | def test_backtest_reports_evaluation(self): 28 | path = os.path.join(self.resource_dir, "backtest_reports_for_testing") 29 | reports = load_backtest_reports(path) 30 | evaluation = BacktestReportsEvaluation(reports) 31 | self.assertEqual(len(evaluation.backtest_reports), 3) 32 | -------------------------------------------------------------------------------- /tests/domain/models/test_order.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework.domain import Order 5 | 6 | 7 | class Test(TestCase): 8 | 9 | def test_from_ccxt_order(self): 10 | order = Order.from_ccxt_order( 11 | { 12 | "id": "123", 13 | "symbol": "BTC/USDT", 14 | "side": "buy", 15 | "type": "limit", 16 | "price": 10000, 17 | "amount": 1, 18 | "cost": 10000, 19 | "filled": 1, 20 | "remaining": 0, 21 | "status": "open", 22 | "timestamp": 1600000000000, 23 | "datetime": '2017-08-17 12:42:48.000', 24 | "lastTradeTimestamp": 1600000000000, 25 | "fee": { 26 | "cost": 0.1, 27 | "currency": "USDT" 28 | } 29 | } 30 | ) 31 | self.assertEqual(order.get_external_id(), "123") 32 | self.assertEqual(order.get_symbol(), "BTC/USDT") 33 | self.assertEqual(order.get_order_side(), "BUY") 34 | self.assertEqual(order.get_order_type(), "LIMIT") 35 | self.assertEqual(order.get_price(), 10000) 36 | self.assertEqual(order.get_amount(), 1) 37 | self.assertEqual(order.get_filled(), 1) 38 | self.assertEqual(order.get_remaining(), 0) 39 | self.assertEqual(order.get_status(), "OPEN") 40 | self.assertEqual( 41 | order.get_created_at(), datetime(2017, 8, 17, 12, 42, 48) 42 | ) 43 | -------------------------------------------------------------------------------- /tests/domain/models/test_portfolio.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/models/test_portfolio.py -------------------------------------------------------------------------------- /tests/domain/models/test_portfolio_configuration.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework.domain import PortfolioConfiguration 5 | 6 | 7 | class Test(TestCase): 8 | 9 | def test_portfolio_configuration(self): 10 | portfolio_configuration = PortfolioConfiguration( 11 | track_from="01/01/2022", 12 | trading_symbol="USDT", 13 | identifier="test", 14 | market="BINANCE", 15 | initial_balance=400 16 | ) 17 | self.assertIsNotNone(portfolio_configuration.trading_symbol) 18 | self.assertIsNotNone(portfolio_configuration.identifier) 19 | self.assertIsNotNone(portfolio_configuration.market) 20 | self.assertIsNotNone(portfolio_configuration.track_from) 21 | self.assertIsNotNone(portfolio_configuration.track_from) 22 | self.assertIsInstance(portfolio_configuration.track_from, datetime) 23 | self.assertEqual(portfolio_configuration.initial_balance, 400) 24 | -------------------------------------------------------------------------------- /tests/domain/models/test_position.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/models/test_position.py -------------------------------------------------------------------------------- /tests/domain/models/trades/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/models/trades/__init__.py -------------------------------------------------------------------------------- /tests/domain/test_decimal_parsing.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework.domain import \ 5 | parse_string_to_decimal, parse_decimal_to_string 6 | 7 | 8 | class Test(TestCase): 9 | 10 | def test_decimal_to_string(self): 11 | # Create a string value of a decimal 12 | decimal_value = Decimal('97.17312522036036245646') 13 | string_value = parse_decimal_to_string(decimal_value) 14 | self.assertEqual(string_value, '97.17312522036036245646') 15 | 16 | def test_string_to_decimal(self): 17 | parse_string_to_decimal('97.17312522036036245646') 18 | parse_string_to_decimal('2004.5303357979318') 19 | -------------------------------------------------------------------------------- /tests/domain/test_load_backtest_reports.py: -------------------------------------------------------------------------------- 1 | import os 2 | from unittest import TestCase 3 | 4 | from investing_algorithm_framework import load_backtest_reports 5 | 6 | 7 | class Test(TestCase): 8 | 9 | def setUp(self) -> None: 10 | self.resource_dir = os.path.abspath( 11 | os.path.join( 12 | os.path.join( 13 | os.path.join( 14 | os.path.realpath(__file__), 15 | os.pardir 16 | ), 17 | os.pardir 18 | ), 19 | "resources" 20 | ) 21 | ) 22 | 23 | def test_backtest_reports_evaluation(self): 24 | path = os.path.join(self.resource_dir, "backtest_reports_for_testing") 25 | reports = load_backtest_reports(path) 26 | -------------------------------------------------------------------------------- /tests/domain/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/domain/utils/__init__.py -------------------------------------------------------------------------------- /tests/domain/utils/test_polars.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | from polars import DataFrame 3 | from pandas import Timestamp 4 | from investing_algorithm_framework import convert_polars_to_pandas 5 | 6 | class TestConvertPandasToPolars(TestCase): 7 | 8 | def test_convert_pandas_to_polars(self): 9 | polars_df = DataFrame({ 10 | "Datetime": ["2021-01-01", "2021-01-02", "2021-01-03"], 11 | "Close": [1, 2, 3] 12 | }) 13 | 14 | polars_df_converted = convert_polars_to_pandas(polars_df) 15 | self.assertEqual(polars_df_converted.shape, (3, 2)) 16 | 17 | # Check if the columns are as expected 18 | column_names = polars_df_converted.columns.tolist() 19 | self.assertEqual(column_names, ['Close', 'Datetime']) 20 | 21 | # Check if the index is a datetime object 22 | self.assertEqual(polars_df_converted.index.dtype, "datetime64[ns]") 23 | self.assertEqual(polars_df_converted.index[0], Timestamp('2021-01-01 00:00:00')) 24 | -------------------------------------------------------------------------------- /tests/infrastructure/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/infrastructure/__init__.py -------------------------------------------------------------------------------- /tests/infrastructure/market_data_sources/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/infrastructure/market_data_sources/__init__.py -------------------------------------------------------------------------------- /tests/infrastructure/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/infrastructure/models/__init__.py -------------------------------------------------------------------------------- /tests/infrastructure/models/test_order.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import \ 2 | PortfolioConfiguration, MarketCredential 3 | from investing_algorithm_framework.infrastructure.models import SQLOrder 4 | from tests.resources import TestBase 5 | 6 | 7 | class Test(TestBase): 8 | portfolio_configurations = [ 9 | PortfolioConfiguration( 10 | market="binance", 11 | trading_symbol="USDT" 12 | ) 13 | ] 14 | external_balances = { 15 | "USDT": 1000 16 | } 17 | market_credentials = [ 18 | MarketCredential( 19 | market="binance", 20 | api_key="api_key", 21 | secret_key="secret_key" 22 | ) 23 | ] 24 | 25 | def test_creation(self): 26 | order = SQLOrder( 27 | amount=2004.5303357979318, 28 | price=0.2431, 29 | order_side="BUY", 30 | order_type="LIMIT", 31 | status="OPEN", 32 | target_symbol="ADA", 33 | trading_symbol="USDT", 34 | ) 35 | self.assertEqual(order.amount, 2004.5303357979318) 36 | self.assertEqual(order.get_amount(), 2004.5303357979318) 37 | self.assertEqual(order.price, 0.2431) 38 | self.assertEqual(order.get_price(), 0.2431) 39 | -------------------------------------------------------------------------------- /tests/infrastructure/order_validators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/infrastructure/order_validators/__init__.py -------------------------------------------------------------------------------- /tests/infrastructure/repositories/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/infrastructure/repositories/__init__.py -------------------------------------------------------------------------------- /tests/infrastructure/repositories/test_order_repository.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import PortfolioConfiguration, \ 2 | MarketCredential 3 | from tests.resources import TestBase 4 | 5 | 6 | class Test(TestBase): 7 | market_credentials = [ 8 | MarketCredential( 9 | market="BINANCE", 10 | api_key="api_key", 11 | secret_key="secret_key", 12 | ) 13 | ] 14 | portfolio_configurations = [ 15 | PortfolioConfiguration( 16 | market="BINANCE", 17 | trading_symbol="EUR" 18 | ) 19 | ] 20 | external_balances = { 21 | "EUR": 1000, 22 | } 23 | 24 | def test_get_all(self): 25 | self.app.run(number_of_iterations=1) 26 | order_service = self.app.container.order_service() 27 | portfolio_service = self.app.container.portfolio_service() 28 | portfolio = portfolio_service.get_all()[0] 29 | order_service.create( 30 | { 31 | "portfolio_id": 1, 32 | "target_symbol": "BTC", 33 | "amount": 1, 34 | "trading_symbol": "EUR", 35 | "price": 10, 36 | "order_side": "BUY", 37 | "order_type": "LIMIT", 38 | "status": "OPEN", 39 | } 40 | ) 41 | self.assertEqual( 42 | 1, order_service.count({"portfolio_id": portfolio.id}) 43 | ) 44 | self.assertEqual( 45 | 0, order_service.count( 46 | {"portfolio_id": f"{portfolio.id}aeokgopge"} 47 | ) 48 | ) 49 | -------------------------------------------------------------------------------- /tests/infrastructure/repositories/test_position_repository.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import PortfolioConfiguration, \ 2 | MarketCredential 3 | from tests.resources import TestBase 4 | 5 | 6 | class Test(TestBase): 7 | market_credentials = [ 8 | MarketCredential( 9 | market="BINANCE", 10 | api_key="api_key", 11 | secret_key="secret_key" 12 | ) 13 | ] 14 | portfolio_configurations = [ 15 | PortfolioConfiguration( 16 | market="BINANCE", 17 | trading_symbol="EUR" 18 | ) 19 | ] 20 | external_balances = { 21 | "EUR": 1000, 22 | } 23 | 24 | def test_get_all(self): 25 | self.app.run(number_of_iterations=1) 26 | order_service = self.app.container.order_service() 27 | portfolio_service = self.app.container.portfolio_service() 28 | position_service = self.app.container.position_service() 29 | portfolio = portfolio_service.get_all()[0] 30 | order_service.create( 31 | { 32 | "portfolio_id": 1, 33 | "target_symbol": "BTC", 34 | "amount": 1, 35 | "trading_symbol": "EUR", 36 | "price": 10, 37 | "order_side": "BUY", 38 | "order_type": "LIMIT", 39 | "status": "OPEN", 40 | } 41 | ) 42 | self.assertEqual( 43 | 1, order_service.count({"portfolio": portfolio.identifier}) 44 | ) 45 | self.assertEqual( 46 | 2, position_service.count({"portfolio": portfolio.id}) 47 | ) 48 | self.assertEqual( 49 | 0, position_service 50 | .count({"portfolio": f"{portfolio.identifier}aeokgopge"}) 51 | ) 52 | -------------------------------------------------------------------------------- /tests/infrastructure/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/infrastructure/services/__init__.py -------------------------------------------------------------------------------- /tests/infrastructure/services/test_ccxt_market_service.py: -------------------------------------------------------------------------------- 1 | # from investing_algorithm_framework.configuration.constants import \ 2 | # API_KEY, SECRET_KEY, CCXT_ENABLED, MARKET 3 | # from tests.resources import TestBase, random_string 4 | # 5 | # 6 | # class Test(TestBase): 7 | # 8 | # def setUp(self): 9 | # super(Test, self).setUp() 10 | # self.algo_app.initialize(config={ 11 | # API_KEY: random_string(10), 12 | # SECRET_KEY: random_string(10), 13 | # CCXT_ENABLED: True, 14 | # MARKET: "binance" 15 | # }) 16 | # self.start_algorithm() 17 | # 18 | # def test(self) -> None: 19 | # market_service = self.algo_app.algorithm 20 | # .get_market_service("binance") 21 | # self.assertIsNotNone(market_service.exchange_class) 22 | -------------------------------------------------------------------------------- /tests/resources/__init__.py: -------------------------------------------------------------------------------- 1 | from .stubs import MarketServiceStub, RandomPriceMarketDataSourceServiceStub,\ 2 | MarketDataSourceServiceStub 3 | from .test_base import TestBase, FlaskTestBase, OrderExecutorTest, \ 4 | PortfolioProviderTest 5 | from .utils import random_string 6 | 7 | __all__ = [ 8 | 'random_string', 9 | "TestBase", 10 | "MarketServiceStub", 11 | "FlaskTestBase", 12 | "RandomPriceMarketDataSourceServiceStub", 13 | "MarketDataSourceServiceStub", 14 | "OrderExecutorTest", 15 | "PortfolioProviderTest", 16 | ] 17 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2020-12-31-22-00_2021-01-01-00-30.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2020-12-31 22:00:00,23720.51,23870.0,23626.66,23713.33,264.708666 3 | 2021-01-01 00:00:00,23713.67,24060.0,23583.3,23992.48,317.645368 4 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-07-37_2025-05-24-09-37.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 08:00:00,97608.56,98108.34,97602.9,98062.39,23.3015 3 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 4 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 5 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 6 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 7 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 8 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 9 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 10 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 11 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 12 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 13 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 14 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95434.41,18.19913 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-07-40_2025-05-24-09-40.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 08:00:00,97608.56,98108.34,97602.9,98062.39,23.3015 3 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 4 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 5 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 6 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 7 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 8 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 9 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 10 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 11 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 12 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 13 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 14 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95345.66,18.37812 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-07-42_2025-05-24-09-42.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 08:00:00,97608.56,98108.34,97602.9,98062.39,23.3015 3 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 4 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 5 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 6 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 7 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 8 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 9 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 10 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 11 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 12 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 13 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 14 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95342.36,18.47285 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-07-44_2025-05-24-09-44.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 08:00:00,97608.56,98108.34,97602.9,98062.39,23.3015 3 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 4 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 5 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 6 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 7 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 8 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 9 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 10 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 11 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 12 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 13 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 14 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95329.59,18.54661 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-07-49_2025-05-24-09-49.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 08:00:00,97608.56,98108.34,97602.9,98062.39,23.3015 3 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 4 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 5 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 6 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 7 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 8 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 9 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 10 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 11 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 12 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 13 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 14 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95301.01,18.88077 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-07-55_2025-05-24-09-55.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 08:00:00,97608.56,98108.34,97602.9,98062.39,23.3015 3 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 4 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 5 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 6 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 7 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 8 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 9 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 10 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 11 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 12 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 13 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 14 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95321.35,19.32985 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-08-11_2025-05-24-10-11.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 3 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 4 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 5 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 6 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 7 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 8 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 9 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 10 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 11 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 12 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 13 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 14 | 2025-05-24 10:00:00,95263.83,95338.68,95250.71,95269.69,1.64372 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-08-13_2025-05-24-10-13.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 3 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 4 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 5 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 6 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 7 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 8 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 9 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 10 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 11 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 12 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 13 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 14 | 2025-05-24 10:00:00,95263.83,95338.68,95250.71,95280.32,1.66831 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-23-08-22_2025-05-24-10-22.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-23 10:00:00,98066.59,98079.98,96104.82,96308.92,85.49888 3 | 2025-05-23 12:00:00,96317.7,97000.0,94873.61,96969.09,136.54756 4 | 2025-05-23 14:00:00,96958.28,97245.71,96039.03,96063.12,67.71745 5 | 2025-05-23 16:00:00,96059.0,96764.7,95209.12,96343.33,67.77146 6 | 2025-05-23 18:00:00,96334.1,96414.2,95632.45,95694.08,40.88471 7 | 2025-05-23 20:00:00,95714.26,95721.9,95093.02,95278.52,23.79046 8 | 2025-05-23 22:00:00,95293.62,95549.63,94023.63,94499.08,51.56579 9 | 2025-05-24 00:00:00,94483.33,94997.72,94105.0,94662.26,23.52372 10 | 2025-05-24 02:00:00,94656.13,95447.43,94446.44,95447.43,16.5936 11 | 2025-05-24 04:00:00,95451.11,95637.89,95238.38,95347.45,14.64587 12 | 2025-05-24 06:00:00,95347.45,95426.67,94767.91,94797.95,19.81373 13 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 14 | 2025-05-24 10:00:00,95263.83,95368.15,95250.71,95321.94,2.64859 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-06-07_2025-05-25-08-07.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 3 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 4 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 5 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 6 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 7 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 8 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 9 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 10 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 11 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 12 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 13 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 14 | 2025-05-25 08:00:00,94893.02,94906.53,94755.18,94803.83,1.44176 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-06-29_2025-05-25-08-29.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 3 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 4 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 5 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 6 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 7 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 8 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 9 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 10 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 11 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 12 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 13 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 14 | 2025-05-25 08:00:00,94893.02,94906.53,94612.7,94665.6,4.26832 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-06-43_2025-05-25-08-43.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 3 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 4 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 5 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 6 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 7 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 8 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 9 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 10 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 11 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 12 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 13 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 14 | 2025-05-25 08:00:00,94893.02,94906.53,94527.49,94571.27,12.789 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-06-53_2025-05-25-08-53.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 3 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 4 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 5 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 6 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 7 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 8 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 9 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 10 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 11 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 12 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 13 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 14 | 2025-05-25 08:00:00,94893.02,94906.53,94240.09,94315.7,18.73972 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-07-53_2025-05-25-09-53.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 3 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 4 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 5 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 6 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 7 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 8 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 9 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 10 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 11 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 12 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 13 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 14 | 2025-05-25 08:00:00,94893.02,94906.53,94052.64,94371.28,38.72075 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-07-55_2025-05-25-09-55.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 08:00:00,94797.92,95532.32,94797.92,95263.61,19.60838 3 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 4 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 5 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 6 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 7 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 8 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 9 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 10 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 11 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 12 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 13 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 14 | 2025-05-25 08:00:00,94893.02,94906.53,94052.64,94394.98,38.79396 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-09-11_2025-05-25-11-11.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 3 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 4 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 5 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 6 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 7 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 8 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 9 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 10 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 11 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 12 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 13 | 2025-05-25 08:00:00,94893.02,94906.53,94052.64,94413.97,39.42085 14 | 2025-05-25 10:00:00,94414.11,94574.87,94050.0,94228.03,38.3534 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-09-13_2025-05-25-11-13.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 3 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 4 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 5 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 6 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 7 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 8 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 9 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 10 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 11 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 12 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 13 | 2025-05-25 08:00:00,94893.02,94906.53,94052.64,94413.97,39.42085 14 | 2025-05-25 10:00:00,94414.11,94574.87,94050.0,94254.58,38.7434 15 | -------------------------------------------------------------------------------- /tests/resources/backtest_data/TICKER_BTC-EUR_BINANCE_2025-05-24-09-22_2025-05-25-11-22.csv: -------------------------------------------------------------------------------- 1 | Datetime,Open,High,Low,Close,Volume 2 | 2025-05-24 10:00:00,95263.83,96445.05,95250.71,96174.3,37.25566 3 | 2025-05-24 12:00:00,96176.36,96250.28,95618.13,95666.4,20.38949 4 | 2025-05-24 14:00:00,95664.86,96210.44,95657.89,95966.05,13.52513 5 | 2025-05-24 16:00:00,95971.28,96203.29,95752.05,95953.06,10.72676 6 | 2025-05-24 18:00:00,95948.93,96094.55,95825.37,95975.88,8.5039 7 | 2025-05-24 20:00:00,95981.63,96128.03,95656.97,95855.67,9.51244 8 | 2025-05-24 22:00:00,95860.98,95863.49,94684.06,94937.85,59.71346 9 | 2025-05-25 00:00:00,94935.26,95262.19,94500.0,94886.97,31.55389 10 | 2025-05-25 02:00:00,94891.05,95406.63,94741.63,95270.65,11.70117 11 | 2025-05-25 04:00:00,95266.3,95435.63,95139.78,95277.33,5.77381 12 | 2025-05-25 06:00:00,95278.58,95302.29,94684.34,94891.85,13.07014 13 | 2025-05-25 08:00:00,94893.02,94906.53,94052.64,94413.97,39.42085 14 | 2025-05-25 10:00:00,94414.11,94574.87,94050.0,94265.17,39.70205 15 | -------------------------------------------------------------------------------- /tests/resources/serialization_dicts.py: -------------------------------------------------------------------------------- 1 | order_serialization_dict = { 2 | 'amount', 3 | 'trading_symbol', 4 | 'initial_price', 5 | 'closing_price', 6 | 'price', 7 | 'target_symbol', 8 | 'order_side', 9 | 'amount', 10 | 'status', 11 | 'order_type', 12 | 'reference_id' 13 | } 14 | 15 | position_serialization_dict = { 16 | 'symbol', 'amount', 'orders' 17 | } 18 | 19 | portfolio_serialization_dict = { 20 | 'identifier', 21 | 'trading_symbol', 22 | 'unallocated', 23 | 'positions', 24 | 'orders', 25 | } 26 | -------------------------------------------------------------------------------- /tests/resources/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | BASE_DIR = str(Path(__file__).parent.parent) 4 | 5 | DATABASE_CONFIG = { 6 | 'DATABASE_TYPE': 'sqlite3', 7 | 'DATABASE_NAME': 'test_db', 8 | 'DATABASE_DIRECTORY_PATH': BASE_DIR 9 | } 10 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy_one import StrategyOne 2 | from .strategy_two import StrategyTwo 3 | 4 | __all__ = [ 5 | "StrategyOne", 6 | "StrategyTwo" 7 | ] 8 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_one.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import TradingStrategy, TimeUnit 2 | 3 | 4 | class StrategyOne(TradingStrategy): 5 | strategy_id = "strategy_one" 6 | time_unit = TimeUnit.MINUTE 7 | interval = 1 8 | 9 | def run_strategy(self, context, market_data): 10 | pass 11 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_two.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import TradingStrategy, TimeUnit 2 | 3 | 4 | class StrategyTwo(TradingStrategy): 5 | strategy_id = "strategy_two" 6 | time_unit = TimeUnit.MINUTE 7 | interval = 1 8 | 9 | def run_strategy(self, context, market_data): 10 | pass 11 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_v1/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy_v1 import CrossOverStrategyV1 2 | 3 | __all__ = [ 4 | "CrossOverStrategyV1", 5 | ] 6 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_v1/data_sources.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import ( 2 | CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource 3 | ) 4 | 5 | bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 6 | identifier="BTC/EUR-ohlcv-2h", 7 | market="BINANCE", 8 | symbol="BTC/EUR", 9 | time_frame="2h", 10 | window_size=200 11 | ) 12 | bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 13 | identifier="DOT/EUR-ohlch-2h", 14 | market="BINANCE", 15 | symbol="DOT/EUR", 16 | time_frame="2h", 17 | window_size=200 18 | ) 19 | bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( 20 | identifier="DOT/EUR-ticker", 21 | market="BINANCE", 22 | symbol="DOT/EUR", 23 | backtest_time_frame="2h", 24 | ) 25 | bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( 26 | identifier="BTC/EUR-ticker", 27 | market="BINANCE", 28 | symbol="BTC/EUR", 29 | backtest_time_frame="2h", 30 | ) 31 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_v2/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy_v2 import CrossOverStrategyV2 2 | 3 | __all__ = [ 4 | "CrossOverStrategyV2", 5 | ] 6 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_v2/data_sources.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import ( 2 | CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource 3 | ) 4 | 5 | bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 6 | identifier="BTC/EUR-ohlcv-2h", 7 | market="BINANCE", 8 | symbol="BTC/EUR", 9 | time_frame="2h", 10 | window_size=200 11 | ) 12 | bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 13 | identifier="DOT/EUR-ohlch-2h", 14 | market="BINANCE", 15 | symbol="DOT/EUR", 16 | time_frame="2h", 17 | window_size=200 18 | ) 19 | bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( 20 | identifier="DOT/EUR-ticker", 21 | market="BINANCE", 22 | symbol="DOT/EUR", 23 | backtest_time_frame="2h", 24 | ) 25 | bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( 26 | identifier="BTC/EUR-ticker", 27 | market="BINANCE", 28 | symbol="BTC/EUR", 29 | backtest_time_frame="2h", 30 | ) 31 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_v3/__init__.py: -------------------------------------------------------------------------------- 1 | from .strategy_v3 import CrossOverStrategyV3 2 | 3 | __all__ = [ 4 | "CrossOverStrategyV3", 5 | ] 6 | -------------------------------------------------------------------------------- /tests/resources/strategies_for_testing/strategy_v3/data_sources.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import ( 2 | CCXTOHLCVMarketDataSource, CCXTTickerMarketDataSource 3 | ) 4 | 5 | bitvavo_btc_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 6 | identifier="BTC/EUR-ohlcv-2h", 7 | market="BINANCE", 8 | symbol="BTC/EUR", 9 | time_frame="2h", 10 | window_size=200 11 | ) 12 | bitvavo_dot_eur_ohlcv_2h = CCXTOHLCVMarketDataSource( 13 | identifier="DOT/EUR-ohlch-2h", 14 | market="BINANCE", 15 | symbol="DOT/EUR", 16 | time_frame="2h", 17 | window_size=200 18 | ) 19 | bitvavo_dot_eur_ticker = CCXTTickerMarketDataSource( 20 | identifier="DOT/EUR-ticker", 21 | market="BINANCE", 22 | symbol="DOT/EUR", 23 | backtest_time_frame="2h", 24 | ) 25 | bitvavo_btc_eur_ticker = CCXTTickerMarketDataSource( 26 | identifier="BTC/EUR-ticker", 27 | market="BINANCE", 28 | symbol="BTC/EUR", 29 | backtest_time_frame="2h", 30 | ) 31 | -------------------------------------------------------------------------------- /tests/resources/stubs/__init__.py: -------------------------------------------------------------------------------- 1 | from .market_data_source_service_stub import \ 2 | RandomPriceMarketDataSourceServiceStub, MarketDataSourceServiceStub 3 | from .market_service_stub import MarketServiceStub 4 | from .portfolio_sync_service import PortfolioSyncServiceStub 5 | from .order_executor import OrderExecutorTest 6 | from .portfolio_provider import PortfolioProviderTest 7 | 8 | __all__ = [ 9 | "MarketServiceStub", 10 | "RandomPriceMarketDataSourceServiceStub", 11 | "MarketDataSourceServiceStub", 12 | "PortfolioSyncServiceStub", 13 | "OrderExecutorTest", 14 | "PortfolioProviderTest", 15 | ] 16 | -------------------------------------------------------------------------------- /tests/resources/stubs/market_data_source_service_stub.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from random import randint 3 | 4 | from investing_algorithm_framework.services import MarketDataSourceService 5 | 6 | 7 | class RandomPriceMarketDataSourceServiceStub(MarketDataSourceService): 8 | 9 | def get_ticker(self, symbol, market=None): 10 | return { 11 | "symbol": symbol, 12 | "ask": randint(1, 100), 13 | "bid": randint(1, 100), 14 | "last": randint(1, 100), 15 | "volume": randint(1, 100), 16 | "timestamp": datetime.utcnow() 17 | } 18 | 19 | class MarketDataSourceServiceStub(MarketDataSourceService): 20 | 21 | def __init__( 22 | self, 23 | market_service, 24 | market_credential_service, 25 | configuration_service, 26 | market_data_sources 27 | ): 28 | super().__init__( 29 | market_service, 30 | market_credential_service, 31 | configuration_service, 32 | market_data_sources 33 | ) 34 | 35 | def initialize_market_data_sources(self): 36 | pass 37 | 38 | def get_ticker(self, symbol, market=None): 39 | return { 40 | "symbol": symbol, 41 | "ask": randint(1, 100), 42 | "bid": randint(1, 100), 43 | "last": randint(1, 100), 44 | "volume": randint(1, 100), 45 | "timestamp": datetime.utcnow() 46 | } 47 | -------------------------------------------------------------------------------- /tests/resources/stubs/order_executor.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework import Order 2 | from investing_algorithm_framework.domain import OrderExecutor, OrderStatus 3 | from tests.resources.utils import random_string 4 | 5 | 6 | class OrderExecutorTest(OrderExecutor): 7 | """ 8 | Test order executor for testing purposes. 9 | """ 10 | 11 | def __init__(self): 12 | super().__init__() 13 | self.order_amount = None 14 | self.order_amount_filled = None 15 | self.order_status = None 16 | 17 | def execute_order(self, portfolio, order, market_credential) -> Order: 18 | order.external_id = random_string(10) 19 | order.status = OrderStatus.OPEN 20 | 21 | if self.order_amount is not None: 22 | order.amount = self.order_amount 23 | order.remaining = self.order_amount 24 | 25 | if self.order_amount_filled is not None: 26 | order.filled = self.order_amount_filled 27 | order.remaining = order.amount - self.order_amount_filled 28 | 29 | if self.order_status is not None: 30 | order.status = self.order_status 31 | 32 | return order 33 | 34 | def supports_market(self, market): 35 | return True 36 | 37 | def cancel_order(self, portfolio, order, market_credential) -> Order: 38 | order.status = OrderStatus.CANCELED 39 | return order 40 | -------------------------------------------------------------------------------- /tests/resources/stubs/portfolio_provider.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from investing_algorithm_framework import Position, Order, OrderStatus 4 | from investing_algorithm_framework.domain import PortfolioProvider 5 | 6 | 7 | class PortfolioProviderTest(PortfolioProvider): 8 | 9 | def __init__(self): 10 | super().__init__() 11 | self.status = OrderStatus.CLOSED.value 12 | self.external_balances = { 13 | "EUR": 1000, 14 | } 15 | self.order_amount = None 16 | self.order_amount_filled = None 17 | 18 | def get_order( 19 | self, portfolio, order, market_credential 20 | ) -> Union[Order, None]: 21 | 22 | if self.order_amount is not None: 23 | order.amount = self.order_amount 24 | 25 | if self.order_amount_filled is not None: 26 | order.filled = self.order_amount_filled 27 | else: 28 | order.filled = order.amount 29 | 30 | order.status = self.status 31 | order.remaining = 0 32 | return order 33 | 34 | def get_position( 35 | self, portfolio, symbol, market_credential 36 | ) -> Union[Position, None]: 37 | if symbol not in self.external_balances: 38 | position = Position( 39 | symbol=symbol, 40 | amount=1000, 41 | portfolio_id=portfolio.id 42 | ) 43 | else: 44 | position = Position( 45 | symbol=symbol, 46 | amount=self.external_balances[symbol], 47 | portfolio_id=portfolio.id 48 | ) 49 | return position 50 | 51 | def supports_market(self, market) -> bool: 52 | return True -------------------------------------------------------------------------------- /tests/resources/stubs/portfolio_sync_service.py: -------------------------------------------------------------------------------- 1 | from investing_algorithm_framework.services import PortfolioSyncService 2 | 3 | 4 | class PortfolioSyncServiceStub: 5 | """ 6 | Stub class for PortfolioSyncService. This class is used to test the 7 | PortfolioSyncService class without actually executing any orders. 8 | """ 9 | 10 | def __init__(self, portfolio_repository, position_repository): 11 | self.portfolio_repository = portfolio_repository 12 | self.position_repository = position_repository 13 | 14 | def sync_unallocated(self, portfolio): 15 | if portfolio.initial_balance is None: 16 | unallocated = 1000 17 | else: 18 | unallocated = portfolio.initial_balance 19 | 20 | update_data = { 21 | "unallocated": unallocated, 22 | "net_size": unallocated, 23 | "initialized": True 24 | } 25 | portfolio = self.portfolio_repository.update( 26 | portfolio.id, update_data 27 | ) 28 | 29 | # Update also a trading symbol position 30 | trading_symbol_position = self.position_repository.find( 31 | { 32 | "symbol": portfolio.trading_symbol, 33 | "portfolio_id": portfolio.id 34 | } 35 | ) 36 | self.position_repository.update( 37 | trading_symbol_position.id, {"amount": unallocated} 38 | ) 39 | 40 | return portfolio 41 | 42 | def sync_orders(self, portfolio): 43 | return 44 | -------------------------------------------------------------------------------- /tests/resources/utils.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | 5 | def random_string(n, spaces: bool = False): 6 | 7 | if spaces: 8 | return ''.join(random.choice(string.ascii_lowercase + ' ') 9 | for _ in range(n)) 10 | 11 | return ''.join(random.choice(string.ascii_lowercase) for _ in range(n)) 12 | -------------------------------------------------------------------------------- /tests/scenarios/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/scenarios/__init__.py -------------------------------------------------------------------------------- /tests/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/services/__init__.py -------------------------------------------------------------------------------- /tests/services/test_portfolio_configuration_service.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/services/test_portfolio_configuration_service.py -------------------------------------------------------------------------------- /tests/services/test_position_service.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coding-kitties/investing-algorithm-framework/87a392ca01fee2940eff10091424a5ce1df06ab6/tests/services/test_position_service.py --------------------------------------------------------------------------------