pfund 0.0.1.dev2__tar.gz → 0.0.1.dev4__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.dev2 → pfund-0.0.1.dev4}/PKG-INFO +8 -21
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/README.md +2 -13
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/__init__.py +12 -3
- pfund-0.0.1.dev4/pfund/cli/__init__.py +4 -0
- pfund-0.0.1.dev4/pfund/cli/commands/config.py +64 -0
- pfund-0.0.1.dev4/pfund/cli/commands/docker_compose.py +30 -0
- pfund-0.0.1.dev4/pfund/cli/main.py +18 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/configuration.py +3 -3
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config_handler.py +22 -16
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/const/paths.py +1 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/engines/backtest_engine.py +1 -2
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/engines/base_engine.py +3 -2
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/exchange.py +1 -1
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/exchange_base.py +1 -1
- pfund-0.0.1.dev4/pfund/main.py +10 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/mixins/backtest.py +14 -9
- pfund-0.0.1.dev4/pfund/models/__init__.py +6 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/models/model_base.py +8 -5
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/plogging/__init__.py +10 -10
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/plogging/config.py +1 -2
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pyproject.toml +17 -13
- pfund-0.0.1.dev2/pfund/models/__init__.py +0 -3
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/LICENSE +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/accounts/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/accounts/account_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/accounts/account_crypto.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/accounts/account_ib.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/adapter.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/balances/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/balances/balance_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/balances/balance_crypto.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/balances/balance_ib.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/broker_backtest.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/broker_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/broker_crypto.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/broker_live.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/ib/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/ib/broker_ib.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/ib/ib_api.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/ib/ib_client.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/brokers/ib/ib_wrapper.py +0 -0
- {pfund-0.0.1.dev2/pfund/exchanges → pfund-0.0.1.dev4/pfund/cli/commands}/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/config.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/lot_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/lot_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/lot_sizes_option.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/lot_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/pdt_matchings_inverse.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/pdt_matchings_linear.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/pdt_matchings_option.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/pdt_matchings_spot.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/tick_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/tick_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/tick_sizes_option.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/bybit/tick_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/ib/config.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/config/logging.yml +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/const/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/const/_zmq_routes.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/const/commons.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/data_tools/data_tool_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/data_tools/data_tool_pandas.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/data_bar.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/data_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/data_quote.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/data_tick.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/data_time_based.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/resolution.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/datas/timeframe.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/engines/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/engines/test_engine.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/engines/trade_engine.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/engines/train_engine.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/errors.py +0 -0
- /pfund-0.0.1.dev2/pfund/main.py → /pfund-0.0.1.dev4/pfund/exchanges/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_inverse +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_linear +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_option +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_spot +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_inverse +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_linear +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_option +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_spot +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/types.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/bybit/ws_api.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/rest_api_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/exchanges/ws_api_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/account_summary_tags.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/client.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/comm.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/commission_report.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/common.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/connection.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/contract.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/decoder.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/enum_implem.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/errors.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/execution.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/ibapi.pyproj +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/message.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/news.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/object_implem.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/order.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/order_condition.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/order_state.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/orderdecoder.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/reader.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/scanner.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/server_versions.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/softdollartier.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/tag_value.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/ticktype.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/utils.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/externals/ibapi/wrapper.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/indicators/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/indicators/indicator_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/indicators/ta_indicator.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/indicators/talib_indicator.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/base_manager.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/connection_manager.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/data_manager.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/order_manager.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/portfolio_manager.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/risk_manager.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/managers/strategy_manager.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/models/model_backtest.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/models/model_meta.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/models/pytorch_model.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/models/sklearn_model.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/orders/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/orders/order_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/orders/order_crypto.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/orders/order_ib.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/orders/order_statuses.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/orders/order_time_in_force.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/plogging/filters.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/plogging/formatter.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/plogging/handlers.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/portfolio.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/positions/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/positions/position_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/positions/position_crypto.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/positions/position_ib.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/products/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/products/product_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/products/product_crypto.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/products/product_ib.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/risk_monitor.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/strategies/__init__.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/strategies/strategy_backtest.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/strategies/strategy_base.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/strategies/strategy_meta.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/utils/aliases.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/utils/envs.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/utils/utils.py +0 -0
- {pfund-0.0.1.dev2 → pfund-0.0.1.dev4}/pfund/zeromq.py +0 -0
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pfund
|
|
3
|
-
Version: 0.0.1.
|
|
3
|
+
Version: 0.0.1.dev4
|
|
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,<
|
|
10
|
+
Requires-Python: >=3.10,<3.12
|
|
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
|
|
16
|
-
Provides-Extra: ml
|
|
17
15
|
Requires-Dist: click (>=8.1.7,<9.0.0)
|
|
18
|
-
Requires-Dist: orjson (>=3.9.
|
|
16
|
+
Requires-Dist: orjson (>=3.9.14,<4.0.0)
|
|
19
17
|
Requires-Dist: pandas (>=2.2.0,<3.0.0)
|
|
20
|
-
Requires-Dist: pfeed (>=0.0.1.
|
|
18
|
+
Requires-Dist: pfeed (>=0.0.1.dev4,<0.0.2)
|
|
21
19
|
Requires-Dist: platformdirs (>=4.2.0,<5.0.0)
|
|
22
20
|
Requires-Dist: psutil (>=5.9.8,<6.0.0)
|
|
23
21
|
Requires-Dist: python-telegram-bot (>=20.7,<21.0)
|
|
@@ -25,9 +23,9 @@ Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
|
25
23
|
Requires-Dist: pyzmq (>=25.1.2,<26.0.0)
|
|
26
24
|
Requires-Dist: rich (>=13.7.0,<14.0.0)
|
|
27
25
|
Requires-Dist: schedule (>=1.2.1,<2.0.0)
|
|
28
|
-
Requires-Dist: scikit-learn (>=1.4.0,<2.0.0)
|
|
26
|
+
Requires-Dist: scikit-learn (>=1.4.0,<2.0.0)
|
|
29
27
|
Requires-Dist: ta (>=0.11.0,<0.12.0)
|
|
30
|
-
Requires-Dist: torch (>=2.1.2,<3.0.0)
|
|
28
|
+
Requires-Dist: torch (>=2.1.2,<3.0.0)
|
|
31
29
|
Requires-Dist: websocket-client (>=1.7.0,<2.0.0)
|
|
32
30
|
Project-URL: Documentation, https://pfund.ai/docs
|
|
33
31
|
Project-URL: Repository, https://github.com/PFund-Software-Ltd/pfund
|
|
@@ -35,6 +33,7 @@ Description-Content-Type: text/markdown
|
|
|
35
33
|
|
|
36
34
|
# PFund: Algo-Trading Framework for Machine Learning, TradFi, CeFi and DeFi ready.
|
|
37
35
|
|
|
36
|
+
[](https://jupyterbook.org)
|
|
38
37
|
[](https://python-poetry.org/)
|
|
39
38
|
[](https://pypi.org/project/pfund)
|
|
40
39
|

|
|
@@ -108,7 +107,7 @@ This overview already omits some intricate steps, such as data handling and API
|
|
|
108
107
|
- [x] Supports machine learning models, features, technical analysis indicators
|
|
109
108
|
- [x] Both Strategy() and Model() are treated as first-class citizens
|
|
110
109
|
- [x] Offers LEGO-style strategy and model building, allowing strategies to add other strategies, models to add other models
|
|
111
|
-
- [x]
|
|
110
|
+
- [x] Streamlines the algo-trading flow, from vectorized backtesting for strategy prototyping and event-driven backtesting for strategy development, to live trading for strategy deployment
|
|
112
111
|
- [x] Enables parallel data processing, e.g. Interactive Brokers and Binance each have their own process for receiving data feeds
|
|
113
112
|
- [ ] Allows choosing your preferred data tool, e.g. pandas, polars, pyspark etc.
|
|
114
113
|
- [ ] Features a modern frontend using [Mantine UI](https://ui.mantine.dev/) and TradingView's Charts library
|
|
@@ -117,28 +116,16 @@ This overview already omits some intricate steps, such as data handling and API
|
|
|
117
116
|
|
|
118
117
|
## Installation
|
|
119
118
|
### Using [Poetry](https://python-poetry.org) (Recommended)
|
|
120
|
-
> If you don't have poetry installed, please visit https://python-poetry.org/docs/
|
|
121
119
|
```bash
|
|
122
120
|
poetry add pfund
|
|
123
121
|
```
|
|
124
122
|
|
|
125
|
-
> To include the machine learning dependencies: \
|
|
126
|
-
(Note that pytorch is not available in python 3.12)
|
|
127
|
-
```bash
|
|
128
|
-
poetry add pfund[ml]
|
|
129
|
-
```
|
|
130
123
|
|
|
131
124
|
### Using Pip
|
|
132
125
|
```bash
|
|
133
126
|
pip install pfund
|
|
134
127
|
```
|
|
135
128
|
|
|
136
|
-
> To include the machine learning dependencies: \
|
|
137
|
-
(Note that pytorch is not available in python 3.12)
|
|
138
|
-
```bash
|
|
139
|
-
pip install pfund[ml]
|
|
140
|
-
```
|
|
141
|
-
|
|
142
129
|
|
|
143
130
|
## Quick Start
|
|
144
131
|
### Backtesting
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# PFund: Algo-Trading Framework for Machine Learning, TradFi, CeFi and DeFi ready.
|
|
2
2
|
|
|
3
|
+
[](https://jupyterbook.org)
|
|
3
4
|
[](https://python-poetry.org/)
|
|
4
5
|
[](https://pypi.org/project/pfund)
|
|
5
6
|

|
|
@@ -73,7 +74,7 @@ This overview already omits some intricate steps, such as data handling and API
|
|
|
73
74
|
- [x] Supports machine learning models, features, technical analysis indicators
|
|
74
75
|
- [x] Both Strategy() and Model() are treated as first-class citizens
|
|
75
76
|
- [x] Offers LEGO-style strategy and model building, allowing strategies to add other strategies, models to add other models
|
|
76
|
-
- [x]
|
|
77
|
+
- [x] Streamlines the algo-trading flow, from vectorized backtesting for strategy prototyping and event-driven backtesting for strategy development, to live trading for strategy deployment
|
|
77
78
|
- [x] Enables parallel data processing, e.g. Interactive Brokers and Binance each have their own process for receiving data feeds
|
|
78
79
|
- [ ] Allows choosing your preferred data tool, e.g. pandas, polars, pyspark etc.
|
|
79
80
|
- [ ] Features a modern frontend using [Mantine UI](https://ui.mantine.dev/) and TradingView's Charts library
|
|
@@ -82,28 +83,16 @@ This overview already omits some intricate steps, such as data handling and API
|
|
|
82
83
|
|
|
83
84
|
## Installation
|
|
84
85
|
### Using [Poetry](https://python-poetry.org) (Recommended)
|
|
85
|
-
> If you don't have poetry installed, please visit https://python-poetry.org/docs/
|
|
86
86
|
```bash
|
|
87
87
|
poetry add pfund
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
> To include the machine learning dependencies: \
|
|
91
|
-
(Note that pytorch is not available in python 3.12)
|
|
92
|
-
```bash
|
|
93
|
-
poetry add pfund[ml]
|
|
94
|
-
```
|
|
95
90
|
|
|
96
91
|
### Using Pip
|
|
97
92
|
```bash
|
|
98
93
|
pip install pfund
|
|
99
94
|
```
|
|
100
95
|
|
|
101
|
-
> To include the machine learning dependencies: \
|
|
102
|
-
(Note that pytorch is not available in python 3.12)
|
|
103
|
-
```bash
|
|
104
|
-
pip install pfund[ml]
|
|
105
|
-
```
|
|
106
|
-
|
|
107
96
|
|
|
108
97
|
## Quick Start
|
|
109
98
|
### Backtesting
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
+
from importlib.metadata import version
|
|
2
|
+
|
|
1
3
|
from pfund.config_handler import configure
|
|
2
4
|
from pfund.engines import BacktestEngine, TrainEngine, TestEngine, TradeEngine
|
|
3
5
|
from pfund.strategies import Strategy
|
|
4
|
-
from pfund.models import Feature, Model
|
|
6
|
+
from pfund.models import Feature, Model
|
|
7
|
+
try:
|
|
8
|
+
from pfund.models import PyTorchModel
|
|
9
|
+
except ImportError:
|
|
10
|
+
pass
|
|
11
|
+
try:
|
|
12
|
+
from pfund.models import SKLearnModel
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|
|
5
15
|
from pfund.indicators import TAIndicator, TALibIndicator
|
|
6
|
-
from importlib.metadata import version
|
|
7
16
|
|
|
8
17
|
|
|
9
18
|
__version__ = version('pfund')
|
|
@@ -15,4 +24,4 @@ __all__ = (
|
|
|
15
24
|
'BacktestEngine', 'TrainEngine', 'TestEngine', 'TradeEngine',
|
|
16
25
|
'Strategy', 'Model', 'PyTorchModel', 'SKLearnModel',
|
|
17
26
|
'Feature', 'TAIndicator', 'TALibIndicator',
|
|
18
|
-
)
|
|
27
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from pprint import pformat
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from pfund.const.paths import USER_CONFIG_FILE_PATH
|
|
9
|
+
from pfund.config_handler import ConfigHandler
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def save_config(config: ConfigHandler, config_file_path: str | Path):
|
|
13
|
+
with open(config_file_path, 'w') as f:
|
|
14
|
+
yaml.dump(config.__dict__, f, default_flow_style=False)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def remove_config(config_file_path: str | Path):
|
|
18
|
+
config_file_path = Path(config_file_path)
|
|
19
|
+
if config_file_path.is_file():
|
|
20
|
+
os.remove(config_file_path)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.command()
|
|
24
|
+
@click.pass_context
|
|
25
|
+
@click.option('--data-path', type=click.Path(resolve_path=True), help='Set the data path for storing strategies, models, features and indicators')
|
|
26
|
+
@click.option('--log-path', type=click.Path(resolve_path=True), help='Set the log path')
|
|
27
|
+
@click.option('--logging-file', 'logging_config_file_path', type=click.Path(resolve_path=True, exists=True), help='Set the logging config file path')
|
|
28
|
+
@click.option('--logging-config', type=dict, help='Set the logging config')
|
|
29
|
+
@click.option('--use-fork-process', type=bool, help='If True, multiprocessing.set_start_method("fork")')
|
|
30
|
+
@click.option('--use-custom-excepthook', type=bool, help='If True, log uncaught exceptions to file')
|
|
31
|
+
@click.option('--list', '-l', is_flag=True, is_eager=True, help='List all available options')
|
|
32
|
+
@click.option('--reset', is_flag=True, is_eager=True, help='Reset the configuration to defaults')
|
|
33
|
+
def config(ctx, **kwargs):
|
|
34
|
+
"""Configures pfund settings."""
|
|
35
|
+
config: ConfigHandler = ctx.obj['config']
|
|
36
|
+
|
|
37
|
+
# Filter out options that were not provided by the user
|
|
38
|
+
provided_options = {k: v for k, v in kwargs.items() if v is not None and v is not False}
|
|
39
|
+
|
|
40
|
+
if kwargs.get('list'): # Check if --list was used
|
|
41
|
+
del provided_options['list']
|
|
42
|
+
assert not provided_options, "No options should be provided with --list"
|
|
43
|
+
config_dict = config.__dict__
|
|
44
|
+
config_dict.update({'config_file_path': USER_CONFIG_FILE_PATH})
|
|
45
|
+
click.echo(f"PFund's config:\n{pformat(config_dict)}")
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
if kwargs.get('reset'): # Check if --reset was used
|
|
49
|
+
del provided_options['reset']
|
|
50
|
+
assert not provided_options, "No options should be provided with --reset"
|
|
51
|
+
remove_config(USER_CONFIG_FILE_PATH)
|
|
52
|
+
click.echo("PFund's config successfully reset.")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
# prints out current config if no options are provided
|
|
56
|
+
if not provided_options:
|
|
57
|
+
raise click.UsageError("No options provided. Use --list to see all available options.")
|
|
58
|
+
else:
|
|
59
|
+
for option, value in provided_options.items():
|
|
60
|
+
setattr(config, option, value)
|
|
61
|
+
click.echo(f"{option} set to: {value}")
|
|
62
|
+
|
|
63
|
+
save_config(config, USER_CONFIG_FILE_PATH)
|
|
64
|
+
click.echo(f"config saved to {USER_CONFIG_FILE_PATH}.")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import importlib.resources
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
from dotenv import find_dotenv, load_dotenv
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from pfund.const.paths import PROJ_NAME
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command(context_settings=dict(
|
|
12
|
+
ignore_unknown_options=True,
|
|
13
|
+
allow_extra_args=True,
|
|
14
|
+
))
|
|
15
|
+
@click.pass_context
|
|
16
|
+
@click.option('--env-file', 'env_file_path', type=click.Path(exists=True), help='Path to the .env file')
|
|
17
|
+
@click.option('--docker-file', 'docker_file_path', type=click.Path(exists=True), help='Path to the docker-compose.yml file')
|
|
18
|
+
def docker_compose(ctx, env_file_path, docker_file_path):
|
|
19
|
+
"""Forwards commands to docker-compose with the package's docker-compose.yml file if not specified."""
|
|
20
|
+
if not env_file_path:
|
|
21
|
+
env_file_path = find_dotenv(usecwd=True, raise_error_if_not_found=True)
|
|
22
|
+
click.echo(f'.env file path is not specified, using env file in "{env_file_path}"')
|
|
23
|
+
load_dotenv(env_file_path, override=True)
|
|
24
|
+
if not docker_file_path:
|
|
25
|
+
package_dir = Path(importlib.resources.files(PROJ_NAME)).resolve().parents[0]
|
|
26
|
+
docker_file_path = package_dir / 'docker-compose.yml'
|
|
27
|
+
else:
|
|
28
|
+
click.echo(f'loaded custom docker-compose.yml file from "{docker_file_path}"')
|
|
29
|
+
command = ['docker-compose', '-f', str(docker_file_path)] + ctx.args
|
|
30
|
+
subprocess.run(command)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from pfund.config_handler import ConfigHandler
|
|
4
|
+
from pfund.cli.commands.docker_compose import docker_compose
|
|
5
|
+
from pfund.cli.commands.config import config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
9
|
+
@click.pass_context
|
|
10
|
+
@click.version_option()
|
|
11
|
+
def pfund_group(ctx):
|
|
12
|
+
"""pfund's CLI"""
|
|
13
|
+
ctx.ensure_object(dict)
|
|
14
|
+
ctx.obj['config'] = ConfigHandler.load_config()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
pfund_group.add_command(docker_compose)
|
|
18
|
+
pfund_group.add_command(config)
|
|
@@ -26,14 +26,14 @@ class Configuration:
|
|
|
26
26
|
def read_config(self, config_name):
|
|
27
27
|
global has_printed
|
|
28
28
|
file_path = f'{self.config_path}/{config_name}.yml'
|
|
29
|
-
short_file_path = short_path(file_path)
|
|
29
|
+
# short_file_path = short_path(file_path)
|
|
30
30
|
if not os.path.exists(file_path):
|
|
31
|
-
print(f'cannot find config {
|
|
31
|
+
print(f'cannot find config {file_path}')
|
|
32
32
|
else:
|
|
33
33
|
with open(file_path, 'r') as f:
|
|
34
34
|
if not has_printed:
|
|
35
35
|
has_printed = True
|
|
36
|
-
print(f'loaded config {
|
|
36
|
+
print(f'loaded config {file_path}')
|
|
37
37
|
return list(yaml.safe_load_all(f))
|
|
38
38
|
|
|
39
39
|
def write_config(self, config_name, content):
|
|
@@ -6,13 +6,14 @@ import logging
|
|
|
6
6
|
from types import TracebackType
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
|
|
9
|
+
import yaml
|
|
9
10
|
# from rich.traceback import install
|
|
10
11
|
|
|
11
|
-
from pfund.const.paths import PROJ_NAME, PROJ_PATH, LOG_PATH, PROJ_CONFIG_PATH,
|
|
12
|
+
from pfund.const.paths import PROJ_NAME, PROJ_PATH, LOG_PATH, PROJ_CONFIG_PATH, DATA_PATH, USER_CONFIG_FILE_PATH
|
|
13
|
+
|
|
12
14
|
# add python path so that for files like "ibapi" (official python code from IB)
|
|
13
15
|
# can find their modules
|
|
14
16
|
sys.path.append(f'{PROJ_PATH}/externals')
|
|
15
|
-
|
|
16
17
|
# install(show_locals=False) # rich will set its own sys.excepthook
|
|
17
18
|
# rich_excepthook = sys.excepthook # get rich's excepthook
|
|
18
19
|
|
|
@@ -50,18 +51,29 @@ def import_strategies_models_features_or_indicators(path: str):
|
|
|
50
51
|
|
|
51
52
|
@dataclass
|
|
52
53
|
class ConfigHandler:
|
|
53
|
-
|
|
54
|
-
model_path: str = str(MODEL_PATH)
|
|
55
|
-
feature_path: str = str(FEATURE_PATH)
|
|
56
|
-
indicator_path: str = str(INDICATOR_PATH)
|
|
54
|
+
data_path: str = str(DATA_PATH)
|
|
57
55
|
log_path: str = str(LOG_PATH)
|
|
58
56
|
logging_config_file_path: str = f'{PROJ_CONFIG_PATH}/logging.yml'
|
|
59
57
|
logging_config: dict | None = None
|
|
60
58
|
use_fork_process: bool = True
|
|
61
59
|
use_custom_excepthook: bool = True
|
|
62
60
|
|
|
61
|
+
@classmethod
|
|
62
|
+
def load_config(cls):
|
|
63
|
+
config_file_path = USER_CONFIG_FILE_PATH
|
|
64
|
+
if config_file_path.is_file():
|
|
65
|
+
with open(config_file_path, 'r') as f:
|
|
66
|
+
config = yaml.safe_load(f) or {}
|
|
67
|
+
else:
|
|
68
|
+
config = {}
|
|
69
|
+
return cls(**config)
|
|
70
|
+
|
|
63
71
|
def __post_init__(self):
|
|
64
|
-
|
|
72
|
+
self.logging_config = self.logging_config or {}
|
|
73
|
+
|
|
74
|
+
strategy_path, model_path = f'{self.data_path}/strategies', f'{self.data_path}/models'
|
|
75
|
+
feature_path, indicator_path = f'{self.data_path}/features', f'{self.data_path}/indicators'
|
|
76
|
+
for path in [strategy_path, model_path, feature_path, indicator_path]:
|
|
65
77
|
if not os.path.exists(path):
|
|
66
78
|
os.makedirs(path)
|
|
67
79
|
print(f'created {path}')
|
|
@@ -73,13 +85,10 @@ class ConfigHandler:
|
|
|
73
85
|
|
|
74
86
|
if self.use_custom_excepthook:
|
|
75
87
|
sys.excepthook = _custom_excepthook
|
|
76
|
-
|
|
88
|
+
|
|
77
89
|
|
|
78
90
|
def configure(
|
|
79
|
-
|
|
80
|
-
model_path: str = str(MODEL_PATH),
|
|
81
|
-
feature_path: str = str(FEATURE_PATH),
|
|
82
|
-
indicator_path: str = str(INDICATOR_PATH),
|
|
91
|
+
data_path: str = str(DATA_PATH),
|
|
83
92
|
log_path: str = str(LOG_PATH),
|
|
84
93
|
logging_config_file_path: str = f'{PROJ_CONFIG_PATH}/logging.yml',
|
|
85
94
|
logging_config: dict | None=None,
|
|
@@ -87,10 +96,7 @@ def configure(
|
|
|
87
96
|
use_custom_excepthook: bool=True,
|
|
88
97
|
):
|
|
89
98
|
return ConfigHandler(
|
|
90
|
-
|
|
91
|
-
model_path=model_path,
|
|
92
|
-
feature_path=feature_path,
|
|
93
|
-
indicator_path=indicator_path,
|
|
99
|
+
data_path=data_path,
|
|
94
100
|
log_path=log_path,
|
|
95
101
|
logging_config_file_path=logging_config_file_path,
|
|
96
102
|
logging_config=logging_config,
|
|
@@ -13,6 +13,7 @@ PROJ_CONFIG_PATH = PROJ_PATH / 'config'
|
|
|
13
13
|
# user paths
|
|
14
14
|
LOG_PATH = Path(user_log_dir()) / PROJ_NAME
|
|
15
15
|
USER_CONFIG_PATH = Path(user_config_dir()) / PROJ_NAME
|
|
16
|
+
USER_CONFIG_FILE_PATH = USER_CONFIG_PATH / f'{PROJ_NAME}_config.yml'
|
|
16
17
|
DATA_PATH = Path(user_data_dir()) / PROJ_NAME
|
|
17
18
|
STRATEGY_PATH = DATA_PATH / 'strategies'
|
|
18
19
|
MODEL_PATH = DATA_PATH / 'models'
|
|
@@ -79,8 +79,7 @@ class BacktestEngine(BaseEngine):
|
|
|
79
79
|
continue
|
|
80
80
|
if not hasattr(strategy, 'backtest'):
|
|
81
81
|
raise Exception(f'Strategy {strat} does not have backtest() method, cannot run vectorized backtesting')
|
|
82
|
-
|
|
83
|
-
strategy.backtest(df.copy(deep=True))
|
|
82
|
+
strategy.backtest()
|
|
84
83
|
elif self.mode == 'event_driven':
|
|
85
84
|
for strat, strategy in self.strategy_manager.strategies.items():
|
|
86
85
|
if strat == '_dummy':
|
|
@@ -12,6 +12,7 @@ from pfund.managers.strategy_manager import StrategyManager
|
|
|
12
12
|
from pfund.const.commons import *
|
|
13
13
|
from pfund.config_handler import ConfigHandler
|
|
14
14
|
from pfund.plogging import set_up_loggers
|
|
15
|
+
from pfund.plogging.config import LoggingDictConfigurator
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
ENV_COLORS = {
|
|
@@ -43,10 +44,10 @@ class BaseEngine(Singleton):
|
|
|
43
44
|
if not hasattr(cls, 'settings'):
|
|
44
45
|
cls.settings = settings
|
|
45
46
|
if not hasattr(cls, 'config'):
|
|
46
|
-
cls.config = config if config else ConfigHandler()
|
|
47
|
+
cls.config = config if config else ConfigHandler.load_config()
|
|
47
48
|
log_path = f'{cls.config.log_path}/{cls.env}'
|
|
48
49
|
logging_config_file_path = cls.config.logging_config_file_path
|
|
49
|
-
set_up_loggers(log_path, logging_config_file_path, user_logging_config=cls.config.logging_config)
|
|
50
|
+
cls.logging_configurator: LoggingDictConfigurator = set_up_loggers(log_path, logging_config_file_path, user_logging_config=cls.config.logging_config)
|
|
50
51
|
return super().__new__(cls)
|
|
51
52
|
|
|
52
53
|
def __init__(self, env, data_tool: DataTool='pandas', config: ConfigHandler | None=None, **settings):
|
|
@@ -26,7 +26,7 @@ class Exchange(BaseExchange):
|
|
|
26
26
|
super().__init__(env, 'BYBIT')
|
|
27
27
|
|
|
28
28
|
@staticmethod
|
|
29
|
-
def
|
|
29
|
+
def categorize_product_type(ptype):
|
|
30
30
|
is_spot = (ptype == 'SPOT')
|
|
31
31
|
is_inverse = (ptype in ['IPERP', 'IFUT'])
|
|
32
32
|
is_linear = not is_inverse and not is_spot
|
|
@@ -57,7 +57,7 @@ class BaseExchange:
|
|
|
57
57
|
self.categories.append(category)
|
|
58
58
|
|
|
59
59
|
def create_product(self, bccy, qccy, ptype, *args, **kwargs) -> CryptoProduct:
|
|
60
|
-
if category := self.
|
|
60
|
+
if category := self.categorize_product_type(ptype) if hasattr(self, 'categorize_product_type') else '':
|
|
61
61
|
self.add_category(category)
|
|
62
62
|
product = CryptoProduct(self.exch, bccy, qccy, ptype, *args, category=category, **kwargs)
|
|
63
63
|
product.load_configs(self.configs)
|
|
@@ -12,7 +12,6 @@ if TYPE_CHECKING:
|
|
|
12
12
|
|
|
13
13
|
from pfund.managers.data_manager import get_resolutions_from_kwargs
|
|
14
14
|
from pfund.models.model_backtest import BacktestModel
|
|
15
|
-
from pfund.strategies.strategy_base import BaseStrategy
|
|
16
15
|
from pfund.models.model_base import BaseModel
|
|
17
16
|
|
|
18
17
|
|
|
@@ -25,7 +24,7 @@ class BacktestMixin:
|
|
|
25
24
|
backtest_kwargs, train_kwargs = backtest or {}, train or {}
|
|
26
25
|
|
|
27
26
|
if backtest_kwargs:
|
|
28
|
-
data_source = self._get_data_source(backtest_kwargs)
|
|
27
|
+
data_source = self._get_data_source(trading_venue, backtest_kwargs)
|
|
29
28
|
feed = self.get_feed(data_source)
|
|
30
29
|
kwargs = self._prepare_kwargs(feed, kwargs)
|
|
31
30
|
|
|
@@ -45,11 +44,15 @@ class BacktestMixin:
|
|
|
45
44
|
model = BacktestModel(type(model), model.ml_model, *model._args, **model._kwargs)
|
|
46
45
|
return super().add_model(model, name=name, model_path=model_path, is_load=is_load)
|
|
47
46
|
|
|
48
|
-
def _get_data_source(self, backtest_kwargs: dict):
|
|
47
|
+
def _get_data_source(self, trading_venue: str, backtest_kwargs: dict):
|
|
49
48
|
from pfeed.const.commons import SUPPORTED_DATA_FEEDS
|
|
49
|
+
trading_venue = trading_venue.upper()
|
|
50
|
+
# if data_source is not defined, use trading_venue as data_source
|
|
51
|
+
if trading_venue in SUPPORTED_DATA_FEEDS and 'data_source' not in backtest_kwargs:
|
|
52
|
+
backtest_kwargs['data_source'] = trading_venue
|
|
50
53
|
assert 'data_source' in backtest_kwargs, f"data_source must be defined"
|
|
51
54
|
data_source = backtest_kwargs['data_source'].upper()
|
|
52
|
-
assert
|
|
55
|
+
assert data_source in SUPPORTED_DATA_FEEDS, f"{data_source=} not in {SUPPORTED_DATA_FEEDS}"
|
|
53
56
|
return data_source
|
|
54
57
|
|
|
55
58
|
def _prepare_kwargs(self, feed: BaseFeed, kwargs: dict):
|
|
@@ -99,10 +102,12 @@ class BacktestMixin:
|
|
|
99
102
|
|
|
100
103
|
@staticmethod
|
|
101
104
|
def get_feed(data_source: str) -> BaseFeed:
|
|
102
|
-
|
|
105
|
+
from pfeed.feeds import YahooFinanceFeed, BybitFeed
|
|
103
106
|
data_source = data_source.upper()
|
|
104
107
|
if data_source == 'YAHOO_FINANCE':
|
|
105
|
-
feed =
|
|
108
|
+
feed = YahooFinanceFeed()
|
|
109
|
+
elif data_source == 'BYBIT':
|
|
110
|
+
feed = BybitFeed()
|
|
106
111
|
# TODO: other feeds
|
|
107
112
|
else:
|
|
108
113
|
raise NotImplementedError
|
|
@@ -123,9 +128,9 @@ class BacktestMixin:
|
|
|
123
128
|
continue
|
|
124
129
|
product = data.product
|
|
125
130
|
resolution = data.resolution
|
|
126
|
-
|
|
127
|
-
df = feed.get_historical_data(
|
|
128
|
-
assert not df.empty, f"dataframe is empty for {
|
|
131
|
+
pdt_or_symbol = product.symbol if feed.name == 'YAHOO_FINANCE' else product.pdt
|
|
132
|
+
df = feed.get_historical_data(pdt_or_symbol, rollback_period=rollback_period, start_date=start_date, end_date=end_date, resolution=repr(resolution), **backtest_kwargs)
|
|
133
|
+
assert not df.empty, f"dataframe is empty for {pdt_or_symbol=}"
|
|
129
134
|
df.reset_index(inplace=True)
|
|
130
135
|
latest_date = str(df['ts'][0])
|
|
131
136
|
if feed.name == 'YAHOO_FINANCE' and latest_date == utcnow_date:
|
|
@@ -8,13 +8,16 @@ from collections import defaultdict
|
|
|
8
8
|
|
|
9
9
|
from typing import TYPE_CHECKING, Any, Union
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
import
|
|
11
|
+
try:
|
|
12
|
+
import joblib
|
|
13
|
+
import torch
|
|
14
|
+
import torch.nn as nn
|
|
15
|
+
from sklearn.base import BaseEstimator, ClassifierMixin, RegressorMixin
|
|
16
|
+
from sklearn.pipeline import Pipeline
|
|
17
|
+
except ImportError:
|
|
18
|
+
pass
|
|
13
19
|
import numpy as np
|
|
14
20
|
import pandas as pd
|
|
15
|
-
import torch.nn as nn
|
|
16
|
-
from sklearn.base import BaseEstimator, ClassifierMixin, RegressorMixin
|
|
17
|
-
from sklearn.pipeline import Pipeline
|
|
18
21
|
|
|
19
22
|
if TYPE_CHECKING:
|
|
20
23
|
from pfund.strategies.strategy_base import BaseStrategy
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import logging
|
|
3
|
-
from pathlib import Path
|
|
4
3
|
|
|
5
4
|
from typing import Literal
|
|
6
5
|
|
|
7
6
|
from pfund.plogging.config import LoggingDictConfigurator
|
|
8
|
-
from pfund.utils.utils import load_yaml_file
|
|
7
|
+
from pfund.utils.utils import load_yaml_file, get_engine_class
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
def print_all_loggers():
|
|
@@ -14,7 +13,7 @@ def print_all_loggers():
|
|
|
14
13
|
print(name, logger, logger.handlers)
|
|
15
14
|
|
|
16
15
|
|
|
17
|
-
def set_up_loggers(log_path, logging_config_file_path, user_logging_config: dict | None=None):
|
|
16
|
+
def set_up_loggers(log_path, logging_config_file_path, user_logging_config: dict | None=None) -> LoggingDictConfigurator:
|
|
18
17
|
def deep_update(default_dict, override_dict, raise_if_key_not_exist=False):
|
|
19
18
|
'''Updates a default dictionary with an override dictionary, supports nested dictionaries.'''
|
|
20
19
|
for key, value in override_dict.items():
|
|
@@ -33,9 +32,8 @@ def set_up_loggers(log_path, logging_config_file_path, user_logging_config: dict
|
|
|
33
32
|
else:
|
|
34
33
|
# Update the key with the override value
|
|
35
34
|
default_dict[key] = value
|
|
36
|
-
print('Setting up loggers...')
|
|
37
|
-
|
|
38
|
-
if not log_path.exists():
|
|
35
|
+
# print('Setting up loggers...')
|
|
36
|
+
if not os.path.exists(log_path):
|
|
39
37
|
os.makedirs(log_path)
|
|
40
38
|
print(f'created {str(log_path)}')
|
|
41
39
|
logging_config: dict = load_yaml_file(logging_config_file_path)
|
|
@@ -43,8 +41,10 @@ def set_up_loggers(log_path, logging_config_file_path, user_logging_config: dict
|
|
|
43
41
|
deep_update(logging_config, user_logging_config)
|
|
44
42
|
logging_config['log_path'] = log_path
|
|
45
43
|
# ≈ logging.config.dictConfig(logging_config) with a custom configurator
|
|
46
|
-
LoggingDictConfigurator(logging_config)
|
|
47
|
-
|
|
44
|
+
logging_configurator = LoggingDictConfigurator(logging_config)
|
|
45
|
+
logging_configurator.configure()
|
|
46
|
+
return logging_configurator
|
|
47
|
+
|
|
48
48
|
|
|
49
49
|
# TODO: support 'feature', 'indicator'
|
|
50
50
|
def create_dynamic_logger(name: str, type_: Literal['strategy', 'model', 'manager']):
|
|
@@ -57,8 +57,8 @@ def create_dynamic_logger(name: str, type_: Literal['strategy', 'model', 'manage
|
|
|
57
57
|
assert name, "logger name cannot be empty/None"
|
|
58
58
|
assert type_ in ['strategy', 'model', 'manager'], f"Unsupported {type_=}"
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
config =
|
|
60
|
+
Engine = get_engine_class()
|
|
61
|
+
config = Engine.logging_configurator
|
|
62
62
|
|
|
63
63
|
logging_config = config.config_orig
|
|
64
64
|
loggers_config = logging_config['loggers']
|
|
@@ -4,7 +4,6 @@ import copy
|
|
|
4
4
|
import logging
|
|
5
5
|
from logging.config import DictConfigurator
|
|
6
6
|
|
|
7
|
-
from pfund.utils.utils import Singleton
|
|
8
7
|
from pfund.plogging.filters import FullPathFilter
|
|
9
8
|
from pfund.plogging.formatter import ColoredFormatter
|
|
10
9
|
|
|
@@ -19,7 +18,7 @@ LEVELS = {
|
|
|
19
18
|
|
|
20
19
|
|
|
21
20
|
# override logging's DictConfigurator as it doesn't pass in logger names to file handlers to create filenames
|
|
22
|
-
class LoggingDictConfigurator(
|
|
21
|
+
class LoggingDictConfigurator(DictConfigurator):
|
|
23
22
|
_MANUALLY_CONFIGURED_HANDLERS = [
|
|
24
23
|
'file_handler',
|
|
25
24
|
'compressed_timed_rotating_file_handler',
|