pfund 0.0.1.dev10__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.dev10 → pfund-0.0.1.dev11}/PKG-INFO +5 -1
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/CONTRIBUTING.md +3 -0
- {pfund-0.0.1.dev10 → 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.dev10 → pfund-0.0.1.dev11}/pfund/config/configuration.py +5 -7
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config_handler.py +4 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/const/paths.py +1 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/data_tools/data_tool_pandas.py +2 -6
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/engines/backtest_engine.py +16 -25
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/engines/trade_engine.py +7 -10
- 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.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/__init__.py +1 -2
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/exchange.py +10 -6
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api.py +3 -1
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/ws_api.py +4 -2
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/exchange_base.py +12 -9
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/strategy_manager.py +1 -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.dev10 → pfund-0.0.1.dev11}/pfund/utils/utils.py +11 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pyproject.toml +13 -3
- pfund-0.0.1.dev10/pfund/analyzer.py +0 -3
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/LICENSE +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/README.md +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/accounts/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/accounts/account_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/accounts/account_crypto.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/accounts/account_ib.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/balances/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/balances/balance_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/balances/balance_crypto.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/balances/balance_ib.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/broker_backtest.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/broker_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/broker_crypto.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/broker_live.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/ib/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/ib/broker_ib.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/ib/ib_api.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/ib/ib_client.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/brokers/ib/ib_wrapper.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/cli/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/cli/commands/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/cli/commands/config.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/cli/commands/docker_compose.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/cli/main.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/config.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_option.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/lot_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_inverse.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_linear.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_option.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/pdt_matchings_spot.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_inverse.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_linear.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_option.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/bybit/tick_sizes_spot.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/ib/config.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/config/logging.yml +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/const/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/const/_zmq_routes.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/const/commons.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/data_tools/data_tool_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/data_bar.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/data_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/data_quote.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/data_tick.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/data_time_based.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/resolution.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/datas/timeframe.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/engines/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/engines/base_engine.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/engines/test_engine.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/engines/train_engine.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/errors.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_inverse +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_linear +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_option +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_spot +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_inverse +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_linear +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_option +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_spot +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/rest_api_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/exchanges/ws_api_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/account_summary_tags.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/client.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/comm.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/commission_report.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/common.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/connection.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/contract.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/decoder.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/enum_implem.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/errors.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/execution.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/ibapi.pyproj +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/message.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/news.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/object_implem.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/order.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/order_condition.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/order_state.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/orderdecoder.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/reader.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/scanner.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/server_versions.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/softdollartier.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/tag_value.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/ticktype.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/utils.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/externals/ibapi/wrapper.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/indicators/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/indicators/indicator_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/indicators/ta_indicator.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/indicators/talib_indicator.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/investment_profile.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/main.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/base_manager.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/connection_manager.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/data_manager.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/order_manager.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/portfolio_manager.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/managers/risk_manager.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/mixins/backtest.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/models/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/models/model_backtest.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/models/model_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/models/model_meta.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/models/pytorch_model.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/models/sklearn_model.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/orders/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/orders/order_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/orders/order_crypto.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/orders/order_ib.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/orders/order_statuses.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/orders/order_time_in_force.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/plogging/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/plogging/config.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/plogging/filters.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/plogging/formatter.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/plogging/handlers.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/portfolio.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/positions/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/positions/position_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/positions/position_crypto.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/positions/position_ib.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/products/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/products/product_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/products/product_crypto.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/products/product_ib.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/risk_monitor.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/__init__.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/allocation_strategy.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/diversification_strategy.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/hedging_strategy.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/optimization_strategy.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/portfolio_strategy.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/rebalancing_strategy.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/strategy_backtest.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/strategy_base.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/strategies/strategy_meta.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/types/backtest.py +0 -0
- /pfund-0.0.1.dev10/pfund/exchanges/bybit/types.py → /pfund-0.0.1.dev11/pfund/types/bybit.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/types/common_literals.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/types/core.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/universe.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/utils/aliases.py +0 -0
- {pfund-0.0.1.dev10 → pfund-0.0.1.dev11}/pfund/utils/envs.py +0 -0
- {pfund-0.0.1.dev10 → 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"
|
|
23
|
+
Requires-Dist: papermill (>=2.5.0,<3.0.0) ; extra == "analytics"
|
|
22
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)
|
|
@@ -88,11 +88,11 @@ class BacktestEngine(BaseEngine):
|
|
|
88
88
|
def _generate_backtest_id() -> str:
|
|
89
89
|
return uuid.uuid4().hex
|
|
90
90
|
|
|
91
|
-
def _create_backtest_name(self, strat: str):
|
|
91
|
+
def _create_backtest_name(self, strat: str, backtest_id: str, backtest_id_length: int=12):
|
|
92
92
|
local_tz = utils.get_local_timezone()
|
|
93
93
|
utcnow = datetime.datetime.now(tz=local_tz).strftime('%Y-%m-%d_%H:%M:%S_UTC%z')
|
|
94
|
-
|
|
95
|
-
return '.'.join([strat, utcnow,
|
|
94
|
+
trimmed_backtest_id = backtest_id[:backtest_id_length]
|
|
95
|
+
return '.'.join([strat, utcnow, trimmed_backtest_id])
|
|
96
96
|
|
|
97
97
|
@staticmethod
|
|
98
98
|
def _generate_backtest_hash(strategy: BaseStrategy):
|
|
@@ -144,47 +144,36 @@ class BacktestEngine(BaseEngine):
|
|
|
144
144
|
self._write_json(file_name, backtest_json)
|
|
145
145
|
return backtest_json[backtest_hash]
|
|
146
146
|
|
|
147
|
-
def
|
|
148
|
-
splits = backtest_name.split('.')
|
|
149
|
-
strat, backtest_id = splits[0], splits[-1]
|
|
147
|
+
def _output_backtest_results(self, strat: str, df, start_time: float, end_time: float):
|
|
150
148
|
strategy = self.get_strategy(strat)
|
|
149
|
+
backtest_id = self._generate_backtest_id()
|
|
150
|
+
backtest_name = self._create_backtest_name(strat, backtest_id)
|
|
151
151
|
local_tz = utils.get_local_timezone()
|
|
152
152
|
duration = end_time - start_time
|
|
153
|
+
df_file_path = os.path.join(self.config.backtest_path, f'{backtest_name}.parquet')
|
|
153
154
|
backtest_history = {
|
|
154
|
-
'settings': self.settings,
|
|
155
155
|
'metadata': {
|
|
156
156
|
'pfund_version': pf.__version__,
|
|
157
|
-
'backtest_name': backtest_name,
|
|
158
157
|
'backtest_id': backtest_id,
|
|
159
158
|
'backtest_iteration': self._generate_backtest_iteration(strategy),
|
|
160
159
|
'duration': f'{duration:.2f}s' if duration > 1 else f'{duration*1000:.2f}ms',
|
|
161
160
|
'start_time': datetime.datetime.fromtimestamp(start_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
|
|
162
161
|
'end_time': datetime.datetime.fromtimestamp(end_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
|
|
162
|
+
'settings': self.settings,
|
|
163
163
|
},
|
|
164
164
|
'strategy': strategy.to_dict(),
|
|
165
|
-
'
|
|
166
|
-
'df_file_path': os.path.join(self.config.backtest_path, f'{backtest_name_trimmed}.parquet'),
|
|
167
|
-
}
|
|
165
|
+
'result': df_file_path
|
|
168
166
|
}
|
|
169
|
-
self.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
splits = backtest_name.split('.')
|
|
173
|
-
backtest_id_len = 12
|
|
174
|
-
splits[-1] = splits[-1][:backtest_id_len]
|
|
175
|
-
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
|
|
176
170
|
|
|
177
|
-
def output_backtest_results(self, strat: str, df, start_time: float, end_time: float):
|
|
178
|
-
backtest_name = self._create_backtest_name(strat)
|
|
179
|
-
backtest_name_trimmed = self.trim_backtest_name(backtest_name)
|
|
180
|
-
self.data_tool.output_df_to_parquet(backtest_name_trimmed, df, self.config.backtest_path)
|
|
181
|
-
self._write_backtest_history(backtest_name, backtest_name_trimmed, start_time, end_time)
|
|
182
|
-
|
|
183
171
|
def run(self):
|
|
184
172
|
for broker in self.brokers.values():
|
|
185
173
|
broker.start()
|
|
186
174
|
self.strategy_manager.start()
|
|
187
175
|
|
|
176
|
+
backtests = {}
|
|
188
177
|
if self.mode == 'vectorized':
|
|
189
178
|
for strat, strategy in self.strategy_manager.strategies.items():
|
|
190
179
|
# _dummy strategy is only created for model training, do nothing
|
|
@@ -196,7 +185,8 @@ class BacktestEngine(BaseEngine):
|
|
|
196
185
|
strategy.backtest()
|
|
197
186
|
end_time = time.time()
|
|
198
187
|
df = strategy.get_df()
|
|
199
|
-
self.
|
|
188
|
+
backtest_history: dict = self._output_backtest_results(strat, df, start_time, end_time)
|
|
189
|
+
backtests[strat] = backtest_history
|
|
200
190
|
elif self.mode == 'event_driven':
|
|
201
191
|
for strat, strategy in self.strategy_manager.strategies.items():
|
|
202
192
|
if strat == '_dummy':
|
|
@@ -248,6 +238,7 @@ class BacktestEngine(BaseEngine):
|
|
|
248
238
|
else:
|
|
249
239
|
raise NotImplementedError(f'Backtesting mode {self.mode} is not supported')
|
|
250
240
|
self.strategy_manager.stop(reason='finished backtesting')
|
|
241
|
+
return backtests
|
|
251
242
|
|
|
252
243
|
def end(self):
|
|
253
244
|
self.strategy_manager.stop(reason='finished backtesting')
|
|
@@ -22,7 +22,7 @@ import schedule
|
|
|
22
22
|
|
|
23
23
|
from pfund.engines.base_engine import BaseEngine
|
|
24
24
|
from pfund.brokers.broker_base import BaseBroker
|
|
25
|
-
from pfund.utils.utils import flatten_dict
|
|
25
|
+
from pfund.utils.utils import flatten_dict, is_port_in_use
|
|
26
26
|
from pfund.zeromq import ZeroMQ
|
|
27
27
|
from pfund.config_handler import ConfigHandler
|
|
28
28
|
|
|
@@ -57,15 +57,12 @@ class TradeEngine(BaseEngine):
|
|
|
57
57
|
def _assign_zmq_ports(self) -> dict:
|
|
58
58
|
_assigned_ports = []
|
|
59
59
|
def _is_port_available(_port):
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
else:
|
|
67
|
-
_assigned_ports.append(_port)
|
|
68
|
-
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
|
|
69
66
|
def _get_port(start_port=None):
|
|
70
67
|
_port = start_port or self._zmq_port
|
|
71
68
|
if _is_port_available(_port):
|
|
@@ -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
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from pfund.exchanges.rest_api_base import BaseRestApi
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# TODO:
|
|
7
|
+
class RestApi(BaseRestApi):
|
|
8
|
+
URLS = {}
|
|
9
|
+
PUBLIC_ENDPOINTS = {}
|
|
10
|
+
PRIVATE_ENDPOINTS = {}
|
|
11
|
+
|
|
12
|
+
def __init__(self, env):
|
|
13
|
+
exch = Path(__file__).parent.name
|
|
14
|
+
super().__init__(env, exch)
|
|
15
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from pfund.exchanges.ws_api_base import BaseWebsocketApi
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# TODO
|
|
7
|
+
class WebsocketApi(BaseWebsocketApi):
|
|
8
|
+
URLS = {}
|
|
9
|
+
|
|
10
|
+
def __init__(self, env, adapter):
|
|
11
|
+
exch = Path(__file__).parent.name
|
|
12
|
+
super().__init__(env, exch, adapter)
|
|
13
|
+
|
|
14
|
+
def _on_message(self, ws, msg):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
def _authenticate(self, acc: str):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
def _create_ws_url(self, ws_name: str) -> str:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
def _create_public_channel(self, channel, product, **kwargs):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def _create_private_channel(self, channel, **kwargs):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
def _subscribe(self, ws, full_channels: list[str]):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def _unsubscribe(self, ws, full_channels: list[str]):
|
|
33
|
+
pass
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from pfund.products.product_crypto import CryptoProduct
|
|
2
1
|
from pfund.exchanges.bybit.exchange import Exchange as Bybit
|
|
3
2
|
from pfund.exchanges.bybit.rest_api import RestApi as BybitRestApi
|
|
4
|
-
from pfund.exchanges.bybit.ws_api import WebsocketApi as BybitWebsocketApi
|
|
3
|
+
from pfund.exchanges.bybit.ws_api import WebsocketApi as BybitWebsocketApi
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import datetime
|
|
2
4
|
from decimal import Decimal
|
|
5
|
+
from pathlib import Path
|
|
3
6
|
|
|
4
|
-
from typing import Callable, Any
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
+
from typing import Callable, Any, TYPE_CHECKING
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from pfund.types.bybit import Category
|
|
10
|
+
from websocket import WebSocket
|
|
7
11
|
|
|
8
12
|
from pfund.exchanges.exchange_base import BaseExchange
|
|
9
|
-
from pfund.exchanges.bybit.types import Category
|
|
10
13
|
from pfund.accounts import CryptoAccount
|
|
11
14
|
from pfund.products import CryptoProduct
|
|
12
15
|
from pfund.orders import CryptoOrder
|
|
@@ -31,8 +34,9 @@ class Exchange(BaseExchange):
|
|
|
31
34
|
_MAX_NUM_OF_PLACE_BATCH_ORDERS = 20
|
|
32
35
|
_MAX_NUM_OF_CANEL_BATCH_ORDERS = 20
|
|
33
36
|
|
|
34
|
-
def __init__(self, env):
|
|
35
|
-
|
|
37
|
+
def __init__(self, env: str):
|
|
38
|
+
exch = Path(__file__).parent.name
|
|
39
|
+
super().__init__(env, exch)
|
|
36
40
|
|
|
37
41
|
def _create_pdt_matchings_config(
|
|
38
42
|
# general to exchanges
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from pathlib import Path
|
|
1
2
|
import urllib
|
|
2
3
|
import hmac
|
|
3
4
|
import hashlib
|
|
@@ -44,7 +45,8 @@ class RestApi(BaseRestApi):
|
|
|
44
45
|
'get_balances': ('GET', '/v5/account/wallet-balance'),
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
def __init__(self, env
|
|
48
|
+
def __init__(self, env):
|
|
49
|
+
exch = Path(__file__).parent.name
|
|
48
50
|
super().__init__(env, exch)
|
|
49
51
|
|
|
50
52
|
def _authenticate(self, req, account):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import time
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
try:
|
|
3
4
|
import orjson as json
|
|
4
5
|
except ImportError:
|
|
@@ -45,8 +46,9 @@ class WebsocketApi(BaseWebsocketApi):
|
|
|
45
46
|
'spot': 10
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
def __init__(self, env,
|
|
49
|
-
|
|
49
|
+
def __init__(self, env, adapter):
|
|
50
|
+
exch = Path(__file__).parent.name
|
|
51
|
+
super().__init__(env, exch, adapter)
|
|
50
52
|
|
|
51
53
|
def _ping(self):
|
|
52
54
|
msg = {"op": "ping"}
|