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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Live functionality module.
|
|
4
|
+
|
|
5
|
+
Provides real-time data processing and client management functionality.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .client import LiveClient
|
|
9
|
+
from .datahandler import LiveDataHandler
|
|
10
|
+
|
|
11
|
+
__all__ = ["LiveClient", "LiveDataHandler"]
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Live client.
|
|
4
|
+
|
|
5
|
+
Manages Bokeh documents and user interactions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from backtrader.utils.log_message import get_logger
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from bokeh.layouts import column, layout, row
|
|
12
|
+
from bokeh.models import Button, Select, Spacer, Tabs
|
|
13
|
+
|
|
14
|
+
BOKEH_AVAILABLE = True
|
|
15
|
+
except ImportError:
|
|
16
|
+
BOKEH_AVAILABLE = False
|
|
17
|
+
|
|
18
|
+
from ..utils import get_datanames
|
|
19
|
+
from .datahandler import LiveDataHandler
|
|
20
|
+
|
|
21
|
+
_logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LiveClient:
|
|
25
|
+
"""Live client.
|
|
26
|
+
|
|
27
|
+
Provides real-time plotting functionality, including:
|
|
28
|
+
- Data filtering
|
|
29
|
+
- Navigation controls (pause/play/forward/backward)
|
|
30
|
+
- Data updates
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
doc: Bokeh document instance
|
|
34
|
+
model: Bokeh root model
|
|
35
|
+
lookback: Historical data retention
|
|
36
|
+
fill_gaps: Whether to fill data gaps
|
|
37
|
+
plotgroup: Plot group for filtering
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
NAV_BUTTON_WIDTH = 38
|
|
41
|
+
|
|
42
|
+
def __init__(self, doc, app, strategy, lookback):
|
|
43
|
+
"""Initialize live client.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
doc: Bokeh document instance
|
|
47
|
+
app: BacktraderBokeh application instance
|
|
48
|
+
strategy: Strategy instance
|
|
49
|
+
lookback: Historical data retention
|
|
50
|
+
"""
|
|
51
|
+
self._app = app
|
|
52
|
+
self._strategy = strategy
|
|
53
|
+
self._refresh_fnc = None
|
|
54
|
+
self._datahandler = None
|
|
55
|
+
self._figurepage = None
|
|
56
|
+
self._paused = False
|
|
57
|
+
self._filter = ""
|
|
58
|
+
|
|
59
|
+
# plotgroup for filter
|
|
60
|
+
self.plotgroup = ""
|
|
61
|
+
# amount of candles to plot
|
|
62
|
+
self.lookback = lookback
|
|
63
|
+
# should gaps in data be filled
|
|
64
|
+
self.fill_gaps = False
|
|
65
|
+
# bokeh document for client
|
|
66
|
+
self.doc = doc
|
|
67
|
+
# model is the root model for bokeh and will be set in baseapp
|
|
68
|
+
self.model = None
|
|
69
|
+
|
|
70
|
+
# append config tab if default tabs should be added
|
|
71
|
+
if hasattr(self._app, "p") and getattr(self._app.p, "use_default_tabs", True):
|
|
72
|
+
from ..tabs import LiveTab
|
|
73
|
+
|
|
74
|
+
if LiveTab not in self._app.tabs:
|
|
75
|
+
self._app.tabs.append(LiveTab)
|
|
76
|
+
|
|
77
|
+
# set plotgroup from app params if provided
|
|
78
|
+
if hasattr(self._app, "p") and hasattr(self._app.p, "filter"):
|
|
79
|
+
if self._app.p.filter and self._app.p.filter.get("group"):
|
|
80
|
+
self.plotgroup = self._app.p.filter["group"]
|
|
81
|
+
|
|
82
|
+
# create figurepage
|
|
83
|
+
self._figid, self._figurepage = self._app.create_figurepage(self._strategy, filldata=False)
|
|
84
|
+
|
|
85
|
+
# create model
|
|
86
|
+
self.model, self._refresh_fnc = self._createmodel()
|
|
87
|
+
|
|
88
|
+
# update model with current figurepage
|
|
89
|
+
self.updatemodel()
|
|
90
|
+
|
|
91
|
+
def _createmodel(self):
|
|
92
|
+
"""Create Bokeh model.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
tuple: (model, refresh_function)
|
|
96
|
+
"""
|
|
97
|
+
if not BOKEH_AVAILABLE:
|
|
98
|
+
return None, None
|
|
99
|
+
|
|
100
|
+
client = self
|
|
101
|
+
|
|
102
|
+
def on_select_filter(a, old, new):
|
|
103
|
+
_logger.debug(f"Switching filter to {new}...")
|
|
104
|
+
# ensure datahandler is stopped
|
|
105
|
+
if client._datahandler is not None:
|
|
106
|
+
client._datahandler.stop()
|
|
107
|
+
client._filter = new
|
|
108
|
+
client.updatemodel()
|
|
109
|
+
_logger.debug("Switching filter finished")
|
|
110
|
+
|
|
111
|
+
def on_click_nav_action():
|
|
112
|
+
if not client._paused:
|
|
113
|
+
client._pause()
|
|
114
|
+
else:
|
|
115
|
+
client._resume()
|
|
116
|
+
refresh()
|
|
117
|
+
|
|
118
|
+
def on_click_nav_prev(steps=1):
|
|
119
|
+
client._pause()
|
|
120
|
+
client._set_data_by_idx(client._datahandler.get_last_idx() - steps)
|
|
121
|
+
update_nav_buttons()
|
|
122
|
+
|
|
123
|
+
def on_click_nav_next(steps=1):
|
|
124
|
+
client._pause()
|
|
125
|
+
client._set_data_by_idx(client._datahandler.get_last_idx() + steps)
|
|
126
|
+
update_nav_buttons()
|
|
127
|
+
|
|
128
|
+
def refresh():
|
|
129
|
+
client.doc.add_next_tick_callback(update_nav_buttons)
|
|
130
|
+
|
|
131
|
+
def reset_nav_buttons():
|
|
132
|
+
btn_nav_prev.disabled = True
|
|
133
|
+
btn_nav_next.disabled = True
|
|
134
|
+
btn_nav_action.label = "❙❙"
|
|
135
|
+
|
|
136
|
+
def update_nav_buttons():
|
|
137
|
+
if client._datahandler is None:
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
last_idx = client._datahandler.get_last_idx()
|
|
141
|
+
last_avail_idx = client._app.get_last_idx(client._figid)
|
|
142
|
+
|
|
143
|
+
if last_idx < client.lookback:
|
|
144
|
+
btn_nav_prev.disabled = True
|
|
145
|
+
btn_nav_prev_big.disabled = True
|
|
146
|
+
else:
|
|
147
|
+
btn_nav_prev.disabled = False
|
|
148
|
+
btn_nav_prev_big.disabled = False
|
|
149
|
+
|
|
150
|
+
if last_idx >= last_avail_idx:
|
|
151
|
+
btn_nav_next.disabled = True
|
|
152
|
+
btn_nav_next_big.disabled = True
|
|
153
|
+
else:
|
|
154
|
+
btn_nav_next.disabled = False
|
|
155
|
+
btn_nav_next_big.disabled = False
|
|
156
|
+
|
|
157
|
+
if client._paused:
|
|
158
|
+
btn_nav_action.label = "▶"
|
|
159
|
+
else:
|
|
160
|
+
btn_nav_action.label = "❙❙"
|
|
161
|
+
|
|
162
|
+
# filter selection
|
|
163
|
+
datanames = get_datanames(self._strategy)
|
|
164
|
+
options = [("", "Strategy")]
|
|
165
|
+
for d in datanames:
|
|
166
|
+
options.append(("D" + d, f"Data: {d}"))
|
|
167
|
+
options.append(("G", "Plot Group"))
|
|
168
|
+
|
|
169
|
+
self._filter = "D" + datanames[0] if datanames else ""
|
|
170
|
+
|
|
171
|
+
select_filter = Select(value=self._filter, options=options, width=200)
|
|
172
|
+
select_filter.on_change("value", on_select_filter)
|
|
173
|
+
|
|
174
|
+
# navigation buttons
|
|
175
|
+
btn_nav_prev = Button(label="❮", width=self.NAV_BUTTON_WIDTH)
|
|
176
|
+
btn_nav_prev.on_click(lambda: on_click_nav_prev(1))
|
|
177
|
+
|
|
178
|
+
btn_nav_prev_big = Button(label="❮❮", width=self.NAV_BUTTON_WIDTH)
|
|
179
|
+
btn_nav_prev_big.on_click(lambda: on_click_nav_prev(10))
|
|
180
|
+
|
|
181
|
+
btn_nav_action = Button(label="❙❙", width=self.NAV_BUTTON_WIDTH)
|
|
182
|
+
btn_nav_action.on_click(on_click_nav_action)
|
|
183
|
+
|
|
184
|
+
btn_nav_next = Button(label="❯", width=self.NAV_BUTTON_WIDTH)
|
|
185
|
+
btn_nav_next.on_click(lambda: on_click_nav_next(1))
|
|
186
|
+
|
|
187
|
+
btn_nav_next_big = Button(label="❯❯", width=self.NAV_BUTTON_WIDTH)
|
|
188
|
+
btn_nav_next_big.on_click(lambda: on_click_nav_next(10))
|
|
189
|
+
|
|
190
|
+
# layout
|
|
191
|
+
controls = row(children=[select_filter])
|
|
192
|
+
nav = row(
|
|
193
|
+
children=[
|
|
194
|
+
btn_nav_prev_big,
|
|
195
|
+
btn_nav_prev,
|
|
196
|
+
btn_nav_action,
|
|
197
|
+
btn_nav_next,
|
|
198
|
+
btn_nav_next_big,
|
|
199
|
+
]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
# tabs
|
|
203
|
+
tabs = Tabs(
|
|
204
|
+
tabs=[],
|
|
205
|
+
sizing_mode=(
|
|
206
|
+
self._app.scheme.plot_sizing_mode
|
|
207
|
+
if hasattr(self._app, "scheme")
|
|
208
|
+
else "stretch_width"
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
tabs.name = "tabs"
|
|
212
|
+
|
|
213
|
+
# model
|
|
214
|
+
model = layout(
|
|
215
|
+
[
|
|
216
|
+
# app settings, top area
|
|
217
|
+
[column(controls, width_policy="min"), Spacer(), column(nav, width_policy="min")],
|
|
218
|
+
Spacer(height=15),
|
|
219
|
+
# layout for tabs
|
|
220
|
+
[tabs],
|
|
221
|
+
],
|
|
222
|
+
sizing_mode="stretch_width",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
return model, refresh
|
|
226
|
+
|
|
227
|
+
def updatemodel(self):
|
|
228
|
+
"""Update model."""
|
|
229
|
+
if not BOKEH_AVAILABLE or self.doc is None:
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
self.doc.hold()
|
|
233
|
+
|
|
234
|
+
# update figurepage with filter
|
|
235
|
+
self._app.update_figurepage(filter=self._get_filter())
|
|
236
|
+
|
|
237
|
+
# generate panels
|
|
238
|
+
panels = self._app.generate_model_panels()
|
|
239
|
+
|
|
240
|
+
# add tab panels
|
|
241
|
+
for t in self._app.tabs:
|
|
242
|
+
tab = t(self._app, self._figurepage, self)
|
|
243
|
+
if tab.is_useable():
|
|
244
|
+
panels.append(tab.get_panel())
|
|
245
|
+
|
|
246
|
+
# set all tabs (filter out None)
|
|
247
|
+
tabs = self._get_tabs()
|
|
248
|
+
if tabs is not None:
|
|
249
|
+
tabs.tabs = [p for p in panels if p is not None]
|
|
250
|
+
|
|
251
|
+
# create new data handler
|
|
252
|
+
if self._datahandler is not None:
|
|
253
|
+
self._datahandler.stop()
|
|
254
|
+
|
|
255
|
+
self._datahandler = LiveDataHandler(
|
|
256
|
+
doc=self.doc,
|
|
257
|
+
app=self._app,
|
|
258
|
+
figid=self._figid,
|
|
259
|
+
lookback=self.lookback,
|
|
260
|
+
fill_gaps=self.fill_gaps,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# refresh model
|
|
264
|
+
if self._refresh_fnc is not None:
|
|
265
|
+
self._refresh_fnc()
|
|
266
|
+
|
|
267
|
+
self.doc.unhold()
|
|
268
|
+
|
|
269
|
+
def _get_filter(self):
|
|
270
|
+
"""Get current filter settings.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
dict: Filter configuration
|
|
274
|
+
"""
|
|
275
|
+
res = {}
|
|
276
|
+
if self._filter.startswith("D"):
|
|
277
|
+
res["dataname"] = self._filter[1:]
|
|
278
|
+
elif self._filter.startswith("G"):
|
|
279
|
+
res["group"] = self.plotgroup
|
|
280
|
+
return res
|
|
281
|
+
|
|
282
|
+
def _pause(self):
|
|
283
|
+
"""Pause data updates."""
|
|
284
|
+
self._paused = True
|
|
285
|
+
|
|
286
|
+
def _resume(self):
|
|
287
|
+
"""Resume data updates."""
|
|
288
|
+
if not self._paused:
|
|
289
|
+
return
|
|
290
|
+
if self._datahandler is not None:
|
|
291
|
+
self._datahandler.update()
|
|
292
|
+
self._paused = False
|
|
293
|
+
|
|
294
|
+
def _set_data_by_idx(self, idx=None):
|
|
295
|
+
"""Set data by index.
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
idx: Data index (optional)
|
|
299
|
+
"""
|
|
300
|
+
if idx is not None:
|
|
301
|
+
# Ensure index is within valid range
|
|
302
|
+
last_avail_idx = self._app.get_last_idx(self._figid)
|
|
303
|
+
idx = min(idx, last_avail_idx)
|
|
304
|
+
idx = max(idx, self.lookback - 1)
|
|
305
|
+
|
|
306
|
+
# Generate data
|
|
307
|
+
df = self._app.generate_data(
|
|
308
|
+
figid=self._figid,
|
|
309
|
+
end=idx,
|
|
310
|
+
back=self.lookback,
|
|
311
|
+
preserveidx=True,
|
|
312
|
+
fill_gaps=self.fill_gaps,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
if self._datahandler is not None:
|
|
316
|
+
self._datahandler.set(df)
|
|
317
|
+
|
|
318
|
+
def _get_tabs(self):
|
|
319
|
+
"""Get Tabs component.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Tabs instance or None
|
|
323
|
+
"""
|
|
324
|
+
if self.model is None:
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
# Find component named 'tabs'
|
|
328
|
+
for child in self.model.children:
|
|
329
|
+
if hasattr(child, "__iter__"):
|
|
330
|
+
for item in child:
|
|
331
|
+
if hasattr(item, "name") and item.name == "tabs":
|
|
332
|
+
return item
|
|
333
|
+
if isinstance(item, Tabs):
|
|
334
|
+
return item
|
|
335
|
+
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
def next(self):
|
|
339
|
+
"""Receive new data and update.
|
|
340
|
+
|
|
341
|
+
Called by LivePlotAnalyzer.next()
|
|
342
|
+
"""
|
|
343
|
+
if not self._paused and self._datahandler is not None:
|
|
344
|
+
self._datahandler.update()
|
|
345
|
+
|
|
346
|
+
if self._refresh_fnc is not None:
|
|
347
|
+
self._refresh_fnc()
|
|
348
|
+
|
|
349
|
+
def stop(self):
|
|
350
|
+
"""Stop client."""
|
|
351
|
+
if self._datahandler is not None:
|
|
352
|
+
self._datahandler.stop()
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Live data handler.
|
|
4
|
+
|
|
5
|
+
Handles real-time data updates and pushes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import importlib.util
|
|
9
|
+
import time
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from threading import Lock, Thread
|
|
12
|
+
|
|
13
|
+
from backtrader.utils.log_message import get_logger
|
|
14
|
+
|
|
15
|
+
TORNADO_AVAILABLE = importlib.util.find_spec("tornado") is not None
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import pandas as pd
|
|
19
|
+
|
|
20
|
+
PANDAS_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
PANDAS_AVAILABLE = False
|
|
23
|
+
|
|
24
|
+
_logger = get_logger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UpdateType(Enum):
|
|
28
|
+
"""Data update type."""
|
|
29
|
+
|
|
30
|
+
ADD = 1 # Add new data
|
|
31
|
+
UPDATE = 2 # Update existing data
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LiveDataHandler:
|
|
35
|
+
"""Live data handler.
|
|
36
|
+
|
|
37
|
+
Responsible for receiving, storing and pushing real-time data.
|
|
38
|
+
|
|
39
|
+
Attributes:
|
|
40
|
+
_doc: Bokeh document
|
|
41
|
+
_app: BacktraderBokeh application
|
|
42
|
+
_figid: Figure page ID
|
|
43
|
+
_lookback: Historical data retention
|
|
44
|
+
_fill_gaps: Whether to fill data gaps
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, doc, app, figid, lookback, fill_gaps=True, timeout=1):
|
|
48
|
+
"""Initialize data handler.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
doc: Bokeh document
|
|
52
|
+
app: BacktraderBokeh application
|
|
53
|
+
figid: Figure page ID
|
|
54
|
+
lookback: Historical data retention
|
|
55
|
+
fill_gaps: Whether to fill data gaps
|
|
56
|
+
timeout: Thread timeout
|
|
57
|
+
"""
|
|
58
|
+
self._doc = doc
|
|
59
|
+
self._app = app
|
|
60
|
+
self._figid = figid
|
|
61
|
+
self._lookback = lookback
|
|
62
|
+
self._fill_gaps = fill_gaps
|
|
63
|
+
self._timeout = timeout
|
|
64
|
+
|
|
65
|
+
# Get figurepage
|
|
66
|
+
self._figurepage = app.get_figurepage(figid)
|
|
67
|
+
|
|
68
|
+
# Thread related
|
|
69
|
+
self._thread = Thread(target=self._t_thread, daemon=True)
|
|
70
|
+
self._lock = Lock()
|
|
71
|
+
self._running = True
|
|
72
|
+
self._new_data = False
|
|
73
|
+
|
|
74
|
+
# Data storage
|
|
75
|
+
self._datastore = None
|
|
76
|
+
self._last_idx = -1
|
|
77
|
+
self._patches = []
|
|
78
|
+
|
|
79
|
+
# Callbacks
|
|
80
|
+
self._cb_patch = None
|
|
81
|
+
self._cb_add = None
|
|
82
|
+
|
|
83
|
+
# Initial data fill
|
|
84
|
+
self._fill()
|
|
85
|
+
|
|
86
|
+
# Start thread
|
|
87
|
+
self._thread.start()
|
|
88
|
+
|
|
89
|
+
def _fill(self):
|
|
90
|
+
"""Fill initial data."""
|
|
91
|
+
if not PANDAS_AVAILABLE:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
df = self._app.generate_data(
|
|
95
|
+
figid=self._figid, back=self._lookback, preserveidx=True, fill_gaps=self._fill_gaps
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
self._set_data(df)
|
|
99
|
+
|
|
100
|
+
# Initialize CDS columns
|
|
101
|
+
if self._figurepage is not None and hasattr(self._figurepage, "set_cds_columns_from_df"):
|
|
102
|
+
self._figurepage.set_cds_columns_from_df(self._datastore)
|
|
103
|
+
|
|
104
|
+
def _set_data(self, data, idx=None):
|
|
105
|
+
"""Set or append data.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
data: DataFrame or Series
|
|
109
|
+
idx: Index (for updating specific row)
|
|
110
|
+
"""
|
|
111
|
+
if not PANDAS_AVAILABLE:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
with self._lock:
|
|
115
|
+
if isinstance(data, pd.DataFrame):
|
|
116
|
+
self._datastore = data
|
|
117
|
+
self._last_idx = -1
|
|
118
|
+
elif isinstance(data, pd.Series):
|
|
119
|
+
if idx is None:
|
|
120
|
+
self._datastore = pd.concat([self._datastore, data.to_frame().T])
|
|
121
|
+
else:
|
|
122
|
+
self._datastore.loc[idx] = data
|
|
123
|
+
else:
|
|
124
|
+
_logger.warning(f"Unsupported data type: {type(data)}")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
# Keep data length within lookback range
|
|
128
|
+
if self._datastore is not None:
|
|
129
|
+
self._datastore = self._datastore.tail(self._get_data_stream_length())
|
|
130
|
+
|
|
131
|
+
def _cb_push_adds(self):
|
|
132
|
+
"""Push new data to ColumnDataSources."""
|
|
133
|
+
if self._datastore is None or "index" not in self._datastore.columns:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
# Get data not yet pushed
|
|
137
|
+
update_df = self._datastore[self._datastore["index"] > self._last_idx]
|
|
138
|
+
|
|
139
|
+
if update_df.shape[0] == 0:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# Update last pushed index
|
|
143
|
+
self._last_idx = update_df["index"].iloc[-1]
|
|
144
|
+
|
|
145
|
+
fp = self._figurepage
|
|
146
|
+
if fp is None:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
# Push to figurepage
|
|
150
|
+
if hasattr(fp, "get_cds_streamdata_from_df") and hasattr(fp, "cds"):
|
|
151
|
+
data = fp.get_cds_streamdata_from_df(update_df)
|
|
152
|
+
if data:
|
|
153
|
+
_logger.debug(f"Streaming data to figurepage: {len(data)} columns")
|
|
154
|
+
fp.cds.stream(data, self._get_data_stream_length())
|
|
155
|
+
|
|
156
|
+
# Push to each figure
|
|
157
|
+
if hasattr(fp, "figures"):
|
|
158
|
+
for f in fp.figures:
|
|
159
|
+
if hasattr(f, "get_cds_streamdata_from_df") and hasattr(f, "cds"):
|
|
160
|
+
data = f.get_cds_streamdata_from_df(update_df)
|
|
161
|
+
if data:
|
|
162
|
+
f.cds.stream(data, self._get_data_stream_length())
|
|
163
|
+
|
|
164
|
+
def _cb_push_patches(self):
|
|
165
|
+
"""Push patch data to ColumnDataSources."""
|
|
166
|
+
patches = []
|
|
167
|
+
while len(self._patches) > 0:
|
|
168
|
+
patches.append(self._patches.pop(0))
|
|
169
|
+
|
|
170
|
+
if len(patches) == 0:
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
fp = self._figurepage
|
|
174
|
+
if fp is None:
|
|
175
|
+
return
|
|
176
|
+
|
|
177
|
+
for patch in patches:
|
|
178
|
+
# Patch figurepage
|
|
179
|
+
if hasattr(fp, "get_cds_patchdata_from_series") and hasattr(fp, "cds"):
|
|
180
|
+
p_data, s_data = fp.get_cds_patchdata_from_series(patch)
|
|
181
|
+
if len(p_data) > 0:
|
|
182
|
+
_logger.debug(f"Patching figurepage: {len(p_data)} fields")
|
|
183
|
+
fp.cds.patch(p_data)
|
|
184
|
+
if len(s_data) > 0:
|
|
185
|
+
fp.cds.stream(s_data, self._get_data_stream_length())
|
|
186
|
+
|
|
187
|
+
# Patch all figures
|
|
188
|
+
if hasattr(fp, "figures"):
|
|
189
|
+
for f in fp.figures:
|
|
190
|
+
if not hasattr(f, "get_cds_patchdata_from_series") or not hasattr(f, "cds"):
|
|
191
|
+
continue
|
|
192
|
+
|
|
193
|
+
# Determine whether to fill NaN
|
|
194
|
+
c_fill_nan = []
|
|
195
|
+
if not self._fill_gaps and hasattr(f, "fill_nan"):
|
|
196
|
+
c_fill_nan = f.fill_nan()
|
|
197
|
+
|
|
198
|
+
p_data, s_data = f.get_cds_patchdata_from_series(patch, c_fill_nan)
|
|
199
|
+
if len(p_data) > 0:
|
|
200
|
+
f.cds.patch(p_data)
|
|
201
|
+
if len(s_data) > 0:
|
|
202
|
+
f.cds.stream(s_data, self._get_data_stream_length())
|
|
203
|
+
|
|
204
|
+
def _push_adds(self):
|
|
205
|
+
"""Trigger new data push."""
|
|
206
|
+
if self._doc is None:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
if self._cb_add is not None:
|
|
211
|
+
self._doc.remove_next_tick_callback(self._cb_add)
|
|
212
|
+
except ValueError:
|
|
213
|
+
# Callback was already consumed/removed by Bokeh; nothing to undo.
|
|
214
|
+
_logger.debug("datahandler:214 ignored ValueError")
|
|
215
|
+
|
|
216
|
+
self._cb_add = self._doc.add_next_tick_callback(self._cb_push_adds)
|
|
217
|
+
|
|
218
|
+
def _push_patches(self):
|
|
219
|
+
"""Trigger patch data push."""
|
|
220
|
+
if self._doc is None:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
if self._cb_patch is not None:
|
|
225
|
+
self._doc.remove_next_tick_callback(self._cb_patch)
|
|
226
|
+
except ValueError:
|
|
227
|
+
# Callback was already consumed/removed by Bokeh; nothing to undo.
|
|
228
|
+
_logger.debug("datahandler:228 ignored ValueError")
|
|
229
|
+
|
|
230
|
+
self._cb_patch = self._doc.add_next_tick_callback(self._cb_push_patches)
|
|
231
|
+
|
|
232
|
+
def _process(self, rows):
|
|
233
|
+
"""Process new data rows.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
rows: DataFrame containing new data
|
|
237
|
+
"""
|
|
238
|
+
if not PANDAS_AVAILABLE or rows is None:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
for idx, row in rows.iterrows():
|
|
242
|
+
if (
|
|
243
|
+
self._datastore is not None
|
|
244
|
+
and self._datastore.shape[0] > 0
|
|
245
|
+
and "index" in self._datastore.columns
|
|
246
|
+
and idx in self._datastore["index"].values
|
|
247
|
+
):
|
|
248
|
+
update_type = UpdateType.UPDATE
|
|
249
|
+
else:
|
|
250
|
+
update_type = UpdateType.ADD
|
|
251
|
+
|
|
252
|
+
if update_type == UpdateType.UPDATE:
|
|
253
|
+
ds_idx = self._datastore.loc[self._datastore["index"] == idx].index[0]
|
|
254
|
+
self._set_data(row, ds_idx)
|
|
255
|
+
self._patches.append(row)
|
|
256
|
+
self._push_patches()
|
|
257
|
+
else:
|
|
258
|
+
self._set_data(row)
|
|
259
|
+
self._push_adds()
|
|
260
|
+
|
|
261
|
+
def _t_thread(self):
|
|
262
|
+
"""Data processing thread."""
|
|
263
|
+
while self._running:
|
|
264
|
+
if self._new_data:
|
|
265
|
+
last_idx = self.get_last_idx()
|
|
266
|
+
last_avail_idx = self._app.get_last_idx(self._figid)
|
|
267
|
+
|
|
268
|
+
if last_avail_idx - last_idx > (2 * self._lookback):
|
|
269
|
+
# If new data exceeds lookback length, load from end
|
|
270
|
+
data = self._app.generate_data(
|
|
271
|
+
figid=self._figid,
|
|
272
|
+
back=self._lookback,
|
|
273
|
+
preserveidx=True,
|
|
274
|
+
fill_gaps=self._fill_gaps,
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
# Otherwise load from last index
|
|
278
|
+
data = self._app.generate_data(
|
|
279
|
+
figid=self._figid,
|
|
280
|
+
start=last_idx,
|
|
281
|
+
preserveidx=True,
|
|
282
|
+
fill_gaps=self._fill_gaps,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
self._new_data = False
|
|
286
|
+
self._process(data)
|
|
287
|
+
|
|
288
|
+
time.sleep(self._timeout)
|
|
289
|
+
|
|
290
|
+
def _get_data_stream_length(self):
|
|
291
|
+
"""Get data stream length.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
int: Data stream length
|
|
295
|
+
"""
|
|
296
|
+
if self._datastore is None:
|
|
297
|
+
return self._lookback
|
|
298
|
+
return min(self._lookback, self._datastore.shape[0])
|
|
299
|
+
|
|
300
|
+
def get_last_idx(self):
|
|
301
|
+
"""Get last data index.
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
int: Last index, returns -1 if no data
|
|
305
|
+
"""
|
|
306
|
+
if self._datastore is not None and self._datastore.shape[0] > 0:
|
|
307
|
+
if "index" in self._datastore.columns:
|
|
308
|
+
return self._datastore["index"].iloc[-1]
|
|
309
|
+
return -1
|
|
310
|
+
|
|
311
|
+
def set(self, df):
|
|
312
|
+
"""Set new DataFrame and push.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
df: New DataFrame
|
|
316
|
+
"""
|
|
317
|
+
self._set_data(df)
|
|
318
|
+
self._push_adds()
|
|
319
|
+
|
|
320
|
+
def update(self):
|
|
321
|
+
"""Notify that new data is available."""
|
|
322
|
+
if self._running:
|
|
323
|
+
self._new_data = True
|
|
324
|
+
|
|
325
|
+
def stop(self):
|
|
326
|
+
"""Stop data handler."""
|
|
327
|
+
self._running = False
|
|
328
|
+
|
|
329
|
+
# Remove pending callbacks
|
|
330
|
+
try:
|
|
331
|
+
if self._cb_patch is not None:
|
|
332
|
+
self._doc.remove_next_tick_callback(self._cb_patch)
|
|
333
|
+
except (ValueError, AttributeError):
|
|
334
|
+
# Callback already gone or doc detached during shutdown; ignore.
|
|
335
|
+
_logger.debug("datahandler:335 ignored ValueError,AttributeError")
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
if self._cb_add is not None:
|
|
339
|
+
self._doc.remove_next_tick_callback(self._cb_add)
|
|
340
|
+
except (ValueError, AttributeError):
|
|
341
|
+
# Callback already gone or doc detached during shutdown; ignore.
|
|
342
|
+
_logger.debug("datahandler:342 ignored ValueError,AttributeError")
|
|
343
|
+
|
|
344
|
+
# Wait for thread to finish
|
|
345
|
+
if self._thread.is_alive():
|
|
346
|
+
self._thread.join(timeout=0.5)
|