pfund 0.0.1.dev5__tar.gz → 0.0.1.dev6__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.dev5 → pfund-0.0.1.dev6}/PKG-INFO +4 -7
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/README.md +0 -1
- pfund-0.0.1.dev6/pfund/analyzer.py +3 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/configuration.py +0 -7
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config_handler.py +38 -6
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/const/commons.py +3 -1
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/const/paths.py +8 -4
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/data_tools/data_tool_base.py +0 -5
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/data_tools/data_tool_pandas.py +8 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/engines/backtest_engine.py +123 -15
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/engines/base_engine.py +12 -8
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/engines/trade_engine.py +10 -11
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/engines/train_engine.py +6 -4
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/exchange.py +1 -1
- pfund-0.0.1.dev6/pfund/investment_profile.py +16 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/mixins/backtest.py +31 -13
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/models/model_backtest.py +22 -11
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/models/model_base.py +22 -8
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/portfolio.py +1 -1
- pfund-0.0.1.dev6/pfund/strategies/allocation_strategy.py +10 -0
- pfund-0.0.1.dev6/pfund/strategies/diversification_strategy.py +13 -0
- pfund-0.0.1.dev6/pfund/strategies/hedging_strategy.py +10 -0
- pfund-0.0.1.dev6/pfund/strategies/optimization_strategy.py +13 -0
- pfund-0.0.1.dev6/pfund/strategies/portfolio_strategy.py +27 -0
- pfund-0.0.1.dev6/pfund/strategies/rebalancing_strategy.py +10 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/strategies/strategy_backtest.py +21 -8
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/strategies/strategy_base.py +30 -16
- pfund-0.0.1.dev6/pfund/types/backtest.py +10 -0
- pfund-0.0.1.dev6/pfund/types/common_literals.py +25 -0
- pfund-0.0.1.dev6/pfund/types/core.py +14 -0
- pfund-0.0.1.dev6/pfund/universe.py +19 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/utils/utils.py +2 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pyproject.toml +14 -8
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/LICENSE +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/CONTRIBUTING.md +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/accounts/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/accounts/account_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/accounts/account_crypto.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/accounts/account_ib.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/adapter.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/balances/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/balances/balance_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/balances/balance_crypto.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/balances/balance_ib.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/broker_backtest.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/broker_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/broker_crypto.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/broker_live.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/ib/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/ib/broker_ib.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/ib/ib_api.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/ib/ib_client.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/brokers/ib/ib_wrapper.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/cli/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/cli/commands/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/cli/commands/config.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/cli/commands/docker_compose.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/cli/main.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/config.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/lot_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/lot_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/lot_sizes_option.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/lot_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/pdt_matchings_inverse.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/pdt_matchings_linear.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/pdt_matchings_option.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/pdt_matchings_spot.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/tick_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/tick_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/tick_sizes_option.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/bybit/tick_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/ib/config.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/config/logging.yml +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/const/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/const/_zmq_routes.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/data_bar.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/data_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/data_quote.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/data_tick.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/data_time_based.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/resolution.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/datas/timeframe.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/engines/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/engines/test_engine.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/errors.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_inverse +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_linear +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_option +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_spot +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_inverse +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_linear +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_option +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_spot +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/types.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/bybit/ws_api.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/exchange_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/rest_api_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/exchanges/ws_api_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/account_summary_tags.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/client.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/comm.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/commission_report.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/common.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/connection.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/contract.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/decoder.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/enum_implem.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/errors.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/execution.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/ibapi.pyproj +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/message.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/news.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/object_implem.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/order.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/order_condition.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/order_state.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/orderdecoder.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/reader.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/scanner.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/server_versions.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/softdollartier.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/tag_value.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/ticktype.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/utils.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/externals/ibapi/wrapper.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/indicators/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/indicators/indicator_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/indicators/ta_indicator.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/indicators/talib_indicator.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/main.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/base_manager.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/connection_manager.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/data_manager.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/order_manager.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/portfolio_manager.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/risk_manager.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/managers/strategy_manager.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/models/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/models/model_meta.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/models/pytorch_model.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/models/sklearn_model.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/orders/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/orders/order_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/orders/order_crypto.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/orders/order_ib.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/orders/order_statuses.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/orders/order_time_in_force.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/plogging/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/plogging/config.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/plogging/filters.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/plogging/formatter.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/plogging/handlers.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/positions/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/positions/position_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/positions/position_crypto.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/positions/position_ib.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/products/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/products/product_base.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/products/product_crypto.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/products/product_ib.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/risk_monitor.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/strategies/__init__.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/strategies/strategy_meta.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/utils/aliases.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/utils/envs.py +0 -0
- {pfund-0.0.1.dev5 → pfund-0.0.1.dev6}/pfund/zeromq.py +0 -0
|
@@ -1,28 +1,26 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pfund
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev6
|
|
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
|
|
7
7
|
Keywords: trading,algo-trading,stocks,cryptos,cryptocurrencies,TradFi,CeFi,DeFi,portfolio management,investment,backtesting,machine learning
|
|
8
8
|
Author: Stephen Yau
|
|
9
9
|
Author-email: softwareentrepreneer+pfund@gmail.com
|
|
10
|
-
Requires-Python: >=3.10,<3.
|
|
10
|
+
Requires-Python: >=3.10,<3.13
|
|
11
11
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
16
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
16
|
-
Requires-Dist:
|
|
17
|
+
Requires-Dist: gitpython (>=3.1.43,<4.0.0)
|
|
17
18
|
Requires-Dist: pfeed (>=0.0.1.dev5,<0.0.2)
|
|
18
19
|
Requires-Dist: platformdirs (>=4.2.0,<5.0.0)
|
|
19
|
-
Requires-Dist: psutil (>=5.9.8,<6.0.0)
|
|
20
20
|
Requires-Dist: python-telegram-bot (>=20.7,<21.0)
|
|
21
21
|
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
22
|
-
Requires-Dist: pyzmq (>=25.1.2,<26.0.0)
|
|
23
22
|
Requires-Dist: rich (>=13.7.0,<14.0.0)
|
|
24
23
|
Requires-Dist: schedule (>=1.2.1,<2.0.0)
|
|
25
|
-
Requires-Dist: ta (>=0.11.0,<0.12.0)
|
|
26
24
|
Requires-Dist: websocket-client (>=1.7.0,<2.0.0)
|
|
27
25
|
Project-URL: Documentation, https://pfund-docs.pfund.ai
|
|
28
26
|
Project-URL: Repository, https://github.com/PFund-Software-Ltd/pfund
|
|
@@ -131,7 +129,6 @@ This overview already omits some intricate steps, such as data handling and API
|
|
|
131
129
|
|
|
132
130
|
|
|
133
131
|
## Installation
|
|
134
|
-
> Python 3.12 is not supported until [PyTorch]supports it
|
|
135
132
|
|
|
136
133
|
### Using [Poetry] (Recommended)
|
|
137
134
|
```bash
|
|
@@ -6,9 +6,6 @@ from pfund.const.paths import PROJ_CONFIG_PATH
|
|
|
6
6
|
from pfund.utils.utils import short_path
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
has_printed = False
|
|
10
|
-
|
|
11
|
-
|
|
12
9
|
class Configuration:
|
|
13
10
|
def __init__(self, config_dir, config_name):
|
|
14
11
|
self.config_dir = config_dir.lower()
|
|
@@ -24,16 +21,12 @@ class Configuration:
|
|
|
24
21
|
return self.config_dir
|
|
25
22
|
|
|
26
23
|
def read_config(self, config_name):
|
|
27
|
-
global has_printed
|
|
28
24
|
file_path = f'{self.config_path}/{config_name}.yml'
|
|
29
25
|
# short_file_path = short_path(file_path)
|
|
30
26
|
if not os.path.exists(file_path):
|
|
31
27
|
print(f'cannot find config {file_path}')
|
|
32
28
|
else:
|
|
33
29
|
with open(file_path, 'r') as f:
|
|
34
|
-
if not has_printed:
|
|
35
|
-
has_printed = True
|
|
36
|
-
print(f'loaded config {file_path}')
|
|
37
30
|
return list(yaml.safe_load_all(f))
|
|
38
31
|
|
|
39
32
|
def write_config(self, config_name, content):
|
|
@@ -25,11 +25,12 @@ def _custom_excepthook(exception_class: type[BaseException], exception: BaseExce
|
|
|
25
25
|
logging.getLogger(PROJ_NAME).exception('Uncaught exception:')
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def
|
|
28
|
+
def dynamic_import(path: str):
|
|
29
29
|
for item in os.listdir(path):
|
|
30
30
|
item_path = os.path.join(path, item)
|
|
31
31
|
if os.path.isdir(item_path) and '__pycache__' not in item_path:
|
|
32
|
-
for type_ in ['strategies', 'models', 'features', 'indicators'
|
|
32
|
+
for type_ in ['strategies', 'models', 'features', 'indicators',
|
|
33
|
+
'backtests', 'notebooks', 'spreadsheets', 'dashboards']:
|
|
33
34
|
if type_ in path:
|
|
34
35
|
break
|
|
35
36
|
else:
|
|
@@ -65,17 +66,48 @@ class ConfigHandler:
|
|
|
65
66
|
config = {}
|
|
66
67
|
return cls(**config)
|
|
67
68
|
|
|
69
|
+
@property
|
|
70
|
+
def strategy_path(self):
|
|
71
|
+
return f'{self.data_path}/hub/strategies'
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def model_path(self):
|
|
75
|
+
return f'{self.data_path}/hub/models'
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def feature_path(self):
|
|
79
|
+
return f'{self.data_path}/hub/features'
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def indicator_path(self):
|
|
83
|
+
return f'{self.data_path}/hub/indicators'
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def backtest_path(self):
|
|
87
|
+
return f'{self.data_path}/backtests'
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def notebook_path(self):
|
|
91
|
+
return f'{self.data_path}/templates/notebooks'
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def spreadsheet_path(self):
|
|
95
|
+
return f'{self.data_path}/templates/spreadsheets'
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def dashboard_path(self):
|
|
99
|
+
return f'{self.data_path}/templates/dashboards'
|
|
100
|
+
|
|
68
101
|
def __post_init__(self):
|
|
69
102
|
self.logging_config = self.logging_config or {}
|
|
70
103
|
|
|
71
|
-
strategy_path, model_path
|
|
72
|
-
|
|
73
|
-
for path in [strategy_path, model_path, feature_path, indicator_path]:
|
|
104
|
+
for path in [self.strategy_path, self.model_path, self.feature_path, self.indicator_path,
|
|
105
|
+
self.backtest_path, self.notebook_path, self.spreadsheet_path, self.dashboard_path]:
|
|
74
106
|
if not os.path.exists(path):
|
|
75
107
|
os.makedirs(path)
|
|
76
108
|
print(f'created {path}')
|
|
77
109
|
sys.path.append(path)
|
|
78
|
-
|
|
110
|
+
dynamic_import(path)
|
|
79
111
|
|
|
80
112
|
if self.use_fork_process and sys.platform != 'win32':
|
|
81
113
|
multiprocessing.set_start_method('fork', force=True)
|
|
@@ -18,4 +18,6 @@ SUPPORTED_TIMEFRAMES = [
|
|
|
18
18
|
'weeks', 'week', 'w',
|
|
19
19
|
'months', 'month', 'M',
|
|
20
20
|
]
|
|
21
|
-
SUPPORTED_DATA_CHANNELS = ['orderbook', 'tradebook', 'kline']
|
|
21
|
+
SUPPORTED_DATA_CHANNELS = ['orderbook', 'tradebook', 'kline']
|
|
22
|
+
SUPPORTED_BACKTEST_MODES = ['vectorized', 'event_driven']
|
|
23
|
+
SUPPORTED_DATA_TOOLS = ['pandas']
|
|
@@ -16,7 +16,11 @@ LOG_PATH = Path(user_log_dir()) / PROJ_NAME
|
|
|
16
16
|
USER_CONFIG_PATH = Path(user_config_dir()) / PROJ_NAME
|
|
17
17
|
USER_CONFIG_FILE_PATH = USER_CONFIG_PATH / f'{PROJ_NAME}_config.yml'
|
|
18
18
|
DATA_PATH = Path(user_data_dir()) / PROJ_NAME
|
|
19
|
-
STRATEGY_PATH = DATA_PATH / 'strategies'
|
|
20
|
-
MODEL_PATH = DATA_PATH / 'models'
|
|
21
|
-
FEATURE_PATH = DATA_PATH / 'features'
|
|
22
|
-
INDICATOR_PATH = DATA_PATH / 'indicators'
|
|
19
|
+
STRATEGY_PATH = DATA_PATH / 'hub' / 'strategies'
|
|
20
|
+
MODEL_PATH = DATA_PATH / 'hub' / 'models'
|
|
21
|
+
FEATURE_PATH = DATA_PATH / 'hub' / 'features'
|
|
22
|
+
INDICATOR_PATH = DATA_PATH / 'hub' / 'indicators'
|
|
23
|
+
BACKTEST_PATH = DATA_PATH / 'backtests'
|
|
24
|
+
NOTEBOOK_PATH = DATA_PATH / 'templates' / 'notebooks'
|
|
25
|
+
SPREADSHEET_PATH = DATA_PATH / 'templates' / 'spreadsheets'
|
|
26
|
+
DASHBOARD_PATH = DATA_PATH / 'templates' / 'dashboards'
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
from typing import Literal
|
|
2
|
-
|
|
3
1
|
from pfund.datas.data_base import BaseData
|
|
4
2
|
from pfund.utils.utils import convert_ts_to_dt
|
|
5
3
|
|
|
6
4
|
|
|
7
|
-
DataTool = Literal['pandas']
|
|
8
|
-
|
|
9
|
-
|
|
10
5
|
class BaseDataTool:
|
|
11
6
|
def __init__(self):
|
|
12
7
|
self.train_periods = {} # {product: ('start_date', 'end_date')}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from collections import defaultdict
|
|
2
2
|
from decimal import Decimal
|
|
3
|
+
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
import pandas as pd
|
|
5
6
|
|
|
@@ -150,6 +151,13 @@ class PandasDataTool(BaseDataTool):
|
|
|
150
151
|
index=pd.MultiIndex(levels=[[]]*len(index_names), codes=[[]]*len(index_names), names=index_names)
|
|
151
152
|
)
|
|
152
153
|
|
|
154
|
+
def output_df_to_parquet(self, name: str, df: pd.DataFrame,path: str | Path):
|
|
155
|
+
if '.parquet' not in name:
|
|
156
|
+
name = name + '.parquet'
|
|
157
|
+
if type(path) is str:
|
|
158
|
+
path = Path(path)
|
|
159
|
+
df.to_parquet(path / name)
|
|
160
|
+
|
|
153
161
|
def _create_multi_index(self, index_data: dict, index_names: list[str]) -> pd.MultiIndex:
|
|
154
162
|
return pd.MultiIndex.from_tuples([tuple(index_data[name] for name in index_names)], names=index_names)
|
|
155
163
|
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
|
|
1
|
+
import hashlib
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
import datetime
|
|
5
|
+
import json
|
|
6
|
+
import uuid
|
|
2
7
|
|
|
3
|
-
from
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from pfund.types.common_literals import tSUPPORTED_BACKTEST_MODES, tSUPPORTED_DATA_TOOLS
|
|
11
|
+
|
|
12
|
+
import pfund as pf
|
|
13
|
+
from pfund.types.core import tStrategy, tModel, tFeature, tIndicator
|
|
4
14
|
from pfund.engines.base_engine import BaseEngine
|
|
5
|
-
from pfund.models.model_base import BaseModel, BaseFeature
|
|
6
|
-
from pfund.indicators.indicator_base import BaseIndicator
|
|
7
15
|
from pfund.brokers.broker_backtest import BacktestBroker
|
|
8
16
|
from pfund.strategies.strategy_base import BaseStrategy
|
|
9
17
|
from pfund.strategies.strategy_backtest import BacktestStrategy
|
|
10
|
-
from pfund.const.commons import *
|
|
11
18
|
from pfund.config_handler import ConfigHandler
|
|
19
|
+
from pfund.utils import utils
|
|
20
|
+
from pfund.mixins.backtest import BacktestMixin
|
|
12
21
|
|
|
13
22
|
|
|
14
|
-
BacktestMode = Literal['vectorized', 'event_driven']
|
|
15
|
-
|
|
16
|
-
|
|
17
23
|
class BacktestEngine(BaseEngine):
|
|
18
|
-
def __new__(cls, *, env: str='BACKTEST', data_tool:
|
|
24
|
+
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):
|
|
19
25
|
if not hasattr(cls, 'mode'):
|
|
20
26
|
cls.mode = mode.lower()
|
|
21
27
|
if not hasattr(cls, 'append_to_strategy_df'):
|
|
@@ -26,13 +32,15 @@ class BacktestEngine(BaseEngine):
|
|
|
26
32
|
cls.use_prepared_signals = use_prepared_signals
|
|
27
33
|
return super().__new__(cls, env, data_tool=data_tool, config=config, **settings)
|
|
28
34
|
|
|
29
|
-
def __init__(self, *, env: str='BACKTEST', data_tool:
|
|
35
|
+
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):
|
|
30
36
|
super().__init__(env, data_tool=data_tool)
|
|
31
37
|
# avoid re-initialization to implement singleton class correctly
|
|
32
38
|
# if not hasattr(self, '_initialized'):
|
|
33
39
|
# pass
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
# HACK: since python doesn't support dynamic typing, true return type should be subclass of BacktestMixin and tStrategy
|
|
42
|
+
# write -> BacktestMixin | tStrategy for better intellisense in IDEs
|
|
43
|
+
def add_strategy(self, strategy: tStrategy, name: str='', is_parallel=False) -> BacktestMixin | tStrategy:
|
|
36
44
|
is_dummy_strategy_exist = '_dummy' in self.strategy_manager.strategies
|
|
37
45
|
assert not is_dummy_strategy_exist, 'dummy strategy is being used for model backtesting, adding another strategy is not allowed'
|
|
38
46
|
if is_parallel:
|
|
@@ -42,7 +50,7 @@ class BacktestEngine(BaseEngine):
|
|
|
42
50
|
strategy = BacktestStrategy(type(strategy), *strategy._args, **strategy._kwargs)
|
|
43
51
|
return super().add_strategy(strategy, name=name, is_parallel=is_parallel)
|
|
44
52
|
|
|
45
|
-
def add_model(self, model:
|
|
53
|
+
def add_model(self, model: tModel, name: str='', model_path: str='', is_load: bool=True) -> BacktestMixin | tModel:
|
|
46
54
|
'''Add model without creating a strategy (using dummy strategy)'''
|
|
47
55
|
is_non_dummy_strategy_exist = bool([strat for strat in self.strategy_manager.strategies if strat != '_dummy'])
|
|
48
56
|
assert not is_non_dummy_strategy_exist, 'Please use strategy.add_model(...) instead of engine.add_model(...) when a strategy is already created'
|
|
@@ -57,10 +65,10 @@ class BacktestEngine(BaseEngine):
|
|
|
57
65
|
model = strategy.add_model(model, name=name, model_path=model_path, is_load=is_load)
|
|
58
66
|
return model
|
|
59
67
|
|
|
60
|
-
def add_feature(self, feature:
|
|
68
|
+
def add_feature(self, feature: tFeature, name: str='', feature_path: str='', is_load: bool=True) -> BacktestMixin | tFeature:
|
|
61
69
|
return self.add_model(feature, name=name, model_path=feature_path, is_load=is_load)
|
|
62
70
|
|
|
63
|
-
def add_indicator(self, indicator:
|
|
71
|
+
def add_indicator(self, indicator: tIndicator, name: str='', indicator_path: str='', is_load: bool=True) -> BacktestMixin | tIndicator:
|
|
64
72
|
return self.add_model(indicator, name=name, model_path=indicator_path, is_load=is_load)
|
|
65
73
|
|
|
66
74
|
def add_broker(self, bkr: str):
|
|
@@ -73,12 +81,108 @@ class BacktestEngine(BaseEngine):
|
|
|
73
81
|
self.brokers[bkr] = broker
|
|
74
82
|
self.logger.debug(f'added {bkr=}')
|
|
75
83
|
return broker
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _generate_backtest_id() -> str:
|
|
87
|
+
return uuid.uuid4().hex
|
|
88
|
+
|
|
89
|
+
def _create_backtest_name(self, strat: str):
|
|
90
|
+
local_tz = utils.get_local_timezone()
|
|
91
|
+
utcnow = datetime.datetime.now(tz=local_tz).strftime('%Y-%m-%d_%H:%M:%S_UTC%z')
|
|
92
|
+
backtest_id = self._generate_backtest_id()
|
|
93
|
+
return '.'.join([strat, utcnow, backtest_id])
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _generate_backtest_hash(strategy: BaseStrategy):
|
|
97
|
+
'''Generate hash based on strategy for backtest traceability
|
|
98
|
+
backtest_hash is used to identify if the backtests are generated by the same strategy.
|
|
99
|
+
Useful for avoiding overfitting the strategy on the same dataset.
|
|
100
|
+
'''
|
|
101
|
+
# REVIEW: currently only use strategy to generate hash, may include other settings in the future
|
|
102
|
+
strategy_dict = strategy.to_dict()
|
|
103
|
+
# since conceptually backtest_hash should be the same regardless of the
|
|
104
|
+
# strategy_signature (params) and data_signatures (e.g. backtest_kwargs, train_kwargs, data_source, resolution etc.)
|
|
105
|
+
# remove them
|
|
106
|
+
del strategy_dict['strategy_signature']
|
|
107
|
+
del strategy_dict['data_signatures']
|
|
108
|
+
strategy_str = json.dumps(strategy_dict)
|
|
109
|
+
return hashlib.sha256(strategy_str.encode()).hexdigest()
|
|
110
|
+
|
|
111
|
+
def read_json(self, file_name: str) -> dict:
|
|
112
|
+
'''Reads json file from backtest_path'''
|
|
113
|
+
file_path = os.path.join(self.config.backtest_path, file_name)
|
|
114
|
+
backtest_json = {}
|
|
115
|
+
try:
|
|
116
|
+
if os.path.exists(file_path):
|
|
117
|
+
with open(file_path, 'r') as f:
|
|
118
|
+
backtest_json = json.load(f)
|
|
119
|
+
except:
|
|
120
|
+
self.logger.exception(f"Error reading from {file_path}:")
|
|
121
|
+
return backtest_json
|
|
122
|
+
|
|
123
|
+
def _write_json(self, file_name: str, json_file: dict) -> None:
|
|
124
|
+
'''Writes json file to backtest_path'''
|
|
125
|
+
file_path = os.path.join(self.config.backtest_path, file_name)
|
|
126
|
+
try:
|
|
127
|
+
with open(file_path, 'w') as f:
|
|
128
|
+
json.dump(json_file, f, indent=4)
|
|
129
|
+
except:
|
|
130
|
+
self.logger.exception(f"Error writing to {file_path}:")
|
|
131
|
+
|
|
132
|
+
def _generate_backtest_iteration(self, strategy: BaseStrategy) -> int:
|
|
133
|
+
'''Generate backtest iteration number for the same backtest_hash.
|
|
134
|
+
Read the existing backtest.json file to get the iteration number for the same strategy hash
|
|
135
|
+
If the backtest hash is not found, create a new entry with iteration number 1
|
|
136
|
+
else increment the iteration number by 1.
|
|
137
|
+
'''
|
|
138
|
+
backtest_hash = self._generate_backtest_hash(strategy)
|
|
139
|
+
file_name = 'backtest.json'
|
|
140
|
+
backtest_json = self.read_json(file_name)
|
|
141
|
+
backtest_json[backtest_hash] = backtest_json.get(backtest_hash, 0) + 1
|
|
142
|
+
self._write_json(file_name, backtest_json)
|
|
143
|
+
return backtest_json[backtest_hash]
|
|
144
|
+
|
|
145
|
+
def _write_backtest_history(self, backtest_name: str, backtest_name_trimmed: str, start_time: float, end_time: float):
|
|
146
|
+
splits = backtest_name.split('.')
|
|
147
|
+
strat, backtest_id = splits[0], splits[-1]
|
|
148
|
+
strategy = self.get_strategy(strat)
|
|
149
|
+
local_tz = utils.get_local_timezone()
|
|
150
|
+
duration = end_time - start_time
|
|
151
|
+
backtest_history = {
|
|
152
|
+
'settings': self.settings,
|
|
153
|
+
'metadata': {
|
|
154
|
+
'pfund_version': pf.__version__,
|
|
155
|
+
'backtest_name': backtest_name,
|
|
156
|
+
'backtest_id': backtest_id,
|
|
157
|
+
'backtest_iteration': self._generate_backtest_iteration(strategy),
|
|
158
|
+
'duration': f'{duration:.2f}s' if duration > 1 else f'{duration*1000:.2f}ms',
|
|
159
|
+
'start_time': datetime.datetime.fromtimestamp(start_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
|
|
160
|
+
'end_time': datetime.datetime.fromtimestamp(end_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
|
|
161
|
+
},
|
|
162
|
+
'strategy': strategy.to_dict(),
|
|
163
|
+
'results': {
|
|
164
|
+
'df_file_path': os.path.join(self.config.backtest_path, f'{backtest_name_trimmed}.parquet'),
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
self._write_json(f'{backtest_name_trimmed}.json', backtest_history)
|
|
168
|
+
|
|
169
|
+
def trim_backtest_name(self, backtest_name: str) -> str:
|
|
170
|
+
splits = backtest_name.split('.')
|
|
171
|
+
backtest_id_len = 12
|
|
172
|
+
splits[-1] = splits[-1][:backtest_id_len]
|
|
173
|
+
return '.'.join(splits)
|
|
174
|
+
|
|
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)
|
|
76
180
|
|
|
77
181
|
def run(self):
|
|
78
182
|
for broker in self.brokers.values():
|
|
79
183
|
broker.start()
|
|
80
184
|
self.strategy_manager.start()
|
|
81
|
-
|
|
185
|
+
|
|
82
186
|
if self.mode == 'vectorized':
|
|
83
187
|
for strat, strategy in self.strategy_manager.strategies.items():
|
|
84
188
|
# _dummy strategy is only created for model training, do nothing
|
|
@@ -86,7 +190,11 @@ class BacktestEngine(BaseEngine):
|
|
|
86
190
|
continue
|
|
87
191
|
if not hasattr(strategy, 'backtest'):
|
|
88
192
|
raise Exception(f'Strategy {strat} does not have backtest() method, cannot run vectorized backtesting')
|
|
193
|
+
start_time = time.time()
|
|
89
194
|
strategy.backtest()
|
|
195
|
+
end_time = time.time()
|
|
196
|
+
df = strategy.get_df()
|
|
197
|
+
self.output_backtest_results(strat, df, start_time, end_time)
|
|
90
198
|
elif self.mode == 'event_driven':
|
|
91
199
|
for strat, strategy in self.strategy_manager.strategies.items():
|
|
92
200
|
if strat == '_dummy':
|
|
@@ -2,14 +2,18 @@ import os
|
|
|
2
2
|
import logging
|
|
3
3
|
import importlib
|
|
4
4
|
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from pfund.types.core import tStrategy
|
|
8
|
+
from pfund.types.common_literals import tSUPPORTED_DATA_TOOLS
|
|
9
|
+
|
|
5
10
|
from rich.console import Console
|
|
6
11
|
|
|
7
12
|
from pfund.utils.utils import Singleton
|
|
8
|
-
from pfund.data_tools.data_tool_base import DataTool
|
|
9
13
|
from pfund.strategies.strategy_base import BaseStrategy
|
|
10
14
|
from pfund.brokers.broker_base import BaseBroker
|
|
11
15
|
from pfund.managers.strategy_manager import StrategyManager
|
|
12
|
-
from pfund.const.commons import
|
|
16
|
+
from pfund.const.commons import SUPPORTED_ENVIRONMENTS, SUPPORTED_BROKERS
|
|
13
17
|
from pfund.config_handler import ConfigHandler
|
|
14
18
|
from pfund.plogging import set_up_loggers
|
|
15
19
|
from pfund.plogging.config import LoggingDictConfigurator
|
|
@@ -27,9 +31,9 @@ ENV_COLORS = {
|
|
|
27
31
|
class BaseEngine(Singleton):
|
|
28
32
|
_PROCESS_NO_PONG_TOLERANCE_IN_SECONDS = 30
|
|
29
33
|
|
|
30
|
-
def __new__(cls, env, data_tool:
|
|
34
|
+
def __new__(cls, env, data_tool: 'tSUPPORTED_DATA_TOOLS'='pandas', config: ConfigHandler | None=None, **settings):
|
|
31
35
|
if not hasattr(cls, 'env'):
|
|
32
|
-
cls.env = env.upper() if
|
|
36
|
+
cls.env = env.upper() if isinstance(env, str) else str(env).upper()
|
|
33
37
|
assert cls.env in SUPPORTED_ENVIRONMENTS, f'env={cls.env} is not supported'
|
|
34
38
|
|
|
35
39
|
# TODO, do NOT allow LIVE env for now
|
|
@@ -50,7 +54,7 @@ class BaseEngine(Singleton):
|
|
|
50
54
|
cls.logging_configurator: LoggingDictConfigurator = set_up_loggers(log_path, logging_config_file_path, user_logging_config=cls.config.logging_config)
|
|
51
55
|
return super().__new__(cls)
|
|
52
56
|
|
|
53
|
-
def __init__(self, env, data_tool:
|
|
57
|
+
def __init__(self, env, data_tool: 'tSUPPORTED_DATA_TOOLS'='pandas', config: ConfigHandler | None=None, **settings):
|
|
54
58
|
# avoid re-initialization to implement singleton class correctly
|
|
55
59
|
if not hasattr(self, '_initialized'):
|
|
56
60
|
self.logger = logging.getLogger('pfund')
|
|
@@ -70,7 +74,7 @@ class BaseEngine(Singleton):
|
|
|
70
74
|
def get_strategy(self, strat: str) -> BaseStrategy | None:
|
|
71
75
|
return self.strategy_manager.get_strategy(strat)
|
|
72
76
|
|
|
73
|
-
def add_strategy(self, strategy:
|
|
77
|
+
def add_strategy(self, strategy: 'tStrategy', name: str='', is_parallel=False) -> 'tStrategy':
|
|
74
78
|
return self.strategy_manager.add_strategy(strategy, name=name, is_parallel=is_parallel)
|
|
75
79
|
|
|
76
80
|
def remove_strategy(self, strat: str):
|
|
@@ -83,7 +87,7 @@ class BaseEngine(Singleton):
|
|
|
83
87
|
bkr = bkr.upper()
|
|
84
88
|
assert bkr in SUPPORTED_BROKERS, f'broker {bkr} is not supported'
|
|
85
89
|
if bkr == 'CRYPTO':
|
|
86
|
-
Broker = getattr(importlib.import_module(f'pfund.brokers.broker_{bkr.lower()}'),
|
|
90
|
+
Broker = getattr(importlib.import_module(f'pfund.brokers.broker_{bkr.lower()}'), 'CryptoBroker')
|
|
87
91
|
elif bkr == 'IB':
|
|
88
|
-
Broker = getattr(importlib.import_module(f'pfund.brokers.ib.broker_{bkr.lower()}'),
|
|
92
|
+
Broker = getattr(importlib.import_module(f'pfund.brokers.ib.broker_{bkr.lower()}'), 'IBBroker')
|
|
89
93
|
return Broker
|
|
@@ -8,19 +8,19 @@ components at the highest level such as:
|
|
|
8
8
|
strategies (your trading strategies)
|
|
9
9
|
In order to communicate with other processes, it uses ZeroMQ as the core
|
|
10
10
|
message queue.
|
|
11
|
-
|
|
12
|
-
Please refer to #TODO for more examples.
|
|
13
11
|
"""
|
|
14
12
|
import time
|
|
15
13
|
from threading import Thread
|
|
16
14
|
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from pfund.types.common_literals import tSUPPORTED_DATA_TOOLS
|
|
18
|
+
|
|
17
19
|
import schedule
|
|
18
20
|
import psutil
|
|
19
21
|
|
|
20
|
-
from pfund.data_tools.data_tool_base import DataTool
|
|
21
22
|
from pfund.engines.base_engine import BaseEngine
|
|
22
23
|
from pfund.brokers.broker_base import BaseBroker
|
|
23
|
-
from pfund.const.commons import *
|
|
24
24
|
from pfund.utils.utils import flatten_dict
|
|
25
25
|
from pfund.zeromq import ZeroMQ
|
|
26
26
|
from pfund.config_handler import ConfigHandler
|
|
@@ -29,13 +29,13 @@ from pfund.config_handler import ConfigHandler
|
|
|
29
29
|
class TradeEngine(BaseEngine):
|
|
30
30
|
zmq_ports = {}
|
|
31
31
|
|
|
32
|
-
def __new__(cls, *, env: str='PAPER', data_tool:
|
|
32
|
+
def __new__(cls, *, env: str='PAPER', data_tool: 'tSUPPORTED_DATA_TOOLS'='pandas', zmq_port=5557, config: ConfigHandler | None=None, **settings):
|
|
33
33
|
if not hasattr(cls, 'zmq_port'):
|
|
34
|
-
assert
|
|
34
|
+
assert isinstance(zmq_port, int), f'{zmq_port=} must be an integer'
|
|
35
35
|
cls._zmq_port = zmq_port
|
|
36
36
|
return super().__new__(cls, env, data_tool=data_tool, config=config, **settings)
|
|
37
37
|
|
|
38
|
-
def __init__(self, *, env: str='PAPER', data_tool:
|
|
38
|
+
def __init__(self, *, env: str='PAPER', data_tool: 'tSUPPORTED_DATA_TOOLS'='pandas', zmq_port=5557, config: ConfigHandler | None=None, **settings):
|
|
39
39
|
super().__init__(env, data_tool=data_tool)
|
|
40
40
|
# avoid re-initialization to implement singleton class correctly
|
|
41
41
|
if not hasattr(self, '_initialized'):
|
|
@@ -157,7 +157,7 @@ class TradeEngine(BaseEngine):
|
|
|
157
157
|
schedule.run_all() # run all tasks at start
|
|
158
158
|
self._background_thread = Thread(target=self._run_background_tasks, daemon=True)
|
|
159
159
|
self._background_thread.start()
|
|
160
|
-
self.logger.debug(
|
|
160
|
+
self.logger.debug('background thread started')
|
|
161
161
|
|
|
162
162
|
# TEMP, zeromq examples
|
|
163
163
|
# self._zmq.send(
|
|
@@ -174,7 +174,6 @@ class TradeEngine(BaseEngine):
|
|
|
174
174
|
# )
|
|
175
175
|
|
|
176
176
|
while self._is_running:
|
|
177
|
-
lats = []
|
|
178
177
|
if msg := self._zmq.recv():
|
|
179
178
|
channel, topic, info = msg
|
|
180
179
|
# TODO
|
|
@@ -193,7 +192,7 @@ class TradeEngine(BaseEngine):
|
|
|
193
192
|
schedule.clear()
|
|
194
193
|
self._is_running = False
|
|
195
194
|
while self._background_thread.is_alive():
|
|
196
|
-
self.logger.debug(
|
|
195
|
+
self.logger.debug('waiting for background thread to finish')
|
|
197
196
|
time.sleep(1)
|
|
198
197
|
else:
|
|
199
|
-
self.logger.debug(
|
|
198
|
+
self.logger.debug('background thread is finished')
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
if TYPE_CHECKING:
|
|
3
|
+
from pfund.types.common_literals import tSUPPORTED_BACKTEST_MODES, tSUPPORTED_DATA_TOOLS
|
|
4
|
+
|
|
1
5
|
from pfund.engines.backtest_engine import BacktestEngine
|
|
2
|
-
from pfund.data_tools.data_tool_base import DataTool
|
|
3
|
-
from pfund.engines.backtest_engine import BacktestMode
|
|
4
6
|
from pfund.config_handler import ConfigHandler
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class TrainEngine(BacktestEngine):
|
|
8
|
-
def __new__(cls, *, data_tool:
|
|
10
|
+
def __new__(cls, *, data_tool: 'tSUPPORTED_DATA_TOOLS'='pandas', mode: 'tSUPPORTED_BACKTEST_MODES'='vectorized', config: ConfigHandler | None=None, **settings):
|
|
9
11
|
return super().__new__(cls, env='TRAIN', data_tool=data_tool, mode=mode, config=config, **settings)
|
|
10
12
|
|
|
11
|
-
def __init__(self, *, data_tool:
|
|
13
|
+
def __init__(self, *, data_tool: 'tSUPPORTED_DATA_TOOLS'='pandas', mode: 'tSUPPORTED_BACKTEST_MODES'='vectorized', config: ConfigHandler | None=None, **settings):
|
|
12
14
|
super().__init__(env='TRAIN', data_tool=data_tool)
|
|
13
15
|
# avoid re-initialization to implement singleton class correctly
|
|
14
16
|
# if not hasattr(self, '_initialized'):
|
|
@@ -236,7 +236,7 @@ class Exchange(BaseExchange):
|
|
|
236
236
|
"""
|
|
237
237
|
def _convert_to_date(time_):
|
|
238
238
|
if type(time_) is float:
|
|
239
|
-
date = datetime.datetime.fromtimestamp(tz=datetime.timezone.utc)
|
|
239
|
+
date = datetime.datetime.fromtimestamp(time_, tz=datetime.timezone.utc)
|
|
240
240
|
elif type(time_) is str:
|
|
241
241
|
date = datetime.datetime.strptime(time_, date_format)
|
|
242
242
|
date = date.replace(tzinfo=datetime.timezone.utc)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from pfund.types.common_literals import tSUPPORTED_PRODUCT_TYPES, tSUPPORTED_CRYPTO_PRODUCT_TYPES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# TODO
|
|
9
|
+
@dataclass
|
|
10
|
+
class InvestmentProfile:
|
|
11
|
+
investment_objectives: list[str]
|
|
12
|
+
risk_tolerance: str
|
|
13
|
+
investment_horizon: str
|
|
14
|
+
asset_classes: list[tSUPPORTED_PRODUCT_TYPES | tSUPPORTED_CRYPTO_PRODUCT_TYPES]
|
|
15
|
+
diversification: str
|
|
16
|
+
rebalancing_period: str
|