pfund 0.0.1.dev9__tar.gz → 0.0.1.dev11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/PKG-INFO +6 -2
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/CONTRIBUTING.md +3 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/adapter.py +4 -7
- pfund-0.0.1.dev11/pfund/analyzer.py +198 -0
- pfund-0.0.1.dev11/pfund/config/binance/linear/config.yml +7 -0
- pfund-0.0.1.dev11/pfund/config/binance/linear/lot_sizes_linear.yml +2 -0
- pfund-0.0.1.dev11/pfund/config/binance/linear/pdt_matchings_linear.yml +2 -0
- pfund-0.0.1.dev11/pfund/config/binance/linear/tick_sizes_linear.yml +2 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/configuration.py +5 -7
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config_handler.py +4 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/const/paths.py +1 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/data_tools/data_tool_pandas.py +2 -6
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/engines/backtest_engine.py +21 -28
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/engines/base_engine.py +5 -3
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/engines/trade_engine.py +11 -12
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/engines/train_engine.py +4 -2
- pfund-0.0.1.dev11/pfund/exchanges/binance/__init__.py +3 -0
- pfund-0.0.1.dev11/pfund/exchanges/binance/exchange.py +37 -0
- pfund-0.0.1.dev11/pfund/exchanges/binance/linear/exchange.py +6 -0
- pfund-0.0.1.dev11/pfund/exchanges/binance/rest_api.py +15 -0
- pfund-0.0.1.dev11/pfund/exchanges/binance/ws_api.py +33 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/__init__.py +1 -2
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/exchange.py +10 -6
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api.py +3 -1
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/ws_api.py +4 -2
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/exchange_base.py +12 -9
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/investment_profile.py +2 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/strategy_manager.py +1 -1
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/models/model_backtest.py +2 -2
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/strategy_backtest.py +6 -1
- pfund-0.0.1.dev11/pfund/templates/dashboards/pfund-overview.streamlit.py +0 -0
- pfund-0.0.1.dev11/pfund/templates/notebooks/pfund-analytics.ipynb +49 -0
- pfund-0.0.1.dev11/pfund/templates/notebooks/pfund-overview.ipynb +49 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/utils/utils.py +11 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pyproject.toml +14 -4
- pfund-0.0.1.dev9/pfund/analyzer.py +0 -3
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/LICENSE +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/README.md +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/accounts/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/accounts/account_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/accounts/account_crypto.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/accounts/account_ib.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/balances/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/balances/balance_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/balances/balance_crypto.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/balances/balance_ib.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/broker_backtest.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/broker_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/broker_crypto.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/broker_live.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/ib/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/ib/broker_ib.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/ib/ib_api.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/ib/ib_client.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/brokers/ib/ib_wrapper.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/cli/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/cli/commands/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/cli/commands/config.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/cli/commands/docker_compose.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/cli/main.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/config.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_option.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_inverse.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_linear.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_option.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_spot.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_option.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/ib/config.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/config/logging.yml +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/const/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/const/_zmq_routes.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/const/commons.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/data_tools/data_tool_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/data_bar.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/data_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/data_quote.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/data_tick.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/data_time_based.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/resolution.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/datas/timeframe.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/engines/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/engines/test_engine.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/errors.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_inverse +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_linear +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_option +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_spot +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_inverse +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_linear +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_option +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_spot +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/rest_api_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/exchanges/ws_api_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/account_summary_tags.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/client.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/comm.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/commission_report.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/common.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/connection.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/contract.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/decoder.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/enum_implem.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/errors.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/execution.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/ibapi.pyproj +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/message.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/news.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/object_implem.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/order.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/order_condition.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/order_state.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/orderdecoder.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/reader.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/scanner.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/server_versions.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/softdollartier.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/tag_value.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/ticktype.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/utils.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/externals/ibapi/wrapper.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/indicators/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/indicators/indicator_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/indicators/ta_indicator.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/indicators/talib_indicator.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/main.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/base_manager.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/connection_manager.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/data_manager.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/order_manager.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/portfolio_manager.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/managers/risk_manager.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/mixins/backtest.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/models/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/models/model_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/models/model_meta.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/models/pytorch_model.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/models/sklearn_model.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/orders/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/orders/order_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/orders/order_crypto.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/orders/order_ib.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/orders/order_statuses.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/orders/order_time_in_force.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/plogging/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/plogging/config.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/plogging/filters.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/plogging/formatter.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/plogging/handlers.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/portfolio.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/positions/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/positions/position_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/positions/position_crypto.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/positions/position_ib.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/products/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/products/product_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/products/product_crypto.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/products/product_ib.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/risk_monitor.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/__init__.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/allocation_strategy.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/diversification_strategy.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/hedging_strategy.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/optimization_strategy.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/portfolio_strategy.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/rebalancing_strategy.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/strategy_base.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/strategies/strategy_meta.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/types/backtest.py +0 -0
- /pfund-0.0.1.dev9/pfund/exchanges/bybit/types.py → /pfund-0.0.1.dev11/pfund/types/bybit.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/types/common_literals.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/types/core.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/universe.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/utils/aliases.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/utils/envs.py +0 -0
- {pfund-0.0.1.dev9 → pfund-0.0.1.dev11}/pfund/zeromq.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pfund
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev11
|
|
4
4
|
Summary: A Complete Algo-Trading Framework for Machine Learning, enabling trading across TradFi, CeFi and DeFi. Supports Vectorized and Event-Driven Backtesting, Paper and Live Trading
|
|
5
5
|
Home-page: https://pfund.ai
|
|
6
6
|
License: Apache-2.0
|
|
@@ -13,13 +13,16 @@ Classifier: Programming Language :: Python :: 3
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Provides-Extra: analytics
|
|
16
17
|
Provides-Extra: data
|
|
17
18
|
Provides-Extra: ml
|
|
18
19
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
19
20
|
Requires-Dist: gitpython (>=3.1.43,<4.0.0)
|
|
20
21
|
Requires-Dist: mlflow (>=2.11.3,<3.0.0) ; extra == "ml"
|
|
21
22
|
Requires-Dist: orjson (>=3.9.14,<4.0.0) ; extra == "data"
|
|
22
|
-
Requires-Dist:
|
|
23
|
+
Requires-Dist: papermill (>=2.5.0,<3.0.0) ; extra == "analytics"
|
|
24
|
+
Requires-Dist: pfeed[boost,data,df] (>=0.0.1.dev10,<0.0.2) ; extra == "data"
|
|
25
|
+
Requires-Dist: pfolio[bayesian,data,portfolio,temporary] (>=0.0.1.dev4,<0.0.2) ; extra == "analytics"
|
|
23
26
|
Requires-Dist: platformdirs (>=4.2.0,<5.0.0)
|
|
24
27
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
25
28
|
Requires-Dist: python-telegram-bot (>=20.7,<21.0)
|
|
@@ -32,6 +35,7 @@ Requires-Dist: scikit-learn (>=1.4.0,<2.0.0) ; extra == "ml"
|
|
|
32
35
|
Requires-Dist: ta (>=0.11.0,<0.12.0) ; extra == "ml"
|
|
33
36
|
Requires-Dist: torch (>=2.1.2,<3.0.0) ; extra == "ml"
|
|
34
37
|
Requires-Dist: tqdm (>=4.66.2,<5.0.0)
|
|
38
|
+
Requires-Dist: voila (>=0.5.6,<0.6.0) ; extra == "analytics"
|
|
35
39
|
Requires-Dist: websocket-client (>=1.7.0,<2.0.0)
|
|
36
40
|
Project-URL: Documentation, https://pfund-docs.pfund.ai
|
|
37
41
|
Project-URL: Repository, https://github.com/PFund-Software-Ltd/pfund
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import yaml
|
|
2
2
|
import os
|
|
3
3
|
|
|
4
|
-
from pfund.const.paths import PROJ_CONFIG_PATH
|
|
5
|
-
|
|
6
4
|
|
|
7
5
|
class Adapter:
|
|
8
|
-
def __init__(self,
|
|
9
|
-
self.
|
|
6
|
+
def __init__(self, config_path, adapter_dict):
|
|
7
|
+
self.config_path = config_path
|
|
10
8
|
self._adapter_dict = adapter_dict
|
|
11
9
|
self._adapter = {}
|
|
12
10
|
self._ref_keys = []
|
|
@@ -25,9 +23,8 @@ class Adapter:
|
|
|
25
23
|
return pdt
|
|
26
24
|
|
|
27
25
|
def load_pdt_matchings(self):
|
|
28
|
-
file_path = f'{PROJ_CONFIG_PATH}/{self._trading_venue.lower()}'
|
|
29
26
|
config_name = 'pdt_matchings'
|
|
30
|
-
for file_name in os.listdir(
|
|
27
|
+
for file_name in os.listdir(self.config_path):
|
|
31
28
|
if not file_name.startswith(config_name):
|
|
32
29
|
continue
|
|
33
30
|
file_splits = file_name.split('_')
|
|
@@ -35,7 +32,7 @@ class Adapter:
|
|
|
35
32
|
category = file_splits[-1].split('.')[0]
|
|
36
33
|
else:
|
|
37
34
|
category = ''
|
|
38
|
-
with open(
|
|
35
|
+
with open(self.config_path + '/' + file_name, 'r') as f:
|
|
39
36
|
if pdt_macthings := yaml.safe_load(f):
|
|
40
37
|
for pdt, epdt in pdt_macthings.items():
|
|
41
38
|
self.update(pdt, epdt, ref_key=category)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from pfund.utils import utils
|
|
7
|
+
from pfund.config_handler import ConfigHandler
|
|
8
|
+
from pfund.const.paths import PROJ_PATH
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Analyzer:
|
|
12
|
+
try:
|
|
13
|
+
Engine = utils.get_engine_class()
|
|
14
|
+
config = Engine.config
|
|
15
|
+
except:
|
|
16
|
+
config = ConfigHandler.load_config()
|
|
17
|
+
|
|
18
|
+
notebook_path = Path(config.notebook_path)
|
|
19
|
+
spreadsheet_path = Path(config.spreadsheet_path)
|
|
20
|
+
dashboard_path = Path(config.dashboard_path)
|
|
21
|
+
|
|
22
|
+
def __init__(self, data: dict | None=None):
|
|
23
|
+
self.data = data or {}
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _is_file(template: str) -> bool:
|
|
27
|
+
if '\\' in template or '/' in template:
|
|
28
|
+
assert Path(template).resolve().is_file(), f"File {template} does not exist"
|
|
29
|
+
return True
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _derive_template_type(template: str) -> Literal['notebook', 'spreadsheet', 'dashboard']:
|
|
34
|
+
if '.ipynb' in template:
|
|
35
|
+
template_type = 'notebook'
|
|
36
|
+
elif '.grid' in template:
|
|
37
|
+
template_type = 'spreadsheet'
|
|
38
|
+
elif '.py' in template:
|
|
39
|
+
template_type = 'dashboard'
|
|
40
|
+
else:
|
|
41
|
+
raise ValueError(f"Template {template} is not a valid template, only .ipynb, .grid, .py are supported.")
|
|
42
|
+
return template_type
|
|
43
|
+
|
|
44
|
+
def _find_template(self, template: str) -> str:
|
|
45
|
+
'''Check if the template exists in pfund's templates or user's templates
|
|
46
|
+
e.g. template = 'notebook.ipynb' or 'spreadsheet.grid' or 'dashboard.py'
|
|
47
|
+
'''
|
|
48
|
+
template_type = self._derive_template_type(template)
|
|
49
|
+
pfund_templates_dir = PROJ_PATH / 'templates' / (template_type+'s')
|
|
50
|
+
user_templates_dir = getattr(self, f'{template_type}_path')
|
|
51
|
+
for templates_dir in [pfund_templates_dir, user_templates_dir]:
|
|
52
|
+
for file_name in os.listdir(templates_dir):
|
|
53
|
+
if template == file_name:
|
|
54
|
+
template_file_path = templates_dir / template
|
|
55
|
+
return str(template_file_path)
|
|
56
|
+
else:
|
|
57
|
+
raise FileNotFoundError(f"Template {template} not found in pfund's templates or user's templates")
|
|
58
|
+
|
|
59
|
+
def _get_editor_cmd(self, editor: Literal['vscode', 'pycharm']) -> str:
|
|
60
|
+
if editor == 'vscode':
|
|
61
|
+
cmd = 'code'
|
|
62
|
+
if utils.is_command_available(cmd):
|
|
63
|
+
return cmd
|
|
64
|
+
else:
|
|
65
|
+
print("VSCode command 'code' is not available, cannot open the output notebook")
|
|
66
|
+
elif editor == 'pycharm':
|
|
67
|
+
for cmd in ['charm', 'pycharm']:
|
|
68
|
+
if utils.is_command_available(cmd):
|
|
69
|
+
return cmd
|
|
70
|
+
else:
|
|
71
|
+
print("PyCharm commands 'charm'/'pycharm' are both not available, cannot open the output notebook")
|
|
72
|
+
else:
|
|
73
|
+
print(f"Editor '{editor}' is not supported, cannot open the output notebook")
|
|
74
|
+
|
|
75
|
+
def run_notebooks(
|
|
76
|
+
self,
|
|
77
|
+
notebooks: list[str] | str,
|
|
78
|
+
*voila_args,
|
|
79
|
+
data: dict | None=None,
|
|
80
|
+
display: bool=True,
|
|
81
|
+
port: int=8866,
|
|
82
|
+
show_results_only: bool=True,
|
|
83
|
+
open_outputs: bool=False,
|
|
84
|
+
outputs_path: str | None=None,
|
|
85
|
+
editor: Literal['vscode', 'pycharm']='vscode'
|
|
86
|
+
) -> None:
|
|
87
|
+
'''
|
|
88
|
+
Args:
|
|
89
|
+
notebook:
|
|
90
|
+
- notebook_template's name
|
|
91
|
+
- notebook's full path in str or Path
|
|
92
|
+
voila_args: additional arguments to pass to voila
|
|
93
|
+
data: data to be analyzed, if None, use the data passed to the Analyzer instance during initialization
|
|
94
|
+
display: if True, display the notebook in voila
|
|
95
|
+
show_results_only: if True, display only the results (no source code) in voila
|
|
96
|
+
open_outputs: if True, open the output notebook in the editor
|
|
97
|
+
outputs_path: path to save the output notebooks, if None, do not save the output notebooks
|
|
98
|
+
'''
|
|
99
|
+
import subprocess
|
|
100
|
+
import papermill as pm
|
|
101
|
+
|
|
102
|
+
def _find_available_port(_port):
|
|
103
|
+
retry_num = 100
|
|
104
|
+
while retry_num:
|
|
105
|
+
if not utils.is_port_in_use(_port):
|
|
106
|
+
return _port
|
|
107
|
+
retry_num -= 1
|
|
108
|
+
_port += 1
|
|
109
|
+
else:
|
|
110
|
+
raise Exception(f"No available ports found starting from {_port - 100}, cannot display the notebook")
|
|
111
|
+
|
|
112
|
+
def _assert_voila_args_are_valid():
|
|
113
|
+
for arg in voila_args:
|
|
114
|
+
if not arg.startswith('--'):
|
|
115
|
+
raise ValueError(f"Voila argument '{arg}' should start with '--'")
|
|
116
|
+
if arg.startswith('--port='):
|
|
117
|
+
raise ValueError(f"Voila argument '{arg}' should not be passed in, use the 'port' argument instead")
|
|
118
|
+
if arg.startswith('--strip_sources='):
|
|
119
|
+
raise ValueError(f"Voila argument '{arg}' should not be passed in, use the 'show_results_only' argument instead")
|
|
120
|
+
|
|
121
|
+
voila_processes = []
|
|
122
|
+
nb_output_file_paths = []
|
|
123
|
+
if isinstance(notebooks, str):
|
|
124
|
+
notebooks = [notebooks]
|
|
125
|
+
data = data or self.data
|
|
126
|
+
if not data:
|
|
127
|
+
raise ValueError("No data passed in or stored in the Analyzer instance, please pass in the data to be analyzed.")
|
|
128
|
+
|
|
129
|
+
if open_outputs:
|
|
130
|
+
assert outputs_path is not None, f"{outputs_path=}, cannot open the output notebook without saving it."
|
|
131
|
+
editor_cmd = self._get_editor_cmd(editor)
|
|
132
|
+
|
|
133
|
+
_assert_voila_args_are_valid()
|
|
134
|
+
is_theme_provided = any(arg.startswith('--theme=') for arg in voila_args)
|
|
135
|
+
if not is_theme_provided:
|
|
136
|
+
default_theme = 'dark'
|
|
137
|
+
voila_args = [f'--theme={default_theme}', *voila_args]
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
for notebook in notebooks:
|
|
141
|
+
if self._is_file(notebook):
|
|
142
|
+
nb_input_file_path: str = notebook
|
|
143
|
+
notebook = Path(notebook).name # e.g. 'notebook.ipynb'
|
|
144
|
+
else:
|
|
145
|
+
nb_input_file_path: str = self._find_template(notebook)
|
|
146
|
+
nb_output_file_path = Path(outputs_path or '.').resolve() / f'{notebook.replace(".ipynb", "")}_output.ipynb'
|
|
147
|
+
nb_output_file_path = str(nb_output_file_path)
|
|
148
|
+
nb_output_file_paths.append(nb_output_file_path)
|
|
149
|
+
|
|
150
|
+
print(f"Executing notebook: {notebook}")
|
|
151
|
+
pm.execute_notebook(
|
|
152
|
+
nb_input_file_path,
|
|
153
|
+
nb_output_file_path,
|
|
154
|
+
parameters=data
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if open_outputs:
|
|
158
|
+
# e.g. code notebook_output.ipynb if using vscode
|
|
159
|
+
if editor_cmd:
|
|
160
|
+
subprocess.run([editor_cmd, nb_output_file_path])
|
|
161
|
+
|
|
162
|
+
if display:
|
|
163
|
+
port = _find_available_port(port)
|
|
164
|
+
is_last_notebook = (notebook == notebooks[-1])
|
|
165
|
+
subprocess_func = subprocess.run if is_last_notebook else subprocess.Popen
|
|
166
|
+
process = subprocess_func([
|
|
167
|
+
'voila',
|
|
168
|
+
f'--port={port}',
|
|
169
|
+
f'--strip_sources={show_results_only}',
|
|
170
|
+
*voila_args,
|
|
171
|
+
nb_output_file_path
|
|
172
|
+
])
|
|
173
|
+
voila_processes.append(process)
|
|
174
|
+
except KeyboardInterrupt:
|
|
175
|
+
print("KeyboardInterrupt: Stopping the execution of the notebooks")
|
|
176
|
+
except Exception:
|
|
177
|
+
raise
|
|
178
|
+
finally:
|
|
179
|
+
if outputs_path is None:
|
|
180
|
+
for nb_output_file_path in nb_output_file_paths:
|
|
181
|
+
print(f"{outputs_path=}, removing output notebook: {nb_output_file_path}")
|
|
182
|
+
os.remove(nb_output_file_path)
|
|
183
|
+
for process in voila_processes:
|
|
184
|
+
process.terminate()
|
|
185
|
+
process.wait()
|
|
186
|
+
|
|
187
|
+
def run_spreadsheets(
|
|
188
|
+
self,
|
|
189
|
+
spreadsheets: list[str] | str
|
|
190
|
+
):
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
# TODO:
|
|
194
|
+
def run_dashboards(
|
|
195
|
+
self,
|
|
196
|
+
dashboards: list[str] | str
|
|
197
|
+
):
|
|
198
|
+
pass
|
|
@@ -2,23 +2,21 @@ import os
|
|
|
2
2
|
|
|
3
3
|
import yaml
|
|
4
4
|
|
|
5
|
-
from pfund.const.paths import PROJ_CONFIG_PATH
|
|
6
5
|
from pfund.utils.utils import short_path
|
|
7
6
|
|
|
8
7
|
|
|
9
8
|
class Configuration:
|
|
10
|
-
def __init__(self,
|
|
11
|
-
self.
|
|
9
|
+
def __init__(self, config_path, config_name):
|
|
10
|
+
self.config_path = config_path
|
|
12
11
|
self.config_name = config_name
|
|
13
|
-
self.config_path = f'{PROJ_CONFIG_PATH}/{self.config_dir}'
|
|
14
12
|
self.configs = None
|
|
15
13
|
self.reload()
|
|
16
14
|
|
|
17
15
|
def reload(self):
|
|
18
16
|
self.configs = self.read_config(self.config_name)
|
|
19
17
|
|
|
20
|
-
def
|
|
21
|
-
return self.
|
|
18
|
+
def get_config_path(self):
|
|
19
|
+
return self.config_path
|
|
22
20
|
|
|
23
21
|
def read_config(self, config_name):
|
|
24
22
|
file_path = f'{self.config_path}/{config_name}.yml'
|
|
@@ -40,7 +38,7 @@ class Configuration:
|
|
|
40
38
|
raise Exception(f'could not find section {section} for config {self.config_name}')
|
|
41
39
|
|
|
42
40
|
def check_if_config_exists_and_not_empty(self, config_name):
|
|
43
|
-
file_path = f'{
|
|
41
|
+
file_path = f'{self.config_path}/{config_name}.yml'
|
|
44
42
|
if os.path.exists(file_path) and os.stat(file_path).st_size != 0:
|
|
45
43
|
return True
|
|
46
44
|
else:
|
|
@@ -98,6 +98,10 @@ class ConfigHandler:
|
|
|
98
98
|
def dashboard_path(self):
|
|
99
99
|
return f'{self.data_path}/templates/dashboards'
|
|
100
100
|
|
|
101
|
+
@property
|
|
102
|
+
def artifact_path(self):
|
|
103
|
+
return f'{self.data_path}/.artifacts'
|
|
104
|
+
|
|
101
105
|
def __post_init__(self):
|
|
102
106
|
self.logging_config = self.logging_config or {}
|
|
103
107
|
|
|
@@ -151,12 +151,8 @@ class PandasDataTool(BaseDataTool):
|
|
|
151
151
|
index=pd.MultiIndex(levels=[[]]*len(index_names), codes=[[]]*len(index_names), names=index_names)
|
|
152
152
|
)
|
|
153
153
|
|
|
154
|
-
def output_df_to_parquet(self,
|
|
155
|
-
|
|
156
|
-
name = name + '.parquet'
|
|
157
|
-
if type(path) is str:
|
|
158
|
-
path = Path(path)
|
|
159
|
-
df.to_parquet(path / name)
|
|
154
|
+
def output_df_to_parquet(self, df: pd.DataFrame, file_path: str):
|
|
155
|
+
df.to_parquet(file_path)
|
|
160
156
|
|
|
161
157
|
def _create_multi_index(self, index_data: dict, index_names: list[str]) -> pd.MultiIndex:
|
|
162
158
|
return pd.MultiIndex.from_tuples([tuple(index_data[name] for name in index_names)], names=index_names)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import hashlib
|
|
2
4
|
import os
|
|
3
5
|
import time
|
|
@@ -8,9 +10,9 @@ import uuid
|
|
|
8
10
|
from typing import TYPE_CHECKING
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from pfund.types.common_literals import tSUPPORTED_BACKTEST_MODES, tSUPPORTED_DATA_TOOLS
|
|
13
|
+
from pfund.types.core import tStrategy, tModel, tFeature, tIndicator
|
|
11
14
|
|
|
12
15
|
import pfund as pf
|
|
13
|
-
from pfund.types.core import tStrategy, tModel, tFeature, tIndicator
|
|
14
16
|
from pfund.engines.base_engine import BaseEngine
|
|
15
17
|
from pfund.brokers.broker_backtest import BacktestBroker
|
|
16
18
|
from pfund.strategies.strategy_base import BaseStrategy
|
|
@@ -21,7 +23,7 @@ from pfund.mixins.backtest import BacktestMixin
|
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class BacktestEngine(BaseEngine):
|
|
24
|
-
def __new__(cls, *, env: str='BACKTEST', data_tool:
|
|
26
|
+
def __new__(cls, *, env: str='BACKTEST', data_tool: tSUPPORTED_DATA_TOOLS='pandas', mode: tSUPPORTED_BACKTEST_MODES='vectorized', append_to_strategy_df=False, use_prepared_signals=True, config: ConfigHandler | None=None, **settings):
|
|
25
27
|
if not hasattr(cls, 'mode'):
|
|
26
28
|
cls.mode = mode.lower()
|
|
27
29
|
if not hasattr(cls, 'append_to_strategy_df'):
|
|
@@ -32,7 +34,7 @@ class BacktestEngine(BaseEngine):
|
|
|
32
34
|
cls.use_prepared_signals = use_prepared_signals
|
|
33
35
|
return super().__new__(cls, env, data_tool=data_tool, config=config, **settings)
|
|
34
36
|
|
|
35
|
-
def __init__(self, *, env: str='BACKTEST', data_tool:
|
|
37
|
+
def __init__(self, *, env: str='BACKTEST', data_tool: tSUPPORTED_DATA_TOOLS='pandas', mode: tSUPPORTED_BACKTEST_MODES='vectorized', append_to_strategy_df=False, use_prepared_signals=True, config: ConfigHandler | None=None, **settings):
|
|
36
38
|
super().__init__(env, data_tool=data_tool)
|
|
37
39
|
# avoid re-initialization to implement singleton class correctly
|
|
38
40
|
# if not hasattr(self, '_initialized'):
|
|
@@ -86,11 +88,11 @@ class BacktestEngine(BaseEngine):
|
|
|
86
88
|
def _generate_backtest_id() -> str:
|
|
87
89
|
return uuid.uuid4().hex
|
|
88
90
|
|
|
89
|
-
def _create_backtest_name(self, strat: str):
|
|
91
|
+
def _create_backtest_name(self, strat: str, backtest_id: str, backtest_id_length: int=12):
|
|
90
92
|
local_tz = utils.get_local_timezone()
|
|
91
93
|
utcnow = datetime.datetime.now(tz=local_tz).strftime('%Y-%m-%d_%H:%M:%S_UTC%z')
|
|
92
|
-
|
|
93
|
-
return '.'.join([strat, utcnow,
|
|
94
|
+
trimmed_backtest_id = backtest_id[:backtest_id_length]
|
|
95
|
+
return '.'.join([strat, utcnow, trimmed_backtest_id])
|
|
94
96
|
|
|
95
97
|
@staticmethod
|
|
96
98
|
def _generate_backtest_hash(strategy: BaseStrategy):
|
|
@@ -142,47 +144,36 @@ class BacktestEngine(BaseEngine):
|
|
|
142
144
|
self._write_json(file_name, backtest_json)
|
|
143
145
|
return backtest_json[backtest_hash]
|
|
144
146
|
|
|
145
|
-
def
|
|
146
|
-
splits = backtest_name.split('.')
|
|
147
|
-
strat, backtest_id = splits[0], splits[-1]
|
|
147
|
+
def _output_backtest_results(self, strat: str, df, start_time: float, end_time: float):
|
|
148
148
|
strategy = self.get_strategy(strat)
|
|
149
|
+
backtest_id = self._generate_backtest_id()
|
|
150
|
+
backtest_name = self._create_backtest_name(strat, backtest_id)
|
|
149
151
|
local_tz = utils.get_local_timezone()
|
|
150
152
|
duration = end_time - start_time
|
|
153
|
+
df_file_path = os.path.join(self.config.backtest_path, f'{backtest_name}.parquet')
|
|
151
154
|
backtest_history = {
|
|
152
|
-
'settings': self.settings,
|
|
153
155
|
'metadata': {
|
|
154
156
|
'pfund_version': pf.__version__,
|
|
155
|
-
'backtest_name': backtest_name,
|
|
156
157
|
'backtest_id': backtest_id,
|
|
157
158
|
'backtest_iteration': self._generate_backtest_iteration(strategy),
|
|
158
159
|
'duration': f'{duration:.2f}s' if duration > 1 else f'{duration*1000:.2f}ms',
|
|
159
160
|
'start_time': datetime.datetime.fromtimestamp(start_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
|
|
160
161
|
'end_time': datetime.datetime.fromtimestamp(end_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
|
|
162
|
+
'settings': self.settings,
|
|
161
163
|
},
|
|
162
164
|
'strategy': strategy.to_dict(),
|
|
163
|
-
'
|
|
164
|
-
'df_file_path': os.path.join(self.config.backtest_path, f'{backtest_name_trimmed}.parquet'),
|
|
165
|
-
}
|
|
165
|
+
'result': df_file_path
|
|
166
166
|
}
|
|
167
|
-
self.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
splits = backtest_name.split('.')
|
|
171
|
-
backtest_id_len = 12
|
|
172
|
-
splits[-1] = splits[-1][:backtest_id_len]
|
|
173
|
-
return '.'.join(splits)
|
|
167
|
+
self.data_tool.output_df_to_parquet(df, df_file_path)
|
|
168
|
+
self._write_json(f'{backtest_name}.json', backtest_history)
|
|
169
|
+
return backtest_history
|
|
174
170
|
|
|
175
|
-
def output_backtest_results(self, strat: str, df, start_time: float, end_time: float):
|
|
176
|
-
backtest_name = self._create_backtest_name(strat)
|
|
177
|
-
backtest_name_trimmed = self.trim_backtest_name(backtest_name)
|
|
178
|
-
self.data_tool.output_df_to_parquet(backtest_name_trimmed, df, self.config.backtest_path)
|
|
179
|
-
self._write_backtest_history(backtest_name, backtest_name_trimmed, start_time, end_time)
|
|
180
|
-
|
|
181
171
|
def run(self):
|
|
182
172
|
for broker in self.brokers.values():
|
|
183
173
|
broker.start()
|
|
184
174
|
self.strategy_manager.start()
|
|
185
175
|
|
|
176
|
+
backtests = {}
|
|
186
177
|
if self.mode == 'vectorized':
|
|
187
178
|
for strat, strategy in self.strategy_manager.strategies.items():
|
|
188
179
|
# _dummy strategy is only created for model training, do nothing
|
|
@@ -194,7 +185,8 @@ class BacktestEngine(BaseEngine):
|
|
|
194
185
|
strategy.backtest()
|
|
195
186
|
end_time = time.time()
|
|
196
187
|
df = strategy.get_df()
|
|
197
|
-
self.
|
|
188
|
+
backtest_history: dict = self._output_backtest_results(strat, df, start_time, end_time)
|
|
189
|
+
backtests[strat] = backtest_history
|
|
198
190
|
elif self.mode == 'event_driven':
|
|
199
191
|
for strat, strategy in self.strategy_manager.strategies.items():
|
|
200
192
|
if strat == '_dummy':
|
|
@@ -246,6 +238,7 @@ class BacktestEngine(BaseEngine):
|
|
|
246
238
|
else:
|
|
247
239
|
raise NotImplementedError(f'Backtesting mode {self.mode} is not supported')
|
|
248
240
|
self.strategy_manager.stop(reason='finished backtesting')
|
|
241
|
+
return backtests
|
|
249
242
|
|
|
250
243
|
def end(self):
|
|
251
244
|
self.strategy_manager.stop(reason='finished backtesting')
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import os
|
|
2
4
|
import logging
|
|
3
5
|
import importlib
|
|
@@ -31,7 +33,7 @@ ENV_COLORS = {
|
|
|
31
33
|
class BaseEngine(Singleton):
|
|
32
34
|
_PROCESS_NO_PONG_TOLERANCE_IN_SECONDS = 30
|
|
33
35
|
|
|
34
|
-
def __new__(cls, env, data_tool:
|
|
36
|
+
def __new__(cls, env, data_tool: tSUPPORTED_DATA_TOOLS='pandas', config: ConfigHandler | None=None, **settings):
|
|
35
37
|
if not hasattr(cls, 'env'):
|
|
36
38
|
cls.env = env.upper() if isinstance(env, str) else str(env).upper()
|
|
37
39
|
assert cls.env in SUPPORTED_ENVIRONMENTS, f'env={cls.env} is not supported'
|
|
@@ -54,7 +56,7 @@ class BaseEngine(Singleton):
|
|
|
54
56
|
cls.logging_configurator: LoggingDictConfigurator = set_up_loggers(log_path, logging_config_file_path, user_logging_config=cls.config.logging_config)
|
|
55
57
|
return super().__new__(cls)
|
|
56
58
|
|
|
57
|
-
def __init__(self, env, data_tool:
|
|
59
|
+
def __init__(self, env, data_tool: tSUPPORTED_DATA_TOOLS='pandas', config: ConfigHandler | None=None, **settings):
|
|
58
60
|
# avoid re-initialization to implement singleton class correctly
|
|
59
61
|
if not hasattr(self, '_initialized'):
|
|
60
62
|
self.logger = logging.getLogger('pfund')
|
|
@@ -74,7 +76,7 @@ class BaseEngine(Singleton):
|
|
|
74
76
|
def get_strategy(self, strat: str) -> BaseStrategy | None:
|
|
75
77
|
return self.strategy_manager.get_strategy(strat)
|
|
76
78
|
|
|
77
|
-
def add_strategy(self, strategy:
|
|
79
|
+
def add_strategy(self, strategy: tStrategy, name: str='', is_parallel=False) -> tStrategy:
|
|
78
80
|
return self.strategy_manager.add_strategy(strategy, name=name, is_parallel=is_parallel)
|
|
79
81
|
|
|
80
82
|
def remove_strategy(self, strat: str):
|
|
@@ -9,6 +9,8 @@ components at the highest level such as:
|
|
|
9
9
|
In order to communicate with other processes, it uses ZeroMQ as the core
|
|
10
10
|
message queue.
|
|
11
11
|
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
12
14
|
import time
|
|
13
15
|
from threading import Thread
|
|
14
16
|
|
|
@@ -20,7 +22,7 @@ import schedule
|
|
|
20
22
|
|
|
21
23
|
from pfund.engines.base_engine import BaseEngine
|
|
22
24
|
from pfund.brokers.broker_base import BaseBroker
|
|
23
|
-
from pfund.utils.utils import flatten_dict
|
|
25
|
+
from pfund.utils.utils import flatten_dict, is_port_in_use
|
|
24
26
|
from pfund.zeromq import ZeroMQ
|
|
25
27
|
from pfund.config_handler import ConfigHandler
|
|
26
28
|
|
|
@@ -28,13 +30,13 @@ from pfund.config_handler import ConfigHandler
|
|
|
28
30
|
class TradeEngine(BaseEngine):
|
|
29
31
|
zmq_ports = {}
|
|
30
32
|
|
|
31
|
-
def __new__(cls, *, env: str='PAPER', data_tool:
|
|
33
|
+
def __new__(cls, *, env: str='PAPER', data_tool: tSUPPORTED_DATA_TOOLS='pandas', zmq_port=5557, config: ConfigHandler | None=None, **settings):
|
|
32
34
|
if not hasattr(cls, 'zmq_port'):
|
|
33
35
|
assert isinstance(zmq_port, int), f'{zmq_port=} must be an integer'
|
|
34
36
|
cls._zmq_port = zmq_port
|
|
35
37
|
return super().__new__(cls, env, data_tool=data_tool, config=config, **settings)
|
|
36
38
|
|
|
37
|
-
def __init__(self, *, env: str='PAPER', data_tool:
|
|
39
|
+
def __init__(self, *, env: str='PAPER', data_tool: tSUPPORTED_DATA_TOOLS='pandas', zmq_port=5557, config: ConfigHandler | None=None, **settings):
|
|
38
40
|
super().__init__(env, data_tool=data_tool)
|
|
39
41
|
# avoid re-initialization to implement singleton class correctly
|
|
40
42
|
if not hasattr(self, '_initialized'):
|
|
@@ -55,15 +57,12 @@ class TradeEngine(BaseEngine):
|
|
|
55
57
|
def _assign_zmq_ports(self) -> dict:
|
|
56
58
|
_assigned_ports = []
|
|
57
59
|
def _is_port_available(_port):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else:
|
|
65
|
-
_assigned_ports.append(_port)
|
|
66
|
-
return True
|
|
60
|
+
_is_port_assigned = (_port in _assigned_ports)
|
|
61
|
+
if is_port_in_use(_port) or _is_port_assigned:
|
|
62
|
+
return False
|
|
63
|
+
else:
|
|
64
|
+
_assigned_ports.append(_port)
|
|
65
|
+
return True
|
|
67
66
|
def _get_port(start_port=None):
|
|
68
67
|
_port = start_port or self._zmq_port
|
|
69
68
|
if _is_port_available(_port):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from typing import TYPE_CHECKING
|
|
2
4
|
if TYPE_CHECKING:
|
|
3
5
|
from pfund.types.common_literals import tSUPPORTED_BACKTEST_MODES, tSUPPORTED_DATA_TOOLS
|
|
@@ -7,10 +9,10 @@ from pfund.config_handler import ConfigHandler
|
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class TrainEngine(BacktestEngine):
|
|
10
|
-
def __new__(cls, *, data_tool:
|
|
12
|
+
def __new__(cls, *, data_tool: tSUPPORTED_DATA_TOOLS='pandas', mode: tSUPPORTED_BACKTEST_MODES='vectorized', config: ConfigHandler | None=None, **settings):
|
|
11
13
|
return super().__new__(cls, env='TRAIN', data_tool=data_tool, mode=mode, config=config, **settings)
|
|
12
14
|
|
|
13
|
-
def __init__(self, *, data_tool:
|
|
15
|
+
def __init__(self, *, data_tool: tSUPPORTED_DATA_TOOLS='pandas', mode: tSUPPORTED_BACKTEST_MODES='vectorized', config: ConfigHandler | None=None, **settings):
|
|
14
16
|
super().__init__(env='TRAIN', data_tool=data_tool)
|
|
15
17
|
# avoid re-initialization to implement singleton class correctly
|
|
16
18
|
# if not hasattr(self, '_initialized'):
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from pfund.exchanges.exchange_base import BaseExchange
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Exchange(BaseExchange):
|
|
7
|
+
SUPPORTED_CATEGORIES = ['linear', 'inverse', 'spot', 'option']
|
|
8
|
+
PTYPE_TO_CATEGORY = {
|
|
9
|
+
'PERP': 'linear',
|
|
10
|
+
'FUT': 'linear',
|
|
11
|
+
'IPERP': 'inverse',
|
|
12
|
+
'IFUT': 'inverse',
|
|
13
|
+
'SPOT': 'spot',
|
|
14
|
+
'OPT': 'option',
|
|
15
|
+
}
|
|
16
|
+
def __new__(cls, env: str, ptype: str):
|
|
17
|
+
from pfund.exchanges.binance.linear.exchange import ExchangeLinear
|
|
18
|
+
|
|
19
|
+
ptype = ptype.upper()
|
|
20
|
+
category = cls.PTYPE_TO_CATEGORY[ptype]
|
|
21
|
+
|
|
22
|
+
if category == 'linear':
|
|
23
|
+
instance = super().__new__(ExchangeLinear)
|
|
24
|
+
instance.category = category
|
|
25
|
+
return instance
|
|
26
|
+
# EXTEND: Add other categories
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError(f"Invalid {category=}")
|
|
29
|
+
|
|
30
|
+
def __init__(self, env: str, ptype: str):
|
|
31
|
+
exch = Path(__file__).parent.name
|
|
32
|
+
super().__init__(env, exch)
|
|
33
|
+
|
|
34
|
+
# FIXME: temporarily override the method, remove it later
|
|
35
|
+
def _setup_configs(self):
|
|
36
|
+
pass
|
|
37
|
+
|