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,294 @@
|
|
|
1
|
+
"""Live data validator for real-time event streams.
|
|
2
|
+
|
|
3
|
+
Validates incoming events for data quality issues such as out-of-order
|
|
4
|
+
timestamps, time jumps, invalid prices/volumes, and stale data detection.
|
|
5
|
+
|
|
6
|
+
Example::
|
|
7
|
+
|
|
8
|
+
validator = LiveDataValidator()
|
|
9
|
+
if validator.validate(event):
|
|
10
|
+
process(event)
|
|
11
|
+
else:
|
|
12
|
+
log_rejected(event)
|
|
13
|
+
|
|
14
|
+
print(validator.get_anomaly_report())
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import math
|
|
18
|
+
import time
|
|
19
|
+
|
|
20
|
+
from ..utils.log_message import get_logger
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
__all__ = ["LiveDataValidator"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LiveDataValidator:
|
|
28
|
+
"""Real-time data quality validator.
|
|
29
|
+
|
|
30
|
+
Checks each incoming event for:
|
|
31
|
+
- Out-of-order timestamps
|
|
32
|
+
- Large time jumps (>1 hour by default)
|
|
33
|
+
- Invalid prices (<=0) or volumes (<0) for tick data
|
|
34
|
+
- Invalid order book structure
|
|
35
|
+
- Stale data detection
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
max_time_jump: Maximum allowed timestamp gap in seconds (default: 3600).
|
|
39
|
+
max_clock_drift: Maximum allowed drift from wall clock in seconds (default: 60).
|
|
40
|
+
enable_clock_check: Whether to check against wall clock time (default: False).
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, max_time_jump=3600, max_clock_drift=60.0, enable_clock_check=False):
|
|
44
|
+
"""Initialize the live data validator.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
max_time_jump: Maximum allowed time jump between events (seconds).
|
|
48
|
+
max_clock_drift: Maximum allowed clock drift across symbols (seconds).
|
|
49
|
+
enable_clock_check: Whether to enable clock synchronization checks.
|
|
50
|
+
"""
|
|
51
|
+
max_time_jump = self._coerce_number(max_time_jump)
|
|
52
|
+
max_clock_drift = self._coerce_number(max_clock_drift)
|
|
53
|
+
if max_time_jump is None or max_time_jump < 0:
|
|
54
|
+
raise ValueError("max_time_jump must be a non-negative number")
|
|
55
|
+
if max_clock_drift is None or max_clock_drift < 0:
|
|
56
|
+
raise ValueError("max_clock_drift must be a non-negative number")
|
|
57
|
+
self._max_time_jump = max_time_jump
|
|
58
|
+
self._max_clock_drift = max_clock_drift
|
|
59
|
+
self._enable_clock_check = enable_clock_check
|
|
60
|
+
self._last_timestamps = {}
|
|
61
|
+
self._anomaly_count = {}
|
|
62
|
+
self._total_validated = 0
|
|
63
|
+
self._total_rejected = 0
|
|
64
|
+
|
|
65
|
+
def validate(self, event):
|
|
66
|
+
"""Validate an incoming event.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
event: Event wrapper with timestamp, channel_type, channel_name, data.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
True if the event passes validation, False if rejected.
|
|
73
|
+
"""
|
|
74
|
+
self._total_validated += 1
|
|
75
|
+
key = self._event_key(event)
|
|
76
|
+
if event is None:
|
|
77
|
+
self._record_anomaly(key, "invalid_event")
|
|
78
|
+
self._total_rejected += 1
|
|
79
|
+
return False
|
|
80
|
+
ts = self._coerce_number(getattr(event, "timestamp", None))
|
|
81
|
+
if ts is None or ts < 0:
|
|
82
|
+
self._record_anomaly(key, "invalid_timestamp")
|
|
83
|
+
self._total_rejected += 1
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
# 1. Timestamp order check
|
|
87
|
+
last_ts = self._last_timestamps.get(key, 0)
|
|
88
|
+
if last_ts > 0 and ts < last_ts:
|
|
89
|
+
self._record_anomaly(key, "out_of_order")
|
|
90
|
+
self._total_rejected += 1
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
# 2. Time jump check (warn but don't reject unless extreme)
|
|
94
|
+
if last_ts > 0 and ts - last_ts > self._max_time_jump:
|
|
95
|
+
self._record_anomaly(key, "time_jump")
|
|
96
|
+
logger.warning(
|
|
97
|
+
"Time jump detected for %s: %.1fs gap",
|
|
98
|
+
key,
|
|
99
|
+
ts - last_ts,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# 3. Wall clock drift check
|
|
103
|
+
if self._enable_clock_check:
|
|
104
|
+
now = time.time()
|
|
105
|
+
drift = abs(ts - now)
|
|
106
|
+
if drift > self._max_clock_drift:
|
|
107
|
+
self._record_anomaly(key, "clock_drift")
|
|
108
|
+
logger.warning(
|
|
109
|
+
"Clock drift for %s: %.1fs from wall clock",
|
|
110
|
+
key,
|
|
111
|
+
drift,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# 4. Data-specific validation
|
|
115
|
+
data = getattr(event, "data", None)
|
|
116
|
+
channel_type = getattr(event, "channel_type", "")
|
|
117
|
+
|
|
118
|
+
if data is not None:
|
|
119
|
+
if channel_type == "tick":
|
|
120
|
+
if not self._validate_tick(key, data):
|
|
121
|
+
self._total_rejected += 1
|
|
122
|
+
return False
|
|
123
|
+
elif channel_type == "orderbook":
|
|
124
|
+
if not self._validate_orderbook(key, data):
|
|
125
|
+
self._total_rejected += 1
|
|
126
|
+
return False
|
|
127
|
+
elif channel_type == "funding":
|
|
128
|
+
if not self._validate_funding(key, data):
|
|
129
|
+
self._total_rejected += 1
|
|
130
|
+
return False
|
|
131
|
+
|
|
132
|
+
self._last_timestamps[key] = ts
|
|
133
|
+
return True
|
|
134
|
+
|
|
135
|
+
def _validate_tick(self, key, tick):
|
|
136
|
+
"""Validate tick data for price and volume constraints.
|
|
137
|
+
|
|
138
|
+
Checks that price is positive and volume is non-negative. Rejects
|
|
139
|
+
ticks that violate these constraints and records the anomaly.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
key: Tuple of (channel_type, channel_name) for tracking.
|
|
143
|
+
tick: TickEvent instance to validate.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
True if the tick is valid, False otherwise.
|
|
147
|
+
"""
|
|
148
|
+
raw_price = getattr(tick, "price", None)
|
|
149
|
+
raw_volume = getattr(tick, "volume", None)
|
|
150
|
+
price = self._coerce_number(raw_price) if raw_price is not None else None
|
|
151
|
+
volume = self._coerce_number(raw_volume) if raw_volume is not None else None
|
|
152
|
+
|
|
153
|
+
if raw_price is not None and (price is None or price <= 0):
|
|
154
|
+
self._record_anomaly(key, "invalid_price")
|
|
155
|
+
return False
|
|
156
|
+
if raw_volume is not None and (volume is None or volume < 0):
|
|
157
|
+
self._record_anomaly(key, "invalid_volume")
|
|
158
|
+
return False
|
|
159
|
+
return True
|
|
160
|
+
|
|
161
|
+
def _validate_orderbook(self, key, ob):
|
|
162
|
+
"""Validate order book data structure and integrity.
|
|
163
|
+
|
|
164
|
+
Checks that the order book is not empty and that there are no
|
|
165
|
+
crossed books (best bid >= best ask), which would indicate
|
|
166
|
+
data corruption or invalid market state.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
key: Tuple of (channel_type, channel_name) for tracking.
|
|
170
|
+
ob: OrderBookSnapshot instance to validate.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
True if the order book is valid, False otherwise.
|
|
174
|
+
"""
|
|
175
|
+
bids = getattr(ob, "bids", None)
|
|
176
|
+
asks = getattr(ob, "asks", None)
|
|
177
|
+
|
|
178
|
+
if not bids and not asks:
|
|
179
|
+
self._record_anomaly(key, "empty_orderbook")
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
# Check crossed book (best bid >= best ask)
|
|
183
|
+
if bids and asks:
|
|
184
|
+
best_bid = self._extract_orderbook_price(bids[0])
|
|
185
|
+
best_ask = self._extract_orderbook_price(asks[0])
|
|
186
|
+
if best_bid is None or best_ask is None:
|
|
187
|
+
self._record_anomaly(key, "invalid_orderbook_price")
|
|
188
|
+
return False
|
|
189
|
+
if best_bid >= best_ask:
|
|
190
|
+
self._record_anomaly(key, "crossed_book")
|
|
191
|
+
logger.warning(
|
|
192
|
+
"Crossed order book for %s: bid=%.2f >= ask=%.2f", key, best_bid, best_ask
|
|
193
|
+
)
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
def _validate_funding(self, key, funding):
|
|
197
|
+
"""Validate funding rate data for extreme values.
|
|
198
|
+
|
|
199
|
+
Checks if the funding rate is within reasonable bounds
|
|
200
|
+
(absolute value <= 0.1). Extreme funding rates are logged
|
|
201
|
+
as warnings but not rejected.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
key: Tuple of (channel_type, channel_name) for tracking.
|
|
205
|
+
funding: FundingEvent instance to validate.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Always returns True (warnings are logged, not rejected).
|
|
209
|
+
"""
|
|
210
|
+
raw_rate = getattr(funding, "rate", None)
|
|
211
|
+
if raw_rate is None:
|
|
212
|
+
return True
|
|
213
|
+
rate = self._coerce_number(raw_rate)
|
|
214
|
+
if rate is None:
|
|
215
|
+
self._record_anomaly(key, "invalid_funding_rate")
|
|
216
|
+
return False
|
|
217
|
+
if abs(rate) > 0.1:
|
|
218
|
+
self._record_anomaly(key, "extreme_funding_rate")
|
|
219
|
+
logger.warning("Extreme funding rate for %s: %.6f", key, rate)
|
|
220
|
+
return True
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def _coerce_number(value):
|
|
224
|
+
if isinstance(value, bool):
|
|
225
|
+
return None
|
|
226
|
+
try:
|
|
227
|
+
number = float(value)
|
|
228
|
+
except (TypeError, ValueError):
|
|
229
|
+
return None
|
|
230
|
+
if not math.isfinite(number):
|
|
231
|
+
return None
|
|
232
|
+
return number
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def _event_key(event):
|
|
236
|
+
return (
|
|
237
|
+
str(getattr(event, "channel_type", "") or ""),
|
|
238
|
+
str(getattr(event, "channel_name", "") or ""),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def _extract_orderbook_price(self, level):
|
|
242
|
+
if isinstance(level, (list, tuple)):
|
|
243
|
+
if not level:
|
|
244
|
+
return None
|
|
245
|
+
return self._coerce_number(level[0])
|
|
246
|
+
return self._coerce_number(getattr(level, "price", None))
|
|
247
|
+
|
|
248
|
+
def _record_anomaly(self, key, anomaly_type):
|
|
249
|
+
"""Record an anomaly occurrence for later reporting.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
key: Tuple of (channel_type, channel_name) for tracking.
|
|
253
|
+
anomaly_type: String identifying the type of anomaly.
|
|
254
|
+
"""
|
|
255
|
+
if key not in self._anomaly_count:
|
|
256
|
+
self._anomaly_count[key] = {}
|
|
257
|
+
self._anomaly_count[key][anomaly_type] = self._anomaly_count[key].get(anomaly_type, 0) + 1
|
|
258
|
+
|
|
259
|
+
def get_anomaly_report(self):
|
|
260
|
+
"""Get the anomaly report.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
Dict mapping (channel_type, channel_name) to {anomaly_type: count}.
|
|
264
|
+
"""
|
|
265
|
+
return dict(self._anomaly_count)
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def stats(self):
|
|
269
|
+
"""Validation statistics."""
|
|
270
|
+
return {
|
|
271
|
+
"total_validated": self._total_validated,
|
|
272
|
+
"total_rejected": self._total_rejected,
|
|
273
|
+
"rejection_rate": (
|
|
274
|
+
self._total_rejected / self._total_validated if self._total_validated > 0 else 0.0
|
|
275
|
+
),
|
|
276
|
+
"anomaly_types": {str(k): v for k, v in self._anomaly_count.items()},
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
def reset(self):
|
|
280
|
+
"""Reset all state."""
|
|
281
|
+
self._last_timestamps.clear()
|
|
282
|
+
self._anomaly_count.clear()
|
|
283
|
+
self._total_validated = 0
|
|
284
|
+
self._total_rejected = 0
|
|
285
|
+
|
|
286
|
+
def __repr__(self):
|
|
287
|
+
"""Return a string representation of the validator.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
str: Representation showing validation statistics.
|
|
291
|
+
"""
|
|
292
|
+
return (
|
|
293
|
+
f"LiveDataValidator(validated={self._total_validated}, rejected={self._total_rejected})"
|
|
294
|
+
)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""OrderBook data channel for order book depth snapshots.
|
|
2
|
+
|
|
3
|
+
Provides OrderBookChannel for loading, validating, and buffering order book
|
|
4
|
+
data from CSV/JSONL files or other sources.
|
|
5
|
+
|
|
6
|
+
Example:
|
|
7
|
+
Loading order book data from CSV::
|
|
8
|
+
|
|
9
|
+
channel = OrderBookChannel(
|
|
10
|
+
symbol='BTC/USDT',
|
|
11
|
+
dataname='data/btc_ob_20210101.csv',
|
|
12
|
+
depth=20
|
|
13
|
+
)
|
|
14
|
+
for event in channel.load():
|
|
15
|
+
print(event.best_bid, event.best_ask, event.spread)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import csv
|
|
19
|
+
import gzip
|
|
20
|
+
import json
|
|
21
|
+
import math
|
|
22
|
+
from typing import IO, Iterator, List, Tuple
|
|
23
|
+
|
|
24
|
+
from ..channel import DataChannel, DataValidationResult
|
|
25
|
+
from ..events import OrderBookSnapshot
|
|
26
|
+
from ..utils.log_message import get_logger
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OrderBookChannel(DataChannel):
|
|
32
|
+
"""Order book depth snapshot channel.
|
|
33
|
+
|
|
34
|
+
Loads order book data from CSV or JSONL files. Supports configurable
|
|
35
|
+
depth levels and validates bid/ask ordering and spread.
|
|
36
|
+
|
|
37
|
+
CSV format expects columns: timestamp, bids, asks
|
|
38
|
+
where bids/asks are JSON-encoded lists of [price, qty] pairs.
|
|
39
|
+
|
|
40
|
+
JSONL format expects one JSON object per line with fields:
|
|
41
|
+
timestamp, symbol, bids, asks.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
symbol: Trading pair symbol (e.g., 'BTC/USDT').
|
|
45
|
+
dataname: Path to the data file (CSV or JSONL).
|
|
46
|
+
depth: Maximum order book depth to retain.
|
|
47
|
+
maxlen: Maximum buffer size.
|
|
48
|
+
validate: Whether to validate incoming events.
|
|
49
|
+
auto_fix: Whether to auto-fix invalid data.
|
|
50
|
+
**kwargs: Additional parameters passed to DataChannel.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
channel_type = "orderbook"
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self, symbol, dataname=None, depth=20, maxlen=10000, validate=True, auto_fix=True, **kwargs
|
|
57
|
+
):
|
|
58
|
+
"""Initialize the order book channel.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
symbol: Trading pair symbol (e.g., 'BTC/USDT:USDT').
|
|
62
|
+
dataname: Optional data name for the feed.
|
|
63
|
+
depth: Order book depth (number of price levels).
|
|
64
|
+
maxlen: Maximum number of events to buffer.
|
|
65
|
+
validate: Whether to validate incoming events.
|
|
66
|
+
auto_fix: Whether to attempt auto-fixing invalid data.
|
|
67
|
+
**kwargs: Additional arguments passed to parent.
|
|
68
|
+
"""
|
|
69
|
+
super().__init__(
|
|
70
|
+
symbol=symbol,
|
|
71
|
+
maxlen=maxlen,
|
|
72
|
+
validate=validate,
|
|
73
|
+
auto_fix=auto_fix,
|
|
74
|
+
dataname=dataname,
|
|
75
|
+
**kwargs,
|
|
76
|
+
)
|
|
77
|
+
self._dataname = dataname
|
|
78
|
+
self._depth = depth
|
|
79
|
+
self._last_best_bid = None
|
|
80
|
+
self._last_best_ask = None
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def depth(self):
|
|
84
|
+
"""Configured maximum order book depth."""
|
|
85
|
+
return self._depth
|
|
86
|
+
|
|
87
|
+
def _validate_event(self, event) -> DataValidationResult:
|
|
88
|
+
"""Validate order book specific fields beyond base validation.
|
|
89
|
+
|
|
90
|
+
Additional checks:
|
|
91
|
+
- Spread consistency with previous snapshots
|
|
92
|
+
- Depth within configured limits
|
|
93
|
+
"""
|
|
94
|
+
result = super()._validate_event(event)
|
|
95
|
+
if not result.valid:
|
|
96
|
+
return result
|
|
97
|
+
|
|
98
|
+
# Truncate to configured depth
|
|
99
|
+
if len(event.bids) > self._depth:
|
|
100
|
+
event.bids = event.bids[: self._depth]
|
|
101
|
+
if len(event.asks) > self._depth:
|
|
102
|
+
event.asks = event.asks[: self._depth]
|
|
103
|
+
|
|
104
|
+
# Track best bid/ask for anomaly detection
|
|
105
|
+
if event.bids and event.asks:
|
|
106
|
+
self._last_best_bid = event.bids[0][0]
|
|
107
|
+
self._last_best_ask = event.asks[0][0]
|
|
108
|
+
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
def load(self) -> Iterator[OrderBookSnapshot]:
|
|
112
|
+
"""Load order book events from file.
|
|
113
|
+
|
|
114
|
+
Supports CSV and JSONL formats. File format is auto-detected
|
|
115
|
+
by extension (.jsonl or .csv/.csv.gz).
|
|
116
|
+
|
|
117
|
+
Yields:
|
|
118
|
+
OrderBookSnapshot instances.
|
|
119
|
+
"""
|
|
120
|
+
dataname = self._dataname
|
|
121
|
+
if dataname is None:
|
|
122
|
+
raise ValueError("dataname (file path) is required for loading")
|
|
123
|
+
|
|
124
|
+
if dataname.endswith((".jsonl", ".jsonl.gz")):
|
|
125
|
+
yield from self._load_jsonl()
|
|
126
|
+
else:
|
|
127
|
+
yield from self._load_csv()
|
|
128
|
+
|
|
129
|
+
def _load_csv(self) -> Iterator[OrderBookSnapshot]:
|
|
130
|
+
"""Load order book events from CSV format.
|
|
131
|
+
|
|
132
|
+
Supports both plain CSV and gzip-compressed CSV (.csv.gz).
|
|
133
|
+
Expects columns: timestamp, bids, asks (where bids/asks are
|
|
134
|
+
JSON-encoded lists of [price, qty] pairs).
|
|
135
|
+
|
|
136
|
+
Yields:
|
|
137
|
+
OrderBookSnapshot instances.
|
|
138
|
+
"""
|
|
139
|
+
dataname = self._dataname
|
|
140
|
+
if dataname is None:
|
|
141
|
+
raise ValueError("dataname (file path) is required for loading")
|
|
142
|
+
|
|
143
|
+
csv_file: IO[str]
|
|
144
|
+
if dataname.endswith(".gz"):
|
|
145
|
+
csv_file = gzip.open(dataname, mode="rt", encoding="utf-8")
|
|
146
|
+
else:
|
|
147
|
+
csv_file = open(dataname, mode="r", encoding="utf-8", newline="")
|
|
148
|
+
|
|
149
|
+
with csv_file as f:
|
|
150
|
+
reader = csv.DictReader(f)
|
|
151
|
+
|
|
152
|
+
required = {"timestamp", "bids", "asks"}
|
|
153
|
+
if reader.fieldnames:
|
|
154
|
+
missing = required - set(reader.fieldnames)
|
|
155
|
+
if missing:
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"Missing required columns: {missing}. Found: {reader.fieldnames}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
for row in reader:
|
|
161
|
+
try:
|
|
162
|
+
bids = _parse_levels(row["bids"])
|
|
163
|
+
asks = _parse_levels(row["asks"])
|
|
164
|
+
|
|
165
|
+
ob = OrderBookSnapshot(
|
|
166
|
+
timestamp=_parse_required_float(row["timestamp"]),
|
|
167
|
+
symbol=row.get("symbol", self.symbol),
|
|
168
|
+
exchange=row.get("exchange", ""),
|
|
169
|
+
asset_type=row.get("asset_type", "spot"),
|
|
170
|
+
bids=bids[: self._depth],
|
|
171
|
+
asks=asks[: self._depth],
|
|
172
|
+
)
|
|
173
|
+
yield ob
|
|
174
|
+
except (ValueError, KeyError, json.JSONDecodeError) as e:
|
|
175
|
+
logger.warning("Skipping invalid OB row: %s (error: %s)", row, e)
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
def _load_jsonl(self) -> Iterator[OrderBookSnapshot]:
|
|
179
|
+
"""Load order book events from JSONL format.
|
|
180
|
+
|
|
181
|
+
Supports both plain JSONL and gzip-compressed JSONL (.jsonl.gz).
|
|
182
|
+
Each line should contain a JSON object with timestamp, symbol,
|
|
183
|
+
bids, and asks fields.
|
|
184
|
+
|
|
185
|
+
Yields:
|
|
186
|
+
OrderBookSnapshot instances.
|
|
187
|
+
"""
|
|
188
|
+
dataname = self._dataname
|
|
189
|
+
if dataname is None:
|
|
190
|
+
raise ValueError("dataname (file path) is required for loading")
|
|
191
|
+
|
|
192
|
+
jsonl_file: IO[str] = (
|
|
193
|
+
gzip.open(dataname, mode="rt", encoding="utf-8")
|
|
194
|
+
if dataname.endswith(".gz")
|
|
195
|
+
else open(dataname, mode="r", encoding="utf-8")
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
with jsonl_file as f:
|
|
199
|
+
for line_num, line in enumerate(f, 1):
|
|
200
|
+
line = line.strip()
|
|
201
|
+
if not line:
|
|
202
|
+
continue
|
|
203
|
+
try:
|
|
204
|
+
data = json.loads(line)
|
|
205
|
+
bids = _parse_level_pairs(data.get("bids", []))
|
|
206
|
+
asks = _parse_level_pairs(data.get("asks", []))
|
|
207
|
+
|
|
208
|
+
ob = OrderBookSnapshot(
|
|
209
|
+
timestamp=_parse_required_float(data["timestamp"]),
|
|
210
|
+
symbol=data.get("symbol", self.symbol),
|
|
211
|
+
exchange=data.get("exchange", ""),
|
|
212
|
+
asset_type=data.get("asset_type", "spot"),
|
|
213
|
+
bids=bids[: self._depth],
|
|
214
|
+
asks=asks[: self._depth],
|
|
215
|
+
)
|
|
216
|
+
yield ob
|
|
217
|
+
except (ValueError, KeyError, json.JSONDecodeError) as e:
|
|
218
|
+
logger.warning("Skipping invalid OB JSONL line %d: %s", line_num, e)
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
def __repr__(self):
|
|
222
|
+
"""Return a string representation of the channel.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
str: Representation showing symbol, depth, and buffer stats.
|
|
226
|
+
"""
|
|
227
|
+
return (
|
|
228
|
+
f"OrderBookChannel(symbol={self.symbol!r}, "
|
|
229
|
+
f"depth={self._depth}, "
|
|
230
|
+
f"dataname={self._dataname!r}, "
|
|
231
|
+
f"events={self._event_count}, "
|
|
232
|
+
f"buffered={len(self._buffer)})"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _parse_levels(text: str) -> List[Tuple[float, float]]:
|
|
237
|
+
"""Parse order book levels from JSON string.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
text: JSON string like '[[50000, 1.0], [49999, 2.0]]'
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
List of (price, quantity) tuples.
|
|
244
|
+
"""
|
|
245
|
+
levels = json.loads(text)
|
|
246
|
+
return _parse_level_pairs(levels)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _parse_required_float(value) -> float:
|
|
250
|
+
number = float(value)
|
|
251
|
+
if not math.isfinite(number):
|
|
252
|
+
raise ValueError(f"Non-finite float value: {value}")
|
|
253
|
+
return number
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _parse_level_pairs(levels) -> List[Tuple[float, float]]:
|
|
257
|
+
return [(_parse_required_float(p), _parse_required_float(q)) for p, q in levels]
|