pfund 0.0.1.dev10__tar.gz → 0.0.1.dev12__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.
Files changed (188) hide show
  1. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/PKG-INFO +5 -1
  2. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/CONTRIBUTING.md +3 -0
  3. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/adapter.py +4 -7
  4. pfund-0.0.1.dev12/pfund/analyzer.py +203 -0
  5. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/ib/broker_ib.py +4 -2
  6. pfund-0.0.1.dev12/pfund/config/binance/linear/config.yml +7 -0
  7. pfund-0.0.1.dev12/pfund/config/binance/linear/lot_sizes_linear.yml +2 -0
  8. pfund-0.0.1.dev12/pfund/config/binance/linear/pdt_matchings_linear.yml +2 -0
  9. pfund-0.0.1.dev12/pfund/config/binance/linear/tick_sizes_linear.yml +2 -0
  10. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/configuration.py +5 -7
  11. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config_handler.py +4 -0
  12. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/const/commons.py +3 -1
  13. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/const/paths.py +1 -0
  14. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/data_tools/data_tool_pandas.py +2 -6
  15. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/engines/backtest_engine.py +50 -33
  16. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/engines/trade_engine.py +7 -10
  17. pfund-0.0.1.dev12/pfund/exchanges/binance/__init__.py +3 -0
  18. pfund-0.0.1.dev12/pfund/exchanges/binance/exchange.py +37 -0
  19. pfund-0.0.1.dev12/pfund/exchanges/binance/linear/exchange.py +6 -0
  20. pfund-0.0.1.dev12/pfund/exchanges/binance/rest_api.py +15 -0
  21. pfund-0.0.1.dev12/pfund/exchanges/binance/ws_api.py +33 -0
  22. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/__init__.py +1 -2
  23. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/exchange.py +10 -6
  24. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api.py +3 -1
  25. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/ws_api.py +4 -2
  26. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/exchange_base.py +12 -9
  27. pfund-0.0.1.dev12/pfund/git_controller.py +60 -0
  28. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/strategy_manager.py +1 -1
  29. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/models/model_base.py +39 -1
  30. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/strategy_base.py +39 -1
  31. pfund-0.0.1.dev12/pfund/templates/dashboards/pfund-overview.streamlit.py +0 -0
  32. pfund-0.0.1.dev12/pfund/templates/notebooks/pfund-analytics.ipynb +49 -0
  33. pfund-0.0.1.dev12/pfund/templates/notebooks/pfund-overview.ipynb +55 -0
  34. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/types/common_literals.py +3 -1
  35. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/utils/utils.py +23 -3
  36. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pyproject.toml +19 -8
  37. pfund-0.0.1.dev10/pfund/analyzer.py +0 -3
  38. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/LICENSE +0 -0
  39. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/README.md +0 -0
  40. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/__init__.py +0 -0
  41. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/accounts/__init__.py +0 -0
  42. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/accounts/account_base.py +0 -0
  43. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/accounts/account_crypto.py +0 -0
  44. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/accounts/account_ib.py +0 -0
  45. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/balances/__init__.py +0 -0
  46. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/balances/balance_base.py +0 -0
  47. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/balances/balance_crypto.py +0 -0
  48. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/balances/balance_ib.py +0 -0
  49. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/__init__.py +0 -0
  50. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/broker_backtest.py +0 -0
  51. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/broker_base.py +0 -0
  52. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/broker_crypto.py +0 -0
  53. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/broker_live.py +0 -0
  54. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/ib/__init__.py +0 -0
  55. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/ib/ib_api.py +0 -0
  56. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/ib/ib_client.py +0 -0
  57. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/brokers/ib/ib_wrapper.py +0 -0
  58. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/cli/__init__.py +0 -0
  59. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/cli/commands/__init__.py +0 -0
  60. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/cli/commands/config.py +0 -0
  61. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/cli/commands/docker_compose.py +0 -0
  62. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/cli/main.py +0 -0
  63. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/config.yml +0 -0
  64. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/lot_sizes_inverse.yml +0 -0
  65. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/lot_sizes_linear.yml +0 -0
  66. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/lot_sizes_option.yml +0 -0
  67. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/lot_sizes_spot.yml +0 -0
  68. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/pdt_matchings_inverse.yml +0 -0
  69. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/pdt_matchings_linear.yml +0 -0
  70. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/pdt_matchings_option.yml +0 -0
  71. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/pdt_matchings_spot.yml +0 -0
  72. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/tick_sizes_inverse.yml +0 -0
  73. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/tick_sizes_linear.yml +0 -0
  74. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/tick_sizes_option.yml +0 -0
  75. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/bybit/tick_sizes_spot.yml +0 -0
  76. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/ib/config.yml +0 -0
  77. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/config/logging.yml +0 -0
  78. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/const/__init__.py +0 -0
  79. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/const/_zmq_routes.py +0 -0
  80. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/data_tools/data_tool_base.py +0 -0
  81. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/__init__.py +0 -0
  82. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/data_bar.py +0 -0
  83. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/data_base.py +0 -0
  84. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/data_quote.py +0 -0
  85. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/data_tick.py +0 -0
  86. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/data_time_based.py +0 -0
  87. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/resolution.py +0 -0
  88. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/datas/timeframe.py +0 -0
  89. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/engines/__init__.py +0 -0
  90. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/engines/base_engine.py +0 -0
  91. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/engines/test_engine.py +0 -0
  92. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/engines/train_engine.py +0 -0
  93. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/errors.py +0 -0
  94. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/__init__.py +0 -0
  95. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_inverse +0 -0
  96. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_linear +0 -0
  97. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_option +0 -0
  98. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_result_spot +0 -0
  99. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_inverse +0 -0
  100. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_linear +0 -0
  101. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_option +0 -0
  102. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/bybit/rest_api_samples/get_markets_return_spot +0 -0
  103. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/rest_api_base.py +0 -0
  104. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/exchanges/ws_api_base.py +0 -0
  105. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/__init__.py +0 -0
  106. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/account_summary_tags.py +0 -0
  107. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/client.py +0 -0
  108. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/comm.py +0 -0
  109. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/commission_report.py +0 -0
  110. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/common.py +0 -0
  111. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/connection.py +0 -0
  112. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/contract.py +0 -0
  113. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/decoder.py +0 -0
  114. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/enum_implem.py +0 -0
  115. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/errors.py +0 -0
  116. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/execution.py +0 -0
  117. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/ibapi.pyproj +0 -0
  118. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/message.py +0 -0
  119. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/news.py +0 -0
  120. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/object_implem.py +0 -0
  121. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/order.py +0 -0
  122. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/order_condition.py +0 -0
  123. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/order_state.py +0 -0
  124. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/orderdecoder.py +0 -0
  125. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/reader.py +0 -0
  126. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/scanner.py +0 -0
  127. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/server_versions.py +0 -0
  128. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/softdollartier.py +0 -0
  129. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/tag_value.py +0 -0
  130. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/ticktype.py +0 -0
  131. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/utils.py +0 -0
  132. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/externals/ibapi/wrapper.py +0 -0
  133. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/indicators/__init__.py +0 -0
  134. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/indicators/indicator_base.py +0 -0
  135. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/indicators/ta_indicator.py +0 -0
  136. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/indicators/talib_indicator.py +0 -0
  137. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/investment_profile.py +0 -0
  138. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/main.py +0 -0
  139. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/__init__.py +0 -0
  140. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/base_manager.py +0 -0
  141. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/connection_manager.py +0 -0
  142. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/data_manager.py +0 -0
  143. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/order_manager.py +0 -0
  144. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/portfolio_manager.py +0 -0
  145. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/managers/risk_manager.py +0 -0
  146. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/mixins/backtest.py +0 -0
  147. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/models/__init__.py +0 -0
  148. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/models/model_backtest.py +0 -0
  149. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/models/model_meta.py +0 -0
  150. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/models/pytorch_model.py +0 -0
  151. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/models/sklearn_model.py +0 -0
  152. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/orders/__init__.py +0 -0
  153. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/orders/order_base.py +0 -0
  154. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/orders/order_crypto.py +0 -0
  155. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/orders/order_ib.py +0 -0
  156. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/orders/order_statuses.py +0 -0
  157. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/orders/order_time_in_force.py +0 -0
  158. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/plogging/__init__.py +0 -0
  159. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/plogging/config.py +0 -0
  160. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/plogging/filters.py +0 -0
  161. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/plogging/formatter.py +0 -0
  162. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/plogging/handlers.py +0 -0
  163. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/portfolio.py +0 -0
  164. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/positions/__init__.py +0 -0
  165. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/positions/position_base.py +0 -0
  166. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/positions/position_crypto.py +0 -0
  167. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/positions/position_ib.py +0 -0
  168. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/products/__init__.py +0 -0
  169. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/products/product_base.py +0 -0
  170. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/products/product_crypto.py +0 -0
  171. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/products/product_ib.py +0 -0
  172. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/risk_monitor.py +0 -0
  173. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/__init__.py +0 -0
  174. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/allocation_strategy.py +0 -0
  175. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/diversification_strategy.py +0 -0
  176. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/hedging_strategy.py +0 -0
  177. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/optimization_strategy.py +0 -0
  178. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/portfolio_strategy.py +0 -0
  179. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/rebalancing_strategy.py +0 -0
  180. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/strategy_backtest.py +0 -0
  181. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/strategies/strategy_meta.py +0 -0
  182. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/types/backtest.py +0 -0
  183. /pfund-0.0.1.dev10/pfund/exchanges/bybit/types.py → /pfund-0.0.1.dev12/pfund/types/bybit.py +0 -0
  184. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/types/core.py +0 -0
  185. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/universe.py +0 -0
  186. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/utils/aliases.py +0 -0
  187. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/utils/envs.py +0 -0
  188. {pfund-0.0.1.dev10 → pfund-0.0.1.dev12}/pfund/zeromq.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pfund
3
- Version: 0.0.1.dev10
3
+ Version: 0.0.1.dev12
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
@@ -16,4 +16,7 @@ git pull --recurse-submodules # = git pull + git submodule update --recursive
16
16
  ```bash
17
17
  # at the root directory, run:
18
18
  jb build docs/ [--all]
19
+
20
+ # check if external links are broken:
21
+ jb build docs/ --builder linkcheck
19
22
  ```
@@ -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, trading_venue, adapter_dict):
9
- self._trading_venue = trading_venue
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(file_path):
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(file_path + '/' + file_name, 'r') as f:
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,203 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from typing import TYPE_CHECKING
7
+ if TYPE_CHECKING:
8
+ from pfund.types.common_literals import tSUPPORTED_CODE_EDITORS, tSUPPORTED_TEMPLATE_TYPES
9
+
10
+ from pfund.utils import utils
11
+ from pfund.config_handler import ConfigHandler
12
+ from pfund.const.paths import PROJ_PATH
13
+
14
+
15
+ class Analyzer:
16
+ try:
17
+ Engine = utils.get_engine_class()
18
+ config = Engine.config
19
+ except:
20
+ config = ConfigHandler.load_config()
21
+
22
+ notebook_path = Path(config.notebook_path)
23
+ spreadsheet_path = Path(config.spreadsheet_path)
24
+ dashboard_path = Path(config.dashboard_path)
25
+
26
+ def __init__(self, data: dict | None=None):
27
+ self.data = data or {}
28
+
29
+ @staticmethod
30
+ def _is_file(template: str) -> bool:
31
+ if '\\' in template or '/' in template:
32
+ assert Path(template).resolve().is_file(), f"File {template} does not exist"
33
+ return True
34
+ return False
35
+
36
+ @staticmethod
37
+ def _derive_template_type(template: str) -> tSUPPORTED_TEMPLATE_TYPES:
38
+ if '.ipynb' in template:
39
+ template_type = 'notebook'
40
+ elif '.grid' in template:
41
+ template_type = 'spreadsheet'
42
+ elif '.py' in template:
43
+ template_type = 'dashboard'
44
+ else:
45
+ raise ValueError(f"Template {template} is not a valid template, only .ipynb, .grid, .py are supported.")
46
+ return template_type
47
+
48
+ def _find_template(self, template: str) -> str:
49
+ '''Check if the template exists in pfund's templates or user's templates
50
+ e.g. template = 'notebook.ipynb' or 'spreadsheet.grid' or 'dashboard.py'
51
+ '''
52
+ template_type = self._derive_template_type(template)
53
+ pfund_templates_dir = PROJ_PATH / 'templates' / (template_type+'s')
54
+ user_templates_dir = getattr(self, f'{template_type}_path')
55
+ for templates_dir in [pfund_templates_dir, user_templates_dir]:
56
+ for file_name in os.listdir(templates_dir):
57
+ if template == file_name:
58
+ template_file_path = templates_dir / template
59
+ return str(template_file_path)
60
+ else:
61
+ raise FileNotFoundError(f"Template {template} not found in pfund's templates or user's templates")
62
+
63
+ def _get_editor_cmd(self, editor: tSUPPORTED_CODE_EDITORS) -> str:
64
+ if editor == 'vscode':
65
+ cmd = 'code'
66
+ if utils.is_command_available(cmd):
67
+ return cmd
68
+ else:
69
+ print("VSCode command 'code' is not available, cannot open the output notebook")
70
+ elif editor == 'pycharm':
71
+ for cmd in ['charm', 'pycharm']:
72
+ if utils.is_command_available(cmd):
73
+ return cmd
74
+ else:
75
+ print("PyCharm commands 'charm'/'pycharm' are both not available, cannot open the output notebook")
76
+ else:
77
+ print(f"Editor '{editor}' is not supported, cannot open the output notebook")
78
+
79
+ def run_notebooks(
80
+ self,
81
+ notebooks: list[str] | str,
82
+ *voila_args,
83
+ data: dict | None=None,
84
+ display: bool=True,
85
+ port: int=8866,
86
+ show_results_only: bool=True,
87
+ open_outputs: bool=False,
88
+ outputs_path: str | None=None,
89
+ editor: tSUPPORTED_CODE_EDITORS='vscode'
90
+ ) -> None:
91
+ '''
92
+ Args:
93
+ notebook:
94
+ - notebook_template's name
95
+ - notebook's full path in str or Path
96
+ voila_args: additional arguments to pass to voila
97
+ data: data to be analyzed, if None, use the data passed to the Analyzer instance during initialization
98
+ display: if True, display the notebook in voila
99
+ show_results_only: if True, display only the results (no source code) in voila
100
+ open_outputs: if True, open the output notebook in the editor
101
+ outputs_path: path to save the output notebooks, if None, do not save the output notebooks
102
+ '''
103
+ import subprocess
104
+ import papermill as pm
105
+
106
+ def _find_available_port(_port):
107
+ retry_num = 100
108
+ while retry_num:
109
+ if not utils.is_port_in_use(_port):
110
+ return _port
111
+ retry_num -= 1
112
+ _port += 1
113
+ else:
114
+ raise Exception(f"No available ports found starting from {_port - 100}, cannot display the notebook")
115
+
116
+ def _assert_voila_args_are_valid():
117
+ for arg in voila_args:
118
+ if not arg.startswith('--'):
119
+ raise ValueError(f"Voila argument '{arg}' should start with '--'")
120
+ if arg.startswith('--port='):
121
+ raise ValueError(f"Voila argument '{arg}' should not be passed in, use the 'port' argument instead")
122
+ if arg.startswith('--strip_sources='):
123
+ raise ValueError(f"Voila argument '{arg}' should not be passed in, use the 'show_results_only' argument instead")
124
+
125
+ voila_processes = []
126
+ nb_output_file_paths = []
127
+ if isinstance(notebooks, str):
128
+ notebooks = [notebooks]
129
+ data = data or self.data
130
+ if not data:
131
+ raise ValueError("No data passed in or stored in the Analyzer instance, please pass in the data to be analyzed.")
132
+
133
+ if open_outputs:
134
+ assert outputs_path is not None, f"{outputs_path=}, cannot open the output notebook without saving it."
135
+ editor_cmd = self._get_editor_cmd(editor)
136
+
137
+ _assert_voila_args_are_valid()
138
+ is_theme_provided = any(arg.startswith('--theme=') for arg in voila_args)
139
+ if not is_theme_provided:
140
+ default_theme = 'dark'
141
+ voila_args = [f'--theme={default_theme}', *voila_args]
142
+
143
+ try:
144
+ for notebook in notebooks:
145
+ if self._is_file(notebook):
146
+ nb_input_file_path: str = notebook
147
+ notebook = Path(notebook).name # e.g. 'notebook.ipynb'
148
+ else:
149
+ nb_input_file_path: str = self._find_template(notebook)
150
+ nb_output_file_path = Path(outputs_path or '.').resolve() / f'{notebook.replace(".ipynb", "")}_output.ipynb'
151
+ nb_output_file_path = str(nb_output_file_path)
152
+ nb_output_file_paths.append(nb_output_file_path)
153
+
154
+ print(f"Executing notebook: {notebook}")
155
+ pm.execute_notebook(
156
+ nb_input_file_path,
157
+ nb_output_file_path,
158
+ parameters=data
159
+ )
160
+
161
+ if open_outputs:
162
+ # e.g. code notebook_output.ipynb if using vscode
163
+ if editor_cmd:
164
+ subprocess.run([editor_cmd, nb_output_file_path])
165
+
166
+ if display:
167
+ port = _find_available_port(port)
168
+ is_last_notebook = (notebook == notebooks[-1])
169
+ subprocess_func = subprocess.run if is_last_notebook else subprocess.Popen
170
+ process = subprocess_func([
171
+ 'voila',
172
+ f'--port={port}',
173
+ f'--strip_sources={show_results_only}',
174
+ *voila_args,
175
+ nb_output_file_path
176
+ ])
177
+ voila_processes.append(process)
178
+ except KeyboardInterrupt:
179
+ print("KeyboardInterrupt: Stopping the execution of the notebooks")
180
+ except Exception:
181
+ raise
182
+ finally:
183
+ if outputs_path is None:
184
+ for nb_output_file_path in nb_output_file_paths:
185
+ print(f"{outputs_path=}, removing output notebook: {nb_output_file_path}")
186
+ os.remove(nb_output_file_path)
187
+ for process in voila_processes:
188
+ process.terminate()
189
+ process.wait()
190
+
191
+ # TODO:
192
+ def run_spreadsheets(
193
+ self,
194
+ spreadsheets: list[str] | str
195
+ ):
196
+ pass
197
+
198
+ # TODO:
199
+ def run_dashboards(
200
+ self,
201
+ dashboards: list[str] | str
202
+ ):
203
+ pass
@@ -5,6 +5,7 @@ from collections import defaultdict
5
5
 
6
6
  from pfund.adapter import Adapter
7
7
  from pfund.config.configuration import Configuration
8
+ from pfund.const.paths import PROJ_CONFIG_PATH
8
9
  from pfund.const.commons import SUPPORTED_PRODUCT_TYPES
9
10
  from pfund.products import IBProduct
10
11
  from pfund.accounts import IBAccount
@@ -19,8 +20,9 @@ from pfund.brokers.ib.ib_api import IBApi
19
20
  class IBBroker(LiveBroker):
20
21
  def __init__(self, env, **configs):
21
22
  super().__init__(env, 'IB', **configs)
22
- self.configs = Configuration(self.bkr, 'config')
23
- self.adapter = Adapter(self.bkr, self.configs.load_config_section('adapter'))
23
+ config_path = f'{PROJ_CONFIG_PATH}/{self.bkr.lower()}'
24
+ self.configs = Configuration(config_path, 'config')
25
+ self.adapter = Adapter(config_path, self.configs.load_config_section('adapter'))
24
26
  self.account = None
25
27
 
26
28
  # API
@@ -0,0 +1,7 @@
1
+ ---
2
+ settings:
3
+ private_channels: []
4
+ ...
5
+ ---
6
+ adapter:
7
+ pdts: {}
@@ -0,0 +1,2 @@
1
+ # FIXME: fake
2
+ BTC_USDT_PERP: 0.001
@@ -0,0 +1,2 @@
1
+ BTC_USDT_PERP: BTCUSDT
2
+ ETH_USDT_PERP: ETHUSDT
@@ -0,0 +1,2 @@
1
+ # FIXME: fake
2
+ BTC_USDT_PERP: 0.01
@@ -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, config_dir, config_name):
11
- self.config_dir = config_dir.lower()
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 get_config_dir(self):
21
- return self.config_dir
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'{PROJ_CONFIG_PATH}/{self.config_dir}/{config_name}.yml'
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
 
@@ -20,4 +20,6 @@ SUPPORTED_TIMEFRAMES = [
20
20
  ]
21
21
  SUPPORTED_DATA_CHANNELS = ['orderbook', 'tradebook', 'kline']
22
22
  SUPPORTED_BACKTEST_MODES = ['vectorized', 'event_driven']
23
- SUPPORTED_DATA_TOOLS = ['pandas']
23
+ SUPPORTED_DATA_TOOLS = ['pandas']
24
+ SUPPORTED_CODE_EDITORS = ['vscode', 'pycharm']
25
+ SUPPORTED_TEMPLATE_TYPES = ['notebook', 'spreadsheet', 'dashboard']
@@ -24,3 +24,4 @@ BACKTEST_PATH = DATA_PATH / 'backtests'
24
24
  NOTEBOOK_PATH = DATA_PATH / 'templates' / 'notebooks'
25
25
  SPREADSHEET_PATH = DATA_PATH / 'templates' / 'spreadsheets'
26
26
  DASHBOARD_PATH = DATA_PATH / 'templates' / 'dashboards'
27
+ ARTIFACT_PATH = DATA_PATH / '.artifacts'
@@ -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, 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)
154
+ def output_df_to_parquet(self, df: pd.DataFrame, file_path: str):
155
+ df.to_parquet(file_path)
160
156
 
161
157
  def _create_multi_index(self, index_data: dict, index_names: list[str]) -> pd.MultiIndex:
162
158
  return pd.MultiIndex.from_tuples([tuple(index_data[name] for name in index_names)], names=index_names)
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import hashlib
4
+ import inspect
4
5
  import os
5
6
  import time
6
7
  import datetime
@@ -13,6 +14,7 @@ if TYPE_CHECKING:
13
14
  from pfund.types.core import tStrategy, tModel, tFeature, tIndicator
14
15
 
15
16
  import pfund as pf
17
+ from pfund.git_controller import GitController
16
18
  from pfund.engines.base_engine import BaseEngine
17
19
  from pfund.brokers.broker_backtest import BacktestBroker
18
20
  from pfund.strategies.strategy_base import BaseStrategy
@@ -23,7 +25,7 @@ from pfund.mixins.backtest import BacktestMixin
23
25
 
24
26
 
25
27
  class BacktestEngine(BaseEngine):
26
- def __new__(cls, *, env: str='BACKTEST', data_tool: tSUPPORTED_DATA_TOOLS='pandas', mode: tSUPPORTED_BACKTEST_MODES='vectorized', append_to_strategy_df=False, use_prepared_signals=True, config: ConfigHandler | None=None, **settings):
28
+ 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, auto_git_commit=False, **settings):
27
29
  if not hasattr(cls, 'mode'):
28
30
  cls.mode = mode.lower()
29
31
  if not hasattr(cls, 'append_to_strategy_df'):
@@ -32,13 +34,18 @@ class BacktestEngine(BaseEngine):
32
34
  # instead of recalculating the signals. This will make event-driven backtesting faster but less consistent with live trading
33
35
  if not hasattr(cls, 'use_prepared_signals'):
34
36
  cls.use_prepared_signals = use_prepared_signals
37
+ if not hasattr(cls, 'auto_git_commit'):
38
+ cls.auto_git_commit = auto_git_commit
35
39
  return super().__new__(cls, env, data_tool=data_tool, config=config, **settings)
36
40
 
37
- def __init__(self, *, env: str='BACKTEST', data_tool: tSUPPORTED_DATA_TOOLS='pandas', mode: tSUPPORTED_BACKTEST_MODES='vectorized', append_to_strategy_df=False, use_prepared_signals=True, config: ConfigHandler | None=None, **settings):
38
- super().__init__(env, data_tool=data_tool)
41
+ 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, auto_git_commit=False, **settings):
39
42
  # avoid re-initialization to implement singleton class correctly
40
- # if not hasattr(self, '_initialized'):
41
- # pass
43
+ if not hasattr(self, '_initialized'):
44
+ # Get the current frame and then the outer frame (where the engine instance is created)
45
+ caller_frame = inspect.currentframe().f_back
46
+ file_path = caller_frame.f_code.co_filename # Extract the file path from the frame
47
+ self._git = GitController(os.path.abspath(file_path))
48
+ super().__init__(env, data_tool=data_tool)
42
49
 
43
50
  # HACK: since python doesn't support dynamic typing, true return type should be subclass of BacktestMixin and tStrategy
44
51
  # write -> BacktestMixin | tStrategy for better intellisense in IDEs
@@ -88,11 +95,11 @@ class BacktestEngine(BaseEngine):
88
95
  def _generate_backtest_id() -> str:
89
96
  return uuid.uuid4().hex
90
97
 
91
- def _create_backtest_name(self, strat: str):
98
+ def _create_backtest_name(self, strat: str, backtest_id: str, backtest_id_length: int=12):
92
99
  local_tz = utils.get_local_timezone()
93
100
  utcnow = datetime.datetime.now(tz=local_tz).strftime('%Y-%m-%d_%H:%M:%S_UTC%z')
94
- backtest_id = self._generate_backtest_id()
95
- return '.'.join([strat, utcnow, backtest_id])
101
+ trimmed_backtest_id = backtest_id[:backtest_id_length]
102
+ return '.'.join([strat, utcnow, trimmed_backtest_id])
96
103
 
97
104
  @staticmethod
98
105
  def _generate_backtest_hash(strategy: BaseStrategy):
@@ -131,60 +138,64 @@ class BacktestEngine(BaseEngine):
131
138
  except:
132
139
  self.logger.exception(f"Error writing to {file_path}:")
133
140
 
134
- def _generate_backtest_iteration(self, strategy: BaseStrategy) -> int:
141
+ def _generate_backtest_iteration(self, backtest_hash: str) -> int:
135
142
  '''Generate backtest iteration number for the same backtest_hash.
136
143
  Read the existing backtest.json file to get the iteration number for the same strategy hash
137
144
  If the backtest hash is not found, create a new entry with iteration number 1
138
145
  else increment the iteration number by 1.
139
146
  '''
140
- backtest_hash = self._generate_backtest_hash(strategy)
141
147
  file_name = 'backtest.json'
142
148
  backtest_json = self.read_json(file_name)
143
149
  backtest_json[backtest_hash] = backtest_json.get(backtest_hash, 0) + 1
144
150
  self._write_json(file_name, backtest_json)
145
151
  return backtest_json[backtest_hash]
146
152
 
147
- def _write_backtest_history(self, backtest_name: str, backtest_name_trimmed: str, start_time: float, end_time: float):
148
- splits = backtest_name.split('.')
149
- strat, backtest_id = splits[0], splits[-1]
153
+ def _commit_strategy(self, strategy: BaseStrategy) -> str | None:
154
+ engine_name = self.__class__.__name__
155
+ strat = strategy.name
156
+ commit_hash: str | None = self._git.commit(strategy._file_path, f'[PFund] {engine_name}: auto-commit strategy "{strat}"')
157
+ if commit_hash:
158
+ self.logger.debug(f"Strategy {strat} committed. {commit_hash=}")
159
+ else:
160
+ commit_hash = self._git.get_last_n_commit(n=1)[0]
161
+ self.logger.debug(f"Strategy {strat} has no changes to commit, return the last {commit_hash=}")
162
+ return commit_hash
163
+
164
+ def _output_backtest_results(self, strat: str, df, start_time: float, end_time: float, commit_hash: str | None):
150
165
  strategy = self.get_strategy(strat)
166
+ backtest_id = self._generate_backtest_id()
167
+ backtest_hash = self._generate_backtest_hash(strategy)
168
+ backtest_name = self._create_backtest_name(strat, backtest_id)
169
+ backtest_iter = self._generate_backtest_iteration(backtest_hash)
151
170
  local_tz = utils.get_local_timezone()
152
171
  duration = end_time - start_time
172
+ df_file_path = os.path.join(self.config.backtest_path, f'{backtest_name}.parquet')
153
173
  backtest_history = {
154
- 'settings': self.settings,
155
174
  'metadata': {
156
175
  'pfund_version': pf.__version__,
157
- 'backtest_name': backtest_name,
158
176
  'backtest_id': backtest_id,
159
- 'backtest_iteration': self._generate_backtest_iteration(strategy),
177
+ 'backtest_hash': backtest_hash,
178
+ 'backtest_name': backtest_name,
179
+ 'backtest_iteration': backtest_iter,
180
+ 'commit_hash': commit_hash,
160
181
  'duration': f'{duration:.2f}s' if duration > 1 else f'{duration*1000:.2f}ms',
161
182
  'start_time': datetime.datetime.fromtimestamp(start_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
162
183
  'end_time': datetime.datetime.fromtimestamp(end_time, tz=local_tz).strftime('%Y-%m-%dT%H:%M:%S%z'),
184
+ 'settings': self.settings,
163
185
  },
164
186
  'strategy': strategy.to_dict(),
165
- 'results': {
166
- 'df_file_path': os.path.join(self.config.backtest_path, f'{backtest_name_trimmed}.parquet'),
167
- }
187
+ 'result': df_file_path
168
188
  }
169
- self._write_json(f'{backtest_name_trimmed}.json', backtest_history)
170
-
171
- def trim_backtest_name(self, backtest_name: str) -> str:
172
- splits = backtest_name.split('.')
173
- backtest_id_len = 12
174
- splits[-1] = splits[-1][:backtest_id_len]
175
- return '.'.join(splits)
176
-
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)
189
+ self.data_tool.output_df_to_parquet(df, df_file_path)
190
+ self._write_json(f'{backtest_name}.json', backtest_history)
191
+ return backtest_history
182
192
 
183
193
  def run(self):
184
194
  for broker in self.brokers.values():
185
195
  broker.start()
186
196
  self.strategy_manager.start()
187
197
 
198
+ backtests = {}
188
199
  if self.mode == 'vectorized':
189
200
  for strat, strategy in self.strategy_manager.strategies.items():
190
201
  # _dummy strategy is only created for model training, do nothing
@@ -192,11 +203,16 @@ class BacktestEngine(BaseEngine):
192
203
  continue
193
204
  if not hasattr(strategy, 'backtest'):
194
205
  raise Exception(f'Strategy {strat} does not have backtest() method, cannot run vectorized backtesting')
206
+ if self.auto_git_commit and self._git.is_git_repo():
207
+ commit_hash = self._commit_strategy(strategy)
208
+ else:
209
+ commit_hash = None
195
210
  start_time = time.time()
196
211
  strategy.backtest()
197
212
  end_time = time.time()
198
213
  df = strategy.get_df()
199
- self.output_backtest_results(strat, df, start_time, end_time)
214
+ backtest_history: dict = self._output_backtest_results(strat, df, start_time, end_time, commit_hash)
215
+ backtests[strat] = backtest_history
200
216
  elif self.mode == 'event_driven':
201
217
  for strat, strategy in self.strategy_manager.strategies.items():
202
218
  if strat == '_dummy':
@@ -248,6 +264,7 @@ class BacktestEngine(BaseEngine):
248
264
  else:
249
265
  raise NotImplementedError(f'Backtesting mode {self.mode} is not supported')
250
266
  self.strategy_manager.stop(reason='finished backtesting')
267
+ return backtests
251
268
 
252
269
  def end(self):
253
270
  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
- import socket
61
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
62
- _is_port_in_use = (s.connect_ex(('localhost', _port)) == 0)
63
- _is_port_assigned = (_port in _assigned_ports)
64
- if _is_port_in_use or _is_port_assigned:
65
- return False
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,3 @@
1
+ from pfund.exchanges.binance.exchange import Exchange as Binance
2
+ from pfund.exchanges.binance.rest_api import RestApi as BinanceRestApi
3
+ from pfund.exchanges.binance.ws_api import WebsocketApi as BinanceWebsocketApi
@@ -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,6 @@
1
+ from pfund.exchanges.binance.exchange import Exchange
2
+
3
+
4
+ class ExchangeLinear(Exchange):
5
+ def __init__(self, env: str, ptype: str):
6
+ super().__init__(env, ptype)