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.
- back_trader_python-1.4.0.dist-info/METADATA +1491 -0
- back_trader_python-1.4.0.dist-info/RECORD +465 -0
- back_trader_python-1.4.0.dist-info/WHEEL +5 -0
- back_trader_python-1.4.0.dist-info/licenses/LICENSE +674 -0
- back_trader_python-1.4.0.dist-info/top_level.txt +1 -0
- backtrader/__init__.py +148 -0
- backtrader/_cerebro/__init__.py +5 -0
- backtrader/_cerebro/channel.py +382 -0
- backtrader/_cerebro/execution.py +377 -0
- backtrader/_cerebro/lifecycle.py +143 -0
- backtrader/_cerebro/notifications.py +150 -0
- backtrader/_cerebro/presentation.py +230 -0
- backtrader/_cerebro/registry.py +593 -0
- backtrader/_cerebro/runnext.py +551 -0
- backtrader/_cerebro/runonce.py +142 -0
- backtrader/analyzer.py +594 -0
- backtrader/analyzers/__init__.py +50 -0
- backtrader/analyzers/annualreturn.py +226 -0
- backtrader/analyzers/calmar.py +165 -0
- backtrader/analyzers/drawdown.py +287 -0
- backtrader/analyzers/leverage.py +112 -0
- backtrader/analyzers/logreturnsrolling.py +190 -0
- backtrader/analyzers/periodstats.py +153 -0
- backtrader/analyzers/positions.py +119 -0
- backtrader/analyzers/pyfolio.py +470 -0
- backtrader/analyzers/returns.py +192 -0
- backtrader/analyzers/sharpe.py +307 -0
- backtrader/analyzers/sharpe_ratio_stats.py +534 -0
- backtrader/analyzers/sqn.py +112 -0
- backtrader/analyzers/timereturn.py +192 -0
- backtrader/analyzers/total_value.py +75 -0
- backtrader/analyzers/tradeanalyzer.py +278 -0
- backtrader/analyzers/transactions.py +141 -0
- backtrader/analyzers/vwr.py +245 -0
- backtrader/bokeh/__init__.py +155 -0
- backtrader/bokeh/analyzers/__init__.py +13 -0
- backtrader/bokeh/analyzers/plot.py +192 -0
- backtrader/bokeh/analyzers/recorder.py +181 -0
- backtrader/bokeh/app.py +1094 -0
- backtrader/bokeh/live/__init__.py +11 -0
- backtrader/bokeh/live/client.py +352 -0
- backtrader/bokeh/live/datahandler.py +346 -0
- backtrader/bokeh/plot_adapter.py +200 -0
- backtrader/bokeh/schemes/__init__.py +14 -0
- backtrader/bokeh/schemes/blackly.py +76 -0
- backtrader/bokeh/schemes/scheme.py +150 -0
- backtrader/bokeh/schemes/tradimo.py +82 -0
- backtrader/bokeh/tab.py +125 -0
- backtrader/bokeh/tabs/__init__.py +30 -0
- backtrader/bokeh/tabs/analyzer.py +120 -0
- backtrader/bokeh/tabs/config.py +154 -0
- backtrader/bokeh/tabs/live.py +109 -0
- backtrader/bokeh/tabs/log.py +185 -0
- backtrader/bokeh/tabs/metadata.py +182 -0
- backtrader/bokeh/tabs/performance.py +359 -0
- backtrader/bokeh/tabs/source.py +70 -0
- backtrader/bokeh/utils/__init__.py +8 -0
- backtrader/bokeh/utils/helpers.py +167 -0
- backtrader/bokeh/webapp.py +164 -0
- backtrader/broker.py +478 -0
- backtrader/brokers/__init__.py +36 -0
- backtrader/brokers/bbroker.py +2576 -0
- backtrader/brokers/btapibroker.py +8227 -0
- backtrader/brokers/hft/__init__.py +89 -0
- backtrader/brokers/hft/binance_bbo.py +625 -0
- backtrader/brokers/hft/binance_bbo_compare.py +1398 -0
- backtrader/brokers/hft/examples.py +1228 -0
- backtrader/brokers/hft/exchange.py +380 -0
- backtrader/brokers/hft/latency.py +309 -0
- backtrader/brokers/hft/matching_core.py +572 -0
- backtrader/brokers/hft/queue.py +238 -0
- backtrader/brokers/hft/recorder.py +88 -0
- backtrader/brokers/hft/state.py +138 -0
- backtrader/brokers/impact_models.py +118 -0
- backtrader/brokers/mixbroker.py +895 -0
- backtrader/brokers/tickbroker.py +1991 -0
- backtrader/btrun/__init__.py +12 -0
- backtrader/btrun/btrun.py +1218 -0
- backtrader/cerebro.py +828 -0
- backtrader/channel.py +682 -0
- backtrader/channels/__init__.py +23 -0
- backtrader/channels/bridge.py +186 -0
- backtrader/channels/funding.py +248 -0
- backtrader/channels/live_queue.py +216 -0
- backtrader/channels/live_validator.py +294 -0
- backtrader/channels/orderbook.py +257 -0
- backtrader/channels/tick.py +202 -0
- backtrader/comminfo.py +665 -0
- backtrader/commissions/__init__.py +106 -0
- backtrader/commissions/ctpoption.py +993 -0
- backtrader/configs/account_config_example.yaml +8 -0
- backtrader/dataseries.py +379 -0
- backtrader/errors.py +106 -0
- backtrader/events.py +980 -0
- backtrader/feed.py +1523 -0
- backtrader/feeds/__init__.py +75 -0
- backtrader/feeds/barrier.py +2006 -0
- backtrader/feeds/blaze.py +118 -0
- backtrader/feeds/btapifeed.py +1538 -0
- backtrader/feeds/btcsv.py +203 -0
- backtrader/feeds/chainer.py +114 -0
- backtrader/feeds/cryptohftdata.py +164 -0
- backtrader/feeds/csvgeneric.py +1205 -0
- backtrader/feeds/ctpcohort.py +1051 -0
- backtrader/feeds/influxfeed.py +158 -0
- backtrader/feeds/livefeed.py +71 -0
- backtrader/feeds/mixed_channel.py +108 -0
- backtrader/feeds/mt4csv.py +42 -0
- backtrader/feeds/pandafeed.py +381 -0
- backtrader/feeds/quandl.py +256 -0
- backtrader/feeds/rollover.py +229 -0
- backtrader/feeds/sierrachart.py +30 -0
- backtrader/feeds/vchart.py +162 -0
- backtrader/feeds/vchartcsv.py +84 -0
- backtrader/feeds/vchartfile.py +153 -0
- backtrader/feeds/yahoo.py +399 -0
- backtrader/fillers.py +148 -0
- backtrader/filters/__init__.py +34 -0
- backtrader/filters/bsplitter.py +127 -0
- backtrader/filters/calendardays.py +121 -0
- backtrader/filters/datafiller.py +192 -0
- backtrader/filters/datafilter.py +74 -0
- backtrader/filters/daysteps.py +96 -0
- backtrader/filters/heikinashi.py +63 -0
- backtrader/filters/renko.py +164 -0
- backtrader/filters/session.py +289 -0
- backtrader/flt.py +80 -0
- backtrader/functions.py +960 -0
- backtrader/indicator.py +449 -0
- backtrader/indicators/__init__.py +148 -0
- backtrader/indicators/accdecoscillator.py +110 -0
- backtrader/indicators/aroon.py +300 -0
- backtrader/indicators/atr.py +315 -0
- backtrader/indicators/awesomeoscillator.py +122 -0
- backtrader/indicators/basicops.py +834 -0
- backtrader/indicators/bollinger.py +223 -0
- backtrader/indicators/cci.py +89 -0
- backtrader/indicators/channels_ext.py +83 -0
- backtrader/indicators/contrib/__init__.py +228 -0
- backtrader/indicators/contrib/absolutely_no_lag_lwma.py +28 -0
- backtrader/indicators/contrib/absolutely_no_lag_lwma_color.py +44 -0
- backtrader/indicators/contrib/accumulation_distribution_line.py +92 -0
- backtrader/indicators/contrib/adx_cross_hull_style_indicator.py +249 -0
- backtrader/indicators/contrib/adxdmi.py +34 -0
- backtrader/indicators/contrib/ai_acceleration_deceleration_oscillator.py +34 -0
- backtrader/indicators/contrib/altr_trend_signal_v22.py +85 -0
- backtrader/indicators/contrib/anchored_momentum_line.py +115 -0
- backtrader/indicators/contrib/any_range_cld_tail_indicator.py +82 -0
- backtrader/indicators/contrib/aroon_horn_sign_indicator.py +96 -0
- backtrader/indicators/contrib/aroon_oscillator_sign_alert.py +50 -0
- backtrader/indicators/contrib/arrows_curves_indicator.py +112 -0
- backtrader/indicators/contrib/as_ctrend_indicator.py +143 -0
- backtrader/indicators/contrib/asimmetric_stoch_nr_indicator.py +187 -0
- backtrader/indicators/contrib/atr_normalize_histogram.py +118 -0
- backtrader/indicators/contrib/average_change_candle.py +165 -0
- backtrader/indicators/contrib/bb_squeeze_indicator.py +60 -0
- backtrader/indicators/contrib/bezier_st_dev_indicator.py +135 -0
- backtrader/indicators/contrib/binary_wave_indicator.py +233 -0
- backtrader/indicators/contrib/blau_c_momentum_indicator.py +123 -0
- backtrader/indicators/contrib/blau_cmi_indicator.py +141 -0
- backtrader/indicators/contrib/blau_csi.py +76 -0
- backtrader/indicators/contrib/blau_ergodic.py +53 -0
- backtrader/indicators/contrib/blau_t_stoch_i.py +72 -0
- backtrader/indicators/contrib/blau_ts_stochastic.py +85 -0
- backtrader/indicators/contrib/blau_tvi.py +55 -0
- backtrader/indicators/contrib/brain_trend2_indicator.py +128 -0
- backtrader/indicators/contrib/brain_trend_signal_proxy.py +47 -0
- backtrader/indicators/contrib/brake_parb_indicator.py +85 -0
- backtrader/indicators/contrib/breakout_bars_trend_v2.py +121 -0
- backtrader/indicators/contrib/bsi_indicator.py +87 -0
- backtrader/indicators/contrib/bulls_bears_eyes.py +67 -0
- backtrader/indicators/contrib/bulls_power.py +56 -0
- backtrader/indicators/contrib/bw_wise_man1_signal.py +102 -0
- backtrader/indicators/contrib/bykov_trend_indicator.py +85 -0
- backtrader/indicators/contrib/candle_stop_color.py +46 -0
- backtrader/indicators/contrib/candles_x_smoothed_indicator.py +69 -0
- backtrader/indicators/contrib/candlesticks_bw.py +45 -0
- backtrader/indicators/contrib/caudate_x_period_candle_color.py +56 -0
- backtrader/indicators/contrib/cci_histogram_indicator.py +53 -0
- backtrader/indicators/contrib/cci_woodies_indicator.py +80 -0
- backtrader/indicators/contrib/center_of_gravity_candle_indicator.py +83 -0
- backtrader/indicators/contrib/center_of_gravity_indicator.py +70 -0
- backtrader/indicators/contrib/cg_oscillator.py +40 -0
- backtrader/indicators/contrib/close_line_cci.py +38 -0
- backtrader/indicators/contrib/close_price_fractals.py +47 -0
- backtrader/indicators/contrib/color3rd_gen_xma_indicator.py +122 -0
- backtrader/indicators/contrib/color_bb_candles_indicator.py +108 -0
- backtrader/indicators/contrib/color_coppock_indicator.py +157 -0
- backtrader/indicators/contrib/color_hma.py +71 -0
- backtrader/indicators/contrib/color_j_variation_indicator.py +53 -0
- backtrader/indicators/contrib/color_metro_de_marker_indicator.py +78 -0
- backtrader/indicators/contrib/color_metro_stochastic_indicator.py +93 -0
- backtrader/indicators/contrib/color_metro_wpr_indicator.py +85 -0
- backtrader/indicators/contrib/color_schaff_de_marker_trend_cycle.py +92 -0
- backtrader/indicators/contrib/color_schaff_trend_cycle_indicator.py +203 -0
- backtrader/indicators/contrib/color_step_xccx_indicator.py +193 -0
- backtrader/indicators/contrib/color_x2_ma.py +49 -0
- backtrader/indicators/contrib/color_x_derivative.py +63 -0
- backtrader/indicators/contrib/color_zerolag_de_marker.py +84 -0
- backtrader/indicators/contrib/corrected_average_indicator.py +127 -0
- backtrader/indicators/contrib/darvas_boxes_system.py +73 -0
- backtrader/indicators/contrib/dema_range_channel_color.py +42 -0
- backtrader/indicators/contrib/derivative_indicator.py +95 -0
- backtrader/indicators/contrib/digital_ft01_indicator.py +112 -0
- backtrader/indicators/contrib/digital_macd.py +200 -0
- backtrader/indicators/contrib/donchian_channels_system.py +45 -0
- backtrader/indicators/contrib/dots_indicator.py +93 -0
- backtrader/indicators/contrib/ef_distance_indicator.py +82 -0
- backtrader/indicators/contrib/ema_rsi_va.py +80 -0
- backtrader/indicators/contrib/envelopes_jp_alonso.py +32 -0
- backtrader/indicators/contrib/f2a_ao_indicator.py +120 -0
- backtrader/indicators/contrib/fatl_filter.py +179 -0
- backtrader/indicators/contrib/fibo_candles_indicator.py +78 -0
- backtrader/indicators/contrib/fine_tuning_ma.py +100 -0
- backtrader/indicators/contrib/fisher_org_v1.py +102 -0
- backtrader/indicators/contrib/fisher_org_v1_sign.py +118 -0
- backtrader/indicators/contrib/force_index_ema.py +96 -0
- backtrader/indicators/contrib/force_index_ema_2.py +27 -0
- backtrader/indicators/contrib/forecast_oscilator.py +145 -0
- backtrader/indicators/contrib/fractal_amambk.py +81 -0
- backtrader/indicators/contrib/frama_series.py +84 -0
- backtrader/indicators/contrib/frasm_av2_indicator.py +104 -0
- backtrader/indicators/contrib/go_indicator.py +93 -0
- backtrader/indicators/contrib/hlr_indicator.py +95 -0
- backtrader/indicators/contrib/hma.py +50 -0
- backtrader/indicators/contrib/i4_drfv2.py +34 -0
- backtrader/indicators/contrib/i4_drfv3.py +38 -0
- backtrader/indicators/contrib/i_anch_mom_indicator.py +72 -0
- backtrader/indicators/contrib/i_de_marker_sign_indicator.py +64 -0
- backtrader/indicators/contrib/i_gap_indicator.py +45 -0
- backtrader/indicators/contrib/i_stoch_komposter_indicator.py +77 -0
- backtrader/indicators/contrib/i_trend_indicator.py +125 -0
- backtrader/indicators/contrib/iamma_indicator.py +39 -0
- backtrader/indicators/contrib/indexed_moving_average.py +33 -0
- backtrader/indicators/contrib/instantaneous_trend_filter_indicator.py +51 -0
- backtrader/indicators/contrib/inverse_reaction_indicator.py +41 -0
- backtrader/indicators/contrib/irsi_sign_indicator.py +95 -0
- backtrader/indicators/contrib/iwpr_sign_indicator.py +59 -0
- backtrader/indicators/contrib/j_brain_trend1_sig_indicator.py +233 -0
- backtrader/indicators/contrib/j_tpo_proxy.py +32 -0
- backtrader/indicators/contrib/jma_slope_indicator.py +73 -0
- backtrader/indicators/contrib/kalman_filter_indicator.py +119 -0
- backtrader/indicators/contrib/kalman_filter_line.py +127 -0
- backtrader/indicators/contrib/kama_indicator.py +150 -0
- backtrader/indicators/contrib/karacatica_indicator.py +99 -0
- backtrader/indicators/contrib/kdj_indicator.py +59 -0
- backtrader/indicators/contrib/kwan_ccc_indicator.py +195 -0
- backtrader/indicators/contrib/kwan_nrp_indicator.py +113 -0
- backtrader/indicators/contrib/kwan_rdp_indicator.py +192 -0
- backtrader/indicators/contrib/laguerre_adx_indicator.py +85 -0
- backtrader/indicators/contrib/laguerre_filter_indicator.py +66 -0
- backtrader/indicators/contrib/laguerre_plus_di_proxy.py +57 -0
- backtrader/indicators/contrib/laguerre_roc_indicator.py +81 -0
- backtrader/indicators/contrib/le_man_signal_indicator.py +63 -0
- backtrader/indicators/contrib/linear_reg_slope_v2_indicator.py +136 -0
- backtrader/indicators/contrib/loco_indicator.py +88 -0
- backtrader/indicators/contrib/lrma_indicator.py +185 -0
- backtrader/indicators/contrib/lsma_angle_indicator.py +106 -0
- backtrader/indicators/contrib/ma_rounding_channel_indicator.py +149 -0
- backtrader/indicators/contrib/macd2_indicator.py +61 -0
- backtrader/indicators/contrib/macd_candle_indicator.py +80 -0
- backtrader/indicators/contrib/malr_indicator.py +77 -0
- backtrader/indicators/contrib/momentum_candle_sign_indicator.py +51 -0
- backtrader/indicators/contrib/moving_average_fn_indicator.py +139 -0
- backtrader/indicators/contrib/mt5_stochastic_close_close.py +57 -0
- backtrader/indicators/contrib/muv_nor_diff_cloud_indicator.py +107 -0
- backtrader/indicators/contrib/non_lag_dot_indicator.py +124 -0
- backtrader/indicators/contrib/nrtr_extr_indicator.py +95 -0
- backtrader/indicators/contrib/nrtr_indicator.py +95 -0
- backtrader/indicators/contrib/p_channel_system.py +40 -0
- backtrader/indicators/contrib/percent_envelope.py +37 -0
- backtrader/indicators/contrib/percentage_crossover_channel.py +47 -0
- backtrader/indicators/contrib/pivot_zig_zag_proxy.py +47 -0
- backtrader/indicators/contrib/price_channel_stop_indicator.py +104 -0
- backtrader/indicators/contrib/price_extreme_channel.py +35 -0
- backtrader/indicators/contrib/qqe_cloud_indicator.py +129 -0
- backtrader/indicators/contrib/ravi_indicator.py +40 -0
- backtrader/indicators/contrib/raw_close_close_stochastic.py +74 -0
- backtrader/indicators/contrib/rd_trend_trigger_indicator.py +51 -0
- backtrader/indicators/contrib/renko_level.py +85 -0
- backtrader/indicators/contrib/renko_line_break.py +91 -0
- backtrader/indicators/contrib/rftl_indicator.py +41 -0
- backtrader/indicators/contrib/rkd_indicator.py +53 -0
- backtrader/indicators/contrib/roc2_vg_indicator.py +68 -0
- backtrader/indicators/contrib/rsi_histogram_indicator.py +43 -0
- backtrader/indicators/contrib/rsi_slowdown.py +57 -0
- backtrader/indicators/contrib/rsioma_v2.py +41 -0
- backtrader/indicators/contrib/rvi_histogram_indicator.py +107 -0
- backtrader/indicators/contrib/safe_adx.py +89 -0
- backtrader/indicators/contrib/shared_strategy_indicators.py +1651 -0
- backtrader/indicators/contrib/sidus_indicator.py +105 -0
- backtrader/indicators/contrib/silver_trend_indicator.py +79 -0
- backtrader/indicators/contrib/sliding_range_color.py +56 -0
- backtrader/indicators/contrib/slow_stoch.py +42 -0
- backtrader/indicators/contrib/smoothed_adx_indicator.py +86 -0
- backtrader/indicators/contrib/smoothed_rsi.py +31 -0
- backtrader/indicators/contrib/spearman_rank_correlation_histogram.py +60 -0
- backtrader/indicators/contrib/stalin_indicator.py +152 -0
- backtrader/indicators/contrib/starter_laguerre_filter.py +62 -0
- backtrader/indicators/contrib/step_manrtr_indicator.py +137 -0
- backtrader/indicators/contrib/stochastic_histogram_indicator.py +143 -0
- backtrader/indicators/contrib/t3_alarm_indicator.py +125 -0
- backtrader/indicators/contrib/t3_average.py +76 -0
- backtrader/indicators/contrib/t3_indicator.py +40 -0
- backtrader/indicators/contrib/the20s_v020_signal.py +93 -0
- backtrader/indicators/contrib/three_candles_indicator.py +70 -0
- backtrader/indicators/contrib/three_line_break_indicator.py +64 -0
- backtrader/indicators/contrib/time_line.py +57 -0
- backtrader/indicators/contrib/trading_channel_index_proxy.py +48 -0
- backtrader/indicators/contrib/trend_arrows_indicator.py +109 -0
- backtrader/indicators/contrib/trend_continuation_indicator.py +127 -0
- backtrader/indicators/contrib/trend_intensity_index_proxy.py +51 -0
- backtrader/indicators/contrib/trend_manager_indicator.py +39 -0
- backtrader/indicators/contrib/tri_x_candle_indicator.py +51 -0
- backtrader/indicators/contrib/trigger_line.py +66 -0
- backtrader/indicators/contrib/triple_ema_rate.py +34 -0
- backtrader/indicators/contrib/trvi_indicator.py +194 -0
- backtrader/indicators/contrib/two_pb_ideal_xosma_indicator.py +127 -0
- backtrader/indicators/contrib/ultra_absolutely_no_lag_lwma_color.py +92 -0
- backtrader/indicators/contrib/ultra_wpr_indicator.py +173 -0
- backtrader/indicators/contrib/up_down_candle_strength.py +68 -0
- backtrader/indicators/contrib/vinin_i_trend_indicator.py +139 -0
- backtrader/indicators/contrib/volume_weighted_ma_indicator.py +78 -0
- backtrader/indicators/contrib/volume_weighted_ma_st_dev_indicator.py +111 -0
- backtrader/indicators/contrib/vwap_close_indicator.py +65 -0
- backtrader/indicators/contrib/vwma_candle.py +57 -0
- backtrader/indicators/contrib/vwma_digit_system.py +70 -0
- backtrader/indicators/contrib/wami.py +43 -0
- backtrader/indicators/contrib/wprsi_signal_indicator.py +105 -0
- backtrader/indicators/contrib/x_de_marker_histogram_vol_direct_indicator.py +145 -0
- backtrader/indicators/contrib/x_fisher_indicator.py +64 -0
- backtrader/indicators/contrib/xcci_histogram_vol_direct_indicator.py +56 -0
- backtrader/indicators/contrib/xcci_histogram_vol_indicator.py +85 -0
- backtrader/indicators/contrib/xma_ichimoku.py +163 -0
- backtrader/indicators/contrib/xma_ishimoku_channel_indicator.py +65 -0
- backtrader/indicators/contrib/xma_ishimoku_line.py +68 -0
- backtrader/indicators/contrib/xma_range_bands_indicator.py +107 -0
- backtrader/indicators/contrib/xmacd_indicator.py +70 -0
- backtrader/indicators/contrib/xrsi_de_marker_histogram.py +67 -0
- backtrader/indicators/contrib/xrsi_histogram_vol_direct_indicator.py +52 -0
- backtrader/indicators/contrib/xrsi_histogram_vol_indicator.py +81 -0
- backtrader/indicators/contrib/xrvi_indicator.py +130 -0
- backtrader/indicators/contrib/zero_lag_macd.py +36 -0
- backtrader/indicators/contrib/zig_zag_recent_pivot_signal.py +90 -0
- backtrader/indicators/contrib/zpf_indicator.py +115 -0
- backtrader/indicators/crossover.py +337 -0
- backtrader/indicators/dema.py +175 -0
- backtrader/indicators/demarker.py +270 -0
- backtrader/indicators/deviation.py +284 -0
- backtrader/indicators/directionalmove.py +1071 -0
- backtrader/indicators/dma.py +112 -0
- backtrader/indicators/dpo.py +96 -0
- backtrader/indicators/dv2.py +56 -0
- backtrader/indicators/ema.py +145 -0
- backtrader/indicators/envelope.py +475 -0
- backtrader/indicators/hadelta.py +198 -0
- backtrader/indicators/heikinashi.py +153 -0
- backtrader/indicators/hma.py +153 -0
- backtrader/indicators/hurst.py +151 -0
- backtrader/indicators/ichimoku.py +267 -0
- backtrader/indicators/kama.py +181 -0
- backtrader/indicators/kst.py +159 -0
- backtrader/indicators/lrsi.py +125 -0
- backtrader/indicators/mabase.py +147 -0
- backtrader/indicators/macd.py +322 -0
- backtrader/indicators/momentum.py +267 -0
- backtrader/indicators/moneyflow.py +237 -0
- backtrader/indicators/mt5atr.py +124 -0
- backtrader/indicators/myind.py +179 -0
- backtrader/indicators/obv.py +94 -0
- backtrader/indicators/ols.py +265 -0
- backtrader/indicators/oscillator.py +161 -0
- backtrader/indicators/percentchange.py +83 -0
- backtrader/indicators/percentrank.py +46 -0
- backtrader/indicators/pivotpoint.py +469 -0
- backtrader/indicators/prettygoodoscillator.py +113 -0
- backtrader/indicators/priceops_ext.py +123 -0
- backtrader/indicators/priceoscillator.py +262 -0
- backtrader/indicators/psar.py +212 -0
- backtrader/indicators/rmi.py +69 -0
- backtrader/indicators/rsi.py +440 -0
- backtrader/indicators/sma.py +141 -0
- backtrader/indicators/smma.py +116 -0
- backtrader/indicators/spread.py +54 -0
- backtrader/indicators/stochastic.py +263 -0
- backtrader/indicators/supertrend.py +436 -0
- backtrader/indicators/trend_ext.py +105 -0
- backtrader/indicators/trix.py +202 -0
- backtrader/indicators/tsi.py +155 -0
- backtrader/indicators/ultimateoscillator.py +158 -0
- backtrader/indicators/vortex.py +62 -0
- backtrader/indicators/williams.py +194 -0
- backtrader/indicators/wma.py +103 -0
- backtrader/indicators/zlema.py +135 -0
- backtrader/indicators/zlind.py +104 -0
- backtrader/linebuffer.py +3155 -0
- backtrader/lineiterator.py +2911 -0
- backtrader/lineroot.py +1106 -0
- backtrader/lineseries.py +2559 -0
- backtrader/live_trading/__init__.py +31 -0
- backtrader/live_trading/interface.py +404 -0
- backtrader/mathsupport.py +94 -0
- backtrader/metabase.py +1804 -0
- backtrader/mixins/__init__.py +21 -0
- backtrader/mixins/singleton.py +118 -0
- backtrader/observer.py +106 -0
- backtrader/observers/__init__.py +45 -0
- backtrader/observers/benchmark.py +126 -0
- backtrader/observers/broker.py +184 -0
- backtrader/observers/buysell.py +144 -0
- backtrader/observers/drawdown.py +161 -0
- backtrader/observers/logreturns.py +113 -0
- backtrader/observers/timereturn.py +86 -0
- backtrader/observers/trade_logger.py +2972 -0
- backtrader/observers/tradelogger.py +6 -0
- backtrader/observers/trades.py +258 -0
- backtrader/order.py +1114 -0
- backtrader/parameters.py +2345 -0
- backtrader/plot/__init__.py +54 -0
- backtrader/plot/finance.py +1022 -0
- backtrader/plot/formatters.py +200 -0
- backtrader/plot/locator.py +353 -0
- backtrader/plot/multicursor.py +495 -0
- backtrader/plot/plot.py +2500 -0
- backtrader/plot/plot_plotly.py +1351 -0
- backtrader/plot/scheme.py +253 -0
- backtrader/plot/utils.py +104 -0
- backtrader/position.py +290 -0
- backtrader/position_modes.py +132 -0
- backtrader/profiles.py +254 -0
- backtrader/reports/__init__.py +39 -0
- backtrader/reports/charts.py +371 -0
- backtrader/reports/performance.py +620 -0
- backtrader/reports/reporter.py +660 -0
- backtrader/resamplerfilter.py +1001 -0
- backtrader/signal.py +118 -0
- backtrader/signals/__init__.py +17 -0
- backtrader/sizer.py +114 -0
- backtrader/sizers/__init__.py +26 -0
- backtrader/sizers/fixedsize.py +161 -0
- backtrader/sizers/percents_sizer.py +119 -0
- backtrader/store.py +221 -0
- backtrader/stores/__init__.py +33 -0
- backtrader/stores/btapistore.py +15506 -0
- backtrader/stores/livestore.py +137 -0
- backtrader/stores/vchartfile.py +96 -0
- backtrader/strategy.py +3655 -0
- backtrader/talib.py +280 -0
- backtrader/test_helpers.py +96 -0
- backtrader/timer.py +358 -0
- backtrader/trade.py +442 -0
- backtrader/tradingcal.py +361 -0
- backtrader/utils/__init__.py +68 -0
- backtrader/utils/autodict.py +251 -0
- backtrader/utils/date.py +71 -0
- backtrader/utils/dateintern.py +509 -0
- backtrader/utils/flushfile.py +94 -0
- backtrader/utils/fractal.py +101 -0
- backtrader/utils/get_metrics.py +101 -0
- backtrader/utils/load_data.py +209 -0
- backtrader/utils/log_message.py +998 -0
- backtrader/utils/ordereddefaultdict.py +75 -0
- backtrader/utils/py3.py +296 -0
- backtrader/version.py +21 -0
- backtrader/writer.py +372 -0
backtrader/lineroot.py
ADDED
|
@@ -0,0 +1,1106 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""LineRoot Module - Base classes for line-based data structures.
|
|
3
|
+
|
|
4
|
+
This module defines the base class LineRoot and derived classes LineSingle
|
|
5
|
+
and LineMultiple that provide the foundation and interfaces for all
|
|
6
|
+
line-based objects in backtrader.
|
|
7
|
+
|
|
8
|
+
Key Classes:
|
|
9
|
+
LineRoot: Common base for all line objects with period management.
|
|
10
|
+
LineSingle: Base for single-line objects.
|
|
11
|
+
LineMultiple: Base for multi-line objects.
|
|
12
|
+
LineRootMixin: Mixin providing owner-finding functionality.
|
|
13
|
+
|
|
14
|
+
The module provides:
|
|
15
|
+
- Period management (minperiod)
|
|
16
|
+
- Iteration management
|
|
17
|
+
- Operation management (binary/unary operations)
|
|
18
|
+
- Rich comparison operators
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
Period management:
|
|
22
|
+
>>> obj.setminperiod(20) # Set minimum period to 20
|
|
23
|
+
>>> obj.updateminperiod(30) # Update to max(current, 30)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import operator
|
|
27
|
+
|
|
28
|
+
from . import metabase
|
|
29
|
+
from .utils.log_message import get_logger, throttled_warning
|
|
30
|
+
from .utils.py3 import range
|
|
31
|
+
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class LineRootMixin:
|
|
36
|
+
"""Mixin to provide LineRoot functionality without metaclass"""
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def donew(cls, *args, **kwargs):
|
|
40
|
+
"""Create new instance with owner finding logic"""
|
|
41
|
+
_obj, args, kwargs = (
|
|
42
|
+
super().donew(*args, **kwargs)
|
|
43
|
+
if hasattr(super(), "donew")
|
|
44
|
+
else (cls.__new__(cls), args, kwargs)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Find the owner and store it
|
|
48
|
+
# startlevel = 4 ... to skip intermediate call stacks
|
|
49
|
+
ownerskip = kwargs.pop("_ownerskip", None)
|
|
50
|
+
# Import LineMultiple here to avoid circular imports
|
|
51
|
+
from .lineroot import LineMultiple
|
|
52
|
+
|
|
53
|
+
_obj._owner = metabase.findowner(_obj, _obj._OwnerCls or LineMultiple, skip=ownerskip)
|
|
54
|
+
|
|
55
|
+
# Parameter values have now been set before __init__
|
|
56
|
+
return _obj, args, kwargs
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class LineRoot(LineRootMixin, metabase.BaseMixin):
|
|
60
|
+
"""
|
|
61
|
+
Defines a common base and interfaces for Single and Multiple
|
|
62
|
+
LineXXX instances
|
|
63
|
+
|
|
64
|
+
Period management
|
|
65
|
+
Iteration management
|
|
66
|
+
Operation (dual/single operand) Management
|
|
67
|
+
Rich Comparison operator definition
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# Class attributes during initialization
|
|
71
|
+
_OwnerCls = None # Default parent instance is None
|
|
72
|
+
_minperiod = 1 # Minimum period is 1
|
|
73
|
+
_opstage = 1 # Operation state defaults to 1
|
|
74
|
+
|
|
75
|
+
# Indicator type, strategy type, and observer type values are 0, 1, 2 respectively
|
|
76
|
+
IndType, StratType, ObsType = range(3)
|
|
77
|
+
|
|
78
|
+
# Change operation state to 1
|
|
79
|
+
def _stage1(self):
|
|
80
|
+
self._opstage = 1
|
|
81
|
+
|
|
82
|
+
# Change operation state to 2
|
|
83
|
+
def _stage2(self):
|
|
84
|
+
self._opstage = 2
|
|
85
|
+
|
|
86
|
+
# Decide which operation algorithm to call based on line operation state
|
|
87
|
+
def _operation(self, other, operation, r=False, intify=False):
|
|
88
|
+
if self._opstage == 1:
|
|
89
|
+
return self._operation_stage1(other, operation, r=r, intify=intify)
|
|
90
|
+
|
|
91
|
+
return self._operation_stage2(other, operation, r=r)
|
|
92
|
+
|
|
93
|
+
# Self operation
|
|
94
|
+
def _operationown(self, operation):
|
|
95
|
+
if self._opstage == 1:
|
|
96
|
+
return self._operationown_stage1(operation)
|
|
97
|
+
|
|
98
|
+
return self._operationown_stage2(operation)
|
|
99
|
+
|
|
100
|
+
# Change lines to implement minimum buffer scheme
|
|
101
|
+
def qbuffer(self, savemem=0):
|
|
102
|
+
"""Change the lines to implement a minimum size qbuffer scheme"""
|
|
103
|
+
raise NotImplementedError
|
|
104
|
+
|
|
105
|
+
# Minimum buffer required
|
|
106
|
+
def minbuffer(self, size):
|
|
107
|
+
"""Receive notification of how large the buffer must at least be"""
|
|
108
|
+
raise NotImplementedError
|
|
109
|
+
|
|
110
|
+
# Can be used to set minimum period in strategy, can start running without waiting for indicators to produce specific values
|
|
111
|
+
def setminperiod(self, minperiod):
|
|
112
|
+
"""
|
|
113
|
+
Direct minperiod manipulation.It could be used, for example,
|
|
114
|
+
by a strategy
|
|
115
|
+
to not wait for all indicators to produce a value
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
self._minperiod = minperiod
|
|
119
|
+
|
|
120
|
+
# Update minimum period, minimum period may have been calculated elsewhere, compare with existing minimum period, choose the largest one as minimum period
|
|
121
|
+
def updateminperiod(self, minperiod):
|
|
122
|
+
"""
|
|
123
|
+
Update the minperiod if needed. The minperiod will have been
|
|
124
|
+
calculated elsewhere
|
|
125
|
+
and has to take over if greater that self's
|
|
126
|
+
"""
|
|
127
|
+
self._minperiod = max(self._minperiod, minperiod)
|
|
128
|
+
|
|
129
|
+
# Add minimum period
|
|
130
|
+
def addminperiod(self, minperiod):
|
|
131
|
+
"""
|
|
132
|
+
Add a minperiod to own ... to be defined by subclasses
|
|
133
|
+
"""
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
# Increase minimum period
|
|
137
|
+
def incminperiod(self, minperiod):
|
|
138
|
+
"""
|
|
139
|
+
Increment the minperiod with no considerations
|
|
140
|
+
"""
|
|
141
|
+
raise NotImplementedError
|
|
142
|
+
|
|
143
|
+
# This function will be called during iteration within minimum period
|
|
144
|
+
def prenext(self):
|
|
145
|
+
"""
|
|
146
|
+
It will be called during the "minperiod" phase of an iteration.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
# Called once when minimum period iteration ends, about to start next
|
|
150
|
+
def nextstart(self):
|
|
151
|
+
"""
|
|
152
|
+
It will be called when the minperiod phase is over for the 1st
|
|
153
|
+
post-minperiod value. Only called once and defaults to automatically
|
|
154
|
+
calling next
|
|
155
|
+
"""
|
|
156
|
+
self.next()
|
|
157
|
+
|
|
158
|
+
# Start calling next after minimum period iteration ends
|
|
159
|
+
def next(self):
|
|
160
|
+
"""
|
|
161
|
+
Called to calculate values when the minperiod is over
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
# Call preonce during minimum period iteration
|
|
165
|
+
def preonce(self, start, end):
|
|
166
|
+
"""
|
|
167
|
+
It will be called during the "minperiod" phase of a "once" iteration
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
# Run once when minimum period ends, call once
|
|
171
|
+
def oncestart(self, start, end):
|
|
172
|
+
"""
|
|
173
|
+
It will be called when the minperiod phase is over for the 1st
|
|
174
|
+
post-minperiod value
|
|
175
|
+
|
|
176
|
+
Only called once and defaults to automatically calling once
|
|
177
|
+
|
|
178
|
+
"""
|
|
179
|
+
self.once(start, end)
|
|
180
|
+
|
|
181
|
+
# Called to calculate results when minimum period iteration ends
|
|
182
|
+
def once(self, start, end):
|
|
183
|
+
"""
|
|
184
|
+
Called to calculate values at "once" when the minperiod is over
|
|
185
|
+
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def size(self):
|
|
189
|
+
"""Return the number of lines in this object"""
|
|
190
|
+
# This method provides a size() interface for all LineRoot objects
|
|
191
|
+
# It will be overridden by specific implementations as needed
|
|
192
|
+
if hasattr(self, "lines") and hasattr(self.lines, "size"):
|
|
193
|
+
return self.lines.size()
|
|
194
|
+
if hasattr(self, "lines") and hasattr(self.lines, "__len__"):
|
|
195
|
+
return len(self.lines)
|
|
196
|
+
return 1 # Default to 1 line if no lines object available
|
|
197
|
+
|
|
198
|
+
# Arithmetic operators
|
|
199
|
+
# Some arithmetic operations
|
|
200
|
+
def _makeoperation(self, other, operation, r=False, _ownerskip=None, original_other=None):
|
|
201
|
+
# For LineMultiple, we can implement a basic operation using the first line
|
|
202
|
+
# This provides a fallback when operations are needed
|
|
203
|
+
if hasattr(self, "lines") and self.lines:
|
|
204
|
+
# Use the first line for operations
|
|
205
|
+
from .linebuffer import LinesOperation
|
|
206
|
+
|
|
207
|
+
# CRITICAL FIX: Pass parent indicators so LinesOperation can call their _once
|
|
208
|
+
parent_a = self if hasattr(self, "_once") else None
|
|
209
|
+
# Use original_other (before lines[0] extraction) to get the indicator reference
|
|
210
|
+
parent_b_candidate = original_other if original_other is not None else other
|
|
211
|
+
parent_b = parent_b_candidate if hasattr(parent_b_candidate, "_once") else None
|
|
212
|
+
return LinesOperation(
|
|
213
|
+
self.lines[0], other, operation, r=r, parent_a=parent_a, parent_b=parent_b
|
|
214
|
+
)
|
|
215
|
+
# If no lines, return a simple operation result
|
|
216
|
+
try:
|
|
217
|
+
if r:
|
|
218
|
+
return operation(other, 0) # Use 0 as default value
|
|
219
|
+
return operation(0, other) # Use 0 as default value
|
|
220
|
+
except Exception:
|
|
221
|
+
throttled_warning(
|
|
222
|
+
logger,
|
|
223
|
+
"lineroot.line_root.makeoperation_recovery",
|
|
224
|
+
"LineRoot operation fallback failed; returning safe default",
|
|
225
|
+
exc_info=False,
|
|
226
|
+
)
|
|
227
|
+
# If operation fails, return False for bool operations
|
|
228
|
+
if operation is bool:
|
|
229
|
+
return False
|
|
230
|
+
return 0
|
|
231
|
+
|
|
232
|
+
# Perform self operation
|
|
233
|
+
def _makeoperationown(self, operation, _ownerskip=None):
|
|
234
|
+
# CRITICAL FIX: For bool operations, return a simple boolean result instead of creating objects
|
|
235
|
+
if operation is bool:
|
|
236
|
+
# For bool operations, check if we have any lines and if they have data
|
|
237
|
+
if hasattr(self, "lines") and self.lines:
|
|
238
|
+
try:
|
|
239
|
+
# Try to get the current value from the first line
|
|
240
|
+
if hasattr(self.lines, "__getitem__") and len(self.lines) > 0:
|
|
241
|
+
line = self.lines[0]
|
|
242
|
+
if hasattr(line, "__getitem__") and hasattr(line, "__len__"):
|
|
243
|
+
if len(line) > 0:
|
|
244
|
+
value = line[0]
|
|
245
|
+
# Return True if value is not None, not NaN and not 0
|
|
246
|
+
if value is None:
|
|
247
|
+
return False
|
|
248
|
+
if isinstance(value, float):
|
|
249
|
+
import math
|
|
250
|
+
|
|
251
|
+
if not math.isfinite(value):
|
|
252
|
+
return False
|
|
253
|
+
# bool() for the same reason as in __nonzero__:
|
|
254
|
+
# numpy.float64 subclasses float, so it reaches
|
|
255
|
+
# this branch and `!= 0.0` yields numpy.bool_.
|
|
256
|
+
# This path returns a value rather than going
|
|
257
|
+
# through the __bool__ protocol, so a leak here
|
|
258
|
+
# would propagate silently instead of raising.
|
|
259
|
+
return bool(value != 0.0)
|
|
260
|
+
return bool(value)
|
|
261
|
+
return False
|
|
262
|
+
except Exception:
|
|
263
|
+
throttled_warning(
|
|
264
|
+
logger,
|
|
265
|
+
"lineroot.line_root.makeoperationown_boolean_recovery",
|
|
266
|
+
"LineRoot boolean operation fallback failed; returning False",
|
|
267
|
+
exc_info=False,
|
|
268
|
+
)
|
|
269
|
+
return False
|
|
270
|
+
elif hasattr(self, "__getitem__") and hasattr(self, "__len__"):
|
|
271
|
+
# For LineSingle objects, check the current value directly
|
|
272
|
+
try:
|
|
273
|
+
if len(self) > 0:
|
|
274
|
+
value = self[0]
|
|
275
|
+
if value is None:
|
|
276
|
+
return False
|
|
277
|
+
if isinstance(value, float):
|
|
278
|
+
import math
|
|
279
|
+
|
|
280
|
+
if not math.isfinite(value):
|
|
281
|
+
return False
|
|
282
|
+
# See the note above: guard against numpy.bool_ leaking
|
|
283
|
+
# out of this non-__bool__ return path.
|
|
284
|
+
return bool(value != 0.0)
|
|
285
|
+
return bool(value)
|
|
286
|
+
return False
|
|
287
|
+
except Exception:
|
|
288
|
+
throttled_warning(
|
|
289
|
+
logger,
|
|
290
|
+
"lineroot.line_root.makeoperationown_boolean_recovery",
|
|
291
|
+
"LineRoot boolean operation fallback failed; returning False",
|
|
292
|
+
exc_info=False,
|
|
293
|
+
)
|
|
294
|
+
return False
|
|
295
|
+
else:
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
# For other operations, use the original approach but only if really needed
|
|
299
|
+
if hasattr(self, "lines") and self.lines:
|
|
300
|
+
# Use the first line for self-operations
|
|
301
|
+
from .linebuffer import LineOwnOperation
|
|
302
|
+
|
|
303
|
+
return LineOwnOperation(self.lines[0], operation)
|
|
304
|
+
# If no lines, return a simple operation result
|
|
305
|
+
try:
|
|
306
|
+
return operation(0) # Use 0 as default value
|
|
307
|
+
except Exception:
|
|
308
|
+
throttled_warning(
|
|
309
|
+
logger,
|
|
310
|
+
"lineroot.line_root.makeoperationown_recovery",
|
|
311
|
+
"LineRoot self-operation fallback failed; returning 0",
|
|
312
|
+
exc_info=False,
|
|
313
|
+
)
|
|
314
|
+
# If operation fails, return 0 for most operations
|
|
315
|
+
return 0
|
|
316
|
+
|
|
317
|
+
# Self operation stage 1
|
|
318
|
+
def _operationown_stage1(self, operation):
|
|
319
|
+
"""
|
|
320
|
+
Operation with single operand which is "self"
|
|
321
|
+
"""
|
|
322
|
+
return self._makeoperationown(operation, _ownerskip=self)
|
|
323
|
+
|
|
324
|
+
# Self operation stage 2
|
|
325
|
+
def _operationown_stage2(self, operation):
|
|
326
|
+
return operation(self[0])
|
|
327
|
+
|
|
328
|
+
# Right operation
|
|
329
|
+
def _roperation(self, other, operation, intify=False):
|
|
330
|
+
"""
|
|
331
|
+
Relies on self._operation to and passes "r" True to define a
|
|
332
|
+
reverse operation
|
|
333
|
+
"""
|
|
334
|
+
return self._operation(other, operation, r=True, intify=intify)
|
|
335
|
+
|
|
336
|
+
# Stage 1 operation, determine if other contains multiple lines, if multiple lines, take the first line and perform operation
|
|
337
|
+
def _operation_stage1(self, other, operation, r=False, intify=False):
|
|
338
|
+
"""
|
|
339
|
+
Two operands' operations.Scanning of other happens to understand
|
|
340
|
+
if other must be directly an operand or rather a subitem thereof
|
|
341
|
+
"""
|
|
342
|
+
# CRITICAL FIX: Preserve original indicator reference before extracting lines[0]
|
|
343
|
+
original_other = other
|
|
344
|
+
if isinstance(other, LineMultiple):
|
|
345
|
+
other = other.lines[0]
|
|
346
|
+
|
|
347
|
+
return self._makeoperation(other, operation, r, self, original_other=original_other)
|
|
348
|
+
|
|
349
|
+
# Stage 2 operation, if other is a line, take the current value and perform operation
|
|
350
|
+
def _operation_stage2(self, other, operation, r=False):
|
|
351
|
+
"""
|
|
352
|
+
Rich Comparison operators. Scans other and returns either an
|
|
353
|
+
operation with other directly or a subitem from other
|
|
354
|
+
"""
|
|
355
|
+
if isinstance(other, LineRoot):
|
|
356
|
+
other = other[0]
|
|
357
|
+
|
|
358
|
+
# operation(float, other) ... expecting other to be a float
|
|
359
|
+
# CRITICAL FIX: Handle None values in comparisons to prevent errors
|
|
360
|
+
self_value = self[0]
|
|
361
|
+
|
|
362
|
+
# CRITICAL FIX: Convert None to 0.0 to prevent None vs float comparison errors
|
|
363
|
+
if self_value is None:
|
|
364
|
+
self_value = 0.0
|
|
365
|
+
elif isinstance(self_value, float):
|
|
366
|
+
import math
|
|
367
|
+
|
|
368
|
+
if not math.isfinite(self_value):
|
|
369
|
+
self_value = 0.0
|
|
370
|
+
|
|
371
|
+
# Also handle None in other value
|
|
372
|
+
if other is None:
|
|
373
|
+
other = 0.0
|
|
374
|
+
elif isinstance(other, float):
|
|
375
|
+
import math
|
|
376
|
+
|
|
377
|
+
if not math.isfinite(other):
|
|
378
|
+
other = 0.0
|
|
379
|
+
|
|
380
|
+
# CRITICAL FIX: Actually perform the operation and return the result
|
|
381
|
+
# Don't create LinesOperation objects in stage2 - return actual values
|
|
382
|
+
try:
|
|
383
|
+
if r:
|
|
384
|
+
result = operation(other, self_value)
|
|
385
|
+
else:
|
|
386
|
+
result = operation(self_value, other)
|
|
387
|
+
if isinstance(result, float):
|
|
388
|
+
import math
|
|
389
|
+
|
|
390
|
+
if not math.isfinite(result):
|
|
391
|
+
return 0.0
|
|
392
|
+
return result
|
|
393
|
+
except Exception:
|
|
394
|
+
throttled_warning(
|
|
395
|
+
logger,
|
|
396
|
+
"lineroot.line_root.operation_stage2_recovery",
|
|
397
|
+
"LineRoot stage-2 operation failed; returning safe default",
|
|
398
|
+
exc_info=False,
|
|
399
|
+
)
|
|
400
|
+
# If operation fails, return appropriate default
|
|
401
|
+
if operation in [
|
|
402
|
+
operator.__lt__,
|
|
403
|
+
operator.__le__,
|
|
404
|
+
operator.__gt__,
|
|
405
|
+
operator.__ge__,
|
|
406
|
+
operator.__eq__,
|
|
407
|
+
operator.__ne__,
|
|
408
|
+
]:
|
|
409
|
+
return False # For comparison operations, return False on error
|
|
410
|
+
return 0.0 # For arithmetic operations, return 0.0 on error
|
|
411
|
+
|
|
412
|
+
# Addition
|
|
413
|
+
def __add__(self, other):
|
|
414
|
+
return self._operation(other, operator.__add__)
|
|
415
|
+
|
|
416
|
+
# Right addition
|
|
417
|
+
def __radd__(self, other):
|
|
418
|
+
return self._roperation(other, operator.__add__)
|
|
419
|
+
|
|
420
|
+
# Subtraction
|
|
421
|
+
def __sub__(self, other):
|
|
422
|
+
return self._operation(other, operator.__sub__)
|
|
423
|
+
|
|
424
|
+
# Right subtraction
|
|
425
|
+
def __rsub__(self, other):
|
|
426
|
+
return self._roperation(other, operator.__sub__)
|
|
427
|
+
|
|
428
|
+
# Multiplication
|
|
429
|
+
def __mul__(self, other):
|
|
430
|
+
return self._operation(other, operator.__mul__)
|
|
431
|
+
|
|
432
|
+
# Right multiplication
|
|
433
|
+
def __rmul__(self, other):
|
|
434
|
+
return self._roperation(other, operator.__mul__)
|
|
435
|
+
|
|
436
|
+
# Division (Py2 alias; mapped to true division for Py3 correctness)
|
|
437
|
+
def __div__(self, other):
|
|
438
|
+
return self._operation(other, operator.__truediv__)
|
|
439
|
+
|
|
440
|
+
# Right division
|
|
441
|
+
def __rdiv__(self, other):
|
|
442
|
+
return self._roperation(other, operator.__truediv__)
|
|
443
|
+
|
|
444
|
+
# Floor division
|
|
445
|
+
def __floordiv__(self, other):
|
|
446
|
+
return self._operation(other, operator.__floordiv__)
|
|
447
|
+
|
|
448
|
+
# Right floor division
|
|
449
|
+
def __rfloordiv__(self, other):
|
|
450
|
+
return self._roperation(other, operator.__floordiv__)
|
|
451
|
+
|
|
452
|
+
# True division
|
|
453
|
+
def __truediv__(self, other):
|
|
454
|
+
return self._operation(other, operator.__truediv__)
|
|
455
|
+
|
|
456
|
+
# Right true division
|
|
457
|
+
def __rtruediv__(self, other):
|
|
458
|
+
return self._roperation(other, operator.__truediv__)
|
|
459
|
+
|
|
460
|
+
# Power
|
|
461
|
+
def __pow__(self, other):
|
|
462
|
+
return self._operation(other, operator.__pow__)
|
|
463
|
+
|
|
464
|
+
# Right power
|
|
465
|
+
def __rpow__(self, other):
|
|
466
|
+
return self._roperation(other, operator.__pow__)
|
|
467
|
+
|
|
468
|
+
# Absolute value
|
|
469
|
+
def __abs__(self):
|
|
470
|
+
return self._operationown(operator.__abs__)
|
|
471
|
+
|
|
472
|
+
# Negation result
|
|
473
|
+
def __neg__(self):
|
|
474
|
+
return self._operationown(operator.__neg__)
|
|
475
|
+
|
|
476
|
+
# a<b
|
|
477
|
+
def __lt__(self, other):
|
|
478
|
+
# CRITICAL FIX: Always check opstage first to determine behavior
|
|
479
|
+
if self._opstage == 2:
|
|
480
|
+
# In stage2, return actual boolean values for direct use in strategies
|
|
481
|
+
self_value = self[0] if hasattr(self, "__getitem__") else 0.0
|
|
482
|
+
|
|
483
|
+
# Handle None values and convert to floats for comparison
|
|
484
|
+
if self_value is None:
|
|
485
|
+
self_value = 0.0
|
|
486
|
+
elif isinstance(self_value, float):
|
|
487
|
+
import math
|
|
488
|
+
|
|
489
|
+
if not math.isfinite(self_value):
|
|
490
|
+
self_value = 0.0
|
|
491
|
+
|
|
492
|
+
if other is None:
|
|
493
|
+
other = 0.0
|
|
494
|
+
elif isinstance(other, float):
|
|
495
|
+
import math
|
|
496
|
+
|
|
497
|
+
if not math.isfinite(other):
|
|
498
|
+
other = 0.0
|
|
499
|
+
|
|
500
|
+
# Return actual boolean for direct strategy use
|
|
501
|
+
try:
|
|
502
|
+
return float(self_value) < float(other)
|
|
503
|
+
except (ValueError, TypeError):
|
|
504
|
+
return False
|
|
505
|
+
else:
|
|
506
|
+
# In stage1, use normal operation creation
|
|
507
|
+
return self._operation(other, operator.__lt__)
|
|
508
|
+
|
|
509
|
+
# a>b
|
|
510
|
+
def __gt__(self, other):
|
|
511
|
+
# CRITICAL FIX: Always check opstage first to determine behavior
|
|
512
|
+
if self._opstage == 2:
|
|
513
|
+
# In stage2, return actual boolean values for direct use in strategies
|
|
514
|
+
self_value = self[0] if hasattr(self, "__getitem__") else 0.0
|
|
515
|
+
|
|
516
|
+
# Handle None values and convert to floats for comparison
|
|
517
|
+
if self_value is None:
|
|
518
|
+
self_value = 0.0
|
|
519
|
+
elif isinstance(self_value, float):
|
|
520
|
+
import math
|
|
521
|
+
|
|
522
|
+
if not math.isfinite(self_value):
|
|
523
|
+
self_value = 0.0
|
|
524
|
+
|
|
525
|
+
if other is None:
|
|
526
|
+
other = 0.0
|
|
527
|
+
elif isinstance(other, float):
|
|
528
|
+
import math
|
|
529
|
+
|
|
530
|
+
if not math.isfinite(other):
|
|
531
|
+
other = 0.0
|
|
532
|
+
|
|
533
|
+
# Return actual boolean for direct strategy use
|
|
534
|
+
try:
|
|
535
|
+
return float(self_value) > float(other)
|
|
536
|
+
except (ValueError, TypeError):
|
|
537
|
+
return False
|
|
538
|
+
else:
|
|
539
|
+
# In stage1, use normal operation creation
|
|
540
|
+
return self._operation(other, operator.__gt__)
|
|
541
|
+
|
|
542
|
+
# a<=b
|
|
543
|
+
def __le__(self, other):
|
|
544
|
+
# CRITICAL FIX: Always check opstage first to determine behavior
|
|
545
|
+
if self._opstage == 2:
|
|
546
|
+
# In stage2, return actual boolean values for direct use in strategies
|
|
547
|
+
self_value = self[0] if hasattr(self, "__getitem__") else 0.0
|
|
548
|
+
|
|
549
|
+
# Handle None values and convert to floats for comparison
|
|
550
|
+
if self_value is None:
|
|
551
|
+
self_value = 0.0
|
|
552
|
+
elif isinstance(self_value, float):
|
|
553
|
+
import math
|
|
554
|
+
|
|
555
|
+
if not math.isfinite(self_value):
|
|
556
|
+
self_value = 0.0
|
|
557
|
+
|
|
558
|
+
if other is None:
|
|
559
|
+
other = 0.0
|
|
560
|
+
elif isinstance(other, float):
|
|
561
|
+
import math
|
|
562
|
+
|
|
563
|
+
if not math.isfinite(other):
|
|
564
|
+
other = 0.0
|
|
565
|
+
|
|
566
|
+
# Return actual boolean for direct strategy use
|
|
567
|
+
try:
|
|
568
|
+
return float(self_value) <= float(other)
|
|
569
|
+
except (ValueError, TypeError):
|
|
570
|
+
return False
|
|
571
|
+
else:
|
|
572
|
+
# In stage1, use normal operation creation
|
|
573
|
+
return self._operation(other, operator.__le__)
|
|
574
|
+
|
|
575
|
+
# a>=b
|
|
576
|
+
def __ge__(self, other):
|
|
577
|
+
# CRITICAL FIX: Always check opstage first to determine behavior
|
|
578
|
+
if self._opstage == 2:
|
|
579
|
+
# In stage2, return actual boolean values for direct use in strategies
|
|
580
|
+
self_value = self[0] if hasattr(self, "__getitem__") else 0.0
|
|
581
|
+
|
|
582
|
+
# Handle None values and convert to floats for comparison
|
|
583
|
+
if self_value is None:
|
|
584
|
+
self_value = 0.0
|
|
585
|
+
elif isinstance(self_value, float):
|
|
586
|
+
import math
|
|
587
|
+
|
|
588
|
+
if not math.isfinite(self_value):
|
|
589
|
+
self_value = 0.0
|
|
590
|
+
|
|
591
|
+
if other is None:
|
|
592
|
+
other = 0.0
|
|
593
|
+
elif isinstance(other, float):
|
|
594
|
+
import math
|
|
595
|
+
|
|
596
|
+
if not math.isfinite(other):
|
|
597
|
+
other = 0.0
|
|
598
|
+
|
|
599
|
+
# Return actual boolean for direct strategy use
|
|
600
|
+
try:
|
|
601
|
+
return float(self_value) >= float(other)
|
|
602
|
+
except (ValueError, TypeError):
|
|
603
|
+
return False
|
|
604
|
+
else:
|
|
605
|
+
# In stage1, use normal operation creation
|
|
606
|
+
return self._operation(other, operator.__ge__)
|
|
607
|
+
|
|
608
|
+
# a = b
|
|
609
|
+
def __eq__(self, other):
|
|
610
|
+
return self._operation(other, operator.__eq__)
|
|
611
|
+
|
|
612
|
+
# a!=b
|
|
613
|
+
def __ne__(self, other):
|
|
614
|
+
return self._operation(other, operator.__ne__)
|
|
615
|
+
|
|
616
|
+
# a!=0
|
|
617
|
+
def __nonzero__(self):
|
|
618
|
+
# CRITICAL FIX: __bool__ MUST return a boolean, not a LineOwnOperation object
|
|
619
|
+
# This was causing "TypeError: __bool__ should return bool, returned LineOwnOperation"
|
|
620
|
+
try:
|
|
621
|
+
if hasattr(self, "lines") and self.lines:
|
|
622
|
+
# For LineMultiple objects, check the first line
|
|
623
|
+
if hasattr(self.lines, "__getitem__") and len(self.lines) > 0:
|
|
624
|
+
line = self.lines[0]
|
|
625
|
+
if hasattr(line, "__getitem__") and hasattr(line, "__len__"):
|
|
626
|
+
if len(line) > 0:
|
|
627
|
+
value = line[0]
|
|
628
|
+
# Return True if value exists and is not 0
|
|
629
|
+
if value is None:
|
|
630
|
+
return False
|
|
631
|
+
if isinstance(value, float):
|
|
632
|
+
import math
|
|
633
|
+
|
|
634
|
+
if not math.isfinite(value):
|
|
635
|
+
return False
|
|
636
|
+
# bool() is required, not cosmetic: numpy.float64
|
|
637
|
+
# subclasses float, so numpy scalars reach this branch
|
|
638
|
+
# and `np.float64 != 0.0` yields numpy.bool_, which
|
|
639
|
+
# CPython rejects from __bool__. Such scalars come from
|
|
640
|
+
# PandasData and survive only in QBuffer/exactbars mode,
|
|
641
|
+
# where lines are backed by a deque rather than
|
|
642
|
+
# array.array("d") (which coerces them to float).
|
|
643
|
+
return bool(value != 0.0)
|
|
644
|
+
return bool(value)
|
|
645
|
+
return False
|
|
646
|
+
if hasattr(self, "__getitem__") and hasattr(self, "__len__"):
|
|
647
|
+
# For LineSingle objects, check the current value
|
|
648
|
+
if len(self) > 0:
|
|
649
|
+
value = self[0]
|
|
650
|
+
if value is None:
|
|
651
|
+
return False
|
|
652
|
+
if isinstance(value, float):
|
|
653
|
+
import math
|
|
654
|
+
|
|
655
|
+
if not math.isfinite(value):
|
|
656
|
+
return False
|
|
657
|
+
# See the note above: bool() guards against numpy.float64
|
|
658
|
+
# reaching the isinstance(value, float) branch and making
|
|
659
|
+
# `!=` return numpy.bool_.
|
|
660
|
+
return bool(value != 0.0)
|
|
661
|
+
return bool(value)
|
|
662
|
+
return False
|
|
663
|
+
# Fallback: if no data available, return False
|
|
664
|
+
return False
|
|
665
|
+
except Exception:
|
|
666
|
+
throttled_warning(
|
|
667
|
+
logger,
|
|
668
|
+
"lineroot.line_root.nonzero_recovery",
|
|
669
|
+
"LineRoot boolean evaluation failed; returning False",
|
|
670
|
+
exc_info=False,
|
|
671
|
+
)
|
|
672
|
+
# If any error occurs during boolean evaluation, return False
|
|
673
|
+
# This prevents crashes in strategies when doing "if self.cross > 0:"
|
|
674
|
+
return False
|
|
675
|
+
|
|
676
|
+
__bool__ = __nonzero__
|
|
677
|
+
|
|
678
|
+
# Python 3 forces explicit implementation of hash if
|
|
679
|
+
# the class has redefined __eq__
|
|
680
|
+
__hash__ = object.__hash__
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
class LineMultiple(LineRoot):
|
|
684
|
+
"""Base class for objects containing multiple lines.
|
|
685
|
+
|
|
686
|
+
LineMultiple is the base class for objects that manage multiple
|
|
687
|
+
line instances, such as indicators with multiple outputs. It provides
|
|
688
|
+
common functionality for period management, staging, and operations
|
|
689
|
+
across all contained lines.
|
|
690
|
+
|
|
691
|
+
Attributes:
|
|
692
|
+
lines: Collection of line objects managed by this instance.
|
|
693
|
+
_ltype: Line type indicator (None for base LineMultiple).
|
|
694
|
+
_clock: Clock reference for synchronization.
|
|
695
|
+
_lineiterators: Dictionary tracking registered lineiterators.
|
|
696
|
+
|
|
697
|
+
Example:
|
|
698
|
+
>>> class MyIndicator(LineMultiple):
|
|
699
|
+
... lines = ('output1', 'output2')
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
def __init__(self):
|
|
703
|
+
"""Initialize a LineMultiple instance.
|
|
704
|
+
|
|
705
|
+
Sets up the internal state for managing multiple lines, including
|
|
706
|
+
line type indicator, lines collection, clock reference, and
|
|
707
|
+
line iterator tracking.
|
|
708
|
+
|
|
709
|
+
Initializes:
|
|
710
|
+
_ltype: Line type indicator (None for base LineMultiple).
|
|
711
|
+
lines: Collection of line objects (creates if not exists).
|
|
712
|
+
_clock: Clock reference for synchronization.
|
|
713
|
+
_lineiterators: Dictionary tracking registered lineiterators.
|
|
714
|
+
_minperiod: Minimum period requirement (defaults to 1).
|
|
715
|
+
"""
|
|
716
|
+
super().__init__()
|
|
717
|
+
# CRITICAL FIX: Initialize _ltype for proper strategy/indicator identification
|
|
718
|
+
self._ltype = None
|
|
719
|
+
# CRITICAL FIX: Initialize lines list to prevent index errors
|
|
720
|
+
if not hasattr(self, "lines") or self.lines is None:
|
|
721
|
+
from . import lineseries
|
|
722
|
+
|
|
723
|
+
self.lines = lineseries.Lines()
|
|
724
|
+
|
|
725
|
+
# CRITICAL FIX: Set up minimal clock for timing
|
|
726
|
+
if not hasattr(self, "_clock"):
|
|
727
|
+
self._clock = None
|
|
728
|
+
|
|
729
|
+
# CRITICAL FIX: Initialize line iterators tracking
|
|
730
|
+
if not hasattr(self, "_lineiterators"):
|
|
731
|
+
self._lineiterators = {}
|
|
732
|
+
|
|
733
|
+
# CRITICAL FIX: Ensure minperiod is set
|
|
734
|
+
if not hasattr(self, "_minperiod"):
|
|
735
|
+
self._minperiod = 1
|
|
736
|
+
|
|
737
|
+
def reset(self):
|
|
738
|
+
"""Reset the line multiple to initial state.
|
|
739
|
+
|
|
740
|
+
Resets the operation stage to stage 1 and resets all managed
|
|
741
|
+
lines to their initial state. This is typically called before
|
|
742
|
+
starting a new backtest run.
|
|
743
|
+
"""
|
|
744
|
+
self._stage1()
|
|
745
|
+
self.lines.reset()
|
|
746
|
+
|
|
747
|
+
def _stage1(self):
|
|
748
|
+
super()._stage1()
|
|
749
|
+
for line in self.lines:
|
|
750
|
+
line._stage1()
|
|
751
|
+
|
|
752
|
+
def _stage2(self):
|
|
753
|
+
super()._stage2()
|
|
754
|
+
for line in self.lines:
|
|
755
|
+
line._stage2()
|
|
756
|
+
|
|
757
|
+
def addminperiod(self, minperiod):
|
|
758
|
+
"""
|
|
759
|
+
The passed minperiod is fed to the lines
|
|
760
|
+
"""
|
|
761
|
+
has_child_iterators = False
|
|
762
|
+
try:
|
|
763
|
+
has_child_iterators = any(self._lineiterators.values())
|
|
764
|
+
except (AttributeError, TypeError):
|
|
765
|
+
# No sub-iterator registry yet; treat as having no child iterators.
|
|
766
|
+
pass
|
|
767
|
+
|
|
768
|
+
has_linebinding_output = any(
|
|
769
|
+
getattr(line, "_linebinding_assigned", False) for line in getattr(self, "lines", [])
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
if has_child_iterators and not has_linebinding_output:
|
|
773
|
+
self._minperiod = max(self._minperiod, minperiod)
|
|
774
|
+
for line in self.lines:
|
|
775
|
+
line.updateminperiod(self._minperiod)
|
|
776
|
+
return
|
|
777
|
+
|
|
778
|
+
# minperiod is added with -1 to account for overlapping
|
|
779
|
+
self._minperiod += minperiod - 1
|
|
780
|
+
|
|
781
|
+
for line in self.lines:
|
|
782
|
+
line.addminperiod(minperiod)
|
|
783
|
+
|
|
784
|
+
def incminperiod(self, minperiod):
|
|
785
|
+
"""
|
|
786
|
+
The passed minperiod is fed to the lines
|
|
787
|
+
"""
|
|
788
|
+
for line in self.lines:
|
|
789
|
+
line.incminperiod(minperiod)
|
|
790
|
+
|
|
791
|
+
def _makeoperation(self, other, operation, r=False, _ownerskip=None, original_other=None):
|
|
792
|
+
# For LineMultiple, we can implement a basic operation using the first line
|
|
793
|
+
# This provides a fallback when operations are needed
|
|
794
|
+
if hasattr(self, "lines") and self.lines:
|
|
795
|
+
# Use the first line for operations
|
|
796
|
+
from .linebuffer import LineActions, LinesOperation
|
|
797
|
+
|
|
798
|
+
# Only set parent to LineActions instances — never full indicators.
|
|
799
|
+
# Full indicators are processed via _lineiterators ordering in _once().
|
|
800
|
+
parent_a = self if isinstance(self, LineActions) else None
|
|
801
|
+
parent_b_candidate = original_other if original_other is not None else other
|
|
802
|
+
parent_b = parent_b_candidate if isinstance(parent_b_candidate, LineActions) else None
|
|
803
|
+
return LinesOperation(
|
|
804
|
+
self.lines[0], other, operation, r=r, parent_a=parent_a, parent_b=parent_b
|
|
805
|
+
)
|
|
806
|
+
# If no lines, return a simple operation result
|
|
807
|
+
try:
|
|
808
|
+
if r:
|
|
809
|
+
return operation(other, 0) # Use 0 as default value
|
|
810
|
+
return operation(0, other) # Use 0 as default value
|
|
811
|
+
except Exception:
|
|
812
|
+
throttled_warning(
|
|
813
|
+
logger,
|
|
814
|
+
"lineroot.line_multiple.makeoperation_recovery",
|
|
815
|
+
"LineMultiple operation fallback failed; returning safe default",
|
|
816
|
+
exc_info=False,
|
|
817
|
+
)
|
|
818
|
+
# If operation fails, return False for bool operations
|
|
819
|
+
if operation is bool:
|
|
820
|
+
return False
|
|
821
|
+
return 0
|
|
822
|
+
|
|
823
|
+
def _makeoperationown(self, operation, _ownerskip=None):
|
|
824
|
+
# CRITICAL FIX: For bool operations, return a simple boolean result instead of creating objects
|
|
825
|
+
if operation is bool:
|
|
826
|
+
# For bool operations, check if we have any lines and if they have data
|
|
827
|
+
if hasattr(self, "lines") and self.lines:
|
|
828
|
+
try:
|
|
829
|
+
# Try to get the current value from the first line
|
|
830
|
+
value = self.lines[0][0] if len(self.lines[0]) > 0 else 0
|
|
831
|
+
# Return True if value is not NaN and not 0
|
|
832
|
+
import math
|
|
833
|
+
|
|
834
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
835
|
+
return False
|
|
836
|
+
return bool(value)
|
|
837
|
+
except Exception:
|
|
838
|
+
throttled_warning(
|
|
839
|
+
logger,
|
|
840
|
+
"lineroot.line_multiple.makeoperationown_boolean_recovery",
|
|
841
|
+
"LineMultiple boolean operation fallback failed; returning False",
|
|
842
|
+
exc_info=False,
|
|
843
|
+
)
|
|
844
|
+
return False
|
|
845
|
+
else:
|
|
846
|
+
return False
|
|
847
|
+
|
|
848
|
+
# For other operations, use the original approach but only if really needed
|
|
849
|
+
if hasattr(self, "lines") and self.lines:
|
|
850
|
+
# Use the first line for self-operations
|
|
851
|
+
from .linebuffer import LineOwnOperation
|
|
852
|
+
|
|
853
|
+
return LineOwnOperation(self.lines[0], operation)
|
|
854
|
+
# If no lines, return a simple operation result
|
|
855
|
+
try:
|
|
856
|
+
return operation(0) # Use 0 as default value
|
|
857
|
+
except Exception:
|
|
858
|
+
throttled_warning(
|
|
859
|
+
logger,
|
|
860
|
+
"lineroot.line_multiple.makeoperationown_recovery",
|
|
861
|
+
"LineMultiple self-operation fallback failed; returning 0",
|
|
862
|
+
exc_info=False,
|
|
863
|
+
)
|
|
864
|
+
# If operation fails, return 0 for most operations
|
|
865
|
+
return 0
|
|
866
|
+
|
|
867
|
+
def qbuffer(self, savemem=0):
|
|
868
|
+
"""Apply queued buffering to all managed lines.
|
|
869
|
+
|
|
870
|
+
Changes the lines to implement a minimum size qbuffer scheme
|
|
871
|
+
to control memory usage during backtesting.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
savemem: Memory savings mode (0 = normal, higher values
|
|
875
|
+
reduce memory usage at the cost of performance).
|
|
876
|
+
"""
|
|
877
|
+
for line in self.lines:
|
|
878
|
+
line.qbuffer(savemem=savemem)
|
|
879
|
+
|
|
880
|
+
def minbuffer(self, size):
|
|
881
|
+
"""Set minimum buffer size for all managed lines.
|
|
882
|
+
|
|
883
|
+
Receives notification of how large the buffer must at least be
|
|
884
|
+
and propagates this requirement to all managed lines.
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
size: Minimum buffer size required.
|
|
888
|
+
"""
|
|
889
|
+
for line in self.lines:
|
|
890
|
+
line.minbuffer(size)
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
class LineSingle(LineRoot):
|
|
894
|
+
"""Base class for single-line objects.
|
|
895
|
+
|
|
896
|
+
LineSingle is the base class for objects that represent a single
|
|
897
|
+
line of time-series data. It provides the foundational interface
|
|
898
|
+
for period management and operations on individual lines.
|
|
899
|
+
|
|
900
|
+
Example:
|
|
901
|
+
>>> line = LineSingle()
|
|
902
|
+
>>> line.addminperiod(5) # Require 5 periods before valid
|
|
903
|
+
"""
|
|
904
|
+
|
|
905
|
+
def addminperiod(self, minperiod):
|
|
906
|
+
"""
|
|
907
|
+
Add the minperiod (substracting the overlapping 1 minimum period)
|
|
908
|
+
"""
|
|
909
|
+
self._minperiod += minperiod - 1
|
|
910
|
+
|
|
911
|
+
def incminperiod(self, minperiod):
|
|
912
|
+
"""
|
|
913
|
+
Increment the minperiod with no considerations
|
|
914
|
+
"""
|
|
915
|
+
self._minperiod += minperiod
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
# CRITICAL FIX: Patch Strategy._clk_update to prevent "max() iterable argument is empty" error
|
|
919
|
+
def _apply_strategy_patch():
|
|
920
|
+
"""Apply critical bug fix to Strategy class _clk_update method"""
|
|
921
|
+
try:
|
|
922
|
+
import math
|
|
923
|
+
|
|
924
|
+
# Import after a delay to ensure strategy module is loaded
|
|
925
|
+
def safe_clk_update(self):
|
|
926
|
+
"""CRITICAL FIX: Safe _clk_update method that handles empty data sources"""
|
|
927
|
+
|
|
928
|
+
# CRITICAL FIX: Handle the old sync method safely
|
|
929
|
+
if hasattr(self, "_oldsync") and self._oldsync:
|
|
930
|
+
# Call parent class _clk_update if available
|
|
931
|
+
try:
|
|
932
|
+
# Use the parent class method from StrategyBase if available
|
|
933
|
+
from .lineiterator import StrategyBase
|
|
934
|
+
|
|
935
|
+
if hasattr(StrategyBase, "_clk_update"):
|
|
936
|
+
clk_len = super(type(self), self)._clk_update()
|
|
937
|
+
else:
|
|
938
|
+
clk_len = 1
|
|
939
|
+
except Exception:
|
|
940
|
+
throttled_warning(
|
|
941
|
+
logger,
|
|
942
|
+
"lineroot.strategy_patch.oldsync_clock_recovery",
|
|
943
|
+
"Strategy compatibility clock update failed; using length 1",
|
|
944
|
+
exc_info=False,
|
|
945
|
+
)
|
|
946
|
+
clk_len = 1
|
|
947
|
+
|
|
948
|
+
# CRITICAL FIX: Only set datetime if we have valid data sources with length
|
|
949
|
+
if hasattr(self, "datas") and self.datas:
|
|
950
|
+
valid_data_times = []
|
|
951
|
+
for d in self.datas:
|
|
952
|
+
try:
|
|
953
|
+
if (
|
|
954
|
+
len(d) > 0
|
|
955
|
+
and hasattr(d, "datetime")
|
|
956
|
+
and hasattr(d.datetime, "__getitem__")
|
|
957
|
+
):
|
|
958
|
+
dt_val = d.datetime[0]
|
|
959
|
+
# Only add valid datetime values (not None or NaN)
|
|
960
|
+
if dt_val is not None and not (
|
|
961
|
+
isinstance(dt_val, float) and math.isnan(dt_val)
|
|
962
|
+
):
|
|
963
|
+
valid_data_times.append(dt_val)
|
|
964
|
+
except (IndexError, AttributeError, TypeError):
|
|
965
|
+
continue
|
|
966
|
+
|
|
967
|
+
if (
|
|
968
|
+
valid_data_times
|
|
969
|
+
and hasattr(self, "lines")
|
|
970
|
+
and hasattr(self.lines, "datetime")
|
|
971
|
+
):
|
|
972
|
+
try:
|
|
973
|
+
self.lines.datetime[0] = max(valid_data_times)
|
|
974
|
+
except (ValueError, IndexError, AttributeError):
|
|
975
|
+
# If setting datetime fails, use a default valid ordinal (1 = Jan 1, Year 1)
|
|
976
|
+
self.lines.datetime[0] = 1.0
|
|
977
|
+
elif hasattr(self, "lines") and hasattr(self.lines, "datetime"):
|
|
978
|
+
# No valid times, use default valid ordinal (1 = Jan 1, Year 1)
|
|
979
|
+
self.lines.datetime[0] = 1.0
|
|
980
|
+
|
|
981
|
+
return clk_len
|
|
982
|
+
|
|
983
|
+
# CRITICAL FIX: Handle the normal (non-oldsync) path
|
|
984
|
+
# Initialize _dlens if not present
|
|
985
|
+
if not hasattr(self, "_dlens"):
|
|
986
|
+
self._dlens = [
|
|
987
|
+
len(d) if hasattr(d, "__len__") else 0
|
|
988
|
+
for d in (self.datas if hasattr(self, "datas") else [])
|
|
989
|
+
]
|
|
990
|
+
|
|
991
|
+
# Get current data lengths safely
|
|
992
|
+
if hasattr(self, "datas") and self.datas:
|
|
993
|
+
newdlens = []
|
|
994
|
+
for d in self.datas:
|
|
995
|
+
try:
|
|
996
|
+
newdlens.append(len(d) if hasattr(d, "__len__") else 0)
|
|
997
|
+
except Exception:
|
|
998
|
+
throttled_warning(
|
|
999
|
+
logger,
|
|
1000
|
+
"lineroot.strategy_patch.data_length_recovery",
|
|
1001
|
+
"Strategy compatibility data length lookup failed; using length 0",
|
|
1002
|
+
exc_info=False,
|
|
1003
|
+
)
|
|
1004
|
+
newdlens.append(0)
|
|
1005
|
+
else:
|
|
1006
|
+
newdlens = []
|
|
1007
|
+
|
|
1008
|
+
# Forward if any data source has grown
|
|
1009
|
+
if (
|
|
1010
|
+
newdlens
|
|
1011
|
+
and hasattr(self, "_dlens")
|
|
1012
|
+
and any(
|
|
1013
|
+
nl > old_len
|
|
1014
|
+
for old_len, nl in zip(self._dlens, newdlens)
|
|
1015
|
+
if old_len is not None and nl is not None
|
|
1016
|
+
)
|
|
1017
|
+
):
|
|
1018
|
+
try:
|
|
1019
|
+
if hasattr(self, "forward"):
|
|
1020
|
+
self.forward()
|
|
1021
|
+
except Exception:
|
|
1022
|
+
throttled_warning(
|
|
1023
|
+
logger,
|
|
1024
|
+
"lineroot.strategy_patch.forward_recovery",
|
|
1025
|
+
"Strategy compatibility clock forward failed; continuing update",
|
|
1026
|
+
exc_info=False,
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
# Update _dlens
|
|
1030
|
+
self._dlens = newdlens
|
|
1031
|
+
|
|
1032
|
+
# CRITICAL FIX: Set datetime safely - only use data sources that have valid data
|
|
1033
|
+
if (
|
|
1034
|
+
hasattr(self, "datas")
|
|
1035
|
+
and self.datas
|
|
1036
|
+
and hasattr(self, "lines")
|
|
1037
|
+
and hasattr(self.lines, "datetime")
|
|
1038
|
+
):
|
|
1039
|
+
valid_data_times = []
|
|
1040
|
+
for d in self.datas:
|
|
1041
|
+
try:
|
|
1042
|
+
if (
|
|
1043
|
+
len(d) > 0
|
|
1044
|
+
and hasattr(d, "datetime")
|
|
1045
|
+
and hasattr(d.datetime, "__getitem__")
|
|
1046
|
+
):
|
|
1047
|
+
dt_val = d.datetime[0]
|
|
1048
|
+
# Only add valid datetime values (not None or NaN)
|
|
1049
|
+
if dt_val is not None and not (
|
|
1050
|
+
isinstance(dt_val, float) and math.isnan(dt_val)
|
|
1051
|
+
):
|
|
1052
|
+
valid_data_times.append(dt_val)
|
|
1053
|
+
except (IndexError, AttributeError, TypeError):
|
|
1054
|
+
continue
|
|
1055
|
+
|
|
1056
|
+
# CRITICAL FIX: This is the line that was causing the "max() iterable argument is empty" error
|
|
1057
|
+
# We check if valid_data_times is not empty before calling max()
|
|
1058
|
+
if valid_data_times:
|
|
1059
|
+
try:
|
|
1060
|
+
self.lines.datetime[0] = max(valid_data_times)
|
|
1061
|
+
except (ValueError, IndexError, AttributeError):
|
|
1062
|
+
# If setting datetime fails, use a default valid ordinal (1 = Jan 1, Year 1)
|
|
1063
|
+
self.lines.datetime[0] = 1.0
|
|
1064
|
+
else:
|
|
1065
|
+
# No valid times available, use a reasonable default valid ordinal
|
|
1066
|
+
# This is the critical fix - instead of calling max() on empty list, use default
|
|
1067
|
+
self.lines.datetime[0] = 1.0
|
|
1068
|
+
|
|
1069
|
+
# Return the length of this strategy (number of processed bars)
|
|
1070
|
+
try:
|
|
1071
|
+
return len(self)
|
|
1072
|
+
except Exception:
|
|
1073
|
+
throttled_warning(
|
|
1074
|
+
logger,
|
|
1075
|
+
"lineroot.strategy_patch.length_recovery",
|
|
1076
|
+
"Strategy compatibility length lookup failed; returning 0",
|
|
1077
|
+
exc_info=False,
|
|
1078
|
+
)
|
|
1079
|
+
return 0
|
|
1080
|
+
|
|
1081
|
+
# Import Strategy and patch it
|
|
1082
|
+
try:
|
|
1083
|
+
from .strategy import Strategy
|
|
1084
|
+
|
|
1085
|
+
# Monkey patch the Strategy class
|
|
1086
|
+
Strategy._clk_update = safe_clk_update
|
|
1087
|
+
except ImportError:
|
|
1088
|
+
# Strategy not imported yet, try to patch later when it's imported
|
|
1089
|
+
throttled_warning(
|
|
1090
|
+
logger,
|
|
1091
|
+
"lineroot.strategy_patch.import_recovery",
|
|
1092
|
+
"Strategy compatibility patch deferred because Strategy is not importable",
|
|
1093
|
+
exc_info=False,
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
except Exception:
|
|
1097
|
+
throttled_warning(
|
|
1098
|
+
logger,
|
|
1099
|
+
"lineroot.strategy_patch.apply_recovery",
|
|
1100
|
+
"Strategy compatibility patch installation failed; continuing without patch",
|
|
1101
|
+
exc_info=False,
|
|
1102
|
+
)
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
# Apply the patch when this module is imported
|
|
1106
|
+
_apply_strategy_patch()
|