├── .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
--------------------------------------------------------------------------------