back-trader-python 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- back_trader_python-1.4.0.dist-info/METADATA +1491 -0
- back_trader_python-1.4.0.dist-info/RECORD +465 -0
- back_trader_python-1.4.0.dist-info/WHEEL +5 -0
- back_trader_python-1.4.0.dist-info/licenses/LICENSE +674 -0
- back_trader_python-1.4.0.dist-info/top_level.txt +1 -0
- backtrader/__init__.py +148 -0
- backtrader/_cerebro/__init__.py +5 -0
- backtrader/_cerebro/channel.py +382 -0
- backtrader/_cerebro/execution.py +377 -0
- backtrader/_cerebro/lifecycle.py +143 -0
- backtrader/_cerebro/notifications.py +150 -0
- backtrader/_cerebro/presentation.py +230 -0
- backtrader/_cerebro/registry.py +593 -0
- backtrader/_cerebro/runnext.py +551 -0
- backtrader/_cerebro/runonce.py +142 -0
- backtrader/analyzer.py +594 -0
- backtrader/analyzers/__init__.py +50 -0
- backtrader/analyzers/annualreturn.py +226 -0
- backtrader/analyzers/calmar.py +165 -0
- backtrader/analyzers/drawdown.py +287 -0
- backtrader/analyzers/leverage.py +112 -0
- backtrader/analyzers/logreturnsrolling.py +190 -0
- backtrader/analyzers/periodstats.py +153 -0
- backtrader/analyzers/positions.py +119 -0
- backtrader/analyzers/pyfolio.py +470 -0
- backtrader/analyzers/returns.py +192 -0
- backtrader/analyzers/sharpe.py +307 -0
- backtrader/analyzers/sharpe_ratio_stats.py +534 -0
- backtrader/analyzers/sqn.py +112 -0
- backtrader/analyzers/timereturn.py +192 -0
- backtrader/analyzers/total_value.py +75 -0
- backtrader/analyzers/tradeanalyzer.py +278 -0
- backtrader/analyzers/transactions.py +141 -0
- backtrader/analyzers/vwr.py +245 -0
- backtrader/bokeh/__init__.py +155 -0
- backtrader/bokeh/analyzers/__init__.py +13 -0
- backtrader/bokeh/analyzers/plot.py +192 -0
- backtrader/bokeh/analyzers/recorder.py +181 -0
- backtrader/bokeh/app.py +1094 -0
- backtrader/bokeh/live/__init__.py +11 -0
- backtrader/bokeh/live/client.py +352 -0
- backtrader/bokeh/live/datahandler.py +346 -0
- backtrader/bokeh/plot_adapter.py +200 -0
- backtrader/bokeh/schemes/__init__.py +14 -0
- backtrader/bokeh/schemes/blackly.py +76 -0
- backtrader/bokeh/schemes/scheme.py +150 -0
- backtrader/bokeh/schemes/tradimo.py +82 -0
- backtrader/bokeh/tab.py +125 -0
- backtrader/bokeh/tabs/__init__.py +30 -0
- backtrader/bokeh/tabs/analyzer.py +120 -0
- backtrader/bokeh/tabs/config.py +154 -0
- backtrader/bokeh/tabs/live.py +109 -0
- backtrader/bokeh/tabs/log.py +185 -0
- backtrader/bokeh/tabs/metadata.py +182 -0
- backtrader/bokeh/tabs/performance.py +359 -0
- backtrader/bokeh/tabs/source.py +70 -0
- backtrader/bokeh/utils/__init__.py +8 -0
- backtrader/bokeh/utils/helpers.py +167 -0
- backtrader/bokeh/webapp.py +164 -0
- backtrader/broker.py +478 -0
- backtrader/brokers/__init__.py +36 -0
- backtrader/brokers/bbroker.py +2576 -0
- backtrader/brokers/btapibroker.py +8227 -0
- backtrader/brokers/hft/__init__.py +89 -0
- backtrader/brokers/hft/binance_bbo.py +625 -0
- backtrader/brokers/hft/binance_bbo_compare.py +1398 -0
- backtrader/brokers/hft/examples.py +1228 -0
- backtrader/brokers/hft/exchange.py +380 -0
- backtrader/brokers/hft/latency.py +309 -0
- backtrader/brokers/hft/matching_core.py +572 -0
- backtrader/brokers/hft/queue.py +238 -0
- backtrader/brokers/hft/recorder.py +88 -0
- backtrader/brokers/hft/state.py +138 -0
- backtrader/brokers/impact_models.py +118 -0
- backtrader/brokers/mixbroker.py +895 -0
- backtrader/brokers/tickbroker.py +1991 -0
- backtrader/btrun/__init__.py +12 -0
- backtrader/btrun/btrun.py +1218 -0
- backtrader/cerebro.py +828 -0
- backtrader/channel.py +682 -0
- backtrader/channels/__init__.py +23 -0
- backtrader/channels/bridge.py +186 -0
- backtrader/channels/funding.py +248 -0
- backtrader/channels/live_queue.py +216 -0
- backtrader/channels/live_validator.py +294 -0
- backtrader/channels/orderbook.py +257 -0
- backtrader/channels/tick.py +202 -0
- backtrader/comminfo.py +665 -0
- backtrader/commissions/__init__.py +106 -0
- backtrader/commissions/ctpoption.py +993 -0
- backtrader/configs/account_config_example.yaml +8 -0
- backtrader/dataseries.py +379 -0
- backtrader/errors.py +106 -0
- backtrader/events.py +980 -0
- backtrader/feed.py +1523 -0
- backtrader/feeds/__init__.py +75 -0
- backtrader/feeds/barrier.py +2006 -0
- backtrader/feeds/blaze.py +118 -0
- backtrader/feeds/btapifeed.py +1538 -0
- backtrader/feeds/btcsv.py +203 -0
- backtrader/feeds/chainer.py +114 -0
- backtrader/feeds/cryptohftdata.py +164 -0
- backtrader/feeds/csvgeneric.py +1205 -0
- backtrader/feeds/ctpcohort.py +1051 -0
- backtrader/feeds/influxfeed.py +158 -0
- backtrader/feeds/livefeed.py +71 -0
- backtrader/feeds/mixed_channel.py +108 -0
- backtrader/feeds/mt4csv.py +42 -0
- backtrader/feeds/pandafeed.py +381 -0
- backtrader/feeds/quandl.py +256 -0
- backtrader/feeds/rollover.py +229 -0
- backtrader/feeds/sierrachart.py +30 -0
- backtrader/feeds/vchart.py +162 -0
- backtrader/feeds/vchartcsv.py +84 -0
- backtrader/feeds/vchartfile.py +153 -0
- backtrader/feeds/yahoo.py +399 -0
- backtrader/fillers.py +148 -0
- backtrader/filters/__init__.py +34 -0
- backtrader/filters/bsplitter.py +127 -0
- backtrader/filters/calendardays.py +121 -0
- backtrader/filters/datafiller.py +192 -0
- backtrader/filters/datafilter.py +74 -0
- backtrader/filters/daysteps.py +96 -0
- backtrader/filters/heikinashi.py +63 -0
- backtrader/filters/renko.py +164 -0
- backtrader/filters/session.py +289 -0
- backtrader/flt.py +80 -0
- backtrader/functions.py +960 -0
- backtrader/indicator.py +449 -0
- backtrader/indicators/__init__.py +148 -0
- backtrader/indicators/accdecoscillator.py +110 -0
- backtrader/indicators/aroon.py +300 -0
- backtrader/indicators/atr.py +315 -0
- backtrader/indicators/awesomeoscillator.py +122 -0
- backtrader/indicators/basicops.py +834 -0
- backtrader/indicators/bollinger.py +223 -0
- backtrader/indicators/cci.py +89 -0
- backtrader/indicators/channels_ext.py +83 -0
- backtrader/indicators/contrib/__init__.py +228 -0
- backtrader/indicators/contrib/absolutely_no_lag_lwma.py +28 -0
- backtrader/indicators/contrib/absolutely_no_lag_lwma_color.py +44 -0
- backtrader/indicators/contrib/accumulation_distribution_line.py +92 -0
- backtrader/indicators/contrib/adx_cross_hull_style_indicator.py +249 -0
- backtrader/indicators/contrib/adxdmi.py +34 -0
- backtrader/indicators/contrib/ai_acceleration_deceleration_oscillator.py +34 -0
- backtrader/indicators/contrib/altr_trend_signal_v22.py +85 -0
- backtrader/indicators/contrib/anchored_momentum_line.py +115 -0
- backtrader/indicators/contrib/any_range_cld_tail_indicator.py +82 -0
- backtrader/indicators/contrib/aroon_horn_sign_indicator.py +96 -0
- backtrader/indicators/contrib/aroon_oscillator_sign_alert.py +50 -0
- backtrader/indicators/contrib/arrows_curves_indicator.py +112 -0
- backtrader/indicators/contrib/as_ctrend_indicator.py +143 -0
- backtrader/indicators/contrib/asimmetric_stoch_nr_indicator.py +187 -0
- backtrader/indicators/contrib/atr_normalize_histogram.py +118 -0
- backtrader/indicators/contrib/average_change_candle.py +165 -0
- backtrader/indicators/contrib/bb_squeeze_indicator.py +60 -0
- backtrader/indicators/contrib/bezier_st_dev_indicator.py +135 -0
- backtrader/indicators/contrib/binary_wave_indicator.py +233 -0
- backtrader/indicators/contrib/blau_c_momentum_indicator.py +123 -0
- backtrader/indicators/contrib/blau_cmi_indicator.py +141 -0
- backtrader/indicators/contrib/blau_csi.py +76 -0
- backtrader/indicators/contrib/blau_ergodic.py +53 -0
- backtrader/indicators/contrib/blau_t_stoch_i.py +72 -0
- backtrader/indicators/contrib/blau_ts_stochastic.py +85 -0
- backtrader/indicators/contrib/blau_tvi.py +55 -0
- backtrader/indicators/contrib/brain_trend2_indicator.py +128 -0
- backtrader/indicators/contrib/brain_trend_signal_proxy.py +47 -0
- backtrader/indicators/contrib/brake_parb_indicator.py +85 -0
- backtrader/indicators/contrib/breakout_bars_trend_v2.py +121 -0
- backtrader/indicators/contrib/bsi_indicator.py +87 -0
- backtrader/indicators/contrib/bulls_bears_eyes.py +67 -0
- backtrader/indicators/contrib/bulls_power.py +56 -0
- backtrader/indicators/contrib/bw_wise_man1_signal.py +102 -0
- backtrader/indicators/contrib/bykov_trend_indicator.py +85 -0
- backtrader/indicators/contrib/candle_stop_color.py +46 -0
- backtrader/indicators/contrib/candles_x_smoothed_indicator.py +69 -0
- backtrader/indicators/contrib/candlesticks_bw.py +45 -0
- backtrader/indicators/contrib/caudate_x_period_candle_color.py +56 -0
- backtrader/indicators/contrib/cci_histogram_indicator.py +53 -0
- backtrader/indicators/contrib/cci_woodies_indicator.py +80 -0
- backtrader/indicators/contrib/center_of_gravity_candle_indicator.py +83 -0
- backtrader/indicators/contrib/center_of_gravity_indicator.py +70 -0
- backtrader/indicators/contrib/cg_oscillator.py +40 -0
- backtrader/indicators/contrib/close_line_cci.py +38 -0
- backtrader/indicators/contrib/close_price_fractals.py +47 -0
- backtrader/indicators/contrib/color3rd_gen_xma_indicator.py +122 -0
- backtrader/indicators/contrib/color_bb_candles_indicator.py +108 -0
- backtrader/indicators/contrib/color_coppock_indicator.py +157 -0
- backtrader/indicators/contrib/color_hma.py +71 -0
- backtrader/indicators/contrib/color_j_variation_indicator.py +53 -0
- backtrader/indicators/contrib/color_metro_de_marker_indicator.py +78 -0
- backtrader/indicators/contrib/color_metro_stochastic_indicator.py +93 -0
- backtrader/indicators/contrib/color_metro_wpr_indicator.py +85 -0
- backtrader/indicators/contrib/color_schaff_de_marker_trend_cycle.py +92 -0
- backtrader/indicators/contrib/color_schaff_trend_cycle_indicator.py +203 -0
- backtrader/indicators/contrib/color_step_xccx_indicator.py +193 -0
- backtrader/indicators/contrib/color_x2_ma.py +49 -0
- backtrader/indicators/contrib/color_x_derivative.py +63 -0
- backtrader/indicators/contrib/color_zerolag_de_marker.py +84 -0
- backtrader/indicators/contrib/corrected_average_indicator.py +127 -0
- backtrader/indicators/contrib/darvas_boxes_system.py +73 -0
- backtrader/indicators/contrib/dema_range_channel_color.py +42 -0
- backtrader/indicators/contrib/derivative_indicator.py +95 -0
- backtrader/indicators/contrib/digital_ft01_indicator.py +112 -0
- backtrader/indicators/contrib/digital_macd.py +200 -0
- backtrader/indicators/contrib/donchian_channels_system.py +45 -0
- backtrader/indicators/contrib/dots_indicator.py +93 -0
- backtrader/indicators/contrib/ef_distance_indicator.py +82 -0
- backtrader/indicators/contrib/ema_rsi_va.py +80 -0
- backtrader/indicators/contrib/envelopes_jp_alonso.py +32 -0
- backtrader/indicators/contrib/f2a_ao_indicator.py +120 -0
- backtrader/indicators/contrib/fatl_filter.py +179 -0
- backtrader/indicators/contrib/fibo_candles_indicator.py +78 -0
- backtrader/indicators/contrib/fine_tuning_ma.py +100 -0
- backtrader/indicators/contrib/fisher_org_v1.py +102 -0
- backtrader/indicators/contrib/fisher_org_v1_sign.py +118 -0
- backtrader/indicators/contrib/force_index_ema.py +96 -0
- backtrader/indicators/contrib/force_index_ema_2.py +27 -0
- backtrader/indicators/contrib/forecast_oscilator.py +145 -0
- backtrader/indicators/contrib/fractal_amambk.py +81 -0
- backtrader/indicators/contrib/frama_series.py +84 -0
- backtrader/indicators/contrib/frasm_av2_indicator.py +104 -0
- backtrader/indicators/contrib/go_indicator.py +93 -0
- backtrader/indicators/contrib/hlr_indicator.py +95 -0
- backtrader/indicators/contrib/hma.py +50 -0
- backtrader/indicators/contrib/i4_drfv2.py +34 -0
- backtrader/indicators/contrib/i4_drfv3.py +38 -0
- backtrader/indicators/contrib/i_anch_mom_indicator.py +72 -0
- backtrader/indicators/contrib/i_de_marker_sign_indicator.py +64 -0
- backtrader/indicators/contrib/i_gap_indicator.py +45 -0
- backtrader/indicators/contrib/i_stoch_komposter_indicator.py +77 -0
- backtrader/indicators/contrib/i_trend_indicator.py +125 -0
- backtrader/indicators/contrib/iamma_indicator.py +39 -0
- backtrader/indicators/contrib/indexed_moving_average.py +33 -0
- backtrader/indicators/contrib/instantaneous_trend_filter_indicator.py +51 -0
- backtrader/indicators/contrib/inverse_reaction_indicator.py +41 -0
- backtrader/indicators/contrib/irsi_sign_indicator.py +95 -0
- backtrader/indicators/contrib/iwpr_sign_indicator.py +59 -0
- backtrader/indicators/contrib/j_brain_trend1_sig_indicator.py +233 -0
- backtrader/indicators/contrib/j_tpo_proxy.py +32 -0
- backtrader/indicators/contrib/jma_slope_indicator.py +73 -0
- backtrader/indicators/contrib/kalman_filter_indicator.py +119 -0
- backtrader/indicators/contrib/kalman_filter_line.py +127 -0
- backtrader/indicators/contrib/kama_indicator.py +150 -0
- backtrader/indicators/contrib/karacatica_indicator.py +99 -0
- backtrader/indicators/contrib/kdj_indicator.py +59 -0
- backtrader/indicators/contrib/kwan_ccc_indicator.py +195 -0
- backtrader/indicators/contrib/kwan_nrp_indicator.py +113 -0
- backtrader/indicators/contrib/kwan_rdp_indicator.py +192 -0
- backtrader/indicators/contrib/laguerre_adx_indicator.py +85 -0
- backtrader/indicators/contrib/laguerre_filter_indicator.py +66 -0
- backtrader/indicators/contrib/laguerre_plus_di_proxy.py +57 -0
- backtrader/indicators/contrib/laguerre_roc_indicator.py +81 -0
- backtrader/indicators/contrib/le_man_signal_indicator.py +63 -0
- backtrader/indicators/contrib/linear_reg_slope_v2_indicator.py +136 -0
- backtrader/indicators/contrib/loco_indicator.py +88 -0
- backtrader/indicators/contrib/lrma_indicator.py +185 -0
- backtrader/indicators/contrib/lsma_angle_indicator.py +106 -0
- backtrader/indicators/contrib/ma_rounding_channel_indicator.py +149 -0
- backtrader/indicators/contrib/macd2_indicator.py +61 -0
- backtrader/indicators/contrib/macd_candle_indicator.py +80 -0
- backtrader/indicators/contrib/malr_indicator.py +77 -0
- backtrader/indicators/contrib/momentum_candle_sign_indicator.py +51 -0
- backtrader/indicators/contrib/moving_average_fn_indicator.py +139 -0
- backtrader/indicators/contrib/mt5_stochastic_close_close.py +57 -0
- backtrader/indicators/contrib/muv_nor_diff_cloud_indicator.py +107 -0
- backtrader/indicators/contrib/non_lag_dot_indicator.py +124 -0
- backtrader/indicators/contrib/nrtr_extr_indicator.py +95 -0
- backtrader/indicators/contrib/nrtr_indicator.py +95 -0
- backtrader/indicators/contrib/p_channel_system.py +40 -0
- backtrader/indicators/contrib/percent_envelope.py +37 -0
- backtrader/indicators/contrib/percentage_crossover_channel.py +47 -0
- backtrader/indicators/contrib/pivot_zig_zag_proxy.py +47 -0
- backtrader/indicators/contrib/price_channel_stop_indicator.py +104 -0
- backtrader/indicators/contrib/price_extreme_channel.py +35 -0
- backtrader/indicators/contrib/qqe_cloud_indicator.py +129 -0
- backtrader/indicators/contrib/ravi_indicator.py +40 -0
- backtrader/indicators/contrib/raw_close_close_stochastic.py +74 -0
- backtrader/indicators/contrib/rd_trend_trigger_indicator.py +51 -0
- backtrader/indicators/contrib/renko_level.py +85 -0
- backtrader/indicators/contrib/renko_line_break.py +91 -0
- backtrader/indicators/contrib/rftl_indicator.py +41 -0
- backtrader/indicators/contrib/rkd_indicator.py +53 -0
- backtrader/indicators/contrib/roc2_vg_indicator.py +68 -0
- backtrader/indicators/contrib/rsi_histogram_indicator.py +43 -0
- backtrader/indicators/contrib/rsi_slowdown.py +57 -0
- backtrader/indicators/contrib/rsioma_v2.py +41 -0
- backtrader/indicators/contrib/rvi_histogram_indicator.py +107 -0
- backtrader/indicators/contrib/safe_adx.py +89 -0
- backtrader/indicators/contrib/shared_strategy_indicators.py +1651 -0
- backtrader/indicators/contrib/sidus_indicator.py +105 -0
- backtrader/indicators/contrib/silver_trend_indicator.py +79 -0
- backtrader/indicators/contrib/sliding_range_color.py +56 -0
- backtrader/indicators/contrib/slow_stoch.py +42 -0
- backtrader/indicators/contrib/smoothed_adx_indicator.py +86 -0
- backtrader/indicators/contrib/smoothed_rsi.py +31 -0
- backtrader/indicators/contrib/spearman_rank_correlation_histogram.py +60 -0
- backtrader/indicators/contrib/stalin_indicator.py +152 -0
- backtrader/indicators/contrib/starter_laguerre_filter.py +62 -0
- backtrader/indicators/contrib/step_manrtr_indicator.py +137 -0
- backtrader/indicators/contrib/stochastic_histogram_indicator.py +143 -0
- backtrader/indicators/contrib/t3_alarm_indicator.py +125 -0
- backtrader/indicators/contrib/t3_average.py +76 -0
- backtrader/indicators/contrib/t3_indicator.py +40 -0
- backtrader/indicators/contrib/the20s_v020_signal.py +93 -0
- backtrader/indicators/contrib/three_candles_indicator.py +70 -0
- backtrader/indicators/contrib/three_line_break_indicator.py +64 -0
- backtrader/indicators/contrib/time_line.py +57 -0
- backtrader/indicators/contrib/trading_channel_index_proxy.py +48 -0
- backtrader/indicators/contrib/trend_arrows_indicator.py +109 -0
- backtrader/indicators/contrib/trend_continuation_indicator.py +127 -0
- backtrader/indicators/contrib/trend_intensity_index_proxy.py +51 -0
- backtrader/indicators/contrib/trend_manager_indicator.py +39 -0
- backtrader/indicators/contrib/tri_x_candle_indicator.py +51 -0
- backtrader/indicators/contrib/trigger_line.py +66 -0
- backtrader/indicators/contrib/triple_ema_rate.py +34 -0
- backtrader/indicators/contrib/trvi_indicator.py +194 -0
- backtrader/indicators/contrib/two_pb_ideal_xosma_indicator.py +127 -0
- backtrader/indicators/contrib/ultra_absolutely_no_lag_lwma_color.py +92 -0
- backtrader/indicators/contrib/ultra_wpr_indicator.py +173 -0
- backtrader/indicators/contrib/up_down_candle_strength.py +68 -0
- backtrader/indicators/contrib/vinin_i_trend_indicator.py +139 -0
- backtrader/indicators/contrib/volume_weighted_ma_indicator.py +78 -0
- backtrader/indicators/contrib/volume_weighted_ma_st_dev_indicator.py +111 -0
- backtrader/indicators/contrib/vwap_close_indicator.py +65 -0
- backtrader/indicators/contrib/vwma_candle.py +57 -0
- backtrader/indicators/contrib/vwma_digit_system.py +70 -0
- backtrader/indicators/contrib/wami.py +43 -0
- backtrader/indicators/contrib/wprsi_signal_indicator.py +105 -0
- backtrader/indicators/contrib/x_de_marker_histogram_vol_direct_indicator.py +145 -0
- backtrader/indicators/contrib/x_fisher_indicator.py +64 -0
- backtrader/indicators/contrib/xcci_histogram_vol_direct_indicator.py +56 -0
- backtrader/indicators/contrib/xcci_histogram_vol_indicator.py +85 -0
- backtrader/indicators/contrib/xma_ichimoku.py +163 -0
- backtrader/indicators/contrib/xma_ishimoku_channel_indicator.py +65 -0
- backtrader/indicators/contrib/xma_ishimoku_line.py +68 -0
- backtrader/indicators/contrib/xma_range_bands_indicator.py +107 -0
- backtrader/indicators/contrib/xmacd_indicator.py +70 -0
- backtrader/indicators/contrib/xrsi_de_marker_histogram.py +67 -0
- backtrader/indicators/contrib/xrsi_histogram_vol_direct_indicator.py +52 -0
- backtrader/indicators/contrib/xrsi_histogram_vol_indicator.py +81 -0
- backtrader/indicators/contrib/xrvi_indicator.py +130 -0
- backtrader/indicators/contrib/zero_lag_macd.py +36 -0
- backtrader/indicators/contrib/zig_zag_recent_pivot_signal.py +90 -0
- backtrader/indicators/contrib/zpf_indicator.py +115 -0
- backtrader/indicators/crossover.py +337 -0
- backtrader/indicators/dema.py +175 -0
- backtrader/indicators/demarker.py +270 -0
- backtrader/indicators/deviation.py +284 -0
- backtrader/indicators/directionalmove.py +1071 -0
- backtrader/indicators/dma.py +112 -0
- backtrader/indicators/dpo.py +96 -0
- backtrader/indicators/dv2.py +56 -0
- backtrader/indicators/ema.py +145 -0
- backtrader/indicators/envelope.py +475 -0
- backtrader/indicators/hadelta.py +198 -0
- backtrader/indicators/heikinashi.py +153 -0
- backtrader/indicators/hma.py +153 -0
- backtrader/indicators/hurst.py +151 -0
- backtrader/indicators/ichimoku.py +267 -0
- backtrader/indicators/kama.py +181 -0
- backtrader/indicators/kst.py +159 -0
- backtrader/indicators/lrsi.py +125 -0
- backtrader/indicators/mabase.py +147 -0
- backtrader/indicators/macd.py +322 -0
- backtrader/indicators/momentum.py +267 -0
- backtrader/indicators/moneyflow.py +237 -0
- backtrader/indicators/mt5atr.py +124 -0
- backtrader/indicators/myind.py +179 -0
- backtrader/indicators/obv.py +94 -0
- backtrader/indicators/ols.py +265 -0
- backtrader/indicators/oscillator.py +161 -0
- backtrader/indicators/percentchange.py +83 -0
- backtrader/indicators/percentrank.py +46 -0
- backtrader/indicators/pivotpoint.py +469 -0
- backtrader/indicators/prettygoodoscillator.py +113 -0
- backtrader/indicators/priceops_ext.py +123 -0
- backtrader/indicators/priceoscillator.py +262 -0
- backtrader/indicators/psar.py +212 -0
- backtrader/indicators/rmi.py +69 -0
- backtrader/indicators/rsi.py +440 -0
- backtrader/indicators/sma.py +141 -0
- backtrader/indicators/smma.py +116 -0
- backtrader/indicators/spread.py +54 -0
- backtrader/indicators/stochastic.py +263 -0
- backtrader/indicators/supertrend.py +436 -0
- backtrader/indicators/trend_ext.py +105 -0
- backtrader/indicators/trix.py +202 -0
- backtrader/indicators/tsi.py +155 -0
- backtrader/indicators/ultimateoscillator.py +158 -0
- backtrader/indicators/vortex.py +62 -0
- backtrader/indicators/williams.py +194 -0
- backtrader/indicators/wma.py +103 -0
- backtrader/indicators/zlema.py +135 -0
- backtrader/indicators/zlind.py +104 -0
- backtrader/linebuffer.py +3155 -0
- backtrader/lineiterator.py +2911 -0
- backtrader/lineroot.py +1106 -0
- backtrader/lineseries.py +2559 -0
- backtrader/live_trading/__init__.py +31 -0
- backtrader/live_trading/interface.py +404 -0
- backtrader/mathsupport.py +94 -0
- backtrader/metabase.py +1804 -0
- backtrader/mixins/__init__.py +21 -0
- backtrader/mixins/singleton.py +118 -0
- backtrader/observer.py +106 -0
- backtrader/observers/__init__.py +45 -0
- backtrader/observers/benchmark.py +126 -0
- backtrader/observers/broker.py +184 -0
- backtrader/observers/buysell.py +144 -0
- backtrader/observers/drawdown.py +161 -0
- backtrader/observers/logreturns.py +113 -0
- backtrader/observers/timereturn.py +86 -0
- backtrader/observers/trade_logger.py +2972 -0
- backtrader/observers/tradelogger.py +6 -0
- backtrader/observers/trades.py +258 -0
- backtrader/order.py +1114 -0
- backtrader/parameters.py +2345 -0
- backtrader/plot/__init__.py +54 -0
- backtrader/plot/finance.py +1022 -0
- backtrader/plot/formatters.py +200 -0
- backtrader/plot/locator.py +353 -0
- backtrader/plot/multicursor.py +495 -0
- backtrader/plot/plot.py +2500 -0
- backtrader/plot/plot_plotly.py +1351 -0
- backtrader/plot/scheme.py +253 -0
- backtrader/plot/utils.py +104 -0
- backtrader/position.py +290 -0
- backtrader/position_modes.py +132 -0
- backtrader/profiles.py +254 -0
- backtrader/reports/__init__.py +39 -0
- backtrader/reports/charts.py +371 -0
- backtrader/reports/performance.py +620 -0
- backtrader/reports/reporter.py +660 -0
- backtrader/resamplerfilter.py +1001 -0
- backtrader/signal.py +118 -0
- backtrader/signals/__init__.py +17 -0
- backtrader/sizer.py +114 -0
- backtrader/sizers/__init__.py +26 -0
- backtrader/sizers/fixedsize.py +161 -0
- backtrader/sizers/percents_sizer.py +119 -0
- backtrader/store.py +221 -0
- backtrader/stores/__init__.py +33 -0
- backtrader/stores/btapistore.py +15506 -0
- backtrader/stores/livestore.py +137 -0
- backtrader/stores/vchartfile.py +96 -0
- backtrader/strategy.py +3655 -0
- backtrader/talib.py +280 -0
- backtrader/test_helpers.py +96 -0
- backtrader/timer.py +358 -0
- backtrader/trade.py +442 -0
- backtrader/tradingcal.py +361 -0
- backtrader/utils/__init__.py +68 -0
- backtrader/utils/autodict.py +251 -0
- backtrader/utils/date.py +71 -0
- backtrader/utils/dateintern.py +509 -0
- backtrader/utils/flushfile.py +94 -0
- backtrader/utils/fractal.py +101 -0
- backtrader/utils/get_metrics.py +101 -0
- backtrader/utils/load_data.py +209 -0
- backtrader/utils/log_message.py +998 -0
- backtrader/utils/ordereddefaultdict.py +75 -0
- backtrader/utils/py3.py +296 -0
- backtrader/version.py +21 -0
- backtrader/writer.py +372 -0
backtrader/bokeh/app.py
ADDED
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Bokeh Application Core Classes
|
|
4
|
+
|
|
5
|
+
Provides integration between Backtrader and Bokeh
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections import OrderedDict
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from backtrader.parameters import make_legacy_parameter_accessor
|
|
12
|
+
from backtrader.utils.log_message import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
PANDAS_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
PANDAS_AVAILABLE = False
|
|
22
|
+
|
|
23
|
+
BOKEH_AVAILABLE = False
|
|
24
|
+
Panel: Any = None
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
from bokeh.layouts import gridplot
|
|
28
|
+
from bokeh.models import ColumnDataSource, CrosshairTool, HoverTool
|
|
29
|
+
from bokeh.plotting import figure
|
|
30
|
+
|
|
31
|
+
BOKEH_AVAILABLE = True
|
|
32
|
+
|
|
33
|
+
# Handle different Bokeh versions for Panel
|
|
34
|
+
try:
|
|
35
|
+
from bokeh.models import TabPanel as _Panel
|
|
36
|
+
except ImportError:
|
|
37
|
+
try:
|
|
38
|
+
from bokeh.models.widgets import Panel as _Panel
|
|
39
|
+
except ImportError:
|
|
40
|
+
from bokeh.models import Panel as _Panel
|
|
41
|
+
Panel = _Panel
|
|
42
|
+
except ImportError:
|
|
43
|
+
# Bokeh is an optional dependency; BOKEH_AVAILABLE stays False when missing.
|
|
44
|
+
logger.warning("app:44 suppressed ImportError")
|
|
45
|
+
|
|
46
|
+
from . import tabs as default_tabs # noqa: E402
|
|
47
|
+
from .schemes import Tradimo # noqa: E402
|
|
48
|
+
|
|
49
|
+
_logger = get_logger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FigurePage:
|
|
53
|
+
"""Figure Page
|
|
54
|
+
|
|
55
|
+
Manages a group of related charts and data sources.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, strategy=None):
|
|
59
|
+
"""Initialize a FigurePage.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
strategy: Strategy instance associated with this figure page.
|
|
63
|
+
"""
|
|
64
|
+
self.strategy = strategy
|
|
65
|
+
self.figures = []
|
|
66
|
+
self.cds = None # ColumnDataSource
|
|
67
|
+
self._data = None
|
|
68
|
+
|
|
69
|
+
def set_cds_columns_from_df(self, df):
|
|
70
|
+
"""Set CDS columns from DataFrame.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
df: pandas DataFrame
|
|
74
|
+
"""
|
|
75
|
+
if not BOKEH_AVAILABLE or df is None:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
if self.cds is None:
|
|
79
|
+
self.cds = ColumnDataSource(df)
|
|
80
|
+
else:
|
|
81
|
+
self.cds.data = df.to_dict("list")
|
|
82
|
+
|
|
83
|
+
def get_cds_streamdata_from_df(self, df):
|
|
84
|
+
"""Get stream data from DataFrame.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
df: pandas DataFrame
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
dict: Stream data dictionary
|
|
91
|
+
"""
|
|
92
|
+
if df is None or df.empty:
|
|
93
|
+
return {}
|
|
94
|
+
return df.to_dict("list")
|
|
95
|
+
|
|
96
|
+
def get_cds_patchdata_from_series(self, series):
|
|
97
|
+
"""Get patch data from Series.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
series: pandas Series
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
tuple: (patch_data, stream_data)
|
|
104
|
+
"""
|
|
105
|
+
return {}, {}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class Figure:
|
|
109
|
+
"""Single Figure
|
|
110
|
+
|
|
111
|
+
Wraps a Bokeh figure.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, scheme=None):
|
|
115
|
+
"""Initialize a Figure.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
scheme: Theme/scheme instance for styling the figure.
|
|
119
|
+
"""
|
|
120
|
+
self.scheme = scheme
|
|
121
|
+
self.figure = None
|
|
122
|
+
self.cds = None
|
|
123
|
+
|
|
124
|
+
def get_cds_streamdata_from_df(self, df):
|
|
125
|
+
"""Get stream data from DataFrame."""
|
|
126
|
+
if df is None or df.empty:
|
|
127
|
+
return {}
|
|
128
|
+
return df.to_dict("list")
|
|
129
|
+
|
|
130
|
+
def get_cds_patchdata_from_series(self, series, fill_nan=None):
|
|
131
|
+
"""Get patch data from Series."""
|
|
132
|
+
return {}, {}
|
|
133
|
+
|
|
134
|
+
def fill_nan(self):
|
|
135
|
+
"""Return columns that need NaN filling."""
|
|
136
|
+
return []
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class BacktraderBokeh:
|
|
140
|
+
"""Backtrader Bokeh Application
|
|
141
|
+
|
|
142
|
+
Provides the following features:
|
|
143
|
+
- Create and manage figure pages
|
|
144
|
+
- Generate Bokeh models
|
|
145
|
+
- Data processing and formatting
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
style: Chart style, 'bar' or 'candle'
|
|
149
|
+
scheme: Theme instance
|
|
150
|
+
use_default_tabs: Whether to use default tabs
|
|
151
|
+
filter: Data filter configuration
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
app = BacktraderBokeh(style='candle', scheme=Blackly())
|
|
155
|
+
figid, figurepage = app.create_figurepage(strategy)
|
|
156
|
+
panels = app.generate_model_panels()
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
params = (
|
|
160
|
+
("style", "bar"),
|
|
161
|
+
("scheme", None),
|
|
162
|
+
("use_default_tabs", True),
|
|
163
|
+
("filter", None),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def __init__(self, **kwargs):
|
|
167
|
+
"""Initialize BacktraderBokeh application.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
**kwargs: Keyword arguments for configuration:
|
|
171
|
+
- style: Chart style ('bar' or 'candle')
|
|
172
|
+
- scheme: Theme instance for styling
|
|
173
|
+
- use_default_tabs: Whether to use default tabs
|
|
174
|
+
- filter: Data filter configuration
|
|
175
|
+
"""
|
|
176
|
+
# Process parameters
|
|
177
|
+
self.p = make_legacy_parameter_accessor(
|
|
178
|
+
self.params,
|
|
179
|
+
values={name: kwargs[name] for name, _default in self.params if name in kwargs},
|
|
180
|
+
name=f"{self.__class__.__name__}Params",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
# Set theme
|
|
184
|
+
self.scheme = self.p.scheme
|
|
185
|
+
if self.scheme is None:
|
|
186
|
+
self.scheme = Tradimo()
|
|
187
|
+
|
|
188
|
+
# Figure page storage
|
|
189
|
+
self._figurepages = OrderedDict()
|
|
190
|
+
self._figid_counter = 0
|
|
191
|
+
|
|
192
|
+
# Current filter settings
|
|
193
|
+
self._filter = self.p.filter or {}
|
|
194
|
+
|
|
195
|
+
# Tab list
|
|
196
|
+
self.tabs = []
|
|
197
|
+
if self.p.use_default_tabs:
|
|
198
|
+
self.tabs = [
|
|
199
|
+
default_tabs.PerformanceTab,
|
|
200
|
+
default_tabs.AnalyzerTab,
|
|
201
|
+
default_tabs.MetadataTab,
|
|
202
|
+
default_tabs.ConfigTab,
|
|
203
|
+
default_tabs.LogTab,
|
|
204
|
+
default_tabs.SourceTab,
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
def create_figurepage(
|
|
208
|
+
self,
|
|
209
|
+
strategy,
|
|
210
|
+
filldata=True,
|
|
211
|
+
start=None,
|
|
212
|
+
end=None,
|
|
213
|
+
preserveidx=False,
|
|
214
|
+
fill_gaps=False,
|
|
215
|
+
):
|
|
216
|
+
"""Create a figure page.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
strategy: Strategy instance
|
|
220
|
+
filldata: Whether to fill data
|
|
221
|
+
start: Start index for data slicing
|
|
222
|
+
end: End index for data slicing
|
|
223
|
+
preserveidx: Keep original index values in source
|
|
224
|
+
fill_gaps: Unused currently, for future compatibility
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
tuple: (figid, figurepage)
|
|
228
|
+
"""
|
|
229
|
+
figid = self._figid_counter
|
|
230
|
+
self._figid_counter += 1
|
|
231
|
+
|
|
232
|
+
figurepage = FigurePage(strategy)
|
|
233
|
+
self._figurepages[figid] = figurepage
|
|
234
|
+
|
|
235
|
+
if filldata:
|
|
236
|
+
self._fill_figurepage(
|
|
237
|
+
figurepage,
|
|
238
|
+
strategy,
|
|
239
|
+
start=start,
|
|
240
|
+
end=end,
|
|
241
|
+
preserveidx=preserveidx,
|
|
242
|
+
fill_gaps=fill_gaps,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
return figid, figurepage
|
|
246
|
+
|
|
247
|
+
def _fill_figurepage(
|
|
248
|
+
self,
|
|
249
|
+
figurepage,
|
|
250
|
+
strategy,
|
|
251
|
+
start=None,
|
|
252
|
+
end=None,
|
|
253
|
+
preserveidx=False,
|
|
254
|
+
fill_gaps=False,
|
|
255
|
+
):
|
|
256
|
+
"""Fill figure page with data.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
figurepage: Figure page instance
|
|
260
|
+
strategy: Strategy instance
|
|
261
|
+
start: Start index for data slicing
|
|
262
|
+
end: End index for data slicing
|
|
263
|
+
preserveidx: Keep original index values in source
|
|
264
|
+
fill_gaps: Unused currently, for future compatibility
|
|
265
|
+
"""
|
|
266
|
+
if not BOKEH_AVAILABLE or not PANDAS_AVAILABLE:
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
if strategy is None or not hasattr(strategy, "datas") or not strategy.datas:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
# Get the first data source
|
|
273
|
+
data = strategy.datas[0]
|
|
274
|
+
|
|
275
|
+
# Create DataFrame
|
|
276
|
+
df_data = self._create_dataframe(
|
|
277
|
+
data, strategy, start=start, end=end, preserveidx=preserveidx
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if df_data is not None:
|
|
281
|
+
# Add trade signal data
|
|
282
|
+
df_data = self._add_trade_signals(df_data, strategy)
|
|
283
|
+
|
|
284
|
+
figurepage._data = df_data
|
|
285
|
+
figurepage.cds = ColumnDataSource(df_data)
|
|
286
|
+
|
|
287
|
+
# Create main figure (price + trade signals)
|
|
288
|
+
main_figure = self._create_main_figure(df_data, strategy)
|
|
289
|
+
if main_figure is not None:
|
|
290
|
+
figurepage.figures.append(main_figure)
|
|
291
|
+
|
|
292
|
+
# Create equity curve figure
|
|
293
|
+
equity_figure = self._create_equity_figure(df_data, strategy)
|
|
294
|
+
if equity_figure is not None:
|
|
295
|
+
figurepage.figures.append(equity_figure)
|
|
296
|
+
|
|
297
|
+
# Create drawdown figure
|
|
298
|
+
drawdown_figure = self._create_drawdown_figure(df_data, strategy)
|
|
299
|
+
if drawdown_figure is not None:
|
|
300
|
+
figurepage.figures.append(drawdown_figure)
|
|
301
|
+
|
|
302
|
+
def _create_dataframe(self, data, strategy, start=None, end=None, preserveidx=False):
|
|
303
|
+
"""Create DataFrame from data source.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
data: Data source
|
|
307
|
+
strategy: Strategy instance
|
|
308
|
+
start: Start index for slicing
|
|
309
|
+
end: End index for slicing
|
|
310
|
+
preserveidx: Keep original index values in source
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
pandas.DataFrame
|
|
314
|
+
"""
|
|
315
|
+
if not PANDAS_AVAILABLE:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
length = len(data)
|
|
319
|
+
if length == 0:
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
df_dict = {
|
|
323
|
+
"index": list(range(length)),
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
# Add datetime
|
|
327
|
+
try:
|
|
328
|
+
df_dict["datetime"] = [data.datetime.datetime(-length + i + 1) for i in range(length)]
|
|
329
|
+
except Exception:
|
|
330
|
+
logger.warning("app:329 fallback on Exception")
|
|
331
|
+
df_dict["datetime"] = list(range(length))
|
|
332
|
+
|
|
333
|
+
# Add OHLCV data
|
|
334
|
+
for name in ["open", "high", "low", "close", "volume"]:
|
|
335
|
+
if hasattr(data, name):
|
|
336
|
+
line = getattr(data, name)
|
|
337
|
+
try:
|
|
338
|
+
df_dict[name] = [line[-length + i + 1] for i in range(length)]
|
|
339
|
+
except Exception:
|
|
340
|
+
logger.warning("app:338 fallback on Exception")
|
|
341
|
+
df_dict[name] = [0] * length
|
|
342
|
+
|
|
343
|
+
df = pd.DataFrame(df_dict)
|
|
344
|
+
return self._slice_dataframe(df, start=start, end=end, preserveidx=preserveidx)
|
|
345
|
+
|
|
346
|
+
def _slice_dataframe(self, df, start=None, end=None, preserveidx=False):
|
|
347
|
+
"""Slice dataframe by index.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
df: Source dataframe
|
|
351
|
+
start: Start index (inclusive)
|
|
352
|
+
end: End index (exclusive)
|
|
353
|
+
preserveidx: Keep original index instead of sequential index column
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
pandas.DataFrame
|
|
357
|
+
"""
|
|
358
|
+
if df.empty:
|
|
359
|
+
return df
|
|
360
|
+
|
|
361
|
+
length = len(df)
|
|
362
|
+
if start is None:
|
|
363
|
+
start = 0
|
|
364
|
+
if end is None:
|
|
365
|
+
end = length
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
start = int(start)
|
|
369
|
+
except (TypeError, ValueError):
|
|
370
|
+
start = 0
|
|
371
|
+
try:
|
|
372
|
+
end = int(end)
|
|
373
|
+
except (TypeError, ValueError):
|
|
374
|
+
end = length
|
|
375
|
+
|
|
376
|
+
if start < 0:
|
|
377
|
+
start = length + start
|
|
378
|
+
if end < 0:
|
|
379
|
+
end = length + 1 + end
|
|
380
|
+
|
|
381
|
+
if start < 0:
|
|
382
|
+
start = 0
|
|
383
|
+
if end < 0:
|
|
384
|
+
end = 0
|
|
385
|
+
if start > length:
|
|
386
|
+
start = length
|
|
387
|
+
if end > length:
|
|
388
|
+
end = length
|
|
389
|
+
|
|
390
|
+
df = df.iloc[start:end].copy()
|
|
391
|
+
|
|
392
|
+
if not preserveidx:
|
|
393
|
+
df = df.reset_index(drop=True)
|
|
394
|
+
df["index"] = list(range(len(df)))
|
|
395
|
+
|
|
396
|
+
return df
|
|
397
|
+
|
|
398
|
+
def _add_trade_signals(self, df, strategy):
|
|
399
|
+
"""Add trade signal data to DataFrame.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
df: DataFrame
|
|
403
|
+
strategy: Strategy instance
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
DataFrame: DataFrame with trade signals added
|
|
407
|
+
"""
|
|
408
|
+
# Initialize trade signal columns
|
|
409
|
+
df = df.copy()
|
|
410
|
+
df["buy_signal"] = None
|
|
411
|
+
df["sell_signal"] = None
|
|
412
|
+
df["buy_price"] = None
|
|
413
|
+
df["sell_price"] = None
|
|
414
|
+
|
|
415
|
+
# Extract trade records from strategy
|
|
416
|
+
if not hasattr(strategy, "_trades") and not hasattr(strategy, "trades"):
|
|
417
|
+
# Try to get trade info from analyzer
|
|
418
|
+
trade_analyzer = None
|
|
419
|
+
for analyzer in getattr(strategy, "analyzers", []):
|
|
420
|
+
if analyzer.__class__.__name__ == "TradeAnalyzer":
|
|
421
|
+
trade_analyzer = analyzer
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
if trade_analyzer is None:
|
|
425
|
+
return df
|
|
426
|
+
|
|
427
|
+
# Extract trade signals from order history
|
|
428
|
+
try:
|
|
429
|
+
orders = getattr(strategy, "_orders", []) or getattr(strategy, "orders", [])
|
|
430
|
+
for order in orders:
|
|
431
|
+
if order.status == order.Completed:
|
|
432
|
+
exec_dt = order.executed.dt
|
|
433
|
+
exec_price = order.executed.price
|
|
434
|
+
|
|
435
|
+
# Convert backtrader date to datetime
|
|
436
|
+
from ..utils.date import num2date
|
|
437
|
+
|
|
438
|
+
try:
|
|
439
|
+
dt = num2date(exec_dt)
|
|
440
|
+
except Exception:
|
|
441
|
+
# Skip orders whose executed datetime cannot be decoded
|
|
442
|
+
# (e.g. uninitialised/NaN). Plot-only path, so log at
|
|
443
|
+
# debug and move on without aborting the plot.
|
|
444
|
+
logger.debug("skip order: bad executed dt %r", exec_dt, exc_info=True)
|
|
445
|
+
continue
|
|
446
|
+
|
|
447
|
+
# Find the corresponding DataFrame row
|
|
448
|
+
mask = df["datetime"] == dt
|
|
449
|
+
if mask.any():
|
|
450
|
+
idx = df[mask].index[0]
|
|
451
|
+
if order.isbuy():
|
|
452
|
+
df.loc[idx, "buy_signal"] = True
|
|
453
|
+
df.loc[idx, "buy_price"] = exec_price
|
|
454
|
+
else:
|
|
455
|
+
df.loc[idx, "sell_signal"] = True
|
|
456
|
+
df.loc[idx, "sell_price"] = exec_price
|
|
457
|
+
except Exception as e:
|
|
458
|
+
logger.debug("Failed to add trade signals: %s", e)
|
|
459
|
+
|
|
460
|
+
# Add equity curve data
|
|
461
|
+
df = self._add_equity_data(df, strategy)
|
|
462
|
+
|
|
463
|
+
return df
|
|
464
|
+
|
|
465
|
+
def _equity_from_broker_observer(self, strategy, length):
|
|
466
|
+
"""Equity values from the Broker observer's value line (or all-None)."""
|
|
467
|
+
equity_values = [None] * length
|
|
468
|
+
if hasattr(strategy, "observers"):
|
|
469
|
+
for obs in strategy.observers:
|
|
470
|
+
if obs.__class__.__name__ == "Broker":
|
|
471
|
+
if hasattr(obs.lines, "value"):
|
|
472
|
+
value_line = obs.lines.value
|
|
473
|
+
obs_len = len(value_line)
|
|
474
|
+
for i in range(min(length, obs_len)):
|
|
475
|
+
idx = 1 - obs_len + i
|
|
476
|
+
try:
|
|
477
|
+
equity_values[i] = value_line[idx]
|
|
478
|
+
except Exception as e:
|
|
479
|
+
logger.debug("Failed to get equity value at idx %d: %s", idx, e)
|
|
480
|
+
break
|
|
481
|
+
return equity_values
|
|
482
|
+
|
|
483
|
+
def _equity_from_timereturn(self, df, strategy, equity_values):
|
|
484
|
+
"""Fill equity_values in-place from a TimeReturn analyzer (fallback)."""
|
|
485
|
+
time_return = None
|
|
486
|
+
for analyzer in getattr(strategy, "analyzers", []):
|
|
487
|
+
if analyzer.__class__.__name__ == "TimeReturn":
|
|
488
|
+
try:
|
|
489
|
+
time_return = analyzer.get_analysis()
|
|
490
|
+
except Exception as e:
|
|
491
|
+
logger.debug("Failed to get TimeReturn analysis: %s", e)
|
|
492
|
+
break
|
|
493
|
+
|
|
494
|
+
if not time_return:
|
|
495
|
+
return
|
|
496
|
+
|
|
497
|
+
start_cash = 100000 # Default starting capital
|
|
498
|
+
if hasattr(strategy, "broker"):
|
|
499
|
+
try:
|
|
500
|
+
start_cash = strategy.broker.startingcash
|
|
501
|
+
except Exception as e:
|
|
502
|
+
logger.debug("Failed to get starting cash: %s", e)
|
|
503
|
+
|
|
504
|
+
cumulative = start_cash
|
|
505
|
+
sorted_returns = sorted(time_return.items())
|
|
506
|
+
ret_idx = 0
|
|
507
|
+
|
|
508
|
+
for i, row in df.iterrows():
|
|
509
|
+
if ret_idx < len(sorted_returns):
|
|
510
|
+
dt, ret = sorted_returns[ret_idx]
|
|
511
|
+
if row["datetime"].date() >= dt.date() if hasattr(dt, "date") else True:
|
|
512
|
+
cumulative = cumulative * (1 + ret)
|
|
513
|
+
ret_idx += 1
|
|
514
|
+
equity_values[i] = cumulative
|
|
515
|
+
|
|
516
|
+
@staticmethod
|
|
517
|
+
def _compute_drawdown(df, equity_values):
|
|
518
|
+
"""Fill df['drawdown']/['drawdown_pct'] from the equity curve."""
|
|
519
|
+
df["drawdown"] = None
|
|
520
|
+
df["drawdown_pct"] = None
|
|
521
|
+
|
|
522
|
+
max_equity = None
|
|
523
|
+
for i, eq in enumerate(equity_values):
|
|
524
|
+
if eq is not None:
|
|
525
|
+
if max_equity is None or eq > max_equity:
|
|
526
|
+
max_equity = eq
|
|
527
|
+
if max_equity > 0:
|
|
528
|
+
dd = max_equity - eq
|
|
529
|
+
dd_pct = (dd / max_equity) * 100
|
|
530
|
+
df.loc[i, "drawdown"] = dd
|
|
531
|
+
df.loc[i, "drawdown_pct"] = dd_pct
|
|
532
|
+
|
|
533
|
+
def _add_equity_data(self, df, strategy):
|
|
534
|
+
"""Add equity curve data.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
df: DataFrame
|
|
538
|
+
strategy: Strategy instance
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
DataFrame
|
|
542
|
+
"""
|
|
543
|
+
length = len(df)
|
|
544
|
+
|
|
545
|
+
# Get equity data from Broker observer
|
|
546
|
+
equity_values = self._equity_from_broker_observer(strategy, length)
|
|
547
|
+
|
|
548
|
+
# If no Broker observer, try to calculate from TimeReturn analyzer
|
|
549
|
+
if all(v is None for v in equity_values):
|
|
550
|
+
self._equity_from_timereturn(df, strategy, equity_values)
|
|
551
|
+
|
|
552
|
+
df["equity"] = equity_values
|
|
553
|
+
|
|
554
|
+
# Calculate drawdown
|
|
555
|
+
self._compute_drawdown(df, equity_values)
|
|
556
|
+
|
|
557
|
+
return df
|
|
558
|
+
|
|
559
|
+
def _create_main_figure(self, df, strategy=None):
|
|
560
|
+
"""Create main figure (price + trade signals).
|
|
561
|
+
|
|
562
|
+
Args:
|
|
563
|
+
df: DataFrame
|
|
564
|
+
strategy: Strategy instance
|
|
565
|
+
|
|
566
|
+
Returns:
|
|
567
|
+
Figure instance
|
|
568
|
+
"""
|
|
569
|
+
if not BOKEH_AVAILABLE:
|
|
570
|
+
return None
|
|
571
|
+
|
|
572
|
+
fig = Figure(self.scheme)
|
|
573
|
+
|
|
574
|
+
# Create Bokeh figure
|
|
575
|
+
fig.figure = figure(
|
|
576
|
+
title="Price & Trade Signals",
|
|
577
|
+
x_axis_type="datetime" if "datetime" in df.columns else "linear",
|
|
578
|
+
height=self.scheme.plot_height,
|
|
579
|
+
sizing_mode=self.scheme.plot_sizing_mode,
|
|
580
|
+
tools="pan,wheel_zoom,box_zoom,reset,save",
|
|
581
|
+
toolbar_location=self.scheme.toolbar_location,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# Apply theme style
|
|
585
|
+
self._apply_scheme_to_figure(fig.figure)
|
|
586
|
+
|
|
587
|
+
# Plot price data
|
|
588
|
+
if self.p.style == "candle" and all(
|
|
589
|
+
col in df.columns for col in ["open", "high", "low", "close"]
|
|
590
|
+
):
|
|
591
|
+
self._plot_candlestick(fig, df)
|
|
592
|
+
else:
|
|
593
|
+
self._plot_line(fig, df)
|
|
594
|
+
|
|
595
|
+
# Plot trade signals
|
|
596
|
+
self._plot_trade_signals(fig, df)
|
|
597
|
+
|
|
598
|
+
# Add crosshair
|
|
599
|
+
crosshair = CrosshairTool(line_color=self.scheme.crosshair_line_color)
|
|
600
|
+
fig.figure.add_tools(crosshair)
|
|
601
|
+
|
|
602
|
+
# Add HoverTool
|
|
603
|
+
hover = HoverTool(
|
|
604
|
+
tooltips=[
|
|
605
|
+
("Date", "@datetime{%F}"),
|
|
606
|
+
("Open", "@open{0.2f}"),
|
|
607
|
+
("High", "@high{0.2f}"),
|
|
608
|
+
("Low", "@low{0.2f}"),
|
|
609
|
+
("Close", "@close{0.2f}"),
|
|
610
|
+
],
|
|
611
|
+
formatters={"@datetime": "datetime"},
|
|
612
|
+
mode="vline",
|
|
613
|
+
)
|
|
614
|
+
fig.figure.add_tools(hover)
|
|
615
|
+
|
|
616
|
+
# Create data source
|
|
617
|
+
fig.cds = ColumnDataSource(df)
|
|
618
|
+
|
|
619
|
+
return fig
|
|
620
|
+
|
|
621
|
+
def _plot_trade_signals(self, fig, df):
|
|
622
|
+
"""Plot trade signal markers.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
fig: Figure instance
|
|
626
|
+
df: DataFrame
|
|
627
|
+
"""
|
|
628
|
+
x_col = "datetime" if "datetime" in df.columns else "index"
|
|
629
|
+
|
|
630
|
+
# Plot buy signals (green up triangle)
|
|
631
|
+
if "buy_signal" in df.columns:
|
|
632
|
+
buy_signal = df["buy_signal"].fillna(False).astype(bool)
|
|
633
|
+
buy_df = df[buy_signal]
|
|
634
|
+
else:
|
|
635
|
+
buy_df = df.iloc[0:0]
|
|
636
|
+
|
|
637
|
+
if len(buy_df) > 0:
|
|
638
|
+
fig.figure.triangle(
|
|
639
|
+
buy_df[x_col],
|
|
640
|
+
buy_df["buy_price"] if "buy_price" in buy_df.columns else buy_df["low"] * 0.98,
|
|
641
|
+
size=12,
|
|
642
|
+
color="#00ff00",
|
|
643
|
+
alpha=0.8,
|
|
644
|
+
legend_label="Buy",
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
# Plot sell signals (red down triangle)
|
|
648
|
+
if "sell_signal" in df.columns:
|
|
649
|
+
sell_signal = df["sell_signal"].fillna(False).astype(bool)
|
|
650
|
+
sell_df = df[sell_signal]
|
|
651
|
+
else:
|
|
652
|
+
sell_df = df.iloc[0:0]
|
|
653
|
+
if len(sell_df) > 0:
|
|
654
|
+
fig.figure.inverted_triangle(
|
|
655
|
+
sell_df[x_col],
|
|
656
|
+
(
|
|
657
|
+
sell_df["sell_price"]
|
|
658
|
+
if "sell_price" in sell_df.columns
|
|
659
|
+
else sell_df["high"] * 1.02
|
|
660
|
+
),
|
|
661
|
+
size=12,
|
|
662
|
+
color="#ff0000",
|
|
663
|
+
alpha=0.8,
|
|
664
|
+
legend_label="Sell",
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
# Configure legend
|
|
668
|
+
if len(buy_df) > 0 or len(sell_df) > 0:
|
|
669
|
+
fig.figure.legend.location = "top_left"
|
|
670
|
+
fig.figure.legend.click_policy = "hide"
|
|
671
|
+
|
|
672
|
+
def _create_equity_figure(self, df, strategy):
|
|
673
|
+
"""Create equity curve figure.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
df: DataFrame
|
|
677
|
+
strategy: Strategy instance
|
|
678
|
+
|
|
679
|
+
Returns:
|
|
680
|
+
Figure instance
|
|
681
|
+
"""
|
|
682
|
+
if not BOKEH_AVAILABLE:
|
|
683
|
+
return None
|
|
684
|
+
|
|
685
|
+
if "equity" not in df.columns or df["equity"].isna().all():
|
|
686
|
+
return None
|
|
687
|
+
|
|
688
|
+
fig = Figure(self.scheme)
|
|
689
|
+
|
|
690
|
+
# Create Bokeh figure
|
|
691
|
+
fig.figure = figure(
|
|
692
|
+
title="Equity Curve",
|
|
693
|
+
x_axis_type="datetime" if "datetime" in df.columns else "linear",
|
|
694
|
+
height=int(self.scheme.plot_height * 0.6),
|
|
695
|
+
sizing_mode=self.scheme.plot_sizing_mode,
|
|
696
|
+
tools="pan,wheel_zoom,box_zoom,reset,save",
|
|
697
|
+
toolbar_location=self.scheme.toolbar_location,
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# Apply theme style
|
|
701
|
+
self._apply_scheme_to_figure(fig.figure)
|
|
702
|
+
|
|
703
|
+
x_col = "datetime" if "datetime" in df.columns else "index"
|
|
704
|
+
|
|
705
|
+
# Plot equity curve
|
|
706
|
+
equity_df = df[df["equity"].notna()]
|
|
707
|
+
if len(equity_df) > 0:
|
|
708
|
+
fig.figure.line(
|
|
709
|
+
equity_df[x_col],
|
|
710
|
+
equity_df["equity"],
|
|
711
|
+
line_width=2,
|
|
712
|
+
color="#2196F3",
|
|
713
|
+
legend_label="Equity",
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Fill area
|
|
717
|
+
|
|
718
|
+
ColumnDataSource(
|
|
719
|
+
data={
|
|
720
|
+
"x": equity_df[x_col].tolist(),
|
|
721
|
+
"y": equity_df["equity"].tolist(),
|
|
722
|
+
"lower": [equity_df["equity"].min()] * len(equity_df),
|
|
723
|
+
}
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
# Add crosshair
|
|
727
|
+
crosshair = CrosshairTool(line_color=self.scheme.crosshair_line_color)
|
|
728
|
+
fig.figure.add_tools(crosshair)
|
|
729
|
+
|
|
730
|
+
fig.figure.legend.location = "top_left"
|
|
731
|
+
|
|
732
|
+
return fig
|
|
733
|
+
|
|
734
|
+
def _create_drawdown_figure(self, df, strategy):
|
|
735
|
+
"""Create drawdown figure.
|
|
736
|
+
|
|
737
|
+
Args:
|
|
738
|
+
df: DataFrame
|
|
739
|
+
strategy: Strategy instance
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
Figure instance
|
|
743
|
+
"""
|
|
744
|
+
if not BOKEH_AVAILABLE:
|
|
745
|
+
return None
|
|
746
|
+
|
|
747
|
+
if "drawdown_pct" not in df.columns or df["drawdown_pct"].isna().all():
|
|
748
|
+
return None
|
|
749
|
+
|
|
750
|
+
fig = Figure(self.scheme)
|
|
751
|
+
|
|
752
|
+
# Create Bokeh figure
|
|
753
|
+
fig.figure = figure(
|
|
754
|
+
title="Drawdown (%)",
|
|
755
|
+
x_axis_type="datetime" if "datetime" in df.columns else "linear",
|
|
756
|
+
height=int(self.scheme.plot_height * 0.5),
|
|
757
|
+
sizing_mode=self.scheme.plot_sizing_mode,
|
|
758
|
+
tools="pan,wheel_zoom,box_zoom,reset,save",
|
|
759
|
+
toolbar_location=self.scheme.toolbar_location,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# Apply theme style
|
|
763
|
+
self._apply_scheme_to_figure(fig.figure)
|
|
764
|
+
|
|
765
|
+
x_col = "datetime" if "datetime" in df.columns else "index"
|
|
766
|
+
|
|
767
|
+
# Plot drawdown curve
|
|
768
|
+
dd_df = df[df["drawdown_pct"].notna()]
|
|
769
|
+
if len(dd_df) > 0:
|
|
770
|
+
# Use negative values to display as decline on chart
|
|
771
|
+
fig.figure.varea(
|
|
772
|
+
x=dd_df[x_col],
|
|
773
|
+
y1=0,
|
|
774
|
+
y2=-dd_df["drawdown_pct"],
|
|
775
|
+
fill_color="#f44336",
|
|
776
|
+
fill_alpha=0.5,
|
|
777
|
+
legend_label="Drawdown",
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
fig.figure.line(dd_df[x_col], -dd_df["drawdown_pct"], line_width=1, color="#d32f2f")
|
|
781
|
+
|
|
782
|
+
# Add crosshair
|
|
783
|
+
crosshair = CrosshairTool(line_color=self.scheme.crosshair_line_color)
|
|
784
|
+
fig.figure.add_tools(crosshair)
|
|
785
|
+
|
|
786
|
+
fig.figure.legend.location = "bottom_left"
|
|
787
|
+
fig.figure.y_range.flipped = False
|
|
788
|
+
|
|
789
|
+
return fig
|
|
790
|
+
|
|
791
|
+
def _apply_scheme_to_figure(self, fig):
|
|
792
|
+
"""Apply theme style to figure.
|
|
793
|
+
|
|
794
|
+
Args:
|
|
795
|
+
fig: Bokeh figure
|
|
796
|
+
"""
|
|
797
|
+
fig.background_fill_color = self.scheme.background_fill
|
|
798
|
+
fig.border_fill_color = self.scheme.border_fill
|
|
799
|
+
|
|
800
|
+
fig.xgrid.grid_line_color = self.scheme.grid_line_color
|
|
801
|
+
fig.ygrid.grid_line_color = self.scheme.grid_line_color
|
|
802
|
+
|
|
803
|
+
fig.xaxis.axis_line_color = self.scheme.axis_line_color
|
|
804
|
+
fig.yaxis.axis_line_color = self.scheme.axis_line_color
|
|
805
|
+
|
|
806
|
+
fig.xaxis.major_tick_line_color = self.scheme.tick_line_color
|
|
807
|
+
fig.yaxis.major_tick_line_color = self.scheme.tick_line_color
|
|
808
|
+
|
|
809
|
+
fig.xaxis.axis_label_text_color = self.scheme.axis_label_text_color
|
|
810
|
+
fig.yaxis.axis_label_text_color = self.scheme.axis_label_text_color
|
|
811
|
+
|
|
812
|
+
fig.title.text_color = self.scheme.plot_title_text_color
|
|
813
|
+
|
|
814
|
+
def _plot_candlestick(self, fig, df):
|
|
815
|
+
"""Plot candlestick chart.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
fig: Figure instance
|
|
819
|
+
df: DataFrame
|
|
820
|
+
"""
|
|
821
|
+
if "datetime" not in df.columns:
|
|
822
|
+
return
|
|
823
|
+
|
|
824
|
+
# Calculate up/down
|
|
825
|
+
df = df.copy()
|
|
826
|
+
df["is_up"] = df["close"] >= df["open"]
|
|
827
|
+
|
|
828
|
+
# Separate up and down data
|
|
829
|
+
up = df[df["is_up"]]
|
|
830
|
+
down = df[~df["is_up"]]
|
|
831
|
+
|
|
832
|
+
# Calculate candle width
|
|
833
|
+
width = 0.5 * 24 * 60 * 60 * 1000 # Half day in milliseconds
|
|
834
|
+
|
|
835
|
+
# Plot up candles
|
|
836
|
+
if len(up) > 0:
|
|
837
|
+
fig.figure.segment(
|
|
838
|
+
up["datetime"], up["high"], up["datetime"], up["low"], color=self.scheme.barup_wick
|
|
839
|
+
)
|
|
840
|
+
fig.figure.vbar(
|
|
841
|
+
up["datetime"],
|
|
842
|
+
width,
|
|
843
|
+
up["open"],
|
|
844
|
+
up["close"],
|
|
845
|
+
fill_color=self.scheme.barup,
|
|
846
|
+
line_color=self.scheme.barup_outline,
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
# Plot down candles
|
|
850
|
+
if len(down) > 0:
|
|
851
|
+
fig.figure.segment(
|
|
852
|
+
down["datetime"],
|
|
853
|
+
down["high"],
|
|
854
|
+
down["datetime"],
|
|
855
|
+
down["low"],
|
|
856
|
+
color=self.scheme.bardown_wick,
|
|
857
|
+
)
|
|
858
|
+
fig.figure.vbar(
|
|
859
|
+
down["datetime"],
|
|
860
|
+
width,
|
|
861
|
+
down["open"],
|
|
862
|
+
down["close"],
|
|
863
|
+
fill_color=self.scheme.bardown,
|
|
864
|
+
line_color=self.scheme.bardown_outline,
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
def _plot_line(self, fig, df):
|
|
868
|
+
"""Plot line chart.
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
fig: Figure instance
|
|
872
|
+
df: DataFrame
|
|
873
|
+
"""
|
|
874
|
+
x_col = "datetime" if "datetime" in df.columns else "index"
|
|
875
|
+
|
|
876
|
+
if "close" in df.columns:
|
|
877
|
+
fig.figure.line(
|
|
878
|
+
df[x_col], df["close"], line_width=self.scheme.line_width, color=self.scheme.barup
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
def get_figurepage(self, figid):
|
|
882
|
+
"""Get figure page.
|
|
883
|
+
|
|
884
|
+
Args:
|
|
885
|
+
figid: Figure page ID
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
FigurePage instance or None
|
|
889
|
+
"""
|
|
890
|
+
return self._figurepages.get(figid)
|
|
891
|
+
|
|
892
|
+
def get_last_idx(self, figid):
|
|
893
|
+
"""Get last data index of figure page.
|
|
894
|
+
|
|
895
|
+
Args:
|
|
896
|
+
figid: Figure page ID
|
|
897
|
+
|
|
898
|
+
Returns:
|
|
899
|
+
int: Last index
|
|
900
|
+
"""
|
|
901
|
+
figurepage = self.get_figurepage(figid)
|
|
902
|
+
if figurepage is None or figurepage._data is None:
|
|
903
|
+
return -1
|
|
904
|
+
|
|
905
|
+
if "index" in figurepage._data.columns:
|
|
906
|
+
return figurepage._data["index"].iloc[-1]
|
|
907
|
+
return len(figurepage._data) - 1
|
|
908
|
+
|
|
909
|
+
def generate_data(
|
|
910
|
+
self, figid=None, start=None, end=None, back=None, preserveidx=False, fill_gaps=False
|
|
911
|
+
):
|
|
912
|
+
"""Generate chart data.
|
|
913
|
+
|
|
914
|
+
Args:
|
|
915
|
+
figid: Figure page ID
|
|
916
|
+
start: Start index
|
|
917
|
+
end: End index
|
|
918
|
+
back: Number of bars to look back
|
|
919
|
+
preserveidx: Whether to preserve original index
|
|
920
|
+
fill_gaps: Whether to fill gaps
|
|
921
|
+
|
|
922
|
+
Returns:
|
|
923
|
+
pandas.DataFrame
|
|
924
|
+
"""
|
|
925
|
+
if not PANDAS_AVAILABLE:
|
|
926
|
+
return None
|
|
927
|
+
|
|
928
|
+
figurepage = self.get_figurepage(figid) if figid is not None else None
|
|
929
|
+
|
|
930
|
+
if figurepage is None or figurepage._data is None:
|
|
931
|
+
return pd.DataFrame()
|
|
932
|
+
|
|
933
|
+
df = figurepage._data.copy()
|
|
934
|
+
|
|
935
|
+
# Apply range limits
|
|
936
|
+
if back is not None:
|
|
937
|
+
if end is not None:
|
|
938
|
+
start_idx = max(0, end - back + 1)
|
|
939
|
+
df = df.iloc[start_idx : end + 1]
|
|
940
|
+
else:
|
|
941
|
+
df = df.tail(back)
|
|
942
|
+
elif start is not None:
|
|
943
|
+
if "index" in df.columns:
|
|
944
|
+
df = df[df["index"] > start]
|
|
945
|
+
else:
|
|
946
|
+
df = df.iloc[start:]
|
|
947
|
+
|
|
948
|
+
if not preserveidx:
|
|
949
|
+
df = df.reset_index(drop=True)
|
|
950
|
+
|
|
951
|
+
return df
|
|
952
|
+
|
|
953
|
+
def update_figurepage(self, filter=None):
|
|
954
|
+
"""Update figure page.
|
|
955
|
+
|
|
956
|
+
Args:
|
|
957
|
+
filter: Filter configuration
|
|
958
|
+
"""
|
|
959
|
+
if filter is not None:
|
|
960
|
+
self._filter = filter
|
|
961
|
+
|
|
962
|
+
def generate_model_panels(self, figurepages=None):
|
|
963
|
+
"""Generate model panels.
|
|
964
|
+
|
|
965
|
+
Returns:
|
|
966
|
+
list: Panel list
|
|
967
|
+
"""
|
|
968
|
+
if not BOKEH_AVAILABLE:
|
|
969
|
+
return []
|
|
970
|
+
|
|
971
|
+
panels = []
|
|
972
|
+
source_pages = figurepages
|
|
973
|
+
if source_pages is None:
|
|
974
|
+
source_pages = self._figurepages.items()
|
|
975
|
+
|
|
976
|
+
for figid, figurepage in source_pages:
|
|
977
|
+
# Create chart panel
|
|
978
|
+
if figurepage.figures:
|
|
979
|
+
figures = [fig.figure for fig in figurepage.figures if fig.figure is not None]
|
|
980
|
+
if figures:
|
|
981
|
+
grid = gridplot(
|
|
982
|
+
[[fig] for fig in figures], sizing_mode=self.scheme.plot_sizing_mode
|
|
983
|
+
)
|
|
984
|
+
panel = Panel(child=grid, title="Charts")
|
|
985
|
+
panels.append(panel)
|
|
986
|
+
|
|
987
|
+
return panels
|
|
988
|
+
|
|
989
|
+
def build_model(self, figurepages=None):
|
|
990
|
+
"""Build a bokeh model from figure pages.
|
|
991
|
+
|
|
992
|
+
Args:
|
|
993
|
+
figurepages: Iterable of (figid, figurepage) tuples. Defaults to all.
|
|
994
|
+
|
|
995
|
+
Returns:
|
|
996
|
+
Tabs model or None
|
|
997
|
+
"""
|
|
998
|
+
if not BOKEH_AVAILABLE:
|
|
999
|
+
return None
|
|
1000
|
+
|
|
1001
|
+
from bokeh.models import Tabs
|
|
1002
|
+
|
|
1003
|
+
panels = self.generate_model_panels(figurepages=figurepages)
|
|
1004
|
+
if not panels:
|
|
1005
|
+
return None
|
|
1006
|
+
|
|
1007
|
+
return Tabs(tabs=[panel for panel in panels if panel is not None])
|
|
1008
|
+
|
|
1009
|
+
def build_full_model(self, figurepage=None, figurepages=None):
|
|
1010
|
+
"""Build a Tabs model with chart panels plus the extra tabs.
|
|
1011
|
+
|
|
1012
|
+
Extra tabs (Performance/Analyzer/Metadata/Config/Log/Source) are attached
|
|
1013
|
+
to a single figure page. This is the reusable build path used by both
|
|
1014
|
+
``plot()`` and the cerebro-compatible ``BokehPlot`` adapter; it performs
|
|
1015
|
+
no display and writes no file.
|
|
1016
|
+
|
|
1017
|
+
Args:
|
|
1018
|
+
figurepage: FigurePage to attach the extra tabs to. If ``None``, the
|
|
1019
|
+
most recently created figure page is used, or the extra tabs are
|
|
1020
|
+
skipped when no figure page exists.
|
|
1021
|
+
figurepages: Iterable of ``(figid, figurepage)`` tuples for the chart
|
|
1022
|
+
panels. Defaults to all accumulated figure pages.
|
|
1023
|
+
|
|
1024
|
+
Returns:
|
|
1025
|
+
Bokeh ``Tabs`` model, or ``None`` if bokeh is unavailable or no
|
|
1026
|
+
panels could be generated.
|
|
1027
|
+
"""
|
|
1028
|
+
if not BOKEH_AVAILABLE:
|
|
1029
|
+
_logger.error("Bokeh is not available")
|
|
1030
|
+
return None
|
|
1031
|
+
|
|
1032
|
+
model = self.build_model(figurepages=figurepages)
|
|
1033
|
+
panels = list(model.tabs) if model is not None else []
|
|
1034
|
+
|
|
1035
|
+
# Extra tabs attach to a single figure page; prefer the explicit one,
|
|
1036
|
+
# else fall back to the most recently created.
|
|
1037
|
+
if figurepage is None and self._figurepages:
|
|
1038
|
+
figurepage = next(reversed(self._figurepages.values()))
|
|
1039
|
+
|
|
1040
|
+
if figurepage is not None:
|
|
1041
|
+
for tab_class in self.tabs:
|
|
1042
|
+
tab = tab_class(self, figurepage, None)
|
|
1043
|
+
if tab.is_useable():
|
|
1044
|
+
panels.append(tab.get_panel())
|
|
1045
|
+
|
|
1046
|
+
if not panels:
|
|
1047
|
+
_logger.warning("No panels generated")
|
|
1048
|
+
return None
|
|
1049
|
+
|
|
1050
|
+
from bokeh.models import Tabs
|
|
1051
|
+
|
|
1052
|
+
if model is not None:
|
|
1053
|
+
model.tabs = [p for p in panels if p is not None]
|
|
1054
|
+
return model
|
|
1055
|
+
return Tabs(tabs=[p for p in panels if p is not None])
|
|
1056
|
+
|
|
1057
|
+
def plot(self, strategy=None, show=True, filename=None):
|
|
1058
|
+
"""Bindto strategy and generate static chart.
|
|
1059
|
+
|
|
1060
|
+
Args:
|
|
1061
|
+
strategy: Strategy instance
|
|
1062
|
+
show: Whether to show chart
|
|
1063
|
+
filename: Save filename
|
|
1064
|
+
|
|
1065
|
+
Returns:
|
|
1066
|
+
Bokeh model or None
|
|
1067
|
+
"""
|
|
1068
|
+
if not BOKEH_AVAILABLE:
|
|
1069
|
+
_logger.error("Bokeh is not available")
|
|
1070
|
+
return None
|
|
1071
|
+
|
|
1072
|
+
if strategy is None:
|
|
1073
|
+
_logger.warning("No strategy provided")
|
|
1074
|
+
return None
|
|
1075
|
+
|
|
1076
|
+
# Create figure page
|
|
1077
|
+
figid, figurepage = self.create_figurepage(strategy, filldata=True)
|
|
1078
|
+
|
|
1079
|
+
model = self.build_full_model(figurepage=figurepage)
|
|
1080
|
+
if model is None:
|
|
1081
|
+
return None
|
|
1082
|
+
|
|
1083
|
+
if show:
|
|
1084
|
+
from bokeh.io import show as bokeh_show
|
|
1085
|
+
|
|
1086
|
+
bokeh_show(model)
|
|
1087
|
+
|
|
1088
|
+
if filename:
|
|
1089
|
+
from bokeh.io import output_file, save
|
|
1090
|
+
|
|
1091
|
+
output_file(filename)
|
|
1092
|
+
save(model)
|
|
1093
|
+
|
|
1094
|
+
return model
|