back-trader-python 1.4.0__py3-none-any.whl

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 (465) hide show
  1. back_trader_python-1.4.0.dist-info/METADATA +1491 -0
  2. back_trader_python-1.4.0.dist-info/RECORD +465 -0
  3. back_trader_python-1.4.0.dist-info/WHEEL +5 -0
  4. back_trader_python-1.4.0.dist-info/licenses/LICENSE +674 -0
  5. back_trader_python-1.4.0.dist-info/top_level.txt +1 -0
  6. backtrader/__init__.py +148 -0
  7. backtrader/_cerebro/__init__.py +5 -0
  8. backtrader/_cerebro/channel.py +382 -0
  9. backtrader/_cerebro/execution.py +377 -0
  10. backtrader/_cerebro/lifecycle.py +143 -0
  11. backtrader/_cerebro/notifications.py +150 -0
  12. backtrader/_cerebro/presentation.py +230 -0
  13. backtrader/_cerebro/registry.py +593 -0
  14. backtrader/_cerebro/runnext.py +551 -0
  15. backtrader/_cerebro/runonce.py +142 -0
  16. backtrader/analyzer.py +594 -0
  17. backtrader/analyzers/__init__.py +50 -0
  18. backtrader/analyzers/annualreturn.py +226 -0
  19. backtrader/analyzers/calmar.py +165 -0
  20. backtrader/analyzers/drawdown.py +287 -0
  21. backtrader/analyzers/leverage.py +112 -0
  22. backtrader/analyzers/logreturnsrolling.py +190 -0
  23. backtrader/analyzers/periodstats.py +153 -0
  24. backtrader/analyzers/positions.py +119 -0
  25. backtrader/analyzers/pyfolio.py +470 -0
  26. backtrader/analyzers/returns.py +192 -0
  27. backtrader/analyzers/sharpe.py +307 -0
  28. backtrader/analyzers/sharpe_ratio_stats.py +534 -0
  29. backtrader/analyzers/sqn.py +112 -0
  30. backtrader/analyzers/timereturn.py +192 -0
  31. backtrader/analyzers/total_value.py +75 -0
  32. backtrader/analyzers/tradeanalyzer.py +278 -0
  33. backtrader/analyzers/transactions.py +141 -0
  34. backtrader/analyzers/vwr.py +245 -0
  35. backtrader/bokeh/__init__.py +155 -0
  36. backtrader/bokeh/analyzers/__init__.py +13 -0
  37. backtrader/bokeh/analyzers/plot.py +192 -0
  38. backtrader/bokeh/analyzers/recorder.py +181 -0
  39. backtrader/bokeh/app.py +1094 -0
  40. backtrader/bokeh/live/__init__.py +11 -0
  41. backtrader/bokeh/live/client.py +352 -0
  42. backtrader/bokeh/live/datahandler.py +346 -0
  43. backtrader/bokeh/plot_adapter.py +200 -0
  44. backtrader/bokeh/schemes/__init__.py +14 -0
  45. backtrader/bokeh/schemes/blackly.py +76 -0
  46. backtrader/bokeh/schemes/scheme.py +150 -0
  47. backtrader/bokeh/schemes/tradimo.py +82 -0
  48. backtrader/bokeh/tab.py +125 -0
  49. backtrader/bokeh/tabs/__init__.py +30 -0
  50. backtrader/bokeh/tabs/analyzer.py +120 -0
  51. backtrader/bokeh/tabs/config.py +154 -0
  52. backtrader/bokeh/tabs/live.py +109 -0
  53. backtrader/bokeh/tabs/log.py +185 -0
  54. backtrader/bokeh/tabs/metadata.py +182 -0
  55. backtrader/bokeh/tabs/performance.py +359 -0
  56. backtrader/bokeh/tabs/source.py +70 -0
  57. backtrader/bokeh/utils/__init__.py +8 -0
  58. backtrader/bokeh/utils/helpers.py +167 -0
  59. backtrader/bokeh/webapp.py +164 -0
  60. backtrader/broker.py +478 -0
  61. backtrader/brokers/__init__.py +36 -0
  62. backtrader/brokers/bbroker.py +2576 -0
  63. backtrader/brokers/btapibroker.py +8227 -0
  64. backtrader/brokers/hft/__init__.py +89 -0
  65. backtrader/brokers/hft/binance_bbo.py +625 -0
  66. backtrader/brokers/hft/binance_bbo_compare.py +1398 -0
  67. backtrader/brokers/hft/examples.py +1228 -0
  68. backtrader/brokers/hft/exchange.py +380 -0
  69. backtrader/brokers/hft/latency.py +309 -0
  70. backtrader/brokers/hft/matching_core.py +572 -0
  71. backtrader/brokers/hft/queue.py +238 -0
  72. backtrader/brokers/hft/recorder.py +88 -0
  73. backtrader/brokers/hft/state.py +138 -0
  74. backtrader/brokers/impact_models.py +118 -0
  75. backtrader/brokers/mixbroker.py +895 -0
  76. backtrader/brokers/tickbroker.py +1991 -0
  77. backtrader/btrun/__init__.py +12 -0
  78. backtrader/btrun/btrun.py +1218 -0
  79. backtrader/cerebro.py +828 -0
  80. backtrader/channel.py +682 -0
  81. backtrader/channels/__init__.py +23 -0
  82. backtrader/channels/bridge.py +186 -0
  83. backtrader/channels/funding.py +248 -0
  84. backtrader/channels/live_queue.py +216 -0
  85. backtrader/channels/live_validator.py +294 -0
  86. backtrader/channels/orderbook.py +257 -0
  87. backtrader/channels/tick.py +202 -0
  88. backtrader/comminfo.py +665 -0
  89. backtrader/commissions/__init__.py +106 -0
  90. backtrader/commissions/ctpoption.py +993 -0
  91. backtrader/configs/account_config_example.yaml +8 -0
  92. backtrader/dataseries.py +379 -0
  93. backtrader/errors.py +106 -0
  94. backtrader/events.py +980 -0
  95. backtrader/feed.py +1523 -0
  96. backtrader/feeds/__init__.py +75 -0
  97. backtrader/feeds/barrier.py +2006 -0
  98. backtrader/feeds/blaze.py +118 -0
  99. backtrader/feeds/btapifeed.py +1538 -0
  100. backtrader/feeds/btcsv.py +203 -0
  101. backtrader/feeds/chainer.py +114 -0
  102. backtrader/feeds/cryptohftdata.py +164 -0
  103. backtrader/feeds/csvgeneric.py +1205 -0
  104. backtrader/feeds/ctpcohort.py +1051 -0
  105. backtrader/feeds/influxfeed.py +158 -0
  106. backtrader/feeds/livefeed.py +71 -0
  107. backtrader/feeds/mixed_channel.py +108 -0
  108. backtrader/feeds/mt4csv.py +42 -0
  109. backtrader/feeds/pandafeed.py +381 -0
  110. backtrader/feeds/quandl.py +256 -0
  111. backtrader/feeds/rollover.py +229 -0
  112. backtrader/feeds/sierrachart.py +30 -0
  113. backtrader/feeds/vchart.py +162 -0
  114. backtrader/feeds/vchartcsv.py +84 -0
  115. backtrader/feeds/vchartfile.py +153 -0
  116. backtrader/feeds/yahoo.py +399 -0
  117. backtrader/fillers.py +148 -0
  118. backtrader/filters/__init__.py +34 -0
  119. backtrader/filters/bsplitter.py +127 -0
  120. backtrader/filters/calendardays.py +121 -0
  121. backtrader/filters/datafiller.py +192 -0
  122. backtrader/filters/datafilter.py +74 -0
  123. backtrader/filters/daysteps.py +96 -0
  124. backtrader/filters/heikinashi.py +63 -0
  125. backtrader/filters/renko.py +164 -0
  126. backtrader/filters/session.py +289 -0
  127. backtrader/flt.py +80 -0
  128. backtrader/functions.py +960 -0
  129. backtrader/indicator.py +449 -0
  130. backtrader/indicators/__init__.py +148 -0
  131. backtrader/indicators/accdecoscillator.py +110 -0
  132. backtrader/indicators/aroon.py +300 -0
  133. backtrader/indicators/atr.py +315 -0
  134. backtrader/indicators/awesomeoscillator.py +122 -0
  135. backtrader/indicators/basicops.py +834 -0
  136. backtrader/indicators/bollinger.py +223 -0
  137. backtrader/indicators/cci.py +89 -0
  138. backtrader/indicators/channels_ext.py +83 -0
  139. backtrader/indicators/contrib/__init__.py +228 -0
  140. backtrader/indicators/contrib/absolutely_no_lag_lwma.py +28 -0
  141. backtrader/indicators/contrib/absolutely_no_lag_lwma_color.py +44 -0
  142. backtrader/indicators/contrib/accumulation_distribution_line.py +92 -0
  143. backtrader/indicators/contrib/adx_cross_hull_style_indicator.py +249 -0
  144. backtrader/indicators/contrib/adxdmi.py +34 -0
  145. backtrader/indicators/contrib/ai_acceleration_deceleration_oscillator.py +34 -0
  146. backtrader/indicators/contrib/altr_trend_signal_v22.py +85 -0
  147. backtrader/indicators/contrib/anchored_momentum_line.py +115 -0
  148. backtrader/indicators/contrib/any_range_cld_tail_indicator.py +82 -0
  149. backtrader/indicators/contrib/aroon_horn_sign_indicator.py +96 -0
  150. backtrader/indicators/contrib/aroon_oscillator_sign_alert.py +50 -0
  151. backtrader/indicators/contrib/arrows_curves_indicator.py +112 -0
  152. backtrader/indicators/contrib/as_ctrend_indicator.py +143 -0
  153. backtrader/indicators/contrib/asimmetric_stoch_nr_indicator.py +187 -0
  154. backtrader/indicators/contrib/atr_normalize_histogram.py +118 -0
  155. backtrader/indicators/contrib/average_change_candle.py +165 -0
  156. backtrader/indicators/contrib/bb_squeeze_indicator.py +60 -0
  157. backtrader/indicators/contrib/bezier_st_dev_indicator.py +135 -0
  158. backtrader/indicators/contrib/binary_wave_indicator.py +233 -0
  159. backtrader/indicators/contrib/blau_c_momentum_indicator.py +123 -0
  160. backtrader/indicators/contrib/blau_cmi_indicator.py +141 -0
  161. backtrader/indicators/contrib/blau_csi.py +76 -0
  162. backtrader/indicators/contrib/blau_ergodic.py +53 -0
  163. backtrader/indicators/contrib/blau_t_stoch_i.py +72 -0
  164. backtrader/indicators/contrib/blau_ts_stochastic.py +85 -0
  165. backtrader/indicators/contrib/blau_tvi.py +55 -0
  166. backtrader/indicators/contrib/brain_trend2_indicator.py +128 -0
  167. backtrader/indicators/contrib/brain_trend_signal_proxy.py +47 -0
  168. backtrader/indicators/contrib/brake_parb_indicator.py +85 -0
  169. backtrader/indicators/contrib/breakout_bars_trend_v2.py +121 -0
  170. backtrader/indicators/contrib/bsi_indicator.py +87 -0
  171. backtrader/indicators/contrib/bulls_bears_eyes.py +67 -0
  172. backtrader/indicators/contrib/bulls_power.py +56 -0
  173. backtrader/indicators/contrib/bw_wise_man1_signal.py +102 -0
  174. backtrader/indicators/contrib/bykov_trend_indicator.py +85 -0
  175. backtrader/indicators/contrib/candle_stop_color.py +46 -0
  176. backtrader/indicators/contrib/candles_x_smoothed_indicator.py +69 -0
  177. backtrader/indicators/contrib/candlesticks_bw.py +45 -0
  178. backtrader/indicators/contrib/caudate_x_period_candle_color.py +56 -0
  179. backtrader/indicators/contrib/cci_histogram_indicator.py +53 -0
  180. backtrader/indicators/contrib/cci_woodies_indicator.py +80 -0
  181. backtrader/indicators/contrib/center_of_gravity_candle_indicator.py +83 -0
  182. backtrader/indicators/contrib/center_of_gravity_indicator.py +70 -0
  183. backtrader/indicators/contrib/cg_oscillator.py +40 -0
  184. backtrader/indicators/contrib/close_line_cci.py +38 -0
  185. backtrader/indicators/contrib/close_price_fractals.py +47 -0
  186. backtrader/indicators/contrib/color3rd_gen_xma_indicator.py +122 -0
  187. backtrader/indicators/contrib/color_bb_candles_indicator.py +108 -0
  188. backtrader/indicators/contrib/color_coppock_indicator.py +157 -0
  189. backtrader/indicators/contrib/color_hma.py +71 -0
  190. backtrader/indicators/contrib/color_j_variation_indicator.py +53 -0
  191. backtrader/indicators/contrib/color_metro_de_marker_indicator.py +78 -0
  192. backtrader/indicators/contrib/color_metro_stochastic_indicator.py +93 -0
  193. backtrader/indicators/contrib/color_metro_wpr_indicator.py +85 -0
  194. backtrader/indicators/contrib/color_schaff_de_marker_trend_cycle.py +92 -0
  195. backtrader/indicators/contrib/color_schaff_trend_cycle_indicator.py +203 -0
  196. backtrader/indicators/contrib/color_step_xccx_indicator.py +193 -0
  197. backtrader/indicators/contrib/color_x2_ma.py +49 -0
  198. backtrader/indicators/contrib/color_x_derivative.py +63 -0
  199. backtrader/indicators/contrib/color_zerolag_de_marker.py +84 -0
  200. backtrader/indicators/contrib/corrected_average_indicator.py +127 -0
  201. backtrader/indicators/contrib/darvas_boxes_system.py +73 -0
  202. backtrader/indicators/contrib/dema_range_channel_color.py +42 -0
  203. backtrader/indicators/contrib/derivative_indicator.py +95 -0
  204. backtrader/indicators/contrib/digital_ft01_indicator.py +112 -0
  205. backtrader/indicators/contrib/digital_macd.py +200 -0
  206. backtrader/indicators/contrib/donchian_channels_system.py +45 -0
  207. backtrader/indicators/contrib/dots_indicator.py +93 -0
  208. backtrader/indicators/contrib/ef_distance_indicator.py +82 -0
  209. backtrader/indicators/contrib/ema_rsi_va.py +80 -0
  210. backtrader/indicators/contrib/envelopes_jp_alonso.py +32 -0
  211. backtrader/indicators/contrib/f2a_ao_indicator.py +120 -0
  212. backtrader/indicators/contrib/fatl_filter.py +179 -0
  213. backtrader/indicators/contrib/fibo_candles_indicator.py +78 -0
  214. backtrader/indicators/contrib/fine_tuning_ma.py +100 -0
  215. backtrader/indicators/contrib/fisher_org_v1.py +102 -0
  216. backtrader/indicators/contrib/fisher_org_v1_sign.py +118 -0
  217. backtrader/indicators/contrib/force_index_ema.py +96 -0
  218. backtrader/indicators/contrib/force_index_ema_2.py +27 -0
  219. backtrader/indicators/contrib/forecast_oscilator.py +145 -0
  220. backtrader/indicators/contrib/fractal_amambk.py +81 -0
  221. backtrader/indicators/contrib/frama_series.py +84 -0
  222. backtrader/indicators/contrib/frasm_av2_indicator.py +104 -0
  223. backtrader/indicators/contrib/go_indicator.py +93 -0
  224. backtrader/indicators/contrib/hlr_indicator.py +95 -0
  225. backtrader/indicators/contrib/hma.py +50 -0
  226. backtrader/indicators/contrib/i4_drfv2.py +34 -0
  227. backtrader/indicators/contrib/i4_drfv3.py +38 -0
  228. backtrader/indicators/contrib/i_anch_mom_indicator.py +72 -0
  229. backtrader/indicators/contrib/i_de_marker_sign_indicator.py +64 -0
  230. backtrader/indicators/contrib/i_gap_indicator.py +45 -0
  231. backtrader/indicators/contrib/i_stoch_komposter_indicator.py +77 -0
  232. backtrader/indicators/contrib/i_trend_indicator.py +125 -0
  233. backtrader/indicators/contrib/iamma_indicator.py +39 -0
  234. backtrader/indicators/contrib/indexed_moving_average.py +33 -0
  235. backtrader/indicators/contrib/instantaneous_trend_filter_indicator.py +51 -0
  236. backtrader/indicators/contrib/inverse_reaction_indicator.py +41 -0
  237. backtrader/indicators/contrib/irsi_sign_indicator.py +95 -0
  238. backtrader/indicators/contrib/iwpr_sign_indicator.py +59 -0
  239. backtrader/indicators/contrib/j_brain_trend1_sig_indicator.py +233 -0
  240. backtrader/indicators/contrib/j_tpo_proxy.py +32 -0
  241. backtrader/indicators/contrib/jma_slope_indicator.py +73 -0
  242. backtrader/indicators/contrib/kalman_filter_indicator.py +119 -0
  243. backtrader/indicators/contrib/kalman_filter_line.py +127 -0
  244. backtrader/indicators/contrib/kama_indicator.py +150 -0
  245. backtrader/indicators/contrib/karacatica_indicator.py +99 -0
  246. backtrader/indicators/contrib/kdj_indicator.py +59 -0
  247. backtrader/indicators/contrib/kwan_ccc_indicator.py +195 -0
  248. backtrader/indicators/contrib/kwan_nrp_indicator.py +113 -0
  249. backtrader/indicators/contrib/kwan_rdp_indicator.py +192 -0
  250. backtrader/indicators/contrib/laguerre_adx_indicator.py +85 -0
  251. backtrader/indicators/contrib/laguerre_filter_indicator.py +66 -0
  252. backtrader/indicators/contrib/laguerre_plus_di_proxy.py +57 -0
  253. backtrader/indicators/contrib/laguerre_roc_indicator.py +81 -0
  254. backtrader/indicators/contrib/le_man_signal_indicator.py +63 -0
  255. backtrader/indicators/contrib/linear_reg_slope_v2_indicator.py +136 -0
  256. backtrader/indicators/contrib/loco_indicator.py +88 -0
  257. backtrader/indicators/contrib/lrma_indicator.py +185 -0
  258. backtrader/indicators/contrib/lsma_angle_indicator.py +106 -0
  259. backtrader/indicators/contrib/ma_rounding_channel_indicator.py +149 -0
  260. backtrader/indicators/contrib/macd2_indicator.py +61 -0
  261. backtrader/indicators/contrib/macd_candle_indicator.py +80 -0
  262. backtrader/indicators/contrib/malr_indicator.py +77 -0
  263. backtrader/indicators/contrib/momentum_candle_sign_indicator.py +51 -0
  264. backtrader/indicators/contrib/moving_average_fn_indicator.py +139 -0
  265. backtrader/indicators/contrib/mt5_stochastic_close_close.py +57 -0
  266. backtrader/indicators/contrib/muv_nor_diff_cloud_indicator.py +107 -0
  267. backtrader/indicators/contrib/non_lag_dot_indicator.py +124 -0
  268. backtrader/indicators/contrib/nrtr_extr_indicator.py +95 -0
  269. backtrader/indicators/contrib/nrtr_indicator.py +95 -0
  270. backtrader/indicators/contrib/p_channel_system.py +40 -0
  271. backtrader/indicators/contrib/percent_envelope.py +37 -0
  272. backtrader/indicators/contrib/percentage_crossover_channel.py +47 -0
  273. backtrader/indicators/contrib/pivot_zig_zag_proxy.py +47 -0
  274. backtrader/indicators/contrib/price_channel_stop_indicator.py +104 -0
  275. backtrader/indicators/contrib/price_extreme_channel.py +35 -0
  276. backtrader/indicators/contrib/qqe_cloud_indicator.py +129 -0
  277. backtrader/indicators/contrib/ravi_indicator.py +40 -0
  278. backtrader/indicators/contrib/raw_close_close_stochastic.py +74 -0
  279. backtrader/indicators/contrib/rd_trend_trigger_indicator.py +51 -0
  280. backtrader/indicators/contrib/renko_level.py +85 -0
  281. backtrader/indicators/contrib/renko_line_break.py +91 -0
  282. backtrader/indicators/contrib/rftl_indicator.py +41 -0
  283. backtrader/indicators/contrib/rkd_indicator.py +53 -0
  284. backtrader/indicators/contrib/roc2_vg_indicator.py +68 -0
  285. backtrader/indicators/contrib/rsi_histogram_indicator.py +43 -0
  286. backtrader/indicators/contrib/rsi_slowdown.py +57 -0
  287. backtrader/indicators/contrib/rsioma_v2.py +41 -0
  288. backtrader/indicators/contrib/rvi_histogram_indicator.py +107 -0
  289. backtrader/indicators/contrib/safe_adx.py +89 -0
  290. backtrader/indicators/contrib/shared_strategy_indicators.py +1651 -0
  291. backtrader/indicators/contrib/sidus_indicator.py +105 -0
  292. backtrader/indicators/contrib/silver_trend_indicator.py +79 -0
  293. backtrader/indicators/contrib/sliding_range_color.py +56 -0
  294. backtrader/indicators/contrib/slow_stoch.py +42 -0
  295. backtrader/indicators/contrib/smoothed_adx_indicator.py +86 -0
  296. backtrader/indicators/contrib/smoothed_rsi.py +31 -0
  297. backtrader/indicators/contrib/spearman_rank_correlation_histogram.py +60 -0
  298. backtrader/indicators/contrib/stalin_indicator.py +152 -0
  299. backtrader/indicators/contrib/starter_laguerre_filter.py +62 -0
  300. backtrader/indicators/contrib/step_manrtr_indicator.py +137 -0
  301. backtrader/indicators/contrib/stochastic_histogram_indicator.py +143 -0
  302. backtrader/indicators/contrib/t3_alarm_indicator.py +125 -0
  303. backtrader/indicators/contrib/t3_average.py +76 -0
  304. backtrader/indicators/contrib/t3_indicator.py +40 -0
  305. backtrader/indicators/contrib/the20s_v020_signal.py +93 -0
  306. backtrader/indicators/contrib/three_candles_indicator.py +70 -0
  307. backtrader/indicators/contrib/three_line_break_indicator.py +64 -0
  308. backtrader/indicators/contrib/time_line.py +57 -0
  309. backtrader/indicators/contrib/trading_channel_index_proxy.py +48 -0
  310. backtrader/indicators/contrib/trend_arrows_indicator.py +109 -0
  311. backtrader/indicators/contrib/trend_continuation_indicator.py +127 -0
  312. backtrader/indicators/contrib/trend_intensity_index_proxy.py +51 -0
  313. backtrader/indicators/contrib/trend_manager_indicator.py +39 -0
  314. backtrader/indicators/contrib/tri_x_candle_indicator.py +51 -0
  315. backtrader/indicators/contrib/trigger_line.py +66 -0
  316. backtrader/indicators/contrib/triple_ema_rate.py +34 -0
  317. backtrader/indicators/contrib/trvi_indicator.py +194 -0
  318. backtrader/indicators/contrib/two_pb_ideal_xosma_indicator.py +127 -0
  319. backtrader/indicators/contrib/ultra_absolutely_no_lag_lwma_color.py +92 -0
  320. backtrader/indicators/contrib/ultra_wpr_indicator.py +173 -0
  321. backtrader/indicators/contrib/up_down_candle_strength.py +68 -0
  322. backtrader/indicators/contrib/vinin_i_trend_indicator.py +139 -0
  323. backtrader/indicators/contrib/volume_weighted_ma_indicator.py +78 -0
  324. backtrader/indicators/contrib/volume_weighted_ma_st_dev_indicator.py +111 -0
  325. backtrader/indicators/contrib/vwap_close_indicator.py +65 -0
  326. backtrader/indicators/contrib/vwma_candle.py +57 -0
  327. backtrader/indicators/contrib/vwma_digit_system.py +70 -0
  328. backtrader/indicators/contrib/wami.py +43 -0
  329. backtrader/indicators/contrib/wprsi_signal_indicator.py +105 -0
  330. backtrader/indicators/contrib/x_de_marker_histogram_vol_direct_indicator.py +145 -0
  331. backtrader/indicators/contrib/x_fisher_indicator.py +64 -0
  332. backtrader/indicators/contrib/xcci_histogram_vol_direct_indicator.py +56 -0
  333. backtrader/indicators/contrib/xcci_histogram_vol_indicator.py +85 -0
  334. backtrader/indicators/contrib/xma_ichimoku.py +163 -0
  335. backtrader/indicators/contrib/xma_ishimoku_channel_indicator.py +65 -0
  336. backtrader/indicators/contrib/xma_ishimoku_line.py +68 -0
  337. backtrader/indicators/contrib/xma_range_bands_indicator.py +107 -0
  338. backtrader/indicators/contrib/xmacd_indicator.py +70 -0
  339. backtrader/indicators/contrib/xrsi_de_marker_histogram.py +67 -0
  340. backtrader/indicators/contrib/xrsi_histogram_vol_direct_indicator.py +52 -0
  341. backtrader/indicators/contrib/xrsi_histogram_vol_indicator.py +81 -0
  342. backtrader/indicators/contrib/xrvi_indicator.py +130 -0
  343. backtrader/indicators/contrib/zero_lag_macd.py +36 -0
  344. backtrader/indicators/contrib/zig_zag_recent_pivot_signal.py +90 -0
  345. backtrader/indicators/contrib/zpf_indicator.py +115 -0
  346. backtrader/indicators/crossover.py +337 -0
  347. backtrader/indicators/dema.py +175 -0
  348. backtrader/indicators/demarker.py +270 -0
  349. backtrader/indicators/deviation.py +284 -0
  350. backtrader/indicators/directionalmove.py +1071 -0
  351. backtrader/indicators/dma.py +112 -0
  352. backtrader/indicators/dpo.py +96 -0
  353. backtrader/indicators/dv2.py +56 -0
  354. backtrader/indicators/ema.py +145 -0
  355. backtrader/indicators/envelope.py +475 -0
  356. backtrader/indicators/hadelta.py +198 -0
  357. backtrader/indicators/heikinashi.py +153 -0
  358. backtrader/indicators/hma.py +153 -0
  359. backtrader/indicators/hurst.py +151 -0
  360. backtrader/indicators/ichimoku.py +267 -0
  361. backtrader/indicators/kama.py +181 -0
  362. backtrader/indicators/kst.py +159 -0
  363. backtrader/indicators/lrsi.py +125 -0
  364. backtrader/indicators/mabase.py +147 -0
  365. backtrader/indicators/macd.py +322 -0
  366. backtrader/indicators/momentum.py +267 -0
  367. backtrader/indicators/moneyflow.py +237 -0
  368. backtrader/indicators/mt5atr.py +124 -0
  369. backtrader/indicators/myind.py +179 -0
  370. backtrader/indicators/obv.py +94 -0
  371. backtrader/indicators/ols.py +265 -0
  372. backtrader/indicators/oscillator.py +161 -0
  373. backtrader/indicators/percentchange.py +83 -0
  374. backtrader/indicators/percentrank.py +46 -0
  375. backtrader/indicators/pivotpoint.py +469 -0
  376. backtrader/indicators/prettygoodoscillator.py +113 -0
  377. backtrader/indicators/priceops_ext.py +123 -0
  378. backtrader/indicators/priceoscillator.py +262 -0
  379. backtrader/indicators/psar.py +212 -0
  380. backtrader/indicators/rmi.py +69 -0
  381. backtrader/indicators/rsi.py +440 -0
  382. backtrader/indicators/sma.py +141 -0
  383. backtrader/indicators/smma.py +116 -0
  384. backtrader/indicators/spread.py +54 -0
  385. backtrader/indicators/stochastic.py +263 -0
  386. backtrader/indicators/supertrend.py +436 -0
  387. backtrader/indicators/trend_ext.py +105 -0
  388. backtrader/indicators/trix.py +202 -0
  389. backtrader/indicators/tsi.py +155 -0
  390. backtrader/indicators/ultimateoscillator.py +158 -0
  391. backtrader/indicators/vortex.py +62 -0
  392. backtrader/indicators/williams.py +194 -0
  393. backtrader/indicators/wma.py +103 -0
  394. backtrader/indicators/zlema.py +135 -0
  395. backtrader/indicators/zlind.py +104 -0
  396. backtrader/linebuffer.py +3155 -0
  397. backtrader/lineiterator.py +2911 -0
  398. backtrader/lineroot.py +1106 -0
  399. backtrader/lineseries.py +2559 -0
  400. backtrader/live_trading/__init__.py +31 -0
  401. backtrader/live_trading/interface.py +404 -0
  402. backtrader/mathsupport.py +94 -0
  403. backtrader/metabase.py +1804 -0
  404. backtrader/mixins/__init__.py +21 -0
  405. backtrader/mixins/singleton.py +118 -0
  406. backtrader/observer.py +106 -0
  407. backtrader/observers/__init__.py +45 -0
  408. backtrader/observers/benchmark.py +126 -0
  409. backtrader/observers/broker.py +184 -0
  410. backtrader/observers/buysell.py +144 -0
  411. backtrader/observers/drawdown.py +161 -0
  412. backtrader/observers/logreturns.py +113 -0
  413. backtrader/observers/timereturn.py +86 -0
  414. backtrader/observers/trade_logger.py +2972 -0
  415. backtrader/observers/tradelogger.py +6 -0
  416. backtrader/observers/trades.py +258 -0
  417. backtrader/order.py +1114 -0
  418. backtrader/parameters.py +2345 -0
  419. backtrader/plot/__init__.py +54 -0
  420. backtrader/plot/finance.py +1022 -0
  421. backtrader/plot/formatters.py +200 -0
  422. backtrader/plot/locator.py +353 -0
  423. backtrader/plot/multicursor.py +495 -0
  424. backtrader/plot/plot.py +2500 -0
  425. backtrader/plot/plot_plotly.py +1351 -0
  426. backtrader/plot/scheme.py +253 -0
  427. backtrader/plot/utils.py +104 -0
  428. backtrader/position.py +290 -0
  429. backtrader/position_modes.py +132 -0
  430. backtrader/profiles.py +254 -0
  431. backtrader/reports/__init__.py +39 -0
  432. backtrader/reports/charts.py +371 -0
  433. backtrader/reports/performance.py +620 -0
  434. backtrader/reports/reporter.py +660 -0
  435. backtrader/resamplerfilter.py +1001 -0
  436. backtrader/signal.py +118 -0
  437. backtrader/signals/__init__.py +17 -0
  438. backtrader/sizer.py +114 -0
  439. backtrader/sizers/__init__.py +26 -0
  440. backtrader/sizers/fixedsize.py +161 -0
  441. backtrader/sizers/percents_sizer.py +119 -0
  442. backtrader/store.py +221 -0
  443. backtrader/stores/__init__.py +33 -0
  444. backtrader/stores/btapistore.py +15506 -0
  445. backtrader/stores/livestore.py +137 -0
  446. backtrader/stores/vchartfile.py +96 -0
  447. backtrader/strategy.py +3655 -0
  448. backtrader/talib.py +280 -0
  449. backtrader/test_helpers.py +96 -0
  450. backtrader/timer.py +358 -0
  451. backtrader/trade.py +442 -0
  452. backtrader/tradingcal.py +361 -0
  453. backtrader/utils/__init__.py +68 -0
  454. backtrader/utils/autodict.py +251 -0
  455. backtrader/utils/date.py +71 -0
  456. backtrader/utils/dateintern.py +509 -0
  457. backtrader/utils/flushfile.py +94 -0
  458. backtrader/utils/fractal.py +101 -0
  459. backtrader/utils/get_metrics.py +101 -0
  460. backtrader/utils/load_data.py +209 -0
  461. backtrader/utils/log_message.py +998 -0
  462. backtrader/utils/ordereddefaultdict.py +75 -0
  463. backtrader/utils/py3.py +296 -0
  464. backtrader/version.py +21 -0
  465. backtrader/writer.py +372 -0
backtrader/feed.py ADDED
@@ -0,0 +1,1523 @@
1
+ #!/usr/bin/env python
2
+ """Data Feed Module - Financial data feed implementations.
3
+
4
+ This module provides the base classes and implementations for data feeds
5
+ in backtrader. Data feeds are the source of price/volume data for strategies
6
+ and indicators.
7
+
8
+ Key Classes:
9
+ AbstractDataBase: Base class for all data feeds with core functionality.
10
+ DataBase: Full-featured data feed with replay/resample support.
11
+ CSVDataBase: Base class for CSV file data feeds.
12
+ FeedBase: Base for live/real-time data feeds.
13
+
14
+ Data feeds provide:
15
+ - OHLCV (Open, High, Low, Close, Volume) data
16
+ - Timeline management and session handling
17
+ - Replay and resampling capabilities
18
+ - Live data support for trading
19
+
20
+ Example:
21
+ Creating a custom data feed:
22
+ >>> class MyDataFeed(CSVDataBase):
23
+ ... params = (('dataname', 'data.csv'),)
24
+ """
25
+
26
+ import collections
27
+ import datetime
28
+ import inspect
29
+ import os.path
30
+
31
+ from . import dataseries, metabase
32
+ from .dataseries import SimpleFilterWrapper, TimeFrame
33
+ from .resamplerfilter import Replayer, Resampler
34
+ from .tradingcal import PandasMarketCalendar
35
+ from .utils import date2num, num2date, time2num, tzparse
36
+ from .utils.date import Localizer
37
+ from .utils.log_message import get_logger
38
+ from .utils.py3 import range, string_types, zip
39
+
40
+ logger = get_logger(__name__)
41
+
42
+ _INF = float("inf")
43
+ _NEG_INF = float("-inf")
44
+
45
+
46
+ # Refactor: Remove metaclass, use normal class and initialization method
47
+ class AbstractDataBase(dataseries.OHLCDateTime):
48
+ """Base class for all data feed implementations.
49
+
50
+ Provides the core functionality for data feeds including:
51
+ - Data loading and preprocessing
52
+ - Timeline management
53
+ - Session handling
54
+ - Live data support
55
+ - Notification system for data status changes
56
+
57
+ States:
58
+ CONNECTED, DISCONNECTED, CONNBROKEN, DELAYED, LIVE,
59
+ NOTSUBSCRIBED, NOTSUPPORTED_TF, UNKNOWN
60
+
61
+ Params:
62
+ dataname: Data source identifier (filename, URL, etc.).
63
+ name: Display name for the data feed.
64
+ compression: Timeframe compression factor.
65
+ timeframe: TimeFrame period (Days, Minutes, etc.).
66
+ fromdate: Start date for data filtering.
67
+ todate: End date for data filtering.
68
+ sessionstart: Session start time.
69
+ sessionend: Session end time.
70
+ filters: List of data filters to apply.
71
+ tz: Output timezone.
72
+ tzinput: Input timezone.
73
+ qcheck: Timeout in seconds for live event checking.
74
+ calendar: Trading calendar to use.
75
+
76
+ Example:
77
+ >>> data = AbstractDataBase(dataname='data.csv')
78
+ >>> cerebro.adddata(data)
79
+ """
80
+
81
+ # Class-level registry dictionary, replacing metaclass _indcol functionality
82
+ _registry: dict = {}
83
+
84
+ # Parameter initialization settings - use _params_tuple to save original definition
85
+ _params_tuple: tuple = (
86
+ ("dataname", None),
87
+ ("name", ""),
88
+ ("compression", 1),
89
+ ("timeframe", TimeFrame.Days),
90
+ ("fromdate", None),
91
+ ("todate", None),
92
+ ("sessionstart", None),
93
+ ("sessionend", None),
94
+ ("filters", []),
95
+ ("tz", None),
96
+ ("tzinput", None),
97
+ ("qcheck", 0.0), # timeout in seconds (float) to check for events
98
+ ("calendar", None),
99
+ )
100
+
101
+ # Keep original params definition for compatibility with metaclass system
102
+ params = _params_tuple
103
+
104
+ # Eight different states of data
105
+ (
106
+ CONNECTED,
107
+ DISCONNECTED,
108
+ CONNBROKEN,
109
+ DELAYED,
110
+ LIVE,
111
+ NOTSUBSCRIBED,
112
+ NOTSUPPORTED_TF,
113
+ UNKNOWN,
114
+ ) = range(8)
115
+
116
+ # Notification names
117
+ _NOTIFNAMES = [
118
+ "CONNECTED",
119
+ "DISCONNECTED",
120
+ "CONNBROKEN",
121
+ "DELAYED",
122
+ "LIVE",
123
+ "NOTSUBSCRIBED",
124
+ "NOTSUPPORTED_TIMEFRAME",
125
+ "UNKNOWN",
126
+ ]
127
+
128
+ def __init__(self, *args, **kwargs):
129
+ """Initialize the data feed.
130
+
131
+ Args:
132
+ *args: Positional arguments.
133
+ **kwargs: Keyword arguments for data feed parameters.
134
+ """
135
+ # Execute the original metaclass dopreinit functionality
136
+ self._init_preinit(*args, **kwargs)
137
+
138
+ # Call parent class initialization
139
+ super().__init__(*args, **kwargs)
140
+
141
+ # Execute the original metaclass dopostinit functionality
142
+ self._init_postinit(*args, **kwargs)
143
+
144
+ # CRITICAL FIX: Mark all lines as belonging to a data feed
145
+ # This must be done AFTER _init_postinit to ensure lines are fully initialized
146
+ # This allows LineSeries.__getitem__ and LineBuffer.__getitem__ to correctly
147
+ # raise IndexError when accessing out-of-range indices
148
+ # This is essential for expire_order_close() to detect insufficient data
149
+ if hasattr(self, "lines") and self.lines is not None:
150
+ try:
151
+ for line in self.lines:
152
+ if hasattr(line, "__dict__"):
153
+ line._is_data_feed_line = True
154
+ except Exception as e:
155
+ logger.warning("Failed to mark data feed lines: %s", e)
156
+
157
+ # CRITICAL FIX: Also explicitly mark the datetime line
158
+ # The datetime line might be accessed separately (e.g., self.datas[0].datetime)
159
+ # and needs to raise IndexError when accessing out of bounds
160
+ if hasattr(self, "datetime") and self.datetime is not None:
161
+ try:
162
+ if hasattr(self.datetime, "__dict__"):
163
+ self.datetime._is_data_feed_line = True
164
+ except Exception as e:
165
+ logger.warning("Failed to mark datetime line: %s", e)
166
+
167
+ # Original content from __init__
168
+ self._env = None
169
+ self._barstash: collections.deque = collections.deque()
170
+ self._barstack: collections.deque = collections.deque()
171
+ self._laststatus = None
172
+
173
+ def _init_preinit(self, *args, **kwargs):
174
+ """Replace the original MetaAbstractDataBase.dopreinit"""
175
+ # Find the owner and store it
176
+ self._feed = self._find_feed_owner()
177
+ # Initialize a queue to store notifications from cerebro
178
+ self.notifs: collections.deque = collections.deque() # store notifications for cerebro
179
+ # Get _dataname value from parameters
180
+ self._dataname = getattr(self.p, "dataname", None)
181
+ # Default _name attribute is empty
182
+ self._name = ""
183
+
184
+ def _init_postinit(self, *args, **kwargs):
185
+ """Replace the original MetaAbstractDataBase.dopostinit"""
186
+ # Either set by subclass or the parameter or use the dataname (ticker)
187
+ # Reset _name attribute, if _name is not empty, keep it; if empty, set it to name parameter value
188
+ self._name = self._name or getattr(self.p, "name", "")
189
+ # If _name attribute value is still empty and dataname parameter value is string, set _name to dataname value
190
+ if not self._name and isinstance(getattr(self.p, "dataname", None), string_types):
191
+ self._name = self.p.dataname
192
+ # _compression value equals the compression parameter value
193
+ self._compression = getattr(self.p, "compression", 1)
194
+ # _timeframe value equals the timeframe parameter value
195
+ self._timeframe = getattr(self.p, "timeframe", TimeFrame.Days)
196
+
197
+ # Only set sessionstart/sessionend defaults if they weren't explicitly passed
198
+ # If start time is datetime format, equals specific time from sessionstart; if None, equals minimum time
199
+ sessionstart = getattr(self.p, "sessionstart", None)
200
+ if isinstance(sessionstart, datetime.datetime):
201
+ self.p.sessionstart = sessionstart.time()
202
+ elif sessionstart is None:
203
+ # CRITICAL FIX: Always set default if None (kwargs check was unreliable)
204
+ self.p.sessionstart = datetime.time.min
205
+
206
+ # If end time is datetime format, equals specific time from sessionend; if None, equals 23:59:59.999990
207
+ sessionend = getattr(self.p, "sessionend", None)
208
+ if isinstance(sessionend, datetime.datetime):
209
+ self.p.sessionend = sessionend.time()
210
+ elif sessionend is None:
211
+ # CRITICAL FIX: Always set default if None (kwargs check was unreliable)
212
+ # remove 9 to avoid precision rounding errors
213
+ self.p.sessionend = datetime.time(23, 59, 59, 999990)
214
+
215
+ # If start date is date format and has no hour attribute, add sessionstart time to convert start date to date+time format
216
+ fromdate = getattr(self.p, "fromdate", None)
217
+ if isinstance(fromdate, datetime.date):
218
+ # push it to the end of the day, or else intraday
219
+ # values before the end of the day would be gone
220
+ if not hasattr(fromdate, "hour"):
221
+ self.p.fromdate = datetime.datetime.combine(fromdate, self.p.sessionstart)
222
+
223
+ # If end date is date format and has no hour attribute, add sessionend time to convert start date to date+time format
224
+ todate = getattr(self.p, "todate", None)
225
+ if isinstance(todate, datetime.date):
226
+ # push it to the end of the day, or else intraday
227
+ # values before the end of the day would be gone
228
+ if not hasattr(todate, "hour"):
229
+ self.p.todate = datetime.datetime.combine(todate, self.p.sessionend)
230
+
231
+ # Set _barstack and _barstash as queues for filter operations
232
+ self._barstack = collections.deque() # for filter operations
233
+ self._barstash = collections.deque() # for filter operations
234
+ # Set _filters and _ffilters as empty lists
235
+ self._filters: list = []
236
+ self._ffilters: list = []
237
+
238
+ # Iterate through filters in parameters, first check if it's a class; if class, instantiate first; if instance has last attribute, add filter to _ffilters
239
+ # If not a class, directly add filter to _filters
240
+ filters = getattr(self.p, "filters", [])
241
+ for fp in filters:
242
+ if inspect.isclass(fp):
243
+ fp = fp(self)
244
+ if hasattr(fp, "last"):
245
+ self._ffilters.append((fp, [], {}))
246
+
247
+ self._filters.append((fp, [], {}))
248
+
249
+ def _find_feed_owner(self):
250
+ """Find the feed owner using metabase.findowner.
251
+
252
+ This method delegates to metabase.findowner which uses
253
+ OwnerContext for explicit owner management.
254
+ """
255
+ # Use findowner which checks OwnerContext for owner lookup
256
+ return metabase.findowner(self, FeedBase)
257
+
258
+ @classmethod
259
+ def _getstatusname(cls, status):
260
+ return cls._NOTIFNAMES[status]
261
+
262
+ # Initialize the following variables, may be used in live trading
263
+ _compensate = None
264
+ _feed = None
265
+ _store = None
266
+
267
+ _clone = False
268
+ _qcheck = 0.0
269
+
270
+ # Time offset
271
+ _tmoffset = datetime.timedelta()
272
+
273
+ # Set to non 0 if resampling/replaying
274
+ # Whether resampling or replaying, if not, set to 0
275
+ resampling = 0
276
+ replaying = 0
277
+
278
+ # Whether started
279
+ _started = False
280
+
281
+ def _start_finish(self):
282
+ # A live feed (for example) may have learnt something about the
283
+ # timezones after the start, and that's why the date/time related
284
+ # parameters are converted at this late stage
285
+ # Get the output timezone (if any)
286
+ # Get specific timezone
287
+ self._tz = self._gettz()
288
+ # Lines have already been created, set the tz
289
+ # Set specific timezone for time
290
+ self.lines.datetime._settz(self._tz)
291
+
292
+ # This should probably be also called from an override-able method
293
+ # Localize input timezone
294
+ self._tzinput = Localizer(self._gettzinput())
295
+
296
+ # Convert user input times to the output timezone (or min/max)
297
+ # Convert user input start and end times to specific numbers; if None, start time is negative infinity, end time is positive infinity
298
+ # If specific time, use date2num to convert to specific number
299
+ if self.p.fromdate is None:
300
+ self.fromdate = float("-inf")
301
+ else:
302
+ self.fromdate = self.date2num(self.p.fromdate)
303
+
304
+ if self.p.todate is None:
305
+ self.todate = float("inf")
306
+ else:
307
+ self.todate = self.date2num(self.p.todate)
308
+
309
+ # Used by resamplerfilter and DataClone
310
+ self.sessionstart = time2num(self.p.sessionstart)
311
+ self.sessionend = time2num(self.p.sessionend)
312
+
313
+ # Get calendar from parameters; if calendar is None, look for _tradingcal in local environment; if string, use PandasMarketCalendar
314
+ self._calendar = cal = self.p.calendar
315
+ if cal is None:
316
+ self._calendar = self._env._tradingcal if self._env else None
317
+ elif isinstance(cal, string_types):
318
+ self._calendar = PandasMarketCalendar(calendar=cal)
319
+ # Start state
320
+ self._started = True
321
+
322
+ def _start(self):
323
+ self.start()
324
+ # If not in start state yet, initialize first, then enter start state
325
+ if not self._started:
326
+ self._start_finish()
327
+
328
+ def _timeoffset(self):
329
+ # Time offset
330
+ return self._tmoffset
331
+
332
+ # Return next trading day end time in datetime format and numeric format
333
+ def _getnexteos(self):
334
+ """Returns the next eos using a trading calendar if available"""
335
+ if self._clone:
336
+ return self.data._getnexteos()
337
+
338
+ if not len(self):
339
+ return datetime.datetime.min, 0.0
340
+
341
+ dt = self.lines.datetime[0]
342
+ dtime = num2date(dt)
343
+ if self._calendar is None:
344
+ nexteos = datetime.datetime.combine(dtime, self.p.sessionend)
345
+ nextdteos = self.date2num(nexteos) # locl'ed -> utc-like
346
+ nexteos = num2date(nextdteos) # utc
347
+ while dtime > nexteos:
348
+ nexteos += datetime.timedelta(days=1) # already utc-like
349
+
350
+ nextdteos = date2num(nexteos) # -> utc-like
351
+
352
+ else:
353
+ # returns times in utc
354
+ _, nexteos = self._calendar.schedule(dtime, self._tz)
355
+ nextdteos = date2num(nexteos) # nextos is already utc
356
+
357
+ return nexteos, nextdteos
358
+
359
+ # Parse tzinput and return
360
+ def _gettzinput(self):
361
+ """Can be overriden by classes to return a timezone for input"""
362
+ return tzparse(self.p.tzinput)
363
+
364
+ # Parse tz and return
365
+ def _gettz(self):
366
+ """To be overriden by subclasses which may auto-calculate the
367
+ timezone"""
368
+ return tzparse(self.p.tz)
369
+
370
+ # Convert time to number; if timezone info is not None, localize time first, then convert
371
+ def date2num(self, dt):
372
+ """Convert datetime to internal numeric format.
373
+
374
+ Args:
375
+ dt: datetime object to convert.
376
+
377
+ Returns:
378
+ float: Internal numeric representation of the datetime.
379
+ """
380
+ if self._tz is not None:
381
+ return date2num(self._tz.localize(dt))
382
+
383
+ return date2num(dt)
384
+
385
+ # Convert number to date+time
386
+ def num2date(self, dt=None, tz=None, naive=True):
387
+ """Convert internal numeric format to datetime.
388
+
389
+ Args:
390
+ dt: Numeric datetime value (uses current if None).
391
+ tz: Timezone to use (uses feed tz if None).
392
+ naive: Return naive datetime if True.
393
+
394
+ Returns:
395
+ datetime: Converted datetime object.
396
+ """
397
+ if dt is None:
398
+ return num2date(self.lines.datetime[0], tz or self._tz, naive)
399
+
400
+ return num2date(dt, tz or self._tz, naive)
401
+
402
+ # Whether has live data; default is False; if has live data, needs override
403
+ def haslivedata(self):
404
+ """Check if this data feed has live data.
405
+
406
+ Returns:
407
+ bool: False for base class, override for live data feeds.
408
+ """
409
+ return False # must be overriden for those that can
410
+
411
+ # Wait interval when resampling live data
412
+ def do_qcheck(self, onoff, qlapse):
413
+ """Calculate wait interval for queue checking.
414
+
415
+ Args:
416
+ onoff: Whether queue checking is enabled.
417
+ qlapse: Time elapsed since last check.
418
+ """
419
+ # if onoff is True, the data will wait p.qcheck for incoming live data
420
+ # on its queue.
421
+ if not onoff:
422
+ self._qcheck = 0.0
423
+ return
424
+
425
+ self._qcheck = max(0.0, self.p.qcheck - qlapse)
426
+
427
+ # Whether is live data; default is False; if True, cerebro will not use preload and runonce, because live data needs
428
+ # to be fetched tick by tick or bar by bar
429
+ def islive(self):
430
+ """If this returns True, ``Cerebro`` will deactivate ``preload`` and
431
+ ``runonce`` because a live data source must be fetched tick by tick (or
432
+ bar by bar)"""
433
+ return False
434
+
435
+ # If latest status differs from current status, need to add info to notifs to update latest status
436
+ def put_notification(self, status, *args, **kwargs):
437
+ """Add arguments to notification queue"""
438
+ if self._laststatus != status:
439
+ self.notifs.append((status, args, kwargs))
440
+ self._laststatus = status
441
+
442
+ # Get notification info, save to notifs and return as result
443
+ def get_notifications(self):
444
+ """Return the pending "store" notifications"""
445
+ # The background thread could keep on adding notifications. The None
446
+ # mark allows to identify which is the last notification to deliver
447
+ # Add a None, when None is retrieved, it means the queue is empty and all info has been retrieved
448
+ self.notifs.append(None) # put a mark
449
+ notifs = []
450
+ while True:
451
+ notif = self.notifs.popleft()
452
+ if notif is None: # mark is reached
453
+ break
454
+ notifs.append(notif)
455
+
456
+ return notifs
457
+
458
+ # Get feed
459
+ def getfeed(self):
460
+ """Get the parent feed object.
461
+
462
+ Returns:
463
+ FeedBase or None: The parent feed instance.
464
+ """
465
+ return self._feed
466
+
467
+ # Amount of cached data
468
+ def qbuffer(self, savemem=0, replaying=False):
469
+ """Apply queued buffering to all lines.
470
+
471
+ Args:
472
+ savemem: Memory saving mode.
473
+ replaying: Whether replaying is active.
474
+ """
475
+ extrasize = self.resampling or replaying
476
+ for line in self.lines:
477
+ line.qbuffer(savemem=savemem, extrasize=extrasize)
478
+
479
+ # Start, reset _barstack and _barstash
480
+ def start(self):
481
+ """Start the data feed.
482
+
483
+ Resets internal queues and sets initial status to CONNECTED.
484
+ """
485
+ self._barstack = collections.deque()
486
+ self._barstash = collections.deque()
487
+ self._laststatus = self.CONNECTED
488
+
489
+ # End
490
+ def stop(self):
491
+ """Stop the data feed.
492
+
493
+ Override in subclasses for cleanup.
494
+ """
495
+
496
+ # Clone data
497
+ def clone(self, **kwargs):
498
+ """Create a clone of this data feed.
499
+
500
+ Args:
501
+ **kwargs: Additional keyword arguments for the clone.
502
+
503
+ Returns:
504
+ DataClone: A cloned data feed.
505
+ """
506
+ return DataClone(dataname=self, **kwargs)
507
+
508
+ # Copy data and give it a different name
509
+ def copyas(self, _dataname, **kwargs):
510
+ """Copy the data feed with a different name.
511
+
512
+ Args:
513
+ _dataname: New name for the data feed.
514
+ **kwargs: Additional keyword arguments.
515
+
516
+ Returns:
517
+ DataClone: A cloned data feed with the new name.
518
+ """
519
+ d = DataClone(dataname=self, **kwargs)
520
+ d._dataname = _dataname
521
+ d._name = _dataname
522
+ return d
523
+
524
+ # Set environment
525
+ def setenvironment(self, env):
526
+ """Keep a reference to the environment"""
527
+ self._env = env
528
+
529
+ # Get environment
530
+ def getenvironment(self):
531
+ """Get the cerebro environment reference.
532
+
533
+ Returns:
534
+ The cerebro environment instance.
535
+ """
536
+ return self._env
537
+
538
+ # Add simple filter
539
+ def addfilter_simple(self, f, *args, **kwargs):
540
+ """Add a simple filter wrapper to this data feed.
541
+
542
+ Args:
543
+ f: Filter function to apply.
544
+ *args: Positional arguments for the filter.
545
+ **kwargs: Keyword arguments for the filter.
546
+ """
547
+ fp = SimpleFilterWrapper(self, f, *args, **kwargs)
548
+ self._filters.append((fp, fp.args, fp.kwargs))
549
+
550
+ # Add filter
551
+ def addfilter(self, p, *args, **kwargs):
552
+ """Add a filter to this data feed.
553
+
554
+ Args:
555
+ p: Filter class or instance.
556
+ *args: Positional arguments for filter creation.
557
+ **kwargs: Keyword arguments for filter creation.
558
+ """
559
+ if inspect.isclass(p):
560
+ pobj = p(self, *args, **kwargs)
561
+ self._filters.append((pobj, [], {}))
562
+
563
+ if hasattr(pobj, "last"):
564
+ self._ffilters.append((pobj, [], {}))
565
+
566
+ else:
567
+ self._filters.append((p, args, kwargs))
568
+
569
+ # Compensate
570
+ def compensate(self, other):
571
+ """Call it to let the broker know that actions on this asset will
572
+ compensate open positions in another"""
573
+
574
+ self._compensate = other
575
+
576
+ # Set tick_+name attribute to None for non-datetime names, mainly used when synthesizing low-frequency data from high-frequency data
577
+ def _tick_nullify(self):
578
+ # These are the updating prices in case the new bar is "updated"
579
+ # and the length doesn't change like if a replay is happening or
580
+ # a real-time data feed is in use and 1-minute bars are being
581
+ # constructed with 5-second updates
582
+ # PERFORMANCE OPTIMIZATION: Cache tick attribute names to avoid repeated string concat
583
+ tick_cache = getattr(self, "_tick_cache", None)
584
+ if tick_cache is None:
585
+ tick_cache = ["tick_" + alias for alias in self.getlinealiases() if alias != "datetime"]
586
+ self._tick_cache = tick_cache
587
+
588
+ set_attr = object.__setattr__
589
+ for tick_name in tick_cache:
590
+ set_attr(self, tick_name, None)
591
+
592
+ set_attr(self, "tick_last", None)
593
+ set_attr(self, "_tick_direct_filled", False)
594
+
595
+ # If tick_xxx related attribute value is None, need to consider using bar data to fill
596
+ def _tick_fill(self, force=False):
597
+ # If nothing filled the tick_xxx attributes, the bar is the tick
598
+ # PERFORMANCE OPTIMIZATION: Cache tick name/line pairs for faster access
599
+ if not force:
600
+ try:
601
+ if object.__getattribute__(self, "_tick_direct_filled"):
602
+ return
603
+ except AttributeError:
604
+ logger.debug("feed:604 ignored AttributeError")
605
+
606
+ try:
607
+ tick_line_cache = self._tick_line_cache
608
+ except AttributeError:
609
+ tick_line_cache = None
610
+ if tick_line_cache is None:
611
+ tick_line_cache = []
612
+ alias0 = self._getlinealias(0)
613
+ self._tick_alias0 = "tick_" + alias0
614
+ self._tick_last_line = getattr(self.lines, alias0)
615
+ for lalias in self.getlinealiases():
616
+ if lalias != "datetime":
617
+ tick_line_cache.append(
618
+ ("tick_" + lalias, getattr(self.lines, lalias), lalias == alias0)
619
+ )
620
+ self._tick_line_cache = tick_line_cache
621
+
622
+ if force:
623
+ should_fill = True
624
+ else:
625
+ try:
626
+ should_fill = object.__getattribute__(self, self._tick_alias0) is None
627
+ except AttributeError:
628
+ should_fill = True
629
+
630
+ if should_fill:
631
+ set_attr = object.__setattr__
632
+ tick_last = None
633
+ for tick_name, line, is_last in self._tick_line_cache:
634
+ current_idx = line._idx
635
+ line_array = line.array
636
+ try:
637
+ value = line_array[current_idx]
638
+ except IndexError:
639
+ lencount = line.lencount
640
+ if lencount > 0 and current_idx >= lencount:
641
+ value = line_array[lencount - 1]
642
+ else:
643
+ raise
644
+ if value in (_INF, _NEG_INF):
645
+ value = 0.0
646
+ set_attr(self, tick_name, value)
647
+ if is_last:
648
+ tick_last = value
649
+
650
+ if tick_last is None:
651
+ line = self._tick_last_line
652
+ current_idx = line._idx
653
+ line_array = line.array
654
+ try:
655
+ tick_last = line_array[current_idx]
656
+ except IndexError:
657
+ lencount = line.lencount
658
+ if lencount > 0 and current_idx >= lencount:
659
+ tick_last = line_array[lencount - 1]
660
+ else:
661
+ raise
662
+ if tick_last in (_INF, _NEG_INF):
663
+ tick_last = 0.0
664
+
665
+ set_attr(self, "tick_last", tick_last)
666
+
667
+ # Get time of next bar
668
+ # PERFORMANCE OPTIMIZATION: Cache float("inf") as module-level constant
669
+ _INF = float("inf")
670
+
671
+ def advance_peek(self):
672
+ """Peek at the datetime of the next bar.
673
+
674
+ Returns:
675
+ float: Numeric datetime of next bar, or inf if unavailable.
676
+ """
677
+ # PERFORMANCE OPTIMIZATION: Use cached _INF, avoid repeated float("inf") creation
678
+ _inf = self._INF
679
+ try:
680
+ if len(self) < self.buflen():
681
+ # CRITICAL FIX: Check if datetime[1] is valid before returning
682
+ try:
683
+ next_dt = self.lines.datetime[1]
684
+ # If next_dt is 0 or invalid, return inf
685
+ if next_dt is None or next_dt <= 0:
686
+ return _inf
687
+ return next_dt
688
+ except (IndexError, KeyError):
689
+ # If accessing datetime[1] fails, we're at the end
690
+ return _inf
691
+ return _inf # max date else
692
+ except Exception as e:
693
+ logger.debug("Exception in _gettz for %s: %s", getattr(self, "_name", ""), e)
694
+ return _inf
695
+
696
+ # Move data forward by size
697
+ def advance(self, size=1, datamaster=None, ticks=True):
698
+ """Advance the data feed by the specified size.
699
+
700
+ Args:
701
+ size: Number of bars to advance (default: 1).
702
+ datamaster: Master data feed for synchronization.
703
+ ticks: Whether to process tick data.
704
+ """
705
+ if ticks:
706
+ self._tick_nullify()
707
+
708
+ # Need intercepting this call to support datas with
709
+ # different lengths (timeframes)
710
+ self.lines.advance(size)
711
+
712
+ if datamaster is not None:
713
+ if len(self) > self.buflen():
714
+ # if no bar can be delivered, fill with an empty bar
715
+ self.rewind()
716
+ self.lines.forward()
717
+ return
718
+
719
+ if self.lines.datetime[0] > datamaster.lines.datetime[0]:
720
+ self.lines.rewind()
721
+ else:
722
+ if ticks:
723
+ self._tick_fill()
724
+ elif len(self) < self.buflen():
725
+ # a resampler may have advance us past the last point
726
+ if ticks:
727
+ self._tick_fill()
728
+
729
+ # What happens on data when next is called
730
+ def next(self, datamaster=None, ticks=True):
731
+ """Move to the next bar.
732
+
733
+ Args:
734
+ datamaster: Master data feed for synchronization.
735
+ ticks: Whether to process tick data.
736
+
737
+ Returns:
738
+ bool: True if a bar is available, False otherwise.
739
+ """
740
+ # If data length is greater than cached data length, if it's ticks data, call _tick_nullify to generate tick_xxx attributes, then call load to try getting next bar; if ret is empty
741
+ # return ret. If master data is None, if it's ticks data, need to call _tick_fill.
742
+ # If own length is less than cached data length, move forward
743
+ try:
744
+ line_datetime = self._datetime_line
745
+ needs_load = line_datetime.lencount >= (
746
+ len(line_datetime.array) - line_datetime.extension
747
+ )
748
+ except AttributeError:
749
+ needs_load = len(self) >= self.buflen()
750
+
751
+ if needs_load:
752
+ if ticks:
753
+ self._tick_nullify()
754
+
755
+ # not preloaded - request next bar
756
+ ret = self.load()
757
+ if not ret:
758
+ # if the load cannot produce bars - forward the result
759
+ return ret
760
+
761
+ if datamaster is None:
762
+ # bar is there and no master ... return load's result
763
+ if ticks:
764
+ try:
765
+ tick_direct_filled = self._tick_direct_filled
766
+ except AttributeError:
767
+ tick_direct_filled = False
768
+ if not tick_direct_filled:
769
+ self._tick_fill()
770
+ return ret
771
+ else:
772
+ self.advance(ticks=ticks)
773
+ # If master data is not None, if current data time is greater than master data time, need to adjust backward;
774
+ # If current data time is not greater than master data time and data is ticks data, need to fill current data
775
+ # If master data is None and data is ticks data, need to fill current day data
776
+ # a bar is "loaded" or was preloaded - index has been moved to it
777
+ if datamaster is not None:
778
+ # there is a time reference to check against
779
+ if self.lines.datetime[0] > datamaster.lines.datetime[0]:
780
+ # can't deliver new bar, too early, go back
781
+ self.rewind()
782
+ return False
783
+ if ticks:
784
+ try:
785
+ tick_direct_filled = self._tick_direct_filled
786
+ except AttributeError:
787
+ tick_direct_filled = False
788
+ if not tick_direct_filled:
789
+ self._tick_fill()
790
+
791
+ else:
792
+ if ticks:
793
+ try:
794
+ tick_direct_filled = self._tick_direct_filled
795
+ except AttributeError:
796
+ tick_direct_filled = False
797
+ if not tick_direct_filled:
798
+ self._tick_fill()
799
+
800
+ # tell the world there is a bar (either the new or the previous
801
+ # Indicate current bar exists
802
+ return True
803
+
804
+ # Preload data
805
+ def preload(self):
806
+ """Preload all available data from the data feed.
807
+
808
+ Loads all bars and resets position to the beginning.
809
+ """
810
+ # Load data
811
+ while self.load():
812
+ pass
813
+
814
+ self._last()
815
+ self.home()
816
+
817
+ # Last chance to use filters
818
+ def _last(self, datamaster=None):
819
+ # A last chance for filters to deliver something
820
+
821
+ ret = 0
822
+ for ff, fargs, fkwargs in self._ffilters:
823
+ ret += ff.last(self, *fargs, **fkwargs)
824
+
825
+ doticks = False
826
+ if datamaster is not None and self._barstack:
827
+ doticks = True
828
+
829
+ while self._fromstack(forward=True):
830
+ # consume bar(s) produced by "last"s - adding room
831
+ pass
832
+
833
+ if doticks:
834
+ self._tick_fill()
835
+
836
+ return bool(ret)
837
+
838
+ # Check if verification is needed
839
+ def _check(self, forcedata=None):
840
+ for ff, fargs, fkwargs in self._filters:
841
+ if not hasattr(ff, "check"):
842
+ continue
843
+ ff.check(self, _forcedata=forcedata, *fargs, **fkwargs)
844
+
845
+ # Load data
846
+ def load(self):
847
+ """Load the next bar from the data feed.
848
+
849
+ Returns:
850
+ bool: True if a bar was loaded, False if no more data.
851
+
852
+ This method handles:
853
+ - Forwarding the data pointer
854
+ - Processing filters
855
+ - Checking date boundaries
856
+ """
857
+ while True:
858
+ # move a data pointer forward for new bar
859
+ # Move data pointer forward by one
860
+ try:
861
+ forward_lines = self._load_forward_lines
862
+ except AttributeError:
863
+ try:
864
+ lines = self.lines.lines
865
+ if any(line.mode == line.QBuffer or line._clock is not None for line in lines):
866
+ self._load_forward_lines = None
867
+ forward_lines = None
868
+ else:
869
+ forward_lines = tuple(lines)
870
+ self._load_forward_lines = forward_lines
871
+ except AttributeError:
872
+ self._load_forward_lines = None
873
+ forward_lines = None
874
+
875
+ if forward_lines is None:
876
+ self.forward()
877
+ else:
878
+ for line in forward_lines:
879
+ line._idx += 1
880
+ line.lencount += 1
881
+ line.array.append(line._default_value)
882
+
883
+ # If data has been retrieved from self._barstack and saved to line, directly return True
884
+ if self._barstack and self._fromstack(): # bar is available
885
+ return True
886
+ # If data cannot be retrieved from self._barstash, run the following code
887
+ if not self._barstash:
888
+ # _load() returns False, following code must run, but seems unnecessary to call this function or check following result, these two statements seem redundant
889
+ ### Cannot be 100% certain for now, will review after code comments are completed #fix
890
+ _loadret = self._load()
891
+ if not _loadret: # no bar use force to make sure in exactbars
892
+ # the pointer is undone this covers especially (but not
893
+ # uniquely) the case in which the last bar has been seen
894
+ # and a backwards would ruin pointer accounting in the
895
+ # "stop" method of the strategy
896
+ self.backwards(force=True) # undo data pointer
897
+
898
+ # Return the actual returned value which may be None to
899
+ # signal no bar is available, but the data feed is not
900
+ # done. False means game over
901
+ return _loadret
902
+ else:
903
+ self._fromstack(stash=True)
904
+
905
+ # If bar was not retrieved from self._barstack but bar was retrieved from self._barstash, need to process bar
906
+ # Get a reference to current loaded time
907
+ # Get current time
908
+ try:
909
+ line_datetime = self._datetime_line
910
+ except AttributeError:
911
+ line_datetime = self.lines.datetime
912
+ try:
913
+ datetime_idx = line_datetime._idx
914
+ if datetime_idx >= 0:
915
+ dt = line_datetime.array[datetime_idx]
916
+ else:
917
+ dt = line_datetime[0]
918
+ except (AttributeError, IndexError):
919
+ dt = line_datetime[0]
920
+
921
+ # A bar has been loaded, adapt the time
922
+ # If timezone processing is needed for input time, convert number to time, localize time, convert time to number, update current time
923
+ if self._tzinput:
924
+ # Input has been converted at face value, but it's not UTC in
925
+ # the input stream
926
+ dtime = num2date(dt) # get it in a naive datetime
927
+ # localize it
928
+ dtime = self._tzinput.localize(dtime) # pytz compatible-ized
929
+ line_datetime[0] = dt = date2num(dtime) # keep UTC val
930
+
931
+ # Check standard date from/to filters
932
+ # If current time is less than start time, move backward to discard bar and continue
933
+ if dt < self.fromdate:
934
+ # discard loaded bar and carry on
935
+ self.backwards()
936
+ continue
937
+ # If time is greater than end time, move backward and undo data pointer, then break
938
+ if dt > self.todate:
939
+ # discard loaded bar and break out
940
+ self.backwards(force=True)
941
+ break
942
+
943
+ # Pass through filters
944
+ # Iterate through each filter
945
+ retff = False
946
+ for ff, fargs, fkwargs in self._filters:
947
+ # previous filter may have put things onto the stack
948
+ # If self._barstack is not empty
949
+ if self._barstack:
950
+ # Perform self._barstack number of _fromstack function calls, call filter ff
951
+ for i in range(len(self._barstack)):
952
+ self._fromstack(forward=True)
953
+ retff = ff(self, *fargs, **fkwargs)
954
+ # If self._barstack is empty, call filter once
955
+ else:
956
+ retff = ff(self, *fargs, **fkwargs)
957
+ # If retff is True, break out of filter loop
958
+ if retff: # bar removed from systemn
959
+ break # out of the inner loop
960
+ # If True, continue
961
+ if retff: # bar removed from system - loop to get new bar
962
+ continue # in the greater loop
963
+
964
+ # Checks let the bar through ... notify it
965
+ return True
966
+ # End loop, return False, no more bars or reached end date
967
+ # Out of the loop ... no more bars or past todate
968
+ return False
969
+
970
+ # Function that returns False
971
+ def _load(self):
972
+ return False
973
+
974
+ # Add bar data to self._barstack or self._barstash
975
+ def _add2stack(self, bar, stash=False):
976
+ """Saves given bar (list of values) to the stack for later retrieval"""
977
+ if not stash:
978
+ self._barstack.append(bar)
979
+ else:
980
+ self._barstash.append(bar)
981
+
982
+ # Get bar data and save to self._barstack or self._barstash, provides parameter to delete bar
983
+ def _save2stack(self, erase=False, force=False, stash=False):
984
+ """Saves current bar to the bar stack for later retrieval
985
+
986
+ Parameter ``erase`` determines removal from the data stream
987
+ """
988
+
989
+ bar = [line[0] for line in self.itersize()]
990
+ if not stash:
991
+ self._barstack.append(bar)
992
+ else:
993
+ self._barstash.append(bar)
994
+
995
+ if erase: # remove bar if requested
996
+ self.backwards(force=force)
997
+
998
+ # This comment has issues, this function is used to update bar data to specific lines
999
+ def _updatebar(self, bar, forward=False, ago=0):
1000
+ """Load a value from the stack onto the lines to form the new bar
1001
+
1002
+ Returns True if values are present, False otherwise
1003
+ """
1004
+ if forward:
1005
+ self.forward()
1006
+
1007
+ for line, val in zip(self.itersize(), bar):
1008
+ line[0 + ago] = val
1009
+
1010
+ # Get data from self._barstack or self._barstash, then save to line; if successful return True, if not return False
1011
+ def _fromstack(self, forward=False, stash=False):
1012
+ """Load a value from the stack onto the lines to form the new bar
1013
+
1014
+ Returns True if values are present, False otherwise
1015
+ """
1016
+ # When stash is False, coll equals self._barstack, otherwise it's self._barstash
1017
+ coll = self._barstack if not stash else self._barstash
1018
+ # If coll has data
1019
+ if coll:
1020
+ # If forward is True, call forward
1021
+ if forward:
1022
+ self.forward()
1023
+ # Add data to line
1024
+ for line, val in zip(self.itersize(), coll.popleft()):
1025
+ line[0] = val
1026
+
1027
+ self._tick_fill(force=True)
1028
+ return True
1029
+
1030
+ return False
1031
+
1032
+ # Add resample filter
1033
+ def resample(self, **kwargs):
1034
+ """Add a resampling filter to this data feed.
1035
+
1036
+ Resampling converts data to a different timeframe (e.g., minutes to days).
1037
+
1038
+ Args:
1039
+ **kwargs: Arguments for the Resampler filter.
1040
+ """
1041
+ self.addfilter(Resampler, **kwargs)
1042
+
1043
+ # Add replay filter
1044
+ def replay(self, **kwargs):
1045
+ """Add a replay filter to this data feed.
1046
+
1047
+ Replay filters process tick data into bars with precise control.
1048
+
1049
+ Args:
1050
+ **kwargs: Arguments for the Replayer filter.
1051
+ """
1052
+ self.addfilter(Replayer, **kwargs)
1053
+
1054
+ @classmethod
1055
+ def _gettuple(cls):
1056
+ """For compatibility, provide _gettuple method"""
1057
+ return cls._params_tuple if hasattr(cls, "_params_tuple") else cls.params
1058
+
1059
+
1060
+ # DataBase class, directly inherits from abstract DataBase
1061
+ class DataBase(AbstractDataBase):
1062
+ """Full-featured data feed class.
1063
+
1064
+ Inherits all functionality from AbstractDataBase.
1065
+ This is the standard data feed class for most use cases.
1066
+ """
1067
+
1068
+
1069
+ # Refactor: Remove MetaParams metaclass, use normal parameter processing
1070
+ class FeedBase:
1071
+ """Base class for feed containers.
1072
+
1073
+ Manages multiple data feeds and provides parameter processing
1074
+ without using metaclasses.
1075
+ """
1076
+
1077
+ # Parameter processing, originally merged parameters automatically via metaclass, now manual processing
1078
+ def __init__(self, **kwargs):
1079
+ """Initialize the feed base.
1080
+
1081
+ Args:
1082
+ **kwargs: Keyword arguments for parameters.
1083
+ """
1084
+ # Manually set parameters, replacing original metaclass functionality
1085
+ self.p = self._create_params(**kwargs)
1086
+ self.datas = []
1087
+
1088
+ def _create_params(self, **kwargs):
1089
+ """Manually create parameter object, replacing metaclass parameter processing"""
1090
+
1091
+ # Create a simple parameter object
1092
+ class Params:
1093
+ """Parameter container for FeedBase.
1094
+
1095
+ Stores parameter values and provides access via _getitems.
1096
+ """
1097
+
1098
+ def _getitems(self):
1099
+ """Simulate original _getitems method.
1100
+
1101
+ Returns:
1102
+ list: List of (attribute_name, value) tuples for non-private attributes.
1103
+ """
1104
+ # OPTIMIZED: Use __dict__ instead of dir() for better performance
1105
+ items = []
1106
+ for attr_name, value in self.__dict__.items():
1107
+ if not attr_name.startswith("_") and not callable(value):
1108
+ items.append((attr_name, value))
1109
+ return items
1110
+
1111
+ params_obj = Params()
1112
+
1113
+ # Get default parameters from DataBase
1114
+ if hasattr(DataBase, "params"):
1115
+ base_params = DataBase.params
1116
+ if isinstance(base_params, (tuple, list)):
1117
+ for param_tuple in base_params:
1118
+ if isinstance(param_tuple, (tuple, list)) and len(param_tuple) >= 2:
1119
+ param_name, param_default = param_tuple[0], param_tuple[1]
1120
+ setattr(params_obj, param_name, kwargs.get(param_name, param_default))
1121
+
1122
+ # Set other passed parameters
1123
+ for key, value in kwargs.items():
1124
+ if not hasattr(params_obj, key):
1125
+ setattr(params_obj, key, value)
1126
+
1127
+ return params_obj
1128
+
1129
+ # Data start
1130
+ def start(self):
1131
+ """Start all managed data feeds."""
1132
+ for data in self.datas:
1133
+ data.start()
1134
+
1135
+ # Data end
1136
+ def stop(self):
1137
+ """Stop all managed data feeds."""
1138
+ for data in self.datas:
1139
+ data.stop()
1140
+
1141
+ # Get data based on dataname and add data to self.datas
1142
+ def getdata(self, dataname, name=None, **kwargs):
1143
+ """Get or create a data feed and add it to the managed datas.
1144
+
1145
+ Args:
1146
+ dataname: Data source identifier (filename, URL, etc.).
1147
+ name: Display name for the data feed.
1148
+ **kwargs: Additional parameters for the data feed.
1149
+
1150
+ Returns:
1151
+ DataBase: The created or retrieved data feed instance.
1152
+ """
1153
+ # Merge parameters
1154
+ final_kwargs = {}
1155
+ if hasattr(self.p, "_getitems"):
1156
+ for pname, pvalue in self.p._getitems():
1157
+ final_kwargs[pname] = pvalue
1158
+ elif hasattr(self.p, "__dict__"):
1159
+ final_kwargs.update(self.p.__dict__)
1160
+
1161
+ final_kwargs.update(kwargs)
1162
+ final_kwargs["dataname"] = dataname
1163
+
1164
+ data = self._getdata(**final_kwargs)
1165
+ data._name = name
1166
+ self.datas.append(data)
1167
+ return data
1168
+
1169
+ def _getdata(self, dataname, **kwargs):
1170
+ # Set keyword arguments
1171
+ final_kwargs = {}
1172
+ if hasattr(self.p, "_getitems"):
1173
+ for pname, pvalue in self.p._getitems():
1174
+ final_kwargs[pname] = pvalue
1175
+ elif hasattr(self.p, "__dict__"):
1176
+ final_kwargs.update(self.p.__dict__)
1177
+
1178
+ final_kwargs.update(kwargs)
1179
+ final_kwargs["dataname"] = dataname
1180
+ return self.DataCls(**final_kwargs)
1181
+
1182
+
1183
+ # Refactor: Remove MetaCSVDataBase metaclass, use normal initialization method
1184
+ class CSVDataBase(DataBase):
1185
+ """
1186
+ Base class for classes implementing CSV DataFeeds
1187
+
1188
+ The class takes care of opening the file, reading the lines and
1189
+ tokenizing them.
1190
+
1191
+ Subclasses do only need to override:
1192
+
1193
+ - _loadline(tokens)
1194
+
1195
+ The return value of ``_loadline`` (True/False) will be the return value
1196
+ of ``_load`` which has been overriden by this base class
1197
+ """
1198
+
1199
+ # Data defaults to None
1200
+ f = None
1201
+ # Set specific parameters, merge parent class parameters - use _params_tuple to save original definition
1202
+ _params_tuple = (
1203
+ ("headers", True),
1204
+ ("separator", ","),
1205
+ )
1206
+
1207
+ # Keep original params definition for compatibility with metaclass system
1208
+ params = _params_tuple
1209
+
1210
+ # Get data and simple processing
1211
+ def __init__(self, *args, **kwargs):
1212
+ """Initialize the CSV data base.
1213
+
1214
+ Args:
1215
+ *args: Positional arguments.
1216
+ **kwargs: Keyword arguments for parameters.
1217
+ """
1218
+ # Execute original metaclass MetaCSVDataBase.dopostinit functionality
1219
+ self._csv_postinit(**kwargs)
1220
+
1221
+ # Call parent class initialization
1222
+ super().__init__(*args, **kwargs)
1223
+
1224
+ self.separator = None
1225
+
1226
+ def _csv_postinit(self, **kwargs):
1227
+ """Replace original MetaCSVDataBase.dopostinit"""
1228
+ # If parameter has no name and _name attribute is empty, get specific name from data file name
1229
+ # Use existing parameter system
1230
+ dataname = getattr(self, "p", None) and getattr(self.p, "dataname", None)
1231
+ if not dataname:
1232
+ dataname = kwargs.get("dataname", "")
1233
+ name = getattr(self, "p", None) and getattr(self.p, "name", None)
1234
+ if not name:
1235
+ name = kwargs.get("name", "")
1236
+
1237
+ if not name and not getattr(self, "_name", ""):
1238
+ if isinstance(dataname, string_types):
1239
+ self._name, _ = os.path.splitext(os.path.basename(dataname))
1240
+
1241
+ def start(self):
1242
+ """Start the CSV data feed.
1243
+
1244
+ Opens the CSV file and optionally skips headers.
1245
+ """
1246
+ super().start()
1247
+ # If data is None
1248
+ if self.f is None:
1249
+ # If dataname parameter has readline attribute, it means dataname is a data source, directly set f to data in parameter
1250
+ if hasattr(self.p.dataname, "readline"):
1251
+ self.f = self.p.dataname
1252
+ # If no readline attribute, it means dataname is a path, open file based on path to get data
1253
+ else:
1254
+ # Let an exception propagate to let the caller know
1255
+ self.f = open(self.p.dataname)
1256
+ # If there are headers, read a line and skip headers
1257
+ if self.p.headers:
1258
+ self.f.readline() # skip the headers
1259
+ # Separator for each line of data
1260
+ self.separator = self.p.separator
1261
+
1262
+ # Stop
1263
+ def stop(self):
1264
+ """Stop the CSV data feed.
1265
+
1266
+ Closes the CSV file if open.
1267
+ """
1268
+ super().stop()
1269
+ # If data file is not None, close file and set to None
1270
+ if self.f is not None:
1271
+ self.f.close()
1272
+ self.f = None
1273
+
1274
+ # Preload data
1275
+ def preload(self):
1276
+ """Preload all data from the CSV file.
1277
+
1278
+ Loads all available data and closes the file handle.
1279
+ """
1280
+ # Load data
1281
+ while self.load():
1282
+ pass
1283
+ # Settings after load is finished
1284
+ self._last()
1285
+ self.home()
1286
+
1287
+ # preloaded - no need to keep the object around - breaks multip in 3.x
1288
+ # Close data file and set to None
1289
+ if self.f is not None:
1290
+ self.f.close()
1291
+ self.f = None
1292
+
1293
+ # Load a line of data
1294
+ def _load(self):
1295
+ # If data file is None, return False; if line cannot be read, return False; process line, call _loadline to load
1296
+ if self.f is None:
1297
+ return False
1298
+
1299
+ # Let an exception propagate to let the caller know
1300
+ line = self.f.readline()
1301
+
1302
+ if not line:
1303
+ return False
1304
+
1305
+ line = line.rstrip("\n")
1306
+ linetokens = line.split(self.separator)
1307
+ return self._loadline(linetokens)
1308
+
1309
+ # Get next line of data
1310
+ def _getnextline(self):
1311
+ # This function is very similar to previous one, just previous one gets linetokens with additional _loadline call
1312
+ if self.f is None:
1313
+ return None
1314
+
1315
+ # Let an exception propagate to let the caller know
1316
+ line = self.f.readline()
1317
+
1318
+ if not line:
1319
+ return None
1320
+
1321
+ line = line.rstrip("\n")
1322
+ linetokens = line.split(self.separator)
1323
+ return linetokens
1324
+
1325
+
1326
+ class CSVFeedBase(FeedBase):
1327
+ """Base class for CSV feed containers.
1328
+
1329
+ Manages CSV data feeds with support for base path prefixing.
1330
+ """
1331
+
1332
+ # Set parameters
1333
+ def __init__(self, basepath="", **kwargs):
1334
+ """Initialize the CSV feed base.
1335
+
1336
+ Args:
1337
+ basepath: Base path to prepend to data file names.
1338
+ **kwargs: Additional keyword arguments for parameters.
1339
+ """
1340
+ self.basepath = basepath
1341
+ # Merge CSVDataBase parameters
1342
+ csv_params = {}
1343
+ if hasattr(CSVDataBase, "params"):
1344
+ csv_base_params = CSVDataBase.params
1345
+ if isinstance(csv_base_params, (tuple, list)):
1346
+ for param_tuple in csv_base_params:
1347
+ if isinstance(param_tuple, (tuple, list)) and len(param_tuple) >= 2:
1348
+ param_name, param_default = param_tuple[0], param_tuple[1]
1349
+ csv_params[param_name] = kwargs.get(param_name, param_default)
1350
+
1351
+ kwargs.update(csv_params)
1352
+ super().__init__(**kwargs)
1353
+
1354
+ # Get data
1355
+ def _getdata(self, dataname, **kwargs):
1356
+ final_kwargs = {}
1357
+ if hasattr(self.p, "_getitems"):
1358
+ for pname, pvalue in self.p._getitems():
1359
+ final_kwargs[pname] = pvalue
1360
+ elif hasattr(self.p, "__dict__"):
1361
+ final_kwargs.update(self.p.__dict__)
1362
+
1363
+ final_kwargs.update(kwargs)
1364
+ return self.DataCls(dataname=self.basepath + dataname, **final_kwargs)
1365
+
1366
+
1367
+ # Data clone
1368
+ class DataClone(AbstractDataBase):
1369
+ """Clones an existing data feed.
1370
+
1371
+ Creates a new data feed that references an existing data feed.
1372
+ Useful for creating multiple views of the same data with
1373
+ different parameters or filters.
1374
+ """
1375
+
1376
+ # Set _clone attribute to True
1377
+ _clone = True
1378
+
1379
+ # Initialize, data equals dataname parameter value, _datename equals data's _dataname attribute value
1380
+ # Then copy date, time, trading interval, compression parameters
1381
+ def __init__(self, *args, **kwargs):
1382
+ """Initialize the data clone.
1383
+
1384
+ Args:
1385
+ *args: Positional arguments.
1386
+ **kwargs: Keyword arguments, must include 'dataname' (the source data feed).
1387
+
1388
+ Raises:
1389
+ ValueError: If 'dataname' parameter is not provided.
1390
+ """
1391
+ # CRITICAL FIX: Initialize these attributes BEFORE calling super().__init__
1392
+ # to ensure they exist when parent class methods access them
1393
+ self._dlen = 0
1394
+ self._preloading = None
1395
+
1396
+ # Get dataname and set it as self.data
1397
+ dataname = kwargs.get("dataname")
1398
+ if dataname is None and hasattr(self, "p"):
1399
+ dataname = getattr(self.p, "dataname", None)
1400
+ if dataname is None:
1401
+ raise ValueError("DataClone requires 'dataname' parameter")
1402
+
1403
+ # CRITICAL FIX: Store data reference using object.__setattr__ to bypass
1404
+ # any custom __setattr__ that might interfere
1405
+ object.__setattr__(self, "data", dataname)
1406
+ self._dataname = getattr(self.data, "_dataname", None)
1407
+
1408
+ # Copy date/session parameters from source data
1409
+ if hasattr(self.data, "p"):
1410
+ kwargs.setdefault("fromdate", getattr(self.data.p, "fromdate", None))
1411
+ kwargs.setdefault("todate", getattr(self.data.p, "todate", None))
1412
+ kwargs.setdefault("sessionstart", getattr(self.data.p, "sessionstart", None))
1413
+ kwargs.setdefault("sessionend", getattr(self.data.p, "sessionend", None))
1414
+ kwargs.setdefault("timeframe", getattr(self.data.p, "timeframe", None))
1415
+ kwargs.setdefault("compression", getattr(self.data.p, "compression", None))
1416
+
1417
+ super().__init__(*args, **kwargs)
1418
+
1419
+ # CRITICAL FIX: Ensure self.data is still set after parent init
1420
+ # Re-set it to be safe, in case parent class __init__ cleared attributes
1421
+ if not hasattr(self, "data") or object.__getattribute__(self, "data") is None:
1422
+ object.__setattr__(self, "data", dataname)
1423
+
1424
+ def _start(self):
1425
+ # redefine to copy data bits from guest data
1426
+ self.start()
1427
+
1428
+ # Copy tz infos
1429
+ if hasattr(self.data, "_tz"):
1430
+ self._tz = self.data._tz
1431
+ self.lines.datetime._settz(self._tz)
1432
+
1433
+ if hasattr(self.data, "_calendar"):
1434
+ self._calendar = self.data._calendar
1435
+
1436
+ # guest data have already converted input
1437
+ self._tzinput = None # no need to further converr
1438
+
1439
+ # Copy dates/session infos
1440
+ if hasattr(self.data, "fromdate"):
1441
+ self.fromdate = self.data.fromdate
1442
+ if hasattr(self.data, "todate"):
1443
+ self.todate = self.data.todate
1444
+
1445
+ if hasattr(self.data, "sessionstart"):
1446
+ self.sessionstart = self.data.sessionstart
1447
+ if hasattr(self.data, "sessionend"):
1448
+ self.sessionend = self.data.sessionend
1449
+
1450
+ # Start
1451
+ def start(self):
1452
+ """Start the data clone.
1453
+
1454
+ Initializes internal tracking variables.
1455
+ """
1456
+ super().start()
1457
+ self._dlen = 0
1458
+ self._preloading = False
1459
+
1460
+ # Preload data
1461
+ def preload(self):
1462
+ """Preload data from the source data feed.
1463
+
1464
+ After preloading, resets the source data's position.
1465
+ """
1466
+ self._preloading = True
1467
+ super().preload()
1468
+ if hasattr(self.data, "home"):
1469
+ self.data.home() # preloading data was pushed forward
1470
+ self._preloading = False
1471
+
1472
+ # Load data
1473
+ def _load(self):
1474
+ """Load data from the source data feed.
1475
+
1476
+ Returns:
1477
+ bool: True if data was loaded, False otherwise.
1478
+ """
1479
+ # assumption: the data is in the system
1480
+ # copy the lines
1481
+ # If preparing to preload, run following code to copy specific data bit by bit
1482
+ if self._preloading:
1483
+ # data is preloaded, we are preloading too, can move
1484
+ # forward until have full bar or a data source is exhausted
1485
+ # Move data forward
1486
+ if hasattr(self.data, "advance"):
1487
+ self.data.advance()
1488
+ # If current data is greater than data buffer length, return False
1489
+ if len(self.data) > self.data.buflen():
1490
+ return False
1491
+ # If current data length is not greater than buffered data length, set line data to dline data
1492
+ for line, dline in zip(self.lines, self.data.lines):
1493
+ line[0] = dline[0]
1494
+ # Return True after successful setting
1495
+ return True
1496
+
1497
+ # Not preloading
1498
+ # This syntax is not very efficient, changing to len(self.data)<=self._dlen might save one comparison
1499
+ if len(self.data) <= self._dlen:
1500
+ # if not (len(self.data) > self._dlen): # backtrader built-in
1501
+ # Data not beyond last seen bar
1502
+ return False
1503
+
1504
+ # Increase data length by 1
1505
+ self._dlen += 1
1506
+
1507
+ # Set line data to dline data
1508
+ for line, dline in zip(self.lines, self.data.lines):
1509
+ line[0] = dline[0]
1510
+
1511
+ return True
1512
+
1513
+ # Move forward by size
1514
+ def advance(self, size=1, datamaster=None, ticks=True):
1515
+ """Advance the data clone by the specified size.
1516
+
1517
+ Args:
1518
+ size: Number of bars to advance.
1519
+ datamaster: Master data feed for synchronization (unused).
1520
+ ticks: Whether to process tick data.
1521
+ """
1522
+ self._dlen += size
1523
+ super().advance(size, datamaster, ticks=ticks)