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/plot/plot.py
ADDED
|
@@ -0,0 +1,2500 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Plotting module for Backtrader.
|
|
3
|
+
|
|
4
|
+
This module provides plotting functionality for backtrader strategies, including
|
|
5
|
+
matplotlib-based plotting, plotly integration, and pyecharts support for
|
|
6
|
+
creating interactive charts and visualizations of trading results.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
PInfo: Internal plotting information container
|
|
10
|
+
Plot_OldSync: Main plotting class for matplotlib-based chart generation
|
|
11
|
+
|
|
12
|
+
Functions:
|
|
13
|
+
split_data: Split dataframe into chart components
|
|
14
|
+
get_up_scatter: Get upward swing points for chart
|
|
15
|
+
get_dn_scatter: Get downward swing points for chart
|
|
16
|
+
get_valid_point: Get valid swing points
|
|
17
|
+
draw_chart: Draw comprehensive trading chart
|
|
18
|
+
get_rate_sharpe_drawdown: Calculate performance metrics
|
|
19
|
+
run_cerebro_and_plot: Run cerebro backtest and plot results
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import bisect
|
|
23
|
+
import collections
|
|
24
|
+
import copy
|
|
25
|
+
import datetime
|
|
26
|
+
import math
|
|
27
|
+
import operator
|
|
28
|
+
import os
|
|
29
|
+
import sys
|
|
30
|
+
import time
|
|
31
|
+
import traceback
|
|
32
|
+
from collections import OrderedDict
|
|
33
|
+
|
|
34
|
+
import matplotlib
|
|
35
|
+
import matplotlib.font_manager as mfontmgr
|
|
36
|
+
import matplotlib.ticker as mticker
|
|
37
|
+
import numpy as np # guaranteed by matplotlib
|
|
38
|
+
|
|
39
|
+
from ..utils.log_message import get_logger
|
|
40
|
+
|
|
41
|
+
if not hasattr(np, "unicode_"):
|
|
42
|
+
# Runtime shim for numpy>=2.0 which removed np.unicode_ (used by deps).
|
|
43
|
+
np.unicode_ = np.str_ # noqa: E402
|
|
44
|
+
|
|
45
|
+
import pandas as pd
|
|
46
|
+
import plotly.figure_factory as ff
|
|
47
|
+
import plotly.graph_objs as go
|
|
48
|
+
import plotly.offline as py
|
|
49
|
+
from dash import html
|
|
50
|
+
from pyecharts import options as opts
|
|
51
|
+
from pyecharts.charts import Bar, EffectScatter, Grid, Kline, Line
|
|
52
|
+
from pyecharts.commons.utils import JsCode
|
|
53
|
+
from pyecharts.globals import SymbolType
|
|
54
|
+
|
|
55
|
+
from .. import AutoInfoClass, analyzers, date2num
|
|
56
|
+
from ..dataseries import TimeFrame
|
|
57
|
+
from ..parameters import ParameterDescriptor, ParameterizedBase
|
|
58
|
+
from ..utils.py3 import integer_types, range
|
|
59
|
+
from . import locator as loc
|
|
60
|
+
from .finance import plot_candlestick, plot_lineonclose, plot_ohlc, plot_volume
|
|
61
|
+
from .formatters import MyDateFormatter, MyVolFormatter
|
|
62
|
+
from .multicursor import MultiCursor
|
|
63
|
+
from .scheme import PlotScheme
|
|
64
|
+
from .utils import tag_box_style
|
|
65
|
+
|
|
66
|
+
logger = get_logger(__name__)
|
|
67
|
+
|
|
68
|
+
# from jupyter_plotly_dash import JupyterDash
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def cal_macd_system(data, short_=26, long_=12, m=9):
|
|
72
|
+
"""
|
|
73
|
+
data is a standard dataframe containing high, open, low, close, volume
|
|
74
|
+
short_, long_, m are the three parameters of macd
|
|
75
|
+
Return value is a dataframe containing original data and diff, dea, macd columns
|
|
76
|
+
"""
|
|
77
|
+
data["diff"] = (
|
|
78
|
+
data["close"].ewm(adjust=False, alpha=2 / (short_ + 1), ignore_na=True).mean()
|
|
79
|
+
- data["close"].ewm(adjust=False, alpha=2 / (long_ + 1), ignore_na=True).mean()
|
|
80
|
+
)
|
|
81
|
+
data["dea"] = data["diff"].ewm(adjust=False, alpha=2 / (m + 1), ignore_na=True).mean()
|
|
82
|
+
data["macd"] = 2 * (data["diff"] - data["dea"])
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def split_data(df) -> dict:
|
|
87
|
+
"""Split dataframe into components for pyecharts plotting.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
df: DataFrame containing OHLCV data and MACD indicators
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Dictionary with keys: datas, times, vols, macds, difs, deas
|
|
94
|
+
"""
|
|
95
|
+
datas = list(zip(df["open"], df["close"], df["low"], df["high"], df["volume"], df["up_bar"]))
|
|
96
|
+
times = list(df.index)
|
|
97
|
+
vols = list(df["volume"])
|
|
98
|
+
macds = list(df["macd"])
|
|
99
|
+
difs = list(df["diff"])
|
|
100
|
+
deas = list(df["dea"])
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"datas": datas,
|
|
104
|
+
"times": times,
|
|
105
|
+
"vols": vols,
|
|
106
|
+
"macds": macds,
|
|
107
|
+
"difs": difs,
|
|
108
|
+
"deas": deas,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_up_scatter(df):
|
|
113
|
+
"""Get upward swing points from dataframe.
|
|
114
|
+
|
|
115
|
+
Identifies low points in the price swing for marking on charts.
|
|
116
|
+
Returns a list of tuples containing (time, lowest price).
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
df: DataFrame with up_bar and dn_bar columns
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
List of [time, low] tuples marking upward swing points
|
|
123
|
+
"""
|
|
124
|
+
# Mark up points, format is a list containing tuples of (time, lowest price)
|
|
125
|
+
mark_line_data = []
|
|
126
|
+
first_swing = None
|
|
127
|
+
pre_index = None
|
|
128
|
+
pre_low = None
|
|
129
|
+
for index, row in df.iterrows():
|
|
130
|
+
up_bar = row["up_bar"]
|
|
131
|
+
dn_bar = row["dn_bar"]
|
|
132
|
+
low = row["low"]
|
|
133
|
+
if first_swing is None:
|
|
134
|
+
if up_bar == 1:
|
|
135
|
+
first_swing = "up"
|
|
136
|
+
if dn_bar == 1:
|
|
137
|
+
first_swing = "dn"
|
|
138
|
+
if first_swing == "up" and dn_bar == 1:
|
|
139
|
+
# mark_line_data.append([index, high])
|
|
140
|
+
first_swing = "dn"
|
|
141
|
+
if first_swing == "dn" and up_bar == 1:
|
|
142
|
+
mark_line_data.append([pre_index, pre_low])
|
|
143
|
+
first_swing = "up"
|
|
144
|
+
pre_index = index
|
|
145
|
+
pre_low = low
|
|
146
|
+
return mark_line_data
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_dn_scatter(df):
|
|
150
|
+
"""Get downward swing points from dataframe.
|
|
151
|
+
|
|
152
|
+
Identifies high points in the price swing for marking on charts.
|
|
153
|
+
Returns a list of tuples containing (time, highest price).
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
df: DataFrame with up_bar and dn_bar columns
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
List of [time, high] tuples marking downward swing points
|
|
160
|
+
"""
|
|
161
|
+
# Mark down points, format is a list containing tuples of (time, highest price)
|
|
162
|
+
mark_line_data = []
|
|
163
|
+
first_swing = None
|
|
164
|
+
pre_index = None
|
|
165
|
+
pre_high = None
|
|
166
|
+
for index, row in df.iterrows():
|
|
167
|
+
up_bar = row["up_bar"]
|
|
168
|
+
dn_bar = row["dn_bar"]
|
|
169
|
+
high = row["high"]
|
|
170
|
+
if first_swing is None:
|
|
171
|
+
if up_bar == 1:
|
|
172
|
+
first_swing = "up"
|
|
173
|
+
if dn_bar == 1:
|
|
174
|
+
first_swing = "dn"
|
|
175
|
+
if first_swing == "up" and dn_bar == 1:
|
|
176
|
+
mark_line_data.append([pre_index, pre_high])
|
|
177
|
+
first_swing = "dn"
|
|
178
|
+
if first_swing == "dn" and up_bar == 1:
|
|
179
|
+
# mark_line_data.append([index, low])
|
|
180
|
+
first_swing = "up"
|
|
181
|
+
pre_index = index
|
|
182
|
+
pre_high = high
|
|
183
|
+
return mark_line_data
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_valid_point(df):
|
|
187
|
+
"""Get valid swing points for support/resistance lines.
|
|
188
|
+
|
|
189
|
+
Identifies valid swing high and low points that can be used to draw
|
|
190
|
+
support and resistance lines on price charts.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
df: DataFrame with up_bar, dn_bar, high, and low columns
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Tuple of two lists:
|
|
197
|
+
- valid_dn_point_list: Valid downward (high) points
|
|
198
|
+
- valid_up_point_list: Valid upward (low) points
|
|
199
|
+
"""
|
|
200
|
+
valid_dn_point_list = []
|
|
201
|
+
valid_up_point_list = []
|
|
202
|
+
dn_point_point_list = []
|
|
203
|
+
up_point_point_list: list = []
|
|
204
|
+
first_swing = None
|
|
205
|
+
pre_index = None
|
|
206
|
+
pre_low = None
|
|
207
|
+
pre_high = None
|
|
208
|
+
for index, row in df.iterrows():
|
|
209
|
+
up_bar = row["up_bar"]
|
|
210
|
+
dn_bar = row["dn_bar"]
|
|
211
|
+
high = row["high"]
|
|
212
|
+
low = row["low"]
|
|
213
|
+
if first_swing is None:
|
|
214
|
+
if up_bar == 1:
|
|
215
|
+
first_swing = "up"
|
|
216
|
+
if dn_bar == 1:
|
|
217
|
+
first_swing = "dn"
|
|
218
|
+
if first_swing == "up" and dn_bar == 1:
|
|
219
|
+
dn_point_point_list.append([pre_index, pre_high])
|
|
220
|
+
first_swing = "dn"
|
|
221
|
+
if len(dn_point_point_list) > 1 and len(up_point_point_list) > 1:
|
|
222
|
+
pre_pre_high = dn_point_point_list[-2][1]
|
|
223
|
+
# If current highest point is greater than previous highest point, then previous up swing point is at least a test point
|
|
224
|
+
if pre_high > pre_pre_high:
|
|
225
|
+
# Try to get previous dn_point's highest price and pre-previous dn_point's highest price
|
|
226
|
+
pre_1_index, pre_1_low = up_point_point_list[-1]
|
|
227
|
+
pre_2_index, pre_2_low = up_point_point_list[-2]
|
|
228
|
+
# If previous swing point is up
|
|
229
|
+
if pre_1_low < pre_2_low:
|
|
230
|
+
valid_up_point_list.append([pre_1_index, pre_1_low])
|
|
231
|
+
|
|
232
|
+
if first_swing == "dn" and up_bar == 1:
|
|
233
|
+
up_point_point_list.append([pre_index, pre_low])
|
|
234
|
+
first_swing = "up"
|
|
235
|
+
# Get previous up_point
|
|
236
|
+
if len(dn_point_point_list) > 1 and len(up_point_point_list) > 1:
|
|
237
|
+
pre_pre_low = up_point_point_list[-2][1]
|
|
238
|
+
# If current lowest point is less than previous lowest point, then previous highest price is a test point
|
|
239
|
+
if pre_low < pre_pre_low:
|
|
240
|
+
# Try to get previous dn_point's highest price and pre-previous dn_point's highest price
|
|
241
|
+
pre_1_index, pre_1_high = dn_point_point_list[-1]
|
|
242
|
+
pre_2_index, pre_2_high = dn_point_point_list[-2]
|
|
243
|
+
# If previous swing point is up
|
|
244
|
+
if pre_1_high > pre_2_high:
|
|
245
|
+
valid_dn_point_list.append([pre_1_index, pre_1_high])
|
|
246
|
+
|
|
247
|
+
pre_index = index
|
|
248
|
+
pre_low = low
|
|
249
|
+
pre_high = high
|
|
250
|
+
return valid_dn_point_list, valid_up_point_list
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def draw_chart(data, df, bk_list, bp_list, sk_list, sp_list):
|
|
254
|
+
"""Draw comprehensive trading chart using pyecharts.
|
|
255
|
+
|
|
256
|
+
Creates a detailed K-line chart with volume, MACD indicators,
|
|
257
|
+
trading signals, and support/resistance lines.
|
|
258
|
+
|
|
259
|
+
Args:
|
|
260
|
+
data: Dictionary containing times, volumes, MACD data
|
|
261
|
+
df: DataFrame with OHLCV data and swing markers
|
|
262
|
+
bk_list: List of buy signals (open long)
|
|
263
|
+
bp_list: List of sell signals (close long)
|
|
264
|
+
sk_list: List of short signals (open short)
|
|
265
|
+
sp_list: List of cover signals (close short)
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
None (renders chart to HTML file)
|
|
269
|
+
"""
|
|
270
|
+
kline = (
|
|
271
|
+
Kline()
|
|
272
|
+
.add_xaxis(xaxis_data=data["times"])
|
|
273
|
+
.add_yaxis(
|
|
274
|
+
series_name="",
|
|
275
|
+
y_axis=data["datas"],
|
|
276
|
+
itemstyle_opts=opts.ItemStyleOpts(
|
|
277
|
+
color="#ef232a",
|
|
278
|
+
color0="#14b143",
|
|
279
|
+
border_color="#ef232a",
|
|
280
|
+
border_color0="#14b143",
|
|
281
|
+
),
|
|
282
|
+
markpoint_opts=opts.MarkPointOpts(
|
|
283
|
+
data=[
|
|
284
|
+
opts.MarkPointItem(type_="max", name="Maximum"),
|
|
285
|
+
opts.MarkPointItem(type_="min", name="Minimum"),
|
|
286
|
+
]
|
|
287
|
+
),
|
|
288
|
+
# markline_opts = opts.MarkLineOpts(
|
|
289
|
+
# label_opts=opts.LabelOpts(
|
|
290
|
+
# position="middle", color="blue", font_size=15
|
|
291
|
+
# ),
|
|
292
|
+
# data=split_data_part(),
|
|
293
|
+
# symbol=["circle", "none"],
|
|
294
|
+
# ),
|
|
295
|
+
)
|
|
296
|
+
.set_series_opts(
|
|
297
|
+
# To avoid affecting mark points, turn off labels here
|
|
298
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
299
|
+
markpoint_opts=opts.MarkPointOpts(
|
|
300
|
+
data=[
|
|
301
|
+
opts.MarkPointItem(type_="min", name="y-axis minimum", value_index=1),
|
|
302
|
+
opts.MarkPointItem(type_="max", name="y-axis maximum", value_index=1),
|
|
303
|
+
]
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
.set_global_opts(
|
|
307
|
+
title_opts=opts.TitleOpts(title="K-line cycle chart", pos_left="0"),
|
|
308
|
+
xaxis_opts=opts.AxisOpts(
|
|
309
|
+
type_="category",
|
|
310
|
+
is_scale=True,
|
|
311
|
+
boundary_gap=False,
|
|
312
|
+
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
|
|
313
|
+
splitline_opts=opts.SplitLineOpts(is_show=False),
|
|
314
|
+
split_number=20,
|
|
315
|
+
min_="dataMin",
|
|
316
|
+
max_="dataMax",
|
|
317
|
+
),
|
|
318
|
+
yaxis_opts=opts.AxisOpts(
|
|
319
|
+
is_scale=True, splitline_opts=opts.SplitLineOpts(is_show=True)
|
|
320
|
+
),
|
|
321
|
+
tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="line"),
|
|
322
|
+
datazoom_opts=[
|
|
323
|
+
opts.DataZoomOpts(is_show=False, type_="inside", xaxis_index=[0, 0], range_end=100),
|
|
324
|
+
opts.DataZoomOpts(is_show=True, xaxis_index=[0, 1], pos_top="97%", range_end=100),
|
|
325
|
+
opts.DataZoomOpts(is_show=False, xaxis_index=[0, 2], range_end=100),
|
|
326
|
+
],
|
|
327
|
+
# Connect axes of three charts together
|
|
328
|
+
# axispointer_opts=opts.AxisPointerOpts(
|
|
329
|
+
# is_show=True,
|
|
330
|
+
# link=[{"xAxisIndex": "all"}],
|
|
331
|
+
# label=opts.LabelOpts(background_color="#777"),
|
|
332
|
+
# ),
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
esc = get_up_scatter(df)
|
|
336
|
+
esc_dn = get_dn_scatter(df)
|
|
337
|
+
|
|
338
|
+
all_up_dn = esc + esc_dn
|
|
339
|
+
all_up_dn_sorted = sorted(all_up_dn, key=lambda x: x[0])
|
|
340
|
+
line_index = [i[0] for i in all_up_dn_sorted]
|
|
341
|
+
line_value = [i[1] for i in all_up_dn_sorted]
|
|
342
|
+
|
|
343
|
+
kline_line = (
|
|
344
|
+
Line()
|
|
345
|
+
.add_xaxis(xaxis_data=line_index)
|
|
346
|
+
.add_yaxis(
|
|
347
|
+
series_name="Wave",
|
|
348
|
+
y_axis=line_value,
|
|
349
|
+
is_smooth=False,
|
|
350
|
+
# linestyle_opts=opts.LineStyleOpts(opacity=0.5),
|
|
351
|
+
linestyle_opts=opts.LineStyleOpts(color="black", width=4, type_="dashed"),
|
|
352
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
353
|
+
symbol="arrow",
|
|
354
|
+
)
|
|
355
|
+
.set_global_opts(
|
|
356
|
+
xaxis_opts=opts.AxisOpts(
|
|
357
|
+
type_="category",
|
|
358
|
+
grid_index=1,
|
|
359
|
+
axislabel_opts=opts.LabelOpts(is_show=False),
|
|
360
|
+
),
|
|
361
|
+
yaxis_opts=opts.AxisOpts(
|
|
362
|
+
grid_index=1,
|
|
363
|
+
split_number=3,
|
|
364
|
+
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
|
|
365
|
+
axistick_opts=opts.AxisTickOpts(is_show=False),
|
|
366
|
+
splitline_opts=opts.SplitLineOpts(is_show=False),
|
|
367
|
+
axislabel_opts=opts.LabelOpts(is_show=True),
|
|
368
|
+
),
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
# Overlap Kline + Line
|
|
372
|
+
overlap_kline_line = kline.overlap(kline_line)
|
|
373
|
+
|
|
374
|
+
# Try to draw support line
|
|
375
|
+
valid_dn_point_list, valid_up_point_list = get_valid_point(df)
|
|
376
|
+
es = (
|
|
377
|
+
EffectScatter()
|
|
378
|
+
.add_xaxis([i[0] for i in valid_up_point_list])
|
|
379
|
+
.add_yaxis("", [i[1] for i in valid_up_point_list], symbol=SymbolType.TRIANGLE)
|
|
380
|
+
)
|
|
381
|
+
# overlap_kline_line = kline
|
|
382
|
+
overlap_kline_line = overlap_kline_line.overlap(es)
|
|
383
|
+
|
|
384
|
+
es_dn = (
|
|
385
|
+
EffectScatter()
|
|
386
|
+
.add_xaxis([i[0] for i in valid_dn_point_list])
|
|
387
|
+
.add_yaxis("", [i[1] for i in valid_dn_point_list], symbol=SymbolType.DIAMOND)
|
|
388
|
+
)
|
|
389
|
+
# overlap_kline_line = kline
|
|
390
|
+
overlap_kline_line = overlap_kline_line.overlap(es_dn)
|
|
391
|
+
|
|
392
|
+
# Try to add some support lines to the support
|
|
393
|
+
for d1, d2 in zip(valid_dn_point_list[:-1], valid_dn_point_list[1:]):
|
|
394
|
+
dn_line = (
|
|
395
|
+
Line()
|
|
396
|
+
.add_xaxis(xaxis_data=[d1[0], d2[0]])
|
|
397
|
+
.add_yaxis(
|
|
398
|
+
series_name="Support",
|
|
399
|
+
y_axis=[d1[1], d2[1]],
|
|
400
|
+
is_smooth=False,
|
|
401
|
+
# linestyle_opts=opts.LineStyleOpts(opacity=0.5),
|
|
402
|
+
linestyle_opts=opts.LineStyleOpts(color="green", width=2, type_="dotted"),
|
|
403
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
404
|
+
symbol="arrow",
|
|
405
|
+
)
|
|
406
|
+
.set_global_opts(
|
|
407
|
+
xaxis_opts=opts.AxisOpts(
|
|
408
|
+
type_="category",
|
|
409
|
+
grid_index=1,
|
|
410
|
+
axislabel_opts=opts.LabelOpts(is_show=False),
|
|
411
|
+
),
|
|
412
|
+
yaxis_opts=opts.AxisOpts(
|
|
413
|
+
grid_index=1,
|
|
414
|
+
split_number=3,
|
|
415
|
+
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
|
|
416
|
+
axistick_opts=opts.AxisTickOpts(is_show=False),
|
|
417
|
+
splitline_opts=opts.SplitLineOpts(is_show=False),
|
|
418
|
+
axislabel_opts=opts.LabelOpts(is_show=True),
|
|
419
|
+
),
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
overlap_kline_line = kline.overlap(dn_line)
|
|
423
|
+
|
|
424
|
+
for d1, d2 in zip(valid_up_point_list[:-1], valid_up_point_list[1:]):
|
|
425
|
+
dn_line = (
|
|
426
|
+
Line()
|
|
427
|
+
.add_xaxis(xaxis_data=[d1[0], d2[0]])
|
|
428
|
+
.add_yaxis(
|
|
429
|
+
series_name="Support",
|
|
430
|
+
y_axis=[d1[1], d2[1]],
|
|
431
|
+
is_smooth=False,
|
|
432
|
+
# linestyle_opts=opts.LineStyleOpts(opacity=0.5),
|
|
433
|
+
linestyle_opts=opts.LineStyleOpts(color="red", width=2, type_="dotted"),
|
|
434
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
435
|
+
symbol="arrow",
|
|
436
|
+
)
|
|
437
|
+
.set_global_opts(
|
|
438
|
+
xaxis_opts=opts.AxisOpts(
|
|
439
|
+
type_="category",
|
|
440
|
+
grid_index=1,
|
|
441
|
+
axislabel_opts=opts.LabelOpts(is_show=False),
|
|
442
|
+
),
|
|
443
|
+
yaxis_opts=opts.AxisOpts(
|
|
444
|
+
grid_index=1,
|
|
445
|
+
split_number=3,
|
|
446
|
+
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
|
|
447
|
+
axistick_opts=opts.AxisTickOpts(is_show=False),
|
|
448
|
+
splitline_opts=opts.SplitLineOpts(is_show=False),
|
|
449
|
+
axislabel_opts=opts.LabelOpts(is_show=True),
|
|
450
|
+
),
|
|
451
|
+
)
|
|
452
|
+
)
|
|
453
|
+
overlap_kline_line = kline.overlap(dn_line)
|
|
454
|
+
|
|
455
|
+
# Add buy/sell points
|
|
456
|
+
# Open long
|
|
457
|
+
bk_df = df[df.index.isin([str(i[0]) for i in bk_list])]
|
|
458
|
+
bk_c = (
|
|
459
|
+
EffectScatter()
|
|
460
|
+
.add_xaxis(bk_df.index)
|
|
461
|
+
.add_yaxis(
|
|
462
|
+
"", bk_df.low, color="red", symbol="image://c:/result/img/open_long.png", symbol_size=10
|
|
463
|
+
)
|
|
464
|
+
.set_global_opts(title_opts=opts.TitleOpts(title="buy"))
|
|
465
|
+
)
|
|
466
|
+
overlap_kline_line = kline.overlap(bk_c)
|
|
467
|
+
# Close long
|
|
468
|
+
bp_df = df[df.index.isin([str(i[0]) for i in bp_list])]
|
|
469
|
+
bp_c = (
|
|
470
|
+
EffectScatter()
|
|
471
|
+
.add_xaxis(bp_df.index)
|
|
472
|
+
.add_yaxis(
|
|
473
|
+
"",
|
|
474
|
+
bp_df.high,
|
|
475
|
+
color="green",
|
|
476
|
+
symbol="image://c:/result/img/close_long.png",
|
|
477
|
+
symbol_size=10,
|
|
478
|
+
)
|
|
479
|
+
.set_global_opts(title_opts=opts.TitleOpts(title="=sell"))
|
|
480
|
+
)
|
|
481
|
+
overlap_kline_line = kline.overlap(bp_c)
|
|
482
|
+
# Long position line segment
|
|
483
|
+
for bk, bp in zip([str(i[0]) for i in bk_list], [str(i[0]) for i in bp_list]):
|
|
484
|
+
try:
|
|
485
|
+
bk_df = df[df.index >= bk]
|
|
486
|
+
bk_price = list(bk_df["open"])[1]
|
|
487
|
+
bp_df = df[df.index >= bp]
|
|
488
|
+
bp_price = list(bp_df["open"])[1]
|
|
489
|
+
long_line = (
|
|
490
|
+
Line()
|
|
491
|
+
.add_xaxis(xaxis_data=[bk, bp])
|
|
492
|
+
.add_yaxis(
|
|
493
|
+
series_name="long_signal",
|
|
494
|
+
y_axis=[bk_price, bp_price],
|
|
495
|
+
is_smooth=False,
|
|
496
|
+
# linestyle_opts=opts.LineStyleOpts(opacity=0.5),
|
|
497
|
+
linestyle_opts=opts.LineStyleOpts(color="red", width=5, type_="dotted"),
|
|
498
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
499
|
+
symbol="arrow",
|
|
500
|
+
)
|
|
501
|
+
.set_global_opts(
|
|
502
|
+
xaxis_opts=opts.AxisOpts(
|
|
503
|
+
type_="category",
|
|
504
|
+
grid_index=1,
|
|
505
|
+
axislabel_opts=opts.LabelOpts(is_show=False),
|
|
506
|
+
),
|
|
507
|
+
yaxis_opts=opts.AxisOpts(
|
|
508
|
+
grid_index=1,
|
|
509
|
+
split_number=3,
|
|
510
|
+
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
|
|
511
|
+
axistick_opts=opts.AxisTickOpts(is_show=False),
|
|
512
|
+
splitline_opts=opts.SplitLineOpts(is_show=False),
|
|
513
|
+
axislabel_opts=opts.LabelOpts(is_show=True),
|
|
514
|
+
),
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
overlap_kline_line = kline.overlap(long_line)
|
|
518
|
+
except Exception as e:
|
|
519
|
+
logger.warning("Failed to create long line overlay: %s", e)
|
|
520
|
+
|
|
521
|
+
sk_df = df[df.index.isin([str(i[0]) for i in sk_list])]
|
|
522
|
+
sk_c = (
|
|
523
|
+
EffectScatter()
|
|
524
|
+
.add_xaxis(sk_df.index)
|
|
525
|
+
.add_yaxis(
|
|
526
|
+
"",
|
|
527
|
+
sk_df.high,
|
|
528
|
+
color="green",
|
|
529
|
+
symbol="image://c:/result/img/open_short.png",
|
|
530
|
+
symbol_size=10,
|
|
531
|
+
)
|
|
532
|
+
.set_global_opts(title_opts=opts.TitleOpts(title="sellshort"))
|
|
533
|
+
)
|
|
534
|
+
overlap_kline_line = kline.overlap(sk_c)
|
|
535
|
+
|
|
536
|
+
sp_df = df[df.index.isin([str(i[0]) for i in sp_list])]
|
|
537
|
+
sp_c = (
|
|
538
|
+
EffectScatter()
|
|
539
|
+
.add_xaxis(sp_df.index)
|
|
540
|
+
.add_yaxis(
|
|
541
|
+
"",
|
|
542
|
+
sp_df.low,
|
|
543
|
+
color="red",
|
|
544
|
+
symbol="image://c:/result/img/close_short.png",
|
|
545
|
+
symbol_size=10,
|
|
546
|
+
)
|
|
547
|
+
.set_global_opts(title_opts=opts.TitleOpts(title="buytocover"))
|
|
548
|
+
)
|
|
549
|
+
overlap_kline_line = kline.overlap(sp_c)
|
|
550
|
+
|
|
551
|
+
# Short position line segment
|
|
552
|
+
for sk, sp in zip([str(i[0]) for i in sk_list], [str(i[0]) for i in sp_list]):
|
|
553
|
+
try:
|
|
554
|
+
sk_df = df[df.index >= sk]
|
|
555
|
+
sk = list(sk_df.index)[1]
|
|
556
|
+
sk_price = list(sk_df["open"])[1]
|
|
557
|
+
sp_df = df[df.index >= sp]
|
|
558
|
+
sp = list(sp_df.index)[1]
|
|
559
|
+
sp_price = list(sp_df["open"])[1]
|
|
560
|
+
short_line = (
|
|
561
|
+
Line()
|
|
562
|
+
.add_xaxis(xaxis_data=[sk, sp])
|
|
563
|
+
.add_yaxis(
|
|
564
|
+
series_name="short_signal",
|
|
565
|
+
y_axis=[sk_price, sp_price],
|
|
566
|
+
is_smooth=False,
|
|
567
|
+
# linestyle_opts=opts.LineStyleOpts(opacity=0.5),
|
|
568
|
+
linestyle_opts=opts.LineStyleOpts(color="green", width=5, type_="dotted"),
|
|
569
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
570
|
+
symbol="arrow",
|
|
571
|
+
)
|
|
572
|
+
.set_global_opts(
|
|
573
|
+
xaxis_opts=opts.AxisOpts(
|
|
574
|
+
type_="category",
|
|
575
|
+
grid_index=1,
|
|
576
|
+
axislabel_opts=opts.LabelOpts(is_show=False),
|
|
577
|
+
),
|
|
578
|
+
yaxis_opts=opts.AxisOpts(
|
|
579
|
+
grid_index=1,
|
|
580
|
+
split_number=3,
|
|
581
|
+
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
|
|
582
|
+
axistick_opts=opts.AxisTickOpts(is_show=False),
|
|
583
|
+
splitline_opts=opts.SplitLineOpts(is_show=False),
|
|
584
|
+
axislabel_opts=opts.LabelOpts(is_show=True),
|
|
585
|
+
),
|
|
586
|
+
)
|
|
587
|
+
)
|
|
588
|
+
overlap_kline_line = kline.overlap(short_line)
|
|
589
|
+
except Exception as e:
|
|
590
|
+
logger.warning("Failed to create short line overlay: %s", e)
|
|
591
|
+
|
|
592
|
+
# Bar-1
|
|
593
|
+
bar_1 = (
|
|
594
|
+
Bar()
|
|
595
|
+
.add_xaxis(xaxis_data=data["times"])
|
|
596
|
+
.add_yaxis(
|
|
597
|
+
series_name="Volumn",
|
|
598
|
+
y_axis=data["vols"],
|
|
599
|
+
xaxis_index=1,
|
|
600
|
+
yaxis_index=1,
|
|
601
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
602
|
+
# According to echarts demo original version, it's written like this
|
|
603
|
+
# itemstyle_opts=opts.ItemStyleOpts(
|
|
604
|
+
# color=JsCode("""
|
|
605
|
+
# function(params) {
|
|
606
|
+
# var colorList;
|
|
607
|
+
# if (data.datas[params.dataIndex][1]>data.datas[params.dataIndex][0]) {
|
|
608
|
+
# colorList = '#ef232a';
|
|
609
|
+
# } else {
|
|
610
|
+
# colorList = '#14b143';
|
|
611
|
+
# }
|
|
612
|
+
# return colorList;
|
|
613
|
+
# }
|
|
614
|
+
# """)
|
|
615
|
+
# )
|
|
616
|
+
# After improvement, after add_js_funcs in grid, it becomes as follows
|
|
617
|
+
itemstyle_opts=opts.ItemStyleOpts(color=JsCode("""
|
|
618
|
+
function(params) {
|
|
619
|
+
var colorList;
|
|
620
|
+
if (barData[params.dataIndex][1] > barData[params.dataIndex][0]) {
|
|
621
|
+
colorList = '#ef232a';
|
|
622
|
+
} else {
|
|
623
|
+
colorList = '#14b143';
|
|
624
|
+
}
|
|
625
|
+
return colorList;
|
|
626
|
+
}
|
|
627
|
+
""")),
|
|
628
|
+
)
|
|
629
|
+
.set_global_opts(
|
|
630
|
+
xaxis_opts=opts.AxisOpts(
|
|
631
|
+
type_="category",
|
|
632
|
+
grid_index=1,
|
|
633
|
+
axislabel_opts=opts.LabelOpts(is_show=False),
|
|
634
|
+
),
|
|
635
|
+
legend_opts=opts.LegendOpts(is_show=False),
|
|
636
|
+
)
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
# Bar-2 (Overlap Bar + Line)
|
|
640
|
+
bar_2 = (
|
|
641
|
+
Bar()
|
|
642
|
+
.add_xaxis(xaxis_data=data["times"])
|
|
643
|
+
.add_yaxis(
|
|
644
|
+
series_name="MACD",
|
|
645
|
+
y_axis=data["macds"],
|
|
646
|
+
xaxis_index=2,
|
|
647
|
+
yaxis_index=2,
|
|
648
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
649
|
+
itemstyle_opts=opts.ItemStyleOpts(color=JsCode("""
|
|
650
|
+
function(params) {
|
|
651
|
+
var colorList;
|
|
652
|
+
if (params.data >= 0) {
|
|
653
|
+
colorList = '#ef232a';
|
|
654
|
+
} else {
|
|
655
|
+
colorList = '#14b143';
|
|
656
|
+
}
|
|
657
|
+
return colorList;
|
|
658
|
+
}
|
|
659
|
+
""")),
|
|
660
|
+
)
|
|
661
|
+
.set_global_opts(
|
|
662
|
+
xaxis_opts=opts.AxisOpts(
|
|
663
|
+
type_="category",
|
|
664
|
+
grid_index=2,
|
|
665
|
+
axislabel_opts=opts.LabelOpts(is_show=False),
|
|
666
|
+
),
|
|
667
|
+
yaxis_opts=opts.AxisOpts(
|
|
668
|
+
grid_index=2,
|
|
669
|
+
split_number=4,
|
|
670
|
+
axisline_opts=opts.AxisLineOpts(is_on_zero=False),
|
|
671
|
+
axistick_opts=opts.AxisTickOpts(is_show=False),
|
|
672
|
+
splitline_opts=opts.SplitLineOpts(is_show=False),
|
|
673
|
+
axislabel_opts=opts.LabelOpts(is_show=True),
|
|
674
|
+
),
|
|
675
|
+
legend_opts=opts.LegendOpts(is_show=False),
|
|
676
|
+
)
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
line_2 = (
|
|
680
|
+
Line()
|
|
681
|
+
.add_xaxis(xaxis_data=data["times"])
|
|
682
|
+
.add_yaxis(
|
|
683
|
+
series_name="DIF",
|
|
684
|
+
y_axis=data["difs"],
|
|
685
|
+
xaxis_index=2,
|
|
686
|
+
yaxis_index=2,
|
|
687
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
688
|
+
)
|
|
689
|
+
.add_yaxis(
|
|
690
|
+
series_name="DIF",
|
|
691
|
+
y_axis=data["deas"],
|
|
692
|
+
xaxis_index=2,
|
|
693
|
+
yaxis_index=2,
|
|
694
|
+
label_opts=opts.LabelOpts(is_show=False),
|
|
695
|
+
)
|
|
696
|
+
.set_global_opts(legend_opts=opts.LegendOpts(is_show=False))
|
|
697
|
+
)
|
|
698
|
+
# Bottom bar chart and line chart
|
|
699
|
+
overlap_bar_line = bar_2.overlap(line_2)
|
|
700
|
+
|
|
701
|
+
# Final Grid
|
|
702
|
+
grid_chart = Grid(init_opts=opts.InitOpts(width="1400px", height="800px"))
|
|
703
|
+
|
|
704
|
+
# This is to write data.datas into html, haven't figured out how to pass values across series
|
|
705
|
+
# Code in demo also uses global variables
|
|
706
|
+
grid_chart.add_js_funcs("var barData = {}".format(data["datas"]))
|
|
707
|
+
|
|
708
|
+
# K-line chart and MA5 line chart
|
|
709
|
+
grid_chart.add(
|
|
710
|
+
overlap_kline_line,
|
|
711
|
+
grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", height="60%"),
|
|
712
|
+
)
|
|
713
|
+
# Volume bar chart
|
|
714
|
+
grid_chart.add(
|
|
715
|
+
bar_1,
|
|
716
|
+
grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", pos_top="71%", height="10%"),
|
|
717
|
+
)
|
|
718
|
+
# MACD DIFS DEAS
|
|
719
|
+
grid_chart.add(
|
|
720
|
+
overlap_bar_line,
|
|
721
|
+
grid_opts=opts.GridOpts(pos_left="3%", pos_right="1%", pos_top="82%", height="14%"),
|
|
722
|
+
)
|
|
723
|
+
grid_chart.render("c:/result/test_price_action_kline_chart.html")
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
class PInfo:
|
|
727
|
+
"""Container for plotting information and state.
|
|
728
|
+
|
|
729
|
+
This class maintains all the state information needed during
|
|
730
|
+
the plotting process, including figure references, axes,
|
|
731
|
+
color schemes, and layout information.
|
|
732
|
+
|
|
733
|
+
Attributes:
|
|
734
|
+
sch: PlotScheme instance with plotting configuration
|
|
735
|
+
nrows: Total number of rows in the plot
|
|
736
|
+
row: Current row index
|
|
737
|
+
clock: Strategy or data object providing time reference
|
|
738
|
+
x: X-axis data points
|
|
739
|
+
xlen: Length of x-axis data
|
|
740
|
+
sharex: Shared x-axis reference
|
|
741
|
+
figs: List of figure objects
|
|
742
|
+
cursors: List of MultiCursor objects
|
|
743
|
+
daxis: Ordered dictionary mapping objects to axes
|
|
744
|
+
vaxis: List of vertical (twinx) axes
|
|
745
|
+
zorder: Dictionary mapping axes to z-order values
|
|
746
|
+
coloridx: Dictionary tracking color index per axis
|
|
747
|
+
handles: Dictionary of legend handles per axis
|
|
748
|
+
labels: Dictionary of legend labels per axis
|
|
749
|
+
legpos: Dictionary tracking legend position per axis
|
|
750
|
+
prop: FontProperties for subplot text
|
|
751
|
+
"""
|
|
752
|
+
|
|
753
|
+
def __init__(self, sch):
|
|
754
|
+
"""Initialize PInfo with plotting scheme.
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
sch: PlotScheme instance with plotting configuration
|
|
758
|
+
"""
|
|
759
|
+
self.sch = sch
|
|
760
|
+
self.nrows = 0
|
|
761
|
+
self.row = 0
|
|
762
|
+
self.clock = None
|
|
763
|
+
self.x = None
|
|
764
|
+
self.xlen = 0
|
|
765
|
+
self.sharex = None
|
|
766
|
+
self.figs = []
|
|
767
|
+
self.cursors = []
|
|
768
|
+
self.daxis = collections.OrderedDict()
|
|
769
|
+
self.vaxis = []
|
|
770
|
+
self.zorder = {}
|
|
771
|
+
self.coloridx = collections.defaultdict(lambda: -1)
|
|
772
|
+
self.handles = collections.defaultdict(list)
|
|
773
|
+
self.labels = collections.defaultdict(list)
|
|
774
|
+
self.legpos = collections.defaultdict(int)
|
|
775
|
+
|
|
776
|
+
self.prop = mfontmgr.FontProperties(size=self.sch.subtxtsize)
|
|
777
|
+
|
|
778
|
+
def newfig(self, figid, numfig, mpyplot):
|
|
779
|
+
"""Create a new matplotlib figure.
|
|
780
|
+
|
|
781
|
+
Args:
|
|
782
|
+
figid: Base figure identifier
|
|
783
|
+
numfig: Figure number suffix
|
|
784
|
+
mpyplot: Matplotlib pyplot module
|
|
785
|
+
|
|
786
|
+
Returns:
|
|
787
|
+
Figure object
|
|
788
|
+
"""
|
|
789
|
+
fig = mpyplot.figure(figid + numfig)
|
|
790
|
+
self.figs.append(fig)
|
|
791
|
+
self.daxis = collections.OrderedDict()
|
|
792
|
+
self.vaxis = []
|
|
793
|
+
self.row = 0
|
|
794
|
+
self.sharex = None
|
|
795
|
+
return fig
|
|
796
|
+
|
|
797
|
+
def nextcolor(self, ax):
|
|
798
|
+
"""Increment and get next color index for axis.
|
|
799
|
+
|
|
800
|
+
Args:
|
|
801
|
+
ax: Axis object
|
|
802
|
+
|
|
803
|
+
Returns:
|
|
804
|
+
Next color index
|
|
805
|
+
"""
|
|
806
|
+
self.coloridx[ax] += 1
|
|
807
|
+
return self.coloridx[ax]
|
|
808
|
+
|
|
809
|
+
def color(self, ax):
|
|
810
|
+
"""Get current color for axis.
|
|
811
|
+
|
|
812
|
+
Args:
|
|
813
|
+
ax: Axis object
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
Color string for the current color index
|
|
817
|
+
"""
|
|
818
|
+
return self.sch.color(self.coloridx[ax])
|
|
819
|
+
|
|
820
|
+
def zordernext(self, ax):
|
|
821
|
+
"""Get next z-order value for axis.
|
|
822
|
+
|
|
823
|
+
Args:
|
|
824
|
+
ax: Axis object
|
|
825
|
+
|
|
826
|
+
Returns:
|
|
827
|
+
Next z-order value (slightly higher or lower than current)
|
|
828
|
+
"""
|
|
829
|
+
z = self.zorder[ax]
|
|
830
|
+
if self.sch.zdown:
|
|
831
|
+
return z * 0.9999
|
|
832
|
+
return z * 1.0001
|
|
833
|
+
|
|
834
|
+
def zordercur(self, ax):
|
|
835
|
+
"""Get current z-order value for axis.
|
|
836
|
+
|
|
837
|
+
Args:
|
|
838
|
+
ax: Axis object
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
Current z-order value
|
|
842
|
+
"""
|
|
843
|
+
return self.zorder[ax]
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
class Plot_OldSync(ParameterizedBase):
|
|
847
|
+
"""Matplotlib-based plotting class for backtrader strategies.
|
|
848
|
+
|
|
849
|
+
This class provides the main plotting functionality for backtrader,
|
|
850
|
+
creating charts with price data, indicators, volume, and trading signals.
|
|
851
|
+
|
|
852
|
+
Attributes:
|
|
853
|
+
scheme: PlotScheme instance with plotting configuration
|
|
854
|
+
"""
|
|
855
|
+
|
|
856
|
+
scheme = ParameterDescriptor(default=PlotScheme(), doc="Plotting scheme to use")
|
|
857
|
+
|
|
858
|
+
def __init__(self, **kwargs):
|
|
859
|
+
"""Initialize Plot_OldSync with plotting scheme parameters.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
**kwargs: Plotting scheme parameters to override defaults
|
|
863
|
+
"""
|
|
864
|
+
# First call parent class initialization, so self.p can be set correctly
|
|
865
|
+
super().__init__()
|
|
866
|
+
|
|
867
|
+
# Then set scheme attributes
|
|
868
|
+
for pname, pvalue in kwargs.items():
|
|
869
|
+
setattr(self.p.scheme, pname, pvalue)
|
|
870
|
+
|
|
871
|
+
def drawtag(self, ax, x, y, facecolor, edgecolor, alpha=0.9, **kwargs):
|
|
872
|
+
"""Draw a text tag on the chart at specified coordinates.
|
|
873
|
+
|
|
874
|
+
Args:
|
|
875
|
+
ax: Axis object to draw on
|
|
876
|
+
x: X coordinate
|
|
877
|
+
y: Y coordinate
|
|
878
|
+
facecolor: Background color of the tag
|
|
879
|
+
edgecolor: Border color of the tag
|
|
880
|
+
alpha: Transparency level (default: 0.9)
|
|
881
|
+
**kwargs: Additional keyword arguments for text
|
|
882
|
+
"""
|
|
883
|
+
ax.text(
|
|
884
|
+
x,
|
|
885
|
+
y,
|
|
886
|
+
"%.2f" % y,
|
|
887
|
+
va="center",
|
|
888
|
+
ha="left",
|
|
889
|
+
fontsize=self.pinf.sch.subtxtsize,
|
|
890
|
+
bbox={
|
|
891
|
+
"boxstyle": tag_box_style,
|
|
892
|
+
"facecolor": facecolor,
|
|
893
|
+
"edgecolor": edgecolor,
|
|
894
|
+
"alpha": alpha,
|
|
895
|
+
},
|
|
896
|
+
# 3.0 is the minimum default for text
|
|
897
|
+
zorder=self.pinf.zorder[ax] + 3.0,
|
|
898
|
+
**kwargs,
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
def plot(self, strategy, figid=0, numfigs=1, iplot=True, start=None, end=None, **kwargs):
|
|
902
|
+
"""Generate plots for a backtrader strategy.
|
|
903
|
+
|
|
904
|
+
Creates matplotlib figures with price data, indicators, volume,
|
|
905
|
+
and other plot elements. Supports multiple figures and date ranges.
|
|
906
|
+
|
|
907
|
+
Args:
|
|
908
|
+
strategy: Strategy object with data and indicators
|
|
909
|
+
figid: Base figure identifier (default: 0)
|
|
910
|
+
numfigs: Number of figures to create (default: 1)
|
|
911
|
+
iplot: Whether to use interactive plotting (default: True)
|
|
912
|
+
start: Start date or index (default: None for beginning)
|
|
913
|
+
end: End date or index (default: None for end)
|
|
914
|
+
**kwargs: Additional plotting arguments
|
|
915
|
+
|
|
916
|
+
Returns:
|
|
917
|
+
List of matplotlib Figure objects
|
|
918
|
+
"""
|
|
919
|
+
# pfillers={}):
|
|
920
|
+
if not strategy.datas:
|
|
921
|
+
return None
|
|
922
|
+
|
|
923
|
+
if not len(strategy):
|
|
924
|
+
return None
|
|
925
|
+
|
|
926
|
+
self._iplot = iplot
|
|
927
|
+
if iplot:
|
|
928
|
+
if "ipykernel" in sys.modules:
|
|
929
|
+
matplotlib.use("nbagg")
|
|
930
|
+
|
|
931
|
+
# this import must not happen before matplotlib.use
|
|
932
|
+
import matplotlib.pyplot as mpyplot
|
|
933
|
+
|
|
934
|
+
self.mpyplot = mpyplot
|
|
935
|
+
|
|
936
|
+
self.pinf = PInfo(self.p.scheme)
|
|
937
|
+
self.sortdataindicators(strategy)
|
|
938
|
+
self.calcrows(strategy)
|
|
939
|
+
|
|
940
|
+
st_dtime = strategy.lines.datetime.plot()
|
|
941
|
+
if start is None:
|
|
942
|
+
start = 0
|
|
943
|
+
if end is None:
|
|
944
|
+
end = len(st_dtime)
|
|
945
|
+
|
|
946
|
+
if isinstance(start, datetime.date):
|
|
947
|
+
start = bisect.bisect_left(st_dtime, date2num(start))
|
|
948
|
+
|
|
949
|
+
if isinstance(end, datetime.date):
|
|
950
|
+
end = bisect.bisect_right(st_dtime, date2num(end))
|
|
951
|
+
|
|
952
|
+
if end < 0:
|
|
953
|
+
end = len(st_dtime) + 1 + end # -1 = len() -2 = len() - 1
|
|
954
|
+
|
|
955
|
+
slen = len(st_dtime[start:end])
|
|
956
|
+
d, m = divmod(slen, numfigs)
|
|
957
|
+
pranges = []
|
|
958
|
+
for i in range(numfigs):
|
|
959
|
+
a = d * i + start
|
|
960
|
+
if i == (numfigs - 1):
|
|
961
|
+
d += m # add a remainder to last stint
|
|
962
|
+
b = a + d
|
|
963
|
+
|
|
964
|
+
pranges.append([a, b, d])
|
|
965
|
+
|
|
966
|
+
figs = []
|
|
967
|
+
|
|
968
|
+
for numfig in range(numfigs):
|
|
969
|
+
# prepare a figure
|
|
970
|
+
fig = self.pinf.newfig(figid, numfig, self.mpyplot)
|
|
971
|
+
figs.append(fig)
|
|
972
|
+
|
|
973
|
+
self.pinf.pstart, self.pinf.pend, self.pinf.psize = pranges[numfig]
|
|
974
|
+
self.pinf.xstart = self.pinf.pstart
|
|
975
|
+
self.pinf.xend = self.pinf.pend
|
|
976
|
+
|
|
977
|
+
self.pinf.clock = strategy
|
|
978
|
+
self.pinf.xreal = self.pinf.clock.datetime.plot(self.pinf.pstart, self.pinf.psize)
|
|
979
|
+
self.pinf.xlen = len(self.pinf.xreal)
|
|
980
|
+
self.pinf.x = list(range(self.pinf.xlen))
|
|
981
|
+
# self.pinf.pfillers = {None: []}
|
|
982
|
+
# for key, val in pfillers.items():
|
|
983
|
+
# pfstart = bisect.bisect_left(val, self.pinf.pstart)
|
|
984
|
+
# pfend = bisect.bisect_right(val, self.pinf.pend)
|
|
985
|
+
# self.pinf.pfillers[key] = val[pfstart:pfend]
|
|
986
|
+
|
|
987
|
+
# Do the plotting
|
|
988
|
+
# Things that go always at the top (observers)
|
|
989
|
+
self.pinf.xdata = self.pinf.x
|
|
990
|
+
for ptop in self.dplotstop:
|
|
991
|
+
self.plotind(None, ptop, subinds=self.dplotsover[ptop])
|
|
992
|
+
|
|
993
|
+
# Create the rest on a per-data basis
|
|
994
|
+
dt0, dt1 = self.pinf.xreal[0], self.pinf.xreal[-1]
|
|
995
|
+
for data in strategy.datas:
|
|
996
|
+
if not data.plotinfo.plot:
|
|
997
|
+
continue
|
|
998
|
+
|
|
999
|
+
self.pinf.xdata = self.pinf.x
|
|
1000
|
+
xd = data.datetime.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1001
|
+
if len(xd) < self.pinf.xlen:
|
|
1002
|
+
self.pinf.xdata = xdata = []
|
|
1003
|
+
xreal = self.pinf.xreal
|
|
1004
|
+
dts = data.datetime.plot()
|
|
1005
|
+
xtemp = []
|
|
1006
|
+
for dt in (x for x in dts if dt0 <= x <= dt1):
|
|
1007
|
+
dtidx = bisect.bisect_left(xreal, dt)
|
|
1008
|
+
xdata.append(dtidx)
|
|
1009
|
+
xtemp.append(dt)
|
|
1010
|
+
|
|
1011
|
+
self.pinf.xstart = bisect.bisect_left(dts, xtemp[0])
|
|
1012
|
+
self.pinf.xend = bisect.bisect_right(dts, xtemp[-1])
|
|
1013
|
+
|
|
1014
|
+
for ind in self.dplotsup[data]:
|
|
1015
|
+
self.plotind(
|
|
1016
|
+
data,
|
|
1017
|
+
ind,
|
|
1018
|
+
subinds=self.dplotsover[ind],
|
|
1019
|
+
upinds=self.dplotsup[ind],
|
|
1020
|
+
downinds=self.dplotsdown[ind],
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
self.plotdata(data, self.dplotsover[data])
|
|
1024
|
+
|
|
1025
|
+
for ind in self.dplotsdown[data]:
|
|
1026
|
+
self.plotind(
|
|
1027
|
+
data,
|
|
1028
|
+
ind,
|
|
1029
|
+
subinds=self.dplotsover[ind],
|
|
1030
|
+
upinds=self.dplotsup[ind],
|
|
1031
|
+
downinds=self.dplotsdown[ind],
|
|
1032
|
+
)
|
|
1033
|
+
|
|
1034
|
+
cursor = MultiCursor(
|
|
1035
|
+
fig.canvas,
|
|
1036
|
+
list(self.pinf.daxis.values()),
|
|
1037
|
+
useblit=True,
|
|
1038
|
+
horizOn=True,
|
|
1039
|
+
vertOn=True,
|
|
1040
|
+
horizMulti=False,
|
|
1041
|
+
vertMulti=True,
|
|
1042
|
+
horizShared=True,
|
|
1043
|
+
vertShared=False,
|
|
1044
|
+
color="black",
|
|
1045
|
+
lw=1,
|
|
1046
|
+
ls=":",
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
self.pinf.cursors.append(cursor)
|
|
1050
|
+
|
|
1051
|
+
# Put the subplots as indicated by hspace
|
|
1052
|
+
fig.subplots_adjust(
|
|
1053
|
+
hspace=self.pinf.sch.plotdist, top=0.98, left=0.05, bottom=0.05, right=0.95
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
laxis = list(self.pinf.daxis.values())
|
|
1057
|
+
|
|
1058
|
+
# Find the last axis which is not a twinx (date locator fails there)
|
|
1059
|
+
i = -1
|
|
1060
|
+
while True:
|
|
1061
|
+
lastax = laxis[i]
|
|
1062
|
+
if lastax not in self.pinf.vaxis:
|
|
1063
|
+
break
|
|
1064
|
+
|
|
1065
|
+
i -= 1
|
|
1066
|
+
|
|
1067
|
+
self.setlocators(lastax) # place the locators/fmts
|
|
1068
|
+
|
|
1069
|
+
# Applying fig.autofmt_xdate if the data axis is the last one
|
|
1070
|
+
# breaks the presentation of the date labels. why?
|
|
1071
|
+
# Applying the manual rotation with setp cures the problem
|
|
1072
|
+
# but the labels from all axis but the last have to be hidden
|
|
1073
|
+
for ax in laxis:
|
|
1074
|
+
self.mpyplot.setp(ax.get_xticklabels(), visible=False)
|
|
1075
|
+
|
|
1076
|
+
self.mpyplot.setp(
|
|
1077
|
+
lastax.get_xticklabels(), visible=True, rotation=self.pinf.sch.tickrotation
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
# Things must be tight along the x-axis (to fill both ends)
|
|
1081
|
+
axtight = "x" if not self.pinf.sch.ytight else "both"
|
|
1082
|
+
# self.mpyplot.xticks(pd.date_range(start,end),rotation=90)
|
|
1083
|
+
self.mpyplot.autoscale(enable=True, axis=axtight, tight=True)
|
|
1084
|
+
|
|
1085
|
+
return figs
|
|
1086
|
+
|
|
1087
|
+
def setlocators(self, ax):
|
|
1088
|
+
"""Set date locators and formatters for x-axis.
|
|
1089
|
+
|
|
1090
|
+
Configures automatic date formatting based on the timeframe
|
|
1091
|
+
of the data being plotted.
|
|
1092
|
+
|
|
1093
|
+
Args:
|
|
1094
|
+
ax: Axis object to configure
|
|
1095
|
+
"""
|
|
1096
|
+
tframe = getattr(self.pinf.clock, "_timeframe", TimeFrame.Days)
|
|
1097
|
+
|
|
1098
|
+
if self.pinf.sch.fmt_x_data is None:
|
|
1099
|
+
if tframe == TimeFrame.Years:
|
|
1100
|
+
fmtdata = "%Y"
|
|
1101
|
+
elif tframe == TimeFrame.Months:
|
|
1102
|
+
fmtdata = "%Y-%m"
|
|
1103
|
+
elif tframe == TimeFrame.Weeks or tframe == TimeFrame.Days:
|
|
1104
|
+
fmtdata = "%Y-%m-%d"
|
|
1105
|
+
elif tframe == TimeFrame.Minutes:
|
|
1106
|
+
fmtdata = "%Y-%m-%d %H:%M"
|
|
1107
|
+
elif tframe == TimeFrame.Seconds:
|
|
1108
|
+
fmtdata = "%Y-%m-%d %H:%M:%S"
|
|
1109
|
+
elif tframe == TimeFrame.MicroSeconds or tframe == TimeFrame.Ticks:
|
|
1110
|
+
fmtdata = "%Y-%m-%d %H:%M:%S.%f"
|
|
1111
|
+
else:
|
|
1112
|
+
fmtdata = self.pinf.sch.fmt_x_data
|
|
1113
|
+
|
|
1114
|
+
fordata = MyDateFormatter(self.pinf.xreal, fmt=fmtdata)
|
|
1115
|
+
for dax in self.pinf.daxis.values():
|
|
1116
|
+
dax.fmt_xdata = fordata
|
|
1117
|
+
|
|
1118
|
+
# Major locator / formatter
|
|
1119
|
+
locmajor = loc.AutoDateLocator(self.pinf.xreal)
|
|
1120
|
+
ax.xaxis.set_major_locator(locmajor)
|
|
1121
|
+
if self.pinf.sch.fmt_x_ticks is None:
|
|
1122
|
+
autofmt = loc.AutoDateFormatter(self.pinf.xreal, locmajor)
|
|
1123
|
+
else:
|
|
1124
|
+
autofmt = MyDateFormatter(self.pinf.xreal, fmt=self.pinf.sch.fmt_x_ticks)
|
|
1125
|
+
ax.xaxis.set_major_formatter(autofmt)
|
|
1126
|
+
|
|
1127
|
+
def calcrows(self, strategy):
|
|
1128
|
+
"""Calculate the total number of rows needed for plotting.
|
|
1129
|
+
|
|
1130
|
+
Determines how many subplot rows are needed based on data feeds,
|
|
1131
|
+
indicators, observers, and volume plots.
|
|
1132
|
+
|
|
1133
|
+
Args:
|
|
1134
|
+
strategy: Strategy object with data and indicators
|
|
1135
|
+
"""
|
|
1136
|
+
# Calculate the total number of rows
|
|
1137
|
+
rowsmajor = self.pinf.sch.rowsmajor
|
|
1138
|
+
rowsminor = self.pinf.sch.rowsminor
|
|
1139
|
+
nrows = 0
|
|
1140
|
+
|
|
1141
|
+
datasnoplot = 0
|
|
1142
|
+
for data in strategy.datas:
|
|
1143
|
+
if not data.plotinfo.plot:
|
|
1144
|
+
# neither data nor indicators nor volume add rows
|
|
1145
|
+
datasnoplot += 1
|
|
1146
|
+
self.dplotsup.pop(data, None)
|
|
1147
|
+
self.dplotsdown.pop(data, None)
|
|
1148
|
+
self.dplotsover.pop(data, None)
|
|
1149
|
+
|
|
1150
|
+
else:
|
|
1151
|
+
pmaster = data.plotinfo.plotmaster
|
|
1152
|
+
if pmaster is data:
|
|
1153
|
+
pmaster = None
|
|
1154
|
+
if pmaster is not None:
|
|
1155
|
+
# data doesn't add a row, but volume may
|
|
1156
|
+
if self.pinf.sch.volume:
|
|
1157
|
+
nrows += rowsminor
|
|
1158
|
+
else:
|
|
1159
|
+
# data adds rows, volume may
|
|
1160
|
+
nrows += rowsmajor
|
|
1161
|
+
if self.pinf.sch.volume and not self.pinf.sch.voloverlay:
|
|
1162
|
+
nrows += rowsminor
|
|
1163
|
+
|
|
1164
|
+
if False:
|
|
1165
|
+
# Datas and volumes
|
|
1166
|
+
nrows += (len(strategy.datas) - datasnoplot) * rowsmajor
|
|
1167
|
+
if self.pinf.sch.volume and not self.pinf.sch.voloverlay:
|
|
1168
|
+
nrows += (len(strategy.datas) - datasnoplot) * rowsminor
|
|
1169
|
+
|
|
1170
|
+
# top indicators/observers
|
|
1171
|
+
nrows += len(self.dplotstop) * rowsminor
|
|
1172
|
+
|
|
1173
|
+
# indicators above datas
|
|
1174
|
+
nrows += sum(len(v) for v in self.dplotsup.values())
|
|
1175
|
+
nrows += sum(len(v) for v in self.dplotsdown.values())
|
|
1176
|
+
|
|
1177
|
+
self.pinf.nrows = nrows
|
|
1178
|
+
|
|
1179
|
+
def newaxis(self, obj, rowspan):
|
|
1180
|
+
"""Create a new axis for plotting.
|
|
1181
|
+
|
|
1182
|
+
Creates a subplot axis with the specified row span and
|
|
1183
|
+
configures it with appropriate settings.
|
|
1184
|
+
|
|
1185
|
+
Args:
|
|
1186
|
+
obj: Object to associate with this axis
|
|
1187
|
+
rowspan: Number of rows this axis should span
|
|
1188
|
+
|
|
1189
|
+
Returns:
|
|
1190
|
+
Axis object
|
|
1191
|
+
"""
|
|
1192
|
+
ax = self.mpyplot.subplot2grid(
|
|
1193
|
+
(self.pinf.nrows, 1), (self.pinf.row, 0), rowspan=rowspan, sharex=self.pinf.sharex
|
|
1194
|
+
)
|
|
1195
|
+
|
|
1196
|
+
# update the sharex information if not available
|
|
1197
|
+
if self.pinf.sharex is None:
|
|
1198
|
+
self.pinf.sharex = ax
|
|
1199
|
+
|
|
1200
|
+
# update the row index with the taken rows
|
|
1201
|
+
self.pinf.row += rowspan
|
|
1202
|
+
|
|
1203
|
+
# save the mapping indicator - axis and return
|
|
1204
|
+
self.pinf.daxis[obj] = ax
|
|
1205
|
+
|
|
1206
|
+
# Activate grid in all axes if requested
|
|
1207
|
+
ax.yaxis.tick_right()
|
|
1208
|
+
ax.grid(self.pinf.sch.grid, which="both")
|
|
1209
|
+
|
|
1210
|
+
return ax
|
|
1211
|
+
|
|
1212
|
+
def plotind(self, iref, ind, subinds=None, upinds=None, downinds=None, masterax=None):
|
|
1213
|
+
"""Plot an indicator with optional sub-indicators.
|
|
1214
|
+
|
|
1215
|
+
Plots an indicator on an axis, handling line styling, legends,
|
|
1216
|
+
fills, and recursively plotting sub-indicators.
|
|
1217
|
+
|
|
1218
|
+
Args:
|
|
1219
|
+
iref: Reference object (usually data feed)
|
|
1220
|
+
ind: Indicator object to plot
|
|
1221
|
+
subinds: List of sub-indicators to plot on same axis (default: None)
|
|
1222
|
+
upinds: List of indicators to plot above (default: None)
|
|
1223
|
+
downinds: List of indicators to plot below (default: None)
|
|
1224
|
+
masterax: Master axis to plot on (default: None to create new)
|
|
1225
|
+
"""
|
|
1226
|
+
# check subind
|
|
1227
|
+
subinds = subinds or []
|
|
1228
|
+
upinds = upinds or []
|
|
1229
|
+
downinds = downinds or []
|
|
1230
|
+
|
|
1231
|
+
# plot subindicators on self with independent axis above
|
|
1232
|
+
for upind in upinds:
|
|
1233
|
+
self.plotind(iref, upind)
|
|
1234
|
+
|
|
1235
|
+
# Get an axis for this plot
|
|
1236
|
+
ax = masterax or self.newaxis(ind, rowspan=self.pinf.sch.rowsminor)
|
|
1237
|
+
|
|
1238
|
+
indlabel = ind.plotlabel()
|
|
1239
|
+
if not isinstance(indlabel, str):
|
|
1240
|
+
indlabel = ind.__class__.__name__
|
|
1241
|
+
|
|
1242
|
+
# Scan lines quickly to find out if some lines have to be skipped for
|
|
1243
|
+
# legend (because matplotlib reorders the legend)
|
|
1244
|
+
toskip = 0
|
|
1245
|
+
for lineidx in range(ind.size()):
|
|
1246
|
+
line = ind.lines[lineidx]
|
|
1247
|
+
linealias = ind.lines._getlinealias(lineidx)
|
|
1248
|
+
lineplotinfo = getattr(ind.plotlines, "_%d" % lineidx, None)
|
|
1249
|
+
if not lineplotinfo:
|
|
1250
|
+
lineplotinfo = getattr(ind.plotlines, linealias, None)
|
|
1251
|
+
if not lineplotinfo:
|
|
1252
|
+
lineplotinfo = AutoInfoClass()
|
|
1253
|
+
pltmethod = lineplotinfo._get("_method", "plot")
|
|
1254
|
+
if pltmethod != "plot":
|
|
1255
|
+
toskip += 1 - lineplotinfo._get("_plotskip", False)
|
|
1256
|
+
|
|
1257
|
+
if toskip >= ind.size():
|
|
1258
|
+
toskip = 0
|
|
1259
|
+
|
|
1260
|
+
for lineidx in range(ind.size()):
|
|
1261
|
+
line = ind.lines[lineidx]
|
|
1262
|
+
linealias = ind.lines._getlinealias(lineidx)
|
|
1263
|
+
|
|
1264
|
+
lineplotinfo = getattr(ind.plotlines, "_%d" % lineidx, None)
|
|
1265
|
+
if not lineplotinfo:
|
|
1266
|
+
lineplotinfo = getattr(ind.plotlines, linealias, None)
|
|
1267
|
+
|
|
1268
|
+
if not lineplotinfo:
|
|
1269
|
+
lineplotinfo = AutoInfoClass()
|
|
1270
|
+
|
|
1271
|
+
if lineplotinfo._get("_plotskip", False):
|
|
1272
|
+
continue
|
|
1273
|
+
|
|
1274
|
+
# Legend label only when plotting 1st line
|
|
1275
|
+
if masterax and not ind.plotinfo.plotlinelabels:
|
|
1276
|
+
label = indlabel * (not toskip) or "_nolegend"
|
|
1277
|
+
else:
|
|
1278
|
+
label = (indlabel + "\n") * (not toskip)
|
|
1279
|
+
label += lineplotinfo._get("_name", "") or linealias
|
|
1280
|
+
|
|
1281
|
+
toskip -= 1 # one line less until legend can be added
|
|
1282
|
+
|
|
1283
|
+
# plot data
|
|
1284
|
+
lplot = line.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1285
|
+
|
|
1286
|
+
# Global and generic for indicator
|
|
1287
|
+
if self.pinf.sch.linevalues and ind.plotinfo.plotlinevalues:
|
|
1288
|
+
plotlinevalue = lineplotinfo._get("_plotvalue", True)
|
|
1289
|
+
if len(lplot) > 0:
|
|
1290
|
+
if plotlinevalue and not math.isnan(lplot[-1]):
|
|
1291
|
+
label += " %.2f" % lplot[-1]
|
|
1292
|
+
|
|
1293
|
+
plotkwargs = {}
|
|
1294
|
+
get_linekwargs = getattr(lineplotinfo, "_getkwargs", None)
|
|
1295
|
+
linekwargs = get_linekwargs(skip_=True) if get_linekwargs is not None else {}
|
|
1296
|
+
|
|
1297
|
+
if linekwargs.get("color", None) is None:
|
|
1298
|
+
if not lineplotinfo._get("_samecolor", False):
|
|
1299
|
+
self.pinf.nextcolor(ax)
|
|
1300
|
+
plotkwargs["color"] = self.pinf.color(ax)
|
|
1301
|
+
|
|
1302
|
+
plotkwargs.update({"aa": True, "label": label})
|
|
1303
|
+
plotkwargs.update(**linekwargs)
|
|
1304
|
+
|
|
1305
|
+
if ax in self.pinf.zorder:
|
|
1306
|
+
plotkwargs["zorder"] = self.pinf.zordernext(ax)
|
|
1307
|
+
|
|
1308
|
+
pltmethod = getattr(ax, lineplotinfo._get("_method", "plot"))
|
|
1309
|
+
|
|
1310
|
+
xdata, lplotarray = self.pinf.xdata, lplot
|
|
1311
|
+
|
|
1312
|
+
# CRITICAL FIX: Check if array is empty to avoid dimension mismatch error
|
|
1313
|
+
# Fix dimension mismatch error: ValueError: x and y must have same first dimension
|
|
1314
|
+
if not lplotarray or len(lplotarray) == 0:
|
|
1315
|
+
# If data is empty, skip plotting
|
|
1316
|
+
plottedline = None
|
|
1317
|
+
return # Force exit from this line drawing
|
|
1318
|
+
|
|
1319
|
+
if lineplotinfo._get("_skipnan", False):
|
|
1320
|
+
# Get the full array and a mask to skipnan
|
|
1321
|
+
lplotarray = np.array(lplot)
|
|
1322
|
+
lplotmask = np.isfinite(lplotarray)
|
|
1323
|
+
|
|
1324
|
+
# Get both the axis and the data masked
|
|
1325
|
+
lplotarray = lplotarray[lplotmask]
|
|
1326
|
+
xdata = np.array(xdata)[lplotmask]
|
|
1327
|
+
|
|
1328
|
+
# Check again if array is empty
|
|
1329
|
+
if len(lplotarray) == 0 or len(xdata) == 0 or len(lplotarray) != len(xdata):
|
|
1330
|
+
plottedline = None
|
|
1331
|
+
return # Skip plotting
|
|
1332
|
+
|
|
1333
|
+
plottedline = pltmethod(xdata, lplotarray, **plotkwargs)
|
|
1334
|
+
try:
|
|
1335
|
+
plottedline = plottedline[0]
|
|
1336
|
+
except (TypeError, IndexError):
|
|
1337
|
+
# Possibly a container of artists (when plotting bars)
|
|
1338
|
+
logger.debug("plot:1338 ignored TypeError,IndexError")
|
|
1339
|
+
|
|
1340
|
+
self.pinf.zorder[ax] = plottedline.get_zorder()
|
|
1341
|
+
|
|
1342
|
+
vtags = lineplotinfo._get("plotvaluetags", True)
|
|
1343
|
+
if self.pinf.sch.valuetags and vtags:
|
|
1344
|
+
linetag = lineplotinfo._get("_plotvaluetag", True)
|
|
1345
|
+
if linetag and not math.isnan(lplot[-1]):
|
|
1346
|
+
# line has valid values, plot a tag for the last value
|
|
1347
|
+
self.drawtag(
|
|
1348
|
+
ax,
|
|
1349
|
+
len(self.pinf.xreal),
|
|
1350
|
+
lplot[-1],
|
|
1351
|
+
facecolor="white",
|
|
1352
|
+
edgecolor=self.pinf.color(ax),
|
|
1353
|
+
)
|
|
1354
|
+
|
|
1355
|
+
farts = (
|
|
1356
|
+
("_gt", operator.gt),
|
|
1357
|
+
("_lt", operator.lt),
|
|
1358
|
+
("", None),
|
|
1359
|
+
)
|
|
1360
|
+
for fcmp, fop in farts:
|
|
1361
|
+
fattr = "_fill" + fcmp
|
|
1362
|
+
fref, fcol = lineplotinfo._get(fattr, (None, None))
|
|
1363
|
+
if fref is not None:
|
|
1364
|
+
y1 = np.array(lplot)
|
|
1365
|
+
if isinstance(fref, integer_types):
|
|
1366
|
+
y2 = np.full_like(y1, fref)
|
|
1367
|
+
else: # string, naming a line, nothing else is supported
|
|
1368
|
+
l2 = getattr(ind, fref)
|
|
1369
|
+
prl2 = l2.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1370
|
+
y2 = np.array(prl2)
|
|
1371
|
+
kwargs = {}
|
|
1372
|
+
if fop is not None:
|
|
1373
|
+
kwargs["where"] = fop(y1, y2)
|
|
1374
|
+
|
|
1375
|
+
falpha = self.pinf.sch.fillalpha
|
|
1376
|
+
if isinstance(fcol, (list, tuple)):
|
|
1377
|
+
fcol, falpha = fcol
|
|
1378
|
+
|
|
1379
|
+
ax.fill_between(
|
|
1380
|
+
self.pinf.xdata,
|
|
1381
|
+
y1,
|
|
1382
|
+
y2,
|
|
1383
|
+
facecolor=fcol,
|
|
1384
|
+
alpha=falpha,
|
|
1385
|
+
interpolate=True,
|
|
1386
|
+
**kwargs,
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
# plot subindicators that were created on self
|
|
1390
|
+
for subind in subinds:
|
|
1391
|
+
self.plotind(iref, subind, subinds=self.dplotsover[subind], masterax=ax)
|
|
1392
|
+
|
|
1393
|
+
if not masterax:
|
|
1394
|
+
# adjust margin if requested ... general of particular
|
|
1395
|
+
ymargin = ind.plotinfo._get("plotymargin", 0.0) or 0.0
|
|
1396
|
+
ymargin = max(ymargin, self.pinf.sch.yadjust or 0.0)
|
|
1397
|
+
if ymargin:
|
|
1398
|
+
ax.margins(y=ymargin)
|
|
1399
|
+
|
|
1400
|
+
# Set specific or generic ticks
|
|
1401
|
+
yticks = ind.plotinfo._get("plotyticks", [])
|
|
1402
|
+
if not yticks:
|
|
1403
|
+
yticks = ind.plotinfo._get("plotyhlines", [])
|
|
1404
|
+
|
|
1405
|
+
if yticks:
|
|
1406
|
+
ax.set_yticks(yticks)
|
|
1407
|
+
else:
|
|
1408
|
+
locator = mticker.MaxNLocator(nbins=4, prune="both")
|
|
1409
|
+
ax.yaxis.set_major_locator(locator)
|
|
1410
|
+
|
|
1411
|
+
# Set specific hlines if asked to
|
|
1412
|
+
hlines = ind.plotinfo._get("plothlines", [])
|
|
1413
|
+
if not hlines:
|
|
1414
|
+
hlines = ind.plotinfo._get("plotyhlines", [])
|
|
1415
|
+
for hline in hlines or []:
|
|
1416
|
+
ax.axhline(
|
|
1417
|
+
hline,
|
|
1418
|
+
color=self.pinf.sch.hlinescolor,
|
|
1419
|
+
ls=self.pinf.sch.hlinesstyle,
|
|
1420
|
+
lw=self.pinf.sch.hlineswidth,
|
|
1421
|
+
)
|
|
1422
|
+
|
|
1423
|
+
if self.pinf.sch.legendind and ind.plotinfo._get("plotlegend", True):
|
|
1424
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
1425
|
+
# Ensure that we have something to show
|
|
1426
|
+
if labels:
|
|
1427
|
+
# location can come from the user
|
|
1428
|
+
loc = getattr(ind.plotinfo, "legendloc", None) or self.pinf.sch.legendindloc
|
|
1429
|
+
|
|
1430
|
+
# Legend done here to ensure it includes all plots
|
|
1431
|
+
legend = ax.legend(
|
|
1432
|
+
loc=loc,
|
|
1433
|
+
numpoints=1,
|
|
1434
|
+
frameon=False,
|
|
1435
|
+
shadow=False,
|
|
1436
|
+
fancybox=False,
|
|
1437
|
+
prop=self.pinf.prop,
|
|
1438
|
+
)
|
|
1439
|
+
|
|
1440
|
+
# Matplotlib legend box defaults to center alignment; override to left
|
|
1441
|
+
legend._legend_box.align = "left"
|
|
1442
|
+
|
|
1443
|
+
# plot subindicators on self with independent axis below
|
|
1444
|
+
for downind in downinds:
|
|
1445
|
+
self.plotind(iref, downind)
|
|
1446
|
+
|
|
1447
|
+
def plotvolume(self, data, opens, highs, lows, closes, volumes, label):
|
|
1448
|
+
"""Plot volume for a data feed.
|
|
1449
|
+
|
|
1450
|
+
Creates volume bars with appropriate coloring based on price movement.
|
|
1451
|
+
Can be overlaid on price chart or shown in separate subplot.
|
|
1452
|
+
|
|
1453
|
+
Args:
|
|
1454
|
+
data: Data feed object
|
|
1455
|
+
opens: Array of open prices
|
|
1456
|
+
highs: Array of high prices
|
|
1457
|
+
lows: Array of low prices
|
|
1458
|
+
closes: Array of close prices
|
|
1459
|
+
volumes: Array of volume values
|
|
1460
|
+
label: Label for the volume plot
|
|
1461
|
+
|
|
1462
|
+
Returns:
|
|
1463
|
+
Volume plot artist or None
|
|
1464
|
+
"""
|
|
1465
|
+
pmaster = data.plotinfo.plotmaster
|
|
1466
|
+
if pmaster is data:
|
|
1467
|
+
pmaster = None
|
|
1468
|
+
voloverlay = self.pinf.sch.voloverlay and pmaster is None
|
|
1469
|
+
|
|
1470
|
+
# if sefl.pinf.sch.voloverlay:
|
|
1471
|
+
if voloverlay:
|
|
1472
|
+
rowspan = self.pinf.sch.rowsmajor
|
|
1473
|
+
else:
|
|
1474
|
+
rowspan = self.pinf.sch.rowsminor
|
|
1475
|
+
|
|
1476
|
+
ax = self.newaxis(data.volume, rowspan=rowspan)
|
|
1477
|
+
|
|
1478
|
+
# if self.pinf.sch.voloverlay:
|
|
1479
|
+
if voloverlay:
|
|
1480
|
+
volalpha = self.pinf.sch.voltrans
|
|
1481
|
+
else:
|
|
1482
|
+
volalpha = 1.0
|
|
1483
|
+
|
|
1484
|
+
maxvol = volylim = max(volumes)
|
|
1485
|
+
if maxvol:
|
|
1486
|
+
# Plot the volume (no matter if as overlay or standalone)
|
|
1487
|
+
vollabel = label
|
|
1488
|
+
(volplot,) = plot_volume(
|
|
1489
|
+
ax,
|
|
1490
|
+
self.pinf.xdata,
|
|
1491
|
+
opens,
|
|
1492
|
+
closes,
|
|
1493
|
+
volumes,
|
|
1494
|
+
colorup=self.pinf.sch.volup,
|
|
1495
|
+
colordown=self.pinf.sch.voldown,
|
|
1496
|
+
alpha=volalpha,
|
|
1497
|
+
label=vollabel,
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1500
|
+
nbins = 6
|
|
1501
|
+
prune = "both"
|
|
1502
|
+
# if self.pinf.sch.voloverlay:
|
|
1503
|
+
if voloverlay:
|
|
1504
|
+
# store for a potential plot over it
|
|
1505
|
+
nbins = int(nbins / self.pinf.sch.volscaling)
|
|
1506
|
+
prune = None
|
|
1507
|
+
|
|
1508
|
+
volylim /= self.pinf.sch.volscaling
|
|
1509
|
+
ax.set_ylim(0, volylim, auto=True)
|
|
1510
|
+
else:
|
|
1511
|
+
# plot a legend
|
|
1512
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
1513
|
+
if handles:
|
|
1514
|
+
# location can come from the user
|
|
1515
|
+
loc = getattr(data.plotinfo, "legendloc", None) or self.pinf.sch.legendindloc
|
|
1516
|
+
|
|
1517
|
+
# Legend done here to ensure it includes all plots
|
|
1518
|
+
ax.legend(
|
|
1519
|
+
loc=loc,
|
|
1520
|
+
numpoints=1,
|
|
1521
|
+
frameon=False,
|
|
1522
|
+
shadow=False,
|
|
1523
|
+
fancybox=False,
|
|
1524
|
+
prop=self.pinf.prop,
|
|
1525
|
+
)
|
|
1526
|
+
|
|
1527
|
+
locator = mticker.MaxNLocator(nbins=nbins, prune=prune)
|
|
1528
|
+
ax.yaxis.set_major_locator(locator)
|
|
1529
|
+
ax.yaxis.set_major_formatter(MyVolFormatter(maxvol))
|
|
1530
|
+
|
|
1531
|
+
if not maxvol:
|
|
1532
|
+
ax.set_yticks([])
|
|
1533
|
+
return None
|
|
1534
|
+
|
|
1535
|
+
return volplot
|
|
1536
|
+
|
|
1537
|
+
def plotdata(self, data, indicators):
|
|
1538
|
+
"""Plot price data for a data feed.
|
|
1539
|
+
|
|
1540
|
+
Creates candlestick, bar, or line chart for price data along
|
|
1541
|
+
with volume and indicators. Handles overlay indicators and
|
|
1542
|
+
proper axis configuration.
|
|
1543
|
+
|
|
1544
|
+
Args:
|
|
1545
|
+
data: Data feed object with OHLCV data
|
|
1546
|
+
indicators: List of indicators to plot with this data
|
|
1547
|
+
"""
|
|
1548
|
+
for ind in indicators:
|
|
1549
|
+
upinds = self.dplotsup[ind]
|
|
1550
|
+
for upind in upinds:
|
|
1551
|
+
self.plotind(
|
|
1552
|
+
data,
|
|
1553
|
+
upind,
|
|
1554
|
+
subinds=self.dplotsover[upind],
|
|
1555
|
+
upinds=self.dplotsup[upind],
|
|
1556
|
+
downinds=self.dplotsdown[upind],
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
opens = data.open.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1560
|
+
highs = data.high.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1561
|
+
lows = data.low.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1562
|
+
closes = data.close.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1563
|
+
volumes = data.volume.plotrange(self.pinf.xstart, self.pinf.xend)
|
|
1564
|
+
|
|
1565
|
+
vollabel = "Volume"
|
|
1566
|
+
pmaster = data.plotinfo.plotmaster
|
|
1567
|
+
if pmaster is data:
|
|
1568
|
+
pmaster = None
|
|
1569
|
+
|
|
1570
|
+
datalabel = ""
|
|
1571
|
+
if hasattr(data, "_name") and data._name:
|
|
1572
|
+
datalabel += data._name
|
|
1573
|
+
|
|
1574
|
+
voloverlay = self.pinf.sch.voloverlay and pmaster is None
|
|
1575
|
+
|
|
1576
|
+
if not voloverlay:
|
|
1577
|
+
vollabel += f" ({datalabel})"
|
|
1578
|
+
|
|
1579
|
+
# if self.pinf.sch.volume and self.pinf.sch.voloverlay:
|
|
1580
|
+
axdatamaster = None
|
|
1581
|
+
if self.pinf.sch.volume and voloverlay:
|
|
1582
|
+
volplot = self.plotvolume(data, opens, highs, lows, closes, volumes, vollabel)
|
|
1583
|
+
axvol = self.pinf.daxis[data.volume]
|
|
1584
|
+
ax = axvol.twinx()
|
|
1585
|
+
self.pinf.daxis[data] = ax
|
|
1586
|
+
self.pinf.vaxis.append(ax)
|
|
1587
|
+
else:
|
|
1588
|
+
if pmaster is None:
|
|
1589
|
+
ax = self.newaxis(data, rowspan=self.pinf.sch.rowsmajor)
|
|
1590
|
+
elif getattr(data.plotinfo, "sameaxis", False):
|
|
1591
|
+
axdatamaster = self.pinf.daxis[pmaster]
|
|
1592
|
+
ax = axdatamaster
|
|
1593
|
+
else:
|
|
1594
|
+
axdatamaster = self.pinf.daxis[pmaster]
|
|
1595
|
+
ax = axdatamaster.twinx()
|
|
1596
|
+
self.pinf.vaxis.append(ax)
|
|
1597
|
+
|
|
1598
|
+
if hasattr(data, "_compression") and hasattr(data, "_timeframe"):
|
|
1599
|
+
tfname = TimeFrame.getname(data._timeframe, data._compression)
|
|
1600
|
+
datalabel += " (%d %s)" % (data._compression, tfname)
|
|
1601
|
+
|
|
1602
|
+
plinevalues = getattr(data.plotinfo, "plotlinevalues", True)
|
|
1603
|
+
if self.pinf.sch.style.startswith("line"):
|
|
1604
|
+
if self.pinf.sch.linevalues and plinevalues:
|
|
1605
|
+
datalabel += " C:%.2f" % closes[-1]
|
|
1606
|
+
|
|
1607
|
+
if axdatamaster is None:
|
|
1608
|
+
color = self.pinf.sch.loc
|
|
1609
|
+
else:
|
|
1610
|
+
self.pinf.nextcolor(axdatamaster)
|
|
1611
|
+
color = self.pinf.color(axdatamaster)
|
|
1612
|
+
|
|
1613
|
+
plotted = plot_lineonclose(ax, self.pinf.xdata, closes, color=color, label=datalabel)
|
|
1614
|
+
else:
|
|
1615
|
+
if self.pinf.sch.linevalues and plinevalues:
|
|
1616
|
+
datalabel += " O:{:.2f} H:{:.2f} L:{:.2f} C:{:.2f}".format(
|
|
1617
|
+
opens[-1],
|
|
1618
|
+
highs[-1],
|
|
1619
|
+
lows[-1],
|
|
1620
|
+
closes[-1],
|
|
1621
|
+
)
|
|
1622
|
+
if self.pinf.sch.style.startswith("candle"):
|
|
1623
|
+
plotted = plot_candlestick(
|
|
1624
|
+
ax,
|
|
1625
|
+
self.pinf.xdata,
|
|
1626
|
+
opens,
|
|
1627
|
+
highs,
|
|
1628
|
+
lows,
|
|
1629
|
+
closes,
|
|
1630
|
+
colorup=self.pinf.sch.barup,
|
|
1631
|
+
colordown=self.pinf.sch.bardown,
|
|
1632
|
+
label=datalabel,
|
|
1633
|
+
alpha=self.pinf.sch.baralpha,
|
|
1634
|
+
fillup=self.pinf.sch.barupfill,
|
|
1635
|
+
filldown=self.pinf.sch.bardownfill,
|
|
1636
|
+
)
|
|
1637
|
+
|
|
1638
|
+
elif self.pinf.sch.style.startswith("bar") or True:
|
|
1639
|
+
# final default option -- should be "else"
|
|
1640
|
+
plotted = plot_ohlc(
|
|
1641
|
+
ax,
|
|
1642
|
+
self.pinf.xdata,
|
|
1643
|
+
opens,
|
|
1644
|
+
highs,
|
|
1645
|
+
lows,
|
|
1646
|
+
closes,
|
|
1647
|
+
colorup=self.pinf.sch.barup,
|
|
1648
|
+
colordown=self.pinf.sch.bardown,
|
|
1649
|
+
label=datalabel,
|
|
1650
|
+
)
|
|
1651
|
+
|
|
1652
|
+
self.pinf.zorder[ax] = plotted[0].get_zorder()
|
|
1653
|
+
|
|
1654
|
+
# Code to place a label at the right-hand side with the last value
|
|
1655
|
+
vtags = data.plotinfo._get("plotvaluetags", True)
|
|
1656
|
+
if self.pinf.sch.valuetags and vtags:
|
|
1657
|
+
self.drawtag(
|
|
1658
|
+
ax, len(self.pinf.xreal), closes[-1], facecolor="white", edgecolor=self.pinf.sch.loc
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1661
|
+
ax.yaxis.set_major_locator(mticker.MaxNLocator(prune="both"))
|
|
1662
|
+
# make sure "over" indicators do not change our scale
|
|
1663
|
+
if data.plotinfo._get("plotylimited", True):
|
|
1664
|
+
if axdatamaster is None:
|
|
1665
|
+
ax.set_ylim(ax.get_ylim())
|
|
1666
|
+
|
|
1667
|
+
if self.pinf.sch.volume:
|
|
1668
|
+
# if not self.pinf.sch.voloverlay:
|
|
1669
|
+
if not voloverlay:
|
|
1670
|
+
self.plotvolume(data, opens, highs, lows, closes, volumes, vollabel)
|
|
1671
|
+
else:
|
|
1672
|
+
# Prepare overlay scaling/pushup or manage own axis
|
|
1673
|
+
if self.pinf.sch.volpushup:
|
|
1674
|
+
# push up the overlaid axis by lowering the bottom limit
|
|
1675
|
+
axbot, axtop = ax.get_ylim()
|
|
1676
|
+
axbot *= 1.0 - self.pinf.sch.volpushup
|
|
1677
|
+
ax.set_ylim(axbot, axtop)
|
|
1678
|
+
|
|
1679
|
+
for ind in indicators:
|
|
1680
|
+
self.plotind(data, ind, subinds=self.dplotsover[ind], masterax=ax)
|
|
1681
|
+
|
|
1682
|
+
handles, labels = ax.get_legend_handles_labels()
|
|
1683
|
+
a = axdatamaster or ax
|
|
1684
|
+
if handles:
|
|
1685
|
+
# put data and volume legend entries in the 1st positions
|
|
1686
|
+
# because they are "collections" they are considered after Line2D
|
|
1687
|
+
# for the legend entries, which is not our desire
|
|
1688
|
+
# if self.pinf.sch.volume and self.pinf.sch.voloverlay:
|
|
1689
|
+
|
|
1690
|
+
ai = self.pinf.legpos[a]
|
|
1691
|
+
if self.pinf.sch.volume and voloverlay:
|
|
1692
|
+
if volplot:
|
|
1693
|
+
# even if volume plot was requested, there may be no volume
|
|
1694
|
+
labels.insert(ai, vollabel)
|
|
1695
|
+
handles.insert(ai, volplot)
|
|
1696
|
+
|
|
1697
|
+
didx = labels.index(datalabel)
|
|
1698
|
+
labels.insert(ai, labels.pop(didx))
|
|
1699
|
+
handles.insert(ai, handles.pop(didx))
|
|
1700
|
+
|
|
1701
|
+
if axdatamaster is None:
|
|
1702
|
+
self.pinf.handles[ax] = handles
|
|
1703
|
+
self.pinf.labels[ax] = labels
|
|
1704
|
+
else:
|
|
1705
|
+
self.pinf.handles[axdatamaster] = handles
|
|
1706
|
+
self.pinf.labels[axdatamaster] = labels
|
|
1707
|
+
# self.pinf.handles[axdatamaster].extend(handles)
|
|
1708
|
+
# self.pinf.labels[axdatamaster].extend(labels)
|
|
1709
|
+
|
|
1710
|
+
h = self.pinf.handles[a]
|
|
1711
|
+
labels = self.pinf.labels[a]
|
|
1712
|
+
|
|
1713
|
+
axlegend = a
|
|
1714
|
+
loc = getattr(data.plotinfo, "legendloc", None) or self.pinf.sch.legenddataloc
|
|
1715
|
+
legend = axlegend.legend(
|
|
1716
|
+
h,
|
|
1717
|
+
labels,
|
|
1718
|
+
loc=loc,
|
|
1719
|
+
frameon=False,
|
|
1720
|
+
shadow=False,
|
|
1721
|
+
fancybox=False,
|
|
1722
|
+
prop=self.pinf.prop,
|
|
1723
|
+
numpoints=1,
|
|
1724
|
+
ncol=1,
|
|
1725
|
+
)
|
|
1726
|
+
|
|
1727
|
+
# Matplotlib legend box defaults to center alignment; override to left
|
|
1728
|
+
legend._legend_box.align = "left"
|
|
1729
|
+
|
|
1730
|
+
for ind in indicators:
|
|
1731
|
+
downinds = self.dplotsdown[ind]
|
|
1732
|
+
for downind in downinds:
|
|
1733
|
+
self.plotind(
|
|
1734
|
+
data,
|
|
1735
|
+
downind,
|
|
1736
|
+
subinds=self.dplotsover[downind],
|
|
1737
|
+
upinds=self.dplotsup[downind],
|
|
1738
|
+
downinds=self.dplotsdown[downind],
|
|
1739
|
+
)
|
|
1740
|
+
|
|
1741
|
+
self.pinf.legpos[a] = len(self.pinf.handles[a])
|
|
1742
|
+
|
|
1743
|
+
if data.plotinfo._get("plotlog", False):
|
|
1744
|
+
a = axdatamaster or ax
|
|
1745
|
+
a.set_yscale("log")
|
|
1746
|
+
|
|
1747
|
+
def show(self):
|
|
1748
|
+
"""Display the plot using matplotlib."""
|
|
1749
|
+
if getattr(self, "_iplot", True):
|
|
1750
|
+
self.mpyplot.show()
|
|
1751
|
+
|
|
1752
|
+
def savefig(self, fig, filename, width=16, height=9, dpi=300, tight=True):
|
|
1753
|
+
"""Save figure to file.
|
|
1754
|
+
|
|
1755
|
+
Args:
|
|
1756
|
+
fig: Figure object to save
|
|
1757
|
+
filename: Output file path
|
|
1758
|
+
width: Figure width in inches (default: 16)
|
|
1759
|
+
height: Figure height in inches (default: 9)
|
|
1760
|
+
dpi: Resolution in dots per inch (default: 300)
|
|
1761
|
+
tight: Whether to use tight bounding box (default: True)
|
|
1762
|
+
"""
|
|
1763
|
+
fig.set_size_inches(width, height)
|
|
1764
|
+
bbox_inches = "tight" * tight or None
|
|
1765
|
+
fig.savefig(filename, dpi=dpi, bbox_inches=bbox_inches)
|
|
1766
|
+
|
|
1767
|
+
def sortdataindicators(self, strategy):
|
|
1768
|
+
"""Sort indicators and observers into plotting groups.
|
|
1769
|
+
|
|
1770
|
+
Organizes indicators and observers into groups for plotting:
|
|
1771
|
+
- Top: Observers that go above all data
|
|
1772
|
+
- Up: Indicators that plot above their data
|
|
1773
|
+
- Down: Indicators that plot below their data
|
|
1774
|
+
- Over: Indicators that overlay on their data
|
|
1775
|
+
|
|
1776
|
+
Args:
|
|
1777
|
+
strategy: Strategy object with indicators and observers
|
|
1778
|
+
"""
|
|
1779
|
+
# These lists/dictionaries hold the subplots that go above each data
|
|
1780
|
+
self.dplotstop = []
|
|
1781
|
+
self.dplotsup = collections.defaultdict(list)
|
|
1782
|
+
self.dplotsdown = collections.defaultdict(list)
|
|
1783
|
+
self.dplotsover = collections.defaultdict(list)
|
|
1784
|
+
|
|
1785
|
+
# Sort observers in the different lists/dictionaries
|
|
1786
|
+
for x in strategy.getobservers():
|
|
1787
|
+
if not x.plotinfo.plot or x.plotinfo.plotskip:
|
|
1788
|
+
continue
|
|
1789
|
+
|
|
1790
|
+
if x.plotinfo.subplot:
|
|
1791
|
+
self.dplotstop.append(x)
|
|
1792
|
+
else:
|
|
1793
|
+
key = getattr(x._clock, "owner", x._clock)
|
|
1794
|
+
self.dplotsover[key].append(x)
|
|
1795
|
+
|
|
1796
|
+
# Sort indicators in the different lists/dictionaries
|
|
1797
|
+
for x in strategy.getindicators():
|
|
1798
|
+
if not hasattr(x, "plotinfo"):
|
|
1799
|
+
# no plotting support - so far LineSingle derived classes
|
|
1800
|
+
continue
|
|
1801
|
+
|
|
1802
|
+
if not x.plotinfo.plot or x.plotinfo.plotskip:
|
|
1803
|
+
continue
|
|
1804
|
+
|
|
1805
|
+
x._plotinit() # will be plotted ... call its init function
|
|
1806
|
+
|
|
1807
|
+
# support LineSeriesStub, which has "owner" to point to the data
|
|
1808
|
+
key = getattr(x._clock, "owner", x._clock)
|
|
1809
|
+
if key is strategy: # a LinesCoupler
|
|
1810
|
+
key = strategy.data
|
|
1811
|
+
|
|
1812
|
+
if getattr(x.plotinfo, "plotforce", False):
|
|
1813
|
+
if key not in strategy.datas:
|
|
1814
|
+
while True:
|
|
1815
|
+
if key not in strategy.datas:
|
|
1816
|
+
key = key._clock
|
|
1817
|
+
else:
|
|
1818
|
+
break
|
|
1819
|
+
|
|
1820
|
+
xpmaster = x.plotinfo.plotmaster
|
|
1821
|
+
if xpmaster is x:
|
|
1822
|
+
xpmaster = None
|
|
1823
|
+
if xpmaster is not None:
|
|
1824
|
+
key = xpmaster
|
|
1825
|
+
|
|
1826
|
+
if x.plotinfo.subplot and xpmaster is None:
|
|
1827
|
+
if x.plotinfo.plotabove:
|
|
1828
|
+
self.dplotsup[key].append(x)
|
|
1829
|
+
else:
|
|
1830
|
+
self.dplotsdown[key].append(x)
|
|
1831
|
+
else:
|
|
1832
|
+
self.dplotsover[key].append(x)
|
|
1833
|
+
|
|
1834
|
+
|
|
1835
|
+
def plot_results(results, file_name):
|
|
1836
|
+
"""write by myself to plot the result, and I will update this function"""
|
|
1837
|
+
# Total leverage
|
|
1838
|
+
df1 = pd.DataFrame([results[0].analyzers._GrossLeverage.get_analysis()]).T
|
|
1839
|
+
df1.columns = ["GrossLeverage"]
|
|
1840
|
+
# Rolling log returns
|
|
1841
|
+
df2 = pd.DataFrame([results[0].analyzers._LogReturnsRolling.get_analysis()]).T
|
|
1842
|
+
df2.columns = ["log_return"]
|
|
1843
|
+
|
|
1844
|
+
# year_rate
|
|
1845
|
+
df3 = pd.DataFrame([results[0].analyzers._AnnualReturn.get_analysis()]).T
|
|
1846
|
+
df3.columns = ["year_rate"]
|
|
1847
|
+
|
|
1848
|
+
#
|
|
1849
|
+
df4 = pd.DataFrame(results[0].analyzers._PositionsValue.get_analysis()).T
|
|
1850
|
+
df4["total_position_value"] = df4.sum(axis=1)
|
|
1851
|
+
|
|
1852
|
+
GrossLeverage = go.Scatter(x=df1.index, y=df1.GrossLeverage, name="gross_leverage")
|
|
1853
|
+
log_return = go.Scatter(
|
|
1854
|
+
x=df2.index, y=df2.log_return, xaxis="x2", yaxis="y2", name="log_return"
|
|
1855
|
+
)
|
|
1856
|
+
cumsum_return = go.Scatter(
|
|
1857
|
+
x=df2.index, y=df2.log_return.cumsum(), xaxis="x2", yaxis="y2", name="cumsum_return"
|
|
1858
|
+
)
|
|
1859
|
+
|
|
1860
|
+
year_rate = go.Bar(x=df3.index, y=df3.year_rate, xaxis="x3", yaxis="y3", name="year_rate")
|
|
1861
|
+
total_position_value = go.Scatter(
|
|
1862
|
+
x=df4.index, y=df4.total_position_value, xaxis="x4", yaxis="y4", name="total_position_value"
|
|
1863
|
+
)
|
|
1864
|
+
data = [GrossLeverage, log_return, cumsum_return, year_rate, total_position_value]
|
|
1865
|
+
layout = go.Layout(
|
|
1866
|
+
xaxis={"domain": [0, 0.45]},
|
|
1867
|
+
yaxis={"domain": [0, 0.45]},
|
|
1868
|
+
xaxis2={"domain": [0.55, 1]},
|
|
1869
|
+
xaxis3={"domain": [0, 0.45], "anchor": "y3"},
|
|
1870
|
+
xaxis4={"domain": [0.55, 1], "anchor": "y4"},
|
|
1871
|
+
yaxis2={"domain": [0, 0.45], "anchor": "x2"},
|
|
1872
|
+
yaxis3={"domain": [0.55, 1]},
|
|
1873
|
+
yaxis4={"domain": [0.55, 1], "anchor": "x4"},
|
|
1874
|
+
)
|
|
1875
|
+
fig = go.Figure(data=data, layout=layout)
|
|
1876
|
+
py.offline.plot(fig, filename=file_name, auto_open=False)
|
|
1877
|
+
|
|
1878
|
+
|
|
1879
|
+
Plot = Plot_OldSync
|
|
1880
|
+
|
|
1881
|
+
|
|
1882
|
+
def create_table(df, max_rows=18):
|
|
1883
|
+
"""Create HTML table from dataframe for Dash display.
|
|
1884
|
+
|
|
1885
|
+
Args:
|
|
1886
|
+
df: DataFrame to convert to table
|
|
1887
|
+
max_rows: Maximum number of rows to display (default: 18)
|
|
1888
|
+
|
|
1889
|
+
Returns:
|
|
1890
|
+
Dash HTML table object
|
|
1891
|
+
"""
|
|
1892
|
+
|
|
1893
|
+
table = html.Table(
|
|
1894
|
+
# Header
|
|
1895
|
+
[html.Tr([html.Th(col) for col in df.columns])]
|
|
1896
|
+
+
|
|
1897
|
+
# Body
|
|
1898
|
+
[
|
|
1899
|
+
html.Tr([html.Td(df.iloc[i][col]) for col in df.columns])
|
|
1900
|
+
for i in range(min(len(df), max_rows))
|
|
1901
|
+
]
|
|
1902
|
+
)
|
|
1903
|
+
return table
|
|
1904
|
+
|
|
1905
|
+
|
|
1906
|
+
def get_rate_sharpe_drawdown(data):
|
|
1907
|
+
"""Calculate Sharpe ratio, annual return, and maximum drawdown.
|
|
1908
|
+
|
|
1909
|
+
For intraday data, extracts the last value of each day as the daily
|
|
1910
|
+
closing value. Assumes 252 trading days per year.
|
|
1911
|
+
|
|
1912
|
+
Args:
|
|
1913
|
+
data: DataFrame with datetime index and total_value column
|
|
1914
|
+
|
|
1915
|
+
Returns:
|
|
1916
|
+
Tuple of (sharpe_ratio, annual_return, max_drawdown)
|
|
1917
|
+
"""
|
|
1918
|
+
# Calculate Sharpe ratio, compound annual return, maximum drawdown
|
|
1919
|
+
# For periods less than daily, extract the last value of each day as the final value of a trading day,
|
|
1920
|
+
# For futures minute data, it's not calculated based on 15:00 close, which may slightly affect Sharpe ratio and other indicators, but the impact is small.
|
|
1921
|
+
data.index = pd.to_datetime(data.index)
|
|
1922
|
+
data["date"] = [str(i)[:10] for i in data.index]
|
|
1923
|
+
data1 = data.drop_duplicates("date", keep="last")
|
|
1924
|
+
data1.index = pd.to_datetime(data1["date"])
|
|
1925
|
+
if len(data1) == 0:
|
|
1926
|
+
return np.nan, np.nan, np.nan
|
|
1927
|
+
try:
|
|
1928
|
+
# Assume 252 trading days in a year
|
|
1929
|
+
data1["rate1"] = np.log(data1["total_value"]) - np.log(data1["total_value"].shift(1))
|
|
1930
|
+
# data['rate2']=data['total_value'].pct_change()
|
|
1931
|
+
data1 = data1.dropna()
|
|
1932
|
+
sharpe_ratio = data1["rate1"].mean() * 252**0.5 / (data1["rate1"].std())
|
|
1933
|
+
# Annualized return is:
|
|
1934
|
+
value_list = list(data["total_value"])
|
|
1935
|
+
begin_value = value_list[0]
|
|
1936
|
+
end_value = value_list[-1]
|
|
1937
|
+
begin_date = data.index[0]
|
|
1938
|
+
end_date = data.index[-1]
|
|
1939
|
+
days = (end_date - begin_date).days
|
|
1940
|
+
# If the calculated actual return is negative, default to maximum of 0, return cannot be negative
|
|
1941
|
+
total_rate = max((end_value - begin_value) / begin_value, -0.9999)
|
|
1942
|
+
average_rate = (1 + total_rate) ** (1 / (days / 365)) - 1
|
|
1943
|
+
# Calculate maximum drawdown
|
|
1944
|
+
data["rate1"] = np.log(data["total_value"]) - np.log(data["total_value"].shift(1))
|
|
1945
|
+
df = data["rate1"].cumsum()
|
|
1946
|
+
df = df.dropna()
|
|
1947
|
+
# index_j = np.argmax(np.maximum.accumulate(df) - df) # End position
|
|
1948
|
+
index_j = np.argmax(np.array(np.maximum.accumulate(df) - df))
|
|
1949
|
+
index_i = np.argmax(np.array(df[:index_j])) # Start position
|
|
1950
|
+
max_drawdown = (np.e ** df[index_j] - np.e ** df[index_i]) / np.e ** df[index_i]
|
|
1951
|
+
"""
|
|
1952
|
+
begin_max_drawdown_value = data['total_value'][index_i]
|
|
1953
|
+
end_max_drawdown_value = data['total_value'][index_j]
|
|
1954
|
+
# print("begin_max_drawdown_value",begin_max_drawdown_value) # Removed for performance
|
|
1955
|
+
# print("end_max_drawdown_value",end_max_drawdown_value) # Removed for performance
|
|
1956
|
+
maxdrawdown_rate = (end_max_drawdown_value -begin_max_drawdown_value)/begin_max_drawdown_value # Maximum drawdown ratio
|
|
1957
|
+
maxdrawdown_value = data['total_value'][index_j] -data['total_value'][index_i] #Maximum drawdown value
|
|
1958
|
+
# print("Maximum drawdown value is",maxdrawdown_value) # Removed for performance
|
|
1959
|
+
# print("Maximum drawdown ratio is",maxdrawdown_rate) # Removed for performance
|
|
1960
|
+
# Draw chart
|
|
1961
|
+
plt.plot(df[1:len(df)])
|
|
1962
|
+
plt.plot([index_i], [df[index_i]], 'o', color="r", markersize=10)
|
|
1963
|
+
plt.plot([index_j], [df[index_j]], 'o', color="blue", markersize=10)
|
|
1964
|
+
plt.show()
|
|
1965
|
+
"""
|
|
1966
|
+
return sharpe_ratio, average_rate, max_drawdown
|
|
1967
|
+
except Exception as e:
|
|
1968
|
+
logger.warning("plot:1967 fallback on Exception")
|
|
1969
|
+
traceback.format_exception(type(e), e, e.__traceback__)
|
|
1970
|
+
return np.nan, np.nan, np.nan
|
|
1971
|
+
|
|
1972
|
+
|
|
1973
|
+
def get_year_return(data):
|
|
1974
|
+
"""Calculate annualized annual return"""
|
|
1975
|
+
data.index = pd.to_datetime(data.index)
|
|
1976
|
+
data["year"] = [i.year for i in data.index]
|
|
1977
|
+
last_data = data.iloc[-1:, ::]
|
|
1978
|
+
data = data.drop_duplicates("year")
|
|
1979
|
+
# data = data.append(last_data)
|
|
1980
|
+
data = pd.concat([data, last_data], axis=0)
|
|
1981
|
+
data["next_year_value"] = data["total_value"].shift(-1)
|
|
1982
|
+
data["return"] = data["next_year_value"] / data["total_value"] - 1
|
|
1983
|
+
data = data.dropna()
|
|
1984
|
+
data["datetime"] = [str(i) + "-6-30" for i in data.year]
|
|
1985
|
+
data.index = pd.to_datetime(data.datetime)
|
|
1986
|
+
data = data[["return", "datetime"]]
|
|
1987
|
+
return data
|
|
1988
|
+
|
|
1989
|
+
|
|
1990
|
+
def run_cerebro_and_plot(
|
|
1991
|
+
cerebro, strategy, params, score=90, port=8050, optimize=True, auto_open=True, result_path=""
|
|
1992
|
+
):
|
|
1993
|
+
"""Run cerebro backtest and save/plot results.
|
|
1994
|
+
|
|
1995
|
+
Executes a backtest with the given strategy and parameters,
|
|
1996
|
+
calculates performance metrics, and saves results to CSV files.
|
|
1997
|
+
|
|
1998
|
+
Args:
|
|
1999
|
+
cerebro: Cerebro instance configured with data
|
|
2000
|
+
strategy: Strategy class to run
|
|
2001
|
+
params: Dictionary of strategy parameters
|
|
2002
|
+
score: Minimum score threshold (default: 90)
|
|
2003
|
+
port: Port for dashboard server (default: 8050)
|
|
2004
|
+
optimize: Whether to run in optimization mode (default: True)
|
|
2005
|
+
auto_open: Whether to auto-open plot (default: True)
|
|
2006
|
+
result_path: Path to save result files (default: current directory)
|
|
2007
|
+
|
|
2008
|
+
Returns:
|
|
2009
|
+
None (saves results to CSV files)
|
|
2010
|
+
"""
|
|
2011
|
+
strategy_name = strategy.__name__
|
|
2012
|
+
params_str = ""
|
|
2013
|
+
for key in params:
|
|
2014
|
+
if key != "symbol_list" and key != "datas":
|
|
2015
|
+
params_str = params_str + "__" + key + "__" + str(params[key])
|
|
2016
|
+
file_name = strategy_name + params_str + ".csv"
|
|
2017
|
+
if result_path != "":
|
|
2018
|
+
file_list = os.listdir(result_path)
|
|
2019
|
+
else:
|
|
2020
|
+
file_list = os.listdir(os.getcwd())
|
|
2021
|
+
if file_name in file_list:
|
|
2022
|
+
print(f"backtest {params_str} consume time :0 because of it has run")
|
|
2023
|
+
if file_name not in file_list:
|
|
2024
|
+
print(
|
|
2025
|
+
"begin to run this params:{},now_time is {}".format(
|
|
2026
|
+
params_str, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
|
|
2027
|
+
)
|
|
2028
|
+
)
|
|
2029
|
+
cerebro.addstrategy(strategy, **params)
|
|
2030
|
+
begin_time = time.time()
|
|
2031
|
+
if optimize:
|
|
2032
|
+
cerebro.addanalyzer(analyzers.TotalValue, _name="_TotalValue")
|
|
2033
|
+
results = cerebro.run()
|
|
2034
|
+
# plot_results(results,"/home/yun/index_000300_reverse_strategy_hold_day_90.html")
|
|
2035
|
+
end_time = time.time()
|
|
2036
|
+
print(
|
|
2037
|
+
"backtest {} consume time :{}, end time is:{}".format(
|
|
2038
|
+
params_str,
|
|
2039
|
+
end_time - begin_time,
|
|
2040
|
+
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
|
2041
|
+
)
|
|
2042
|
+
)
|
|
2043
|
+
# Get key account value and calculate three major indicators
|
|
2044
|
+
df0 = pd.DataFrame([results[0].analyzers._TotalValue.get_analysis()]).T
|
|
2045
|
+
df0.columns = ["total_value"]
|
|
2046
|
+
df0["datetime"] = df0.index
|
|
2047
|
+
df0 = df0.sort_values("datetime")
|
|
2048
|
+
del df0["datetime"]
|
|
2049
|
+
df0.to_csv(result_path + strategy_name + params_str + "___value.csv")
|
|
2050
|
+
# Calculate annual return based on daily net value
|
|
2051
|
+
df_return = get_year_return(copy.deepcopy(df0))
|
|
2052
|
+
# Calculate Sharpe ratio, average return, maximum drawdown
|
|
2053
|
+
sharpe_ratio, average_rate, max_drawdown_rate = get_rate_sharpe_drawdown(
|
|
2054
|
+
copy.deepcopy(df0)
|
|
2055
|
+
)
|
|
2056
|
+
# Analyze trading performance
|
|
2057
|
+
performance_dict = OrderedDict()
|
|
2058
|
+
# Performance measurement indicators
|
|
2059
|
+
performance_dict["sharpe_ratio"] = sharpe_ratio
|
|
2060
|
+
performance_dict["average_rate"] = average_rate
|
|
2061
|
+
performance_dict["max_drawdown_rate"] = max_drawdown_rate
|
|
2062
|
+
performance_dict["calmar_ratio"] = np.nan
|
|
2063
|
+
performance_dict["average_drawdown_len"] = np.nan
|
|
2064
|
+
performance_dict["average_drawdown_rate"] = np.nan
|
|
2065
|
+
performance_dict["average_drawdown_money"] = np.nan
|
|
2066
|
+
performance_dict["max_drawdown_len"] = np.nan
|
|
2067
|
+
performance_dict["max_drawdown_money"] = np.nan
|
|
2068
|
+
performance_dict["stddev_rate"] = np.nan
|
|
2069
|
+
performance_dict["positive_year"] = np.nan
|
|
2070
|
+
performance_dict["negative_year"] = np.nan
|
|
2071
|
+
performance_dict["nochange_year"] = np.nan
|
|
2072
|
+
performance_dict["best_year"] = np.nan
|
|
2073
|
+
performance_dict["worst_year"] = np.nan
|
|
2074
|
+
performance_dict["sqn_ratio"] = np.nan
|
|
2075
|
+
performance_dict["vwr_ratio"] = np.nan
|
|
2076
|
+
performance_dict["omega"] = np.nan
|
|
2077
|
+
trade_dict_1 = OrderedDict()
|
|
2078
|
+
trade_dict_2 = OrderedDict()
|
|
2079
|
+
trade_dict_1["total_trade_num"] = np.nan
|
|
2080
|
+
trade_dict_1["total_trade_opened"] = np.nan
|
|
2081
|
+
trade_dict_1["total_trade_closed"] = np.nan
|
|
2082
|
+
trade_dict_1["total_trade_len"] = np.nan
|
|
2083
|
+
trade_dict_1["long_trade_len"] = np.nan
|
|
2084
|
+
trade_dict_1["short_trade_len"] = np.nan
|
|
2085
|
+
trade_dict_1["longest_win_num"] = np.nan
|
|
2086
|
+
trade_dict_1["longest_lost_num"] = np.nan
|
|
2087
|
+
trade_dict_1["net_total_pnl"] = np.nan
|
|
2088
|
+
trade_dict_1["net_average_pnl"] = np.nan
|
|
2089
|
+
trade_dict_1["win_num"] = np.nan
|
|
2090
|
+
trade_dict_1["win_total_pnl"] = np.nan
|
|
2091
|
+
trade_dict_1["win_average_pnl"] = np.nan
|
|
2092
|
+
trade_dict_1["win_max_pnl"] = np.nan
|
|
2093
|
+
trade_dict_1["lost_num"] = np.nan
|
|
2094
|
+
trade_dict_1["lost_total_pnl"] = np.nan
|
|
2095
|
+
trade_dict_1["lost_average_pnl"] = np.nan
|
|
2096
|
+
trade_dict_1["lost_max_pnl"] = np.nan
|
|
2097
|
+
|
|
2098
|
+
trade_dict_2["long_num"] = np.nan
|
|
2099
|
+
trade_dict_2["long_win_num"] = np.nan
|
|
2100
|
+
trade_dict_2["long_lost_num"] = np.nan
|
|
2101
|
+
trade_dict_2["long_total_pnl"] = np.nan
|
|
2102
|
+
trade_dict_2["long_average_pnl"] = np.nan
|
|
2103
|
+
trade_dict_2["long_win_total_pnl"] = np.nan
|
|
2104
|
+
trade_dict_2["long_win_max_pnl"] = np.nan
|
|
2105
|
+
trade_dict_2["long_lost_total_pnl"] = np.nan
|
|
2106
|
+
trade_dict_2["long_lost_max_pnl"] = np.nan
|
|
2107
|
+
trade_dict_2["short_num"] = np.nan
|
|
2108
|
+
trade_dict_2["short_win_num"] = np.nan
|
|
2109
|
+
trade_dict_2["short_lost_num"] = np.nan
|
|
2110
|
+
trade_dict_2["short_total_pnl"] = np.nan
|
|
2111
|
+
trade_dict_2["short_average_pnl"] = np.nan
|
|
2112
|
+
trade_dict_2["short_win_total_pnl"] = np.nan
|
|
2113
|
+
trade_dict_2["short_win_max_pnl"] = np.nan
|
|
2114
|
+
trade_dict_2["short_lost_total_pnl"] = np.nan
|
|
2115
|
+
trade_dict_2["short_lost_max_pnl"] = np.nan
|
|
2116
|
+
|
|
2117
|
+
assert len(performance_dict) == len(trade_dict_2) == len(trade_dict_1)
|
|
2118
|
+
df00 = pd.DataFrame(index=range(18))
|
|
2119
|
+
df01 = pd.DataFrame([performance_dict]).T
|
|
2120
|
+
df01.columns = ["Performance indicator value"]
|
|
2121
|
+
df02 = pd.DataFrame([trade_dict_1]).T
|
|
2122
|
+
df02.columns = ["General trading indicator value"]
|
|
2123
|
+
df03 = pd.DataFrame([trade_dict_2]).T
|
|
2124
|
+
df03.columns = ["Long/short trading indicator value"]
|
|
2125
|
+
try:
|
|
2126
|
+
df00["Performance indicator"] = df01.index
|
|
2127
|
+
df00["Performance indicator value"] = [
|
|
2128
|
+
round(float(i), 4) for i in list(df01["Performance indicator value"])
|
|
2129
|
+
]
|
|
2130
|
+
df00["General trading indicator"] = df02.index
|
|
2131
|
+
df00["General trading indicator value"] = [
|
|
2132
|
+
round(float(i), 4) for i in list(df02["General trading indicator value"])
|
|
2133
|
+
]
|
|
2134
|
+
df00["Long/short trading indicator"] = df03.index
|
|
2135
|
+
df00["Long/short trading indicator value"] = [
|
|
2136
|
+
round(float(i), 4) for i in list(df03["Long/short trading indicator value"])
|
|
2137
|
+
]
|
|
2138
|
+
except Exception as e:
|
|
2139
|
+
logger.warning("plot:2137 fallback on Exception")
|
|
2140
|
+
traceback.format_exception(type(e), e, e.__traceback__)
|
|
2141
|
+
df00["Performance indicator"] = df01.index
|
|
2142
|
+
df00["Performance indicator value"] = df01["Performance indicator value"]
|
|
2143
|
+
df00["General trading indicator"] = df02.index
|
|
2144
|
+
df00["General trading indicator value"] = df02["General trading indicator value"]
|
|
2145
|
+
df00["Long/short trading indicator"] = df03.index
|
|
2146
|
+
df00["Long/short trading indicator value"] = df03[
|
|
2147
|
+
"Long/short trading indicator value"
|
|
2148
|
+
]
|
|
2149
|
+
|
|
2150
|
+
if not optimize:
|
|
2151
|
+
# Save required trading indicators
|
|
2152
|
+
# cerebro.addanalyzer(analyzers.PyFolio, _name='pyfolio')
|
|
2153
|
+
# cerebro.addanalyzer(analyzers.AnnualReturn, _name='_AnnualReturn') # Annual return calculation has issues, removed
|
|
2154
|
+
cerebro.addanalyzer(analyzers.Calmar, _name="_Calmar")
|
|
2155
|
+
cerebro.addanalyzer(analyzers.DrawDown, _name="_DrawDown")
|
|
2156
|
+
# cerebro.addanalyzer(analyzers.TimeDrawDown, _name='_TimeDrawDown')
|
|
2157
|
+
cerebro.addanalyzer(analyzers.GrossLeverage, _name="_GrossLeverage")
|
|
2158
|
+
cerebro.addanalyzer(analyzers.PositionsValue, _name="_PositionsValue")
|
|
2159
|
+
# cerebro.addanalyzer(analyzers.LogReturnsRolling, _name='_LogReturnsRolling')
|
|
2160
|
+
cerebro.addanalyzer(analyzers.PeriodStats, _name="_PeriodStats")
|
|
2161
|
+
cerebro.addanalyzer(analyzers.Returns, _name="_Returns")
|
|
2162
|
+
cerebro.addanalyzer(analyzers.SharpeRatio, _name="_SharpeRatio")
|
|
2163
|
+
# cerebro.addanalyzer(analyzers.SharpeRatio_A, _name='_SharpeRatio_A')
|
|
2164
|
+
cerebro.addanalyzer(analyzers.SQN, _name="_SQN")
|
|
2165
|
+
cerebro.addanalyzer(analyzers.TimeReturn, _name="_TimeReturn")
|
|
2166
|
+
cerebro.addanalyzer(analyzers.TradeAnalyzer, _name="_TradeAnalyzer")
|
|
2167
|
+
cerebro.addanalyzer(analyzers.Transactions, _name="_Transactions")
|
|
2168
|
+
cerebro.addanalyzer(analyzers.VWR, _name="_VWR")
|
|
2169
|
+
cerebro.addanalyzer(analyzers.TotalValue, _name="_TotalValue")
|
|
2170
|
+
cerebro.addanalyzer(analyzers.PyFolio)
|
|
2171
|
+
results = cerebro.run()
|
|
2172
|
+
# plot_results(results,"/home/yun/index_000300_reverse_strategy_hold_day_90.html")
|
|
2173
|
+
end_time = time.time()
|
|
2174
|
+
print(
|
|
2175
|
+
"backtest {} consume time :{}, end time is:{}".format(
|
|
2176
|
+
params_str,
|
|
2177
|
+
end_time - begin_time,
|
|
2178
|
+
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
|
2179
|
+
)
|
|
2180
|
+
)
|
|
2181
|
+
# Analyze trading performance
|
|
2182
|
+
performance_dict = OrderedDict()
|
|
2183
|
+
drawdown_info = results[0].analyzers._DrawDown.get_analysis()
|
|
2184
|
+
# Calculate periodic indicators
|
|
2185
|
+
PeriodStats_info = results[0].analyzers._PeriodStats.get_analysis()
|
|
2186
|
+
# Calculate sqn indicator
|
|
2187
|
+
SQN_info = results[0].analyzers._SQN.get_analysis()
|
|
2188
|
+
sqn_ratio = SQN_info.get("sqn", np.nan)
|
|
2189
|
+
# Calculate vwr indicator
|
|
2190
|
+
VWR_info = results[0].analyzers._VWR.get_analysis()
|
|
2191
|
+
vwr_ratio = VWR_info.get("vwr", np.nan)
|
|
2192
|
+
# Calculate calmar indicator
|
|
2193
|
+
# calmar_ratio_list = list(results[0].analyzers._Calmar.get_analysis().values())
|
|
2194
|
+
# calmar_ratio = calmar_ratio_list[-1] if len(calmar_ratio_list) > 0 else np.nan
|
|
2195
|
+
calmar_ratio = np.nan
|
|
2196
|
+
# Calculate Sharpe ratio
|
|
2197
|
+
sharpe_info = results[0].analyzers._SharpeRatio.get_analysis()
|
|
2198
|
+
sharpe_ratio = sharpe_info.get("sharperatio", np.nan)
|
|
2199
|
+
# Get average drawdown indicator
|
|
2200
|
+
average_drawdown_len = drawdown_info.get("len", np.nan)
|
|
2201
|
+
average_drawdown_rate = drawdown_info.get("drawdown", np.nan)
|
|
2202
|
+
average_drawdown_money = drawdown_info.get("moneydown", np.nan)
|
|
2203
|
+
# Get maximum drawdown indicator
|
|
2204
|
+
max_drawdown_info = drawdown_info.get("max", {})
|
|
2205
|
+
max_drawdown_len = max_drawdown_info.get("len", np.nan)
|
|
2206
|
+
max_drawdown_rate = max_drawdown_info.get("drawdown", np.nan)
|
|
2207
|
+
max_drawdown_money = max_drawdown_info.get("moneydown", np.nan)
|
|
2208
|
+
|
|
2209
|
+
average_rate = PeriodStats_info.get("average", np.nan)
|
|
2210
|
+
stddev_rate = PeriodStats_info.get("stddev", np.nan)
|
|
2211
|
+
positive_year = PeriodStats_info.get("positive", np.nan)
|
|
2212
|
+
negative_year = PeriodStats_info.get("negative", np.nan)
|
|
2213
|
+
nochange_year = PeriodStats_info.get("nochange", np.nan)
|
|
2214
|
+
best_year = PeriodStats_info.get("best", np.nan)
|
|
2215
|
+
worst_year = PeriodStats_info.get("worst", np.nan)
|
|
2216
|
+
|
|
2217
|
+
# Get key account value and calculate three major indicators
|
|
2218
|
+
df0 = pd.DataFrame([results[0].analyzers._TotalValue.get_analysis()]).T
|
|
2219
|
+
df0.columns = ["total_value"]
|
|
2220
|
+
df0["datetime"] = df0.index
|
|
2221
|
+
df0 = df0.sort_values("datetime")
|
|
2222
|
+
del df0["datetime"]
|
|
2223
|
+
df0.to_csv(result_path + strategy_name + params_str + "___value.csv")
|
|
2224
|
+
# Calculate annual return based on daily net value
|
|
2225
|
+
df_return = get_year_return(copy.deepcopy(df0))
|
|
2226
|
+
# Calculate Sharpe ratio, average return, maximum drawdown
|
|
2227
|
+
sharpe_ratio, average_rate, max_drawdown_rate = get_rate_sharpe_drawdown(
|
|
2228
|
+
copy.deepcopy(df0)
|
|
2229
|
+
)
|
|
2230
|
+
|
|
2231
|
+
# Performance measurement indicators
|
|
2232
|
+
performance_dict["sharpe_ratio"] = sharpe_ratio
|
|
2233
|
+
performance_dict["average_rate"] = average_rate
|
|
2234
|
+
performance_dict["max_drawdown_rate"] = max_drawdown_rate
|
|
2235
|
+
performance_dict["calmar_ratio"] = calmar_ratio
|
|
2236
|
+
performance_dict["average_drawdown_len"] = average_drawdown_len
|
|
2237
|
+
performance_dict["average_drawdown_rate"] = average_drawdown_rate
|
|
2238
|
+
performance_dict["average_drawdown_money"] = average_drawdown_money
|
|
2239
|
+
performance_dict["max_drawdown_len"] = max_drawdown_len
|
|
2240
|
+
performance_dict["max_drawdown_money"] = max_drawdown_money
|
|
2241
|
+
performance_dict["stddev_rate"] = stddev_rate
|
|
2242
|
+
performance_dict["positive_year"] = positive_year
|
|
2243
|
+
performance_dict["negative_year"] = negative_year
|
|
2244
|
+
performance_dict["nochange_year"] = nochange_year
|
|
2245
|
+
performance_dict["best_year"] = best_year
|
|
2246
|
+
performance_dict["worst_year"] = worst_year
|
|
2247
|
+
performance_dict["sqn_ratio"] = sqn_ratio
|
|
2248
|
+
performance_dict["vwr_ratio"] = vwr_ratio
|
|
2249
|
+
performance_dict["omega"] = np.nan
|
|
2250
|
+
|
|
2251
|
+
trade_dict_1 = OrderedDict()
|
|
2252
|
+
trade_dict_2 = OrderedDict()
|
|
2253
|
+
|
|
2254
|
+
try:
|
|
2255
|
+
trade_info = results[0].analyzers._TradeAnalyzer.get_analysis()
|
|
2256
|
+
total_trade_num = trade_info["total"]["total"]
|
|
2257
|
+
total_trade_opened = trade_info["total"]["open"]
|
|
2258
|
+
total_trade_closed = trade_info["total"]["closed"]
|
|
2259
|
+
total_trade_len = trade_info["len"]["total"]
|
|
2260
|
+
long_trade_len = trade_info["len"]["long"]["total"]
|
|
2261
|
+
short_trade_len = trade_info["len"]["short"]["total"]
|
|
2262
|
+
except Exception as e:
|
|
2263
|
+
logger.warning("plot:2260 fallback on Exception")
|
|
2264
|
+
traceback.format_exception(type(e), e, e.__traceback__)
|
|
2265
|
+
total_trade_num = np.nan
|
|
2266
|
+
total_trade_opened = np.nan
|
|
2267
|
+
total_trade_closed = np.nan
|
|
2268
|
+
total_trade_len = np.nan
|
|
2269
|
+
long_trade_len = np.nan
|
|
2270
|
+
short_trade_len = np.nan
|
|
2271
|
+
|
|
2272
|
+
try:
|
|
2273
|
+
longest_win_num = trade_info["streak"]["won"]["longest"]
|
|
2274
|
+
longest_lost_num = trade_info["streak"]["lost"]["longest"]
|
|
2275
|
+
net_total_pnl = trade_info["pnl"]["net"]["total"]
|
|
2276
|
+
net_average_pnl = trade_info["pnl"]["net"]["average"]
|
|
2277
|
+
win_num = trade_info["won"]["total"]
|
|
2278
|
+
win_total_pnl = trade_info["won"]["pnl"]["total"]
|
|
2279
|
+
win_average_pnl = trade_info["won"]["pnl"]["average"]
|
|
2280
|
+
win_max_pnl = trade_info["won"]["pnl"]["max"]
|
|
2281
|
+
lost_num = trade_info["lost"]["total"]
|
|
2282
|
+
lost_total_pnl = trade_info["lost"]["pnl"]["total"]
|
|
2283
|
+
lost_average_pnl = trade_info["lost"]["pnl"]["average"]
|
|
2284
|
+
lost_max_pnl = trade_info["lost"]["pnl"]["max"]
|
|
2285
|
+
except Exception as e:
|
|
2286
|
+
logger.warning("plot:2282 fallback on Exception")
|
|
2287
|
+
traceback.format_exception(type(e), e, e.__traceback__)
|
|
2288
|
+
longest_win_num = np.nan
|
|
2289
|
+
longest_lost_num = np.nan
|
|
2290
|
+
net_total_pnl = np.nan
|
|
2291
|
+
net_average_pnl = np.nan
|
|
2292
|
+
win_num = np.nan
|
|
2293
|
+
win_total_pnl = np.nan
|
|
2294
|
+
win_average_pnl = np.nan
|
|
2295
|
+
win_max_pnl = np.nan
|
|
2296
|
+
lost_num = np.nan
|
|
2297
|
+
lost_total_pnl = np.nan
|
|
2298
|
+
lost_average_pnl = np.nan
|
|
2299
|
+
lost_max_pnl = np.nan
|
|
2300
|
+
|
|
2301
|
+
trade_dict_1["total_trade_num"] = total_trade_num
|
|
2302
|
+
trade_dict_1["total_trade_opened"] = total_trade_opened
|
|
2303
|
+
trade_dict_1["total_trade_closed"] = total_trade_closed
|
|
2304
|
+
trade_dict_1["total_trade_len"] = total_trade_len
|
|
2305
|
+
trade_dict_1["long_trade_len"] = long_trade_len
|
|
2306
|
+
trade_dict_1["short_trade_len"] = short_trade_len
|
|
2307
|
+
trade_dict_1["longest_win_num"] = longest_win_num
|
|
2308
|
+
trade_dict_1["longest_lost_num"] = longest_lost_num
|
|
2309
|
+
trade_dict_1["net_total_pnl"] = net_total_pnl
|
|
2310
|
+
trade_dict_1["net_average_pnl"] = net_average_pnl
|
|
2311
|
+
trade_dict_1["win_num"] = win_num
|
|
2312
|
+
trade_dict_1["win_total_pnl"] = win_total_pnl
|
|
2313
|
+
trade_dict_1["win_average_pnl"] = win_average_pnl
|
|
2314
|
+
trade_dict_1["win_max_pnl"] = win_max_pnl
|
|
2315
|
+
trade_dict_1["lost_num"] = lost_num
|
|
2316
|
+
trade_dict_1["lost_total_pnl"] = lost_total_pnl
|
|
2317
|
+
trade_dict_1["lost_average_pnl"] = lost_average_pnl
|
|
2318
|
+
trade_dict_1["lost_max_pnl"] = lost_max_pnl
|
|
2319
|
+
|
|
2320
|
+
try:
|
|
2321
|
+
long_num = trade_info["long"]["total"]
|
|
2322
|
+
long_win_num = trade_info["long"]["won"]
|
|
2323
|
+
long_lost_num = trade_info["long"]["lost"]
|
|
2324
|
+
long_total_pnl = trade_info["long"]["pnl"]["total"]
|
|
2325
|
+
long_average_pnl = trade_info["long"]["pnl"]["average"]
|
|
2326
|
+
long_win_total_pnl = trade_info["long"]["pnl"]["won"]["total"]
|
|
2327
|
+
long_win_max_pnl = trade_info["long"]["pnl"]["won"]["max"]
|
|
2328
|
+
long_lost_total_pnl = trade_info["long"]["pnl"]["lost"]["total"]
|
|
2329
|
+
long_lost_max_pnl = trade_info["long"]["pnl"]["lost"]["max"]
|
|
2330
|
+
|
|
2331
|
+
short_num = trade_info["short"]["total"]
|
|
2332
|
+
short_win_num = trade_info["short"]["won"]
|
|
2333
|
+
short_lost_num = trade_info["short"]["lost"]
|
|
2334
|
+
short_total_pnl = trade_info["short"]["pnl"]["total"]
|
|
2335
|
+
short_average_pnl = trade_info["short"]["pnl"]["average"]
|
|
2336
|
+
short_win_total_pnl = trade_info["short"]["pnl"]["won"]["total"]
|
|
2337
|
+
short_win_max_pnl = trade_info["short"]["pnl"]["won"]["max"]
|
|
2338
|
+
short_lost_total_pnl = trade_info["short"]["pnl"]["lost"]["total"]
|
|
2339
|
+
short_lost_max_pnl = trade_info["short"]["pnl"]["lost"]["max"]
|
|
2340
|
+
except Exception as e:
|
|
2341
|
+
logger.warning("plot:2336 fallback on Exception")
|
|
2342
|
+
traceback.format_exception(type(e), e, e.__traceback__)
|
|
2343
|
+
long_num = np.nan
|
|
2344
|
+
long_win_num = np.nan
|
|
2345
|
+
long_lost_num = np.nan
|
|
2346
|
+
long_total_pnl = np.nan
|
|
2347
|
+
long_average_pnl = np.nan
|
|
2348
|
+
long_win_total_pnl = np.nan
|
|
2349
|
+
long_win_max_pnl = np.nan
|
|
2350
|
+
long_lost_total_pnl = np.nan
|
|
2351
|
+
long_lost_max_pnl = np.nan
|
|
2352
|
+
|
|
2353
|
+
short_num = np.nan
|
|
2354
|
+
short_win_num = np.nan
|
|
2355
|
+
short_lost_num = np.nan
|
|
2356
|
+
short_total_pnl = np.nan
|
|
2357
|
+
short_average_pnl = np.nan
|
|
2358
|
+
short_win_total_pnl = np.nan
|
|
2359
|
+
short_win_max_pnl = np.nan
|
|
2360
|
+
short_lost_total_pnl = np.nan
|
|
2361
|
+
short_lost_max_pnl = np.nan
|
|
2362
|
+
|
|
2363
|
+
trade_dict_2["long_num"] = long_num
|
|
2364
|
+
trade_dict_2["long_win_num"] = long_win_num
|
|
2365
|
+
trade_dict_2["long_lost_num"] = long_lost_num
|
|
2366
|
+
trade_dict_2["long_total_pnl"] = long_total_pnl
|
|
2367
|
+
trade_dict_2["long_average_pnl"] = long_average_pnl
|
|
2368
|
+
trade_dict_2["long_win_total_pnl"] = long_win_total_pnl
|
|
2369
|
+
trade_dict_2["long_win_max_pnl"] = long_win_max_pnl
|
|
2370
|
+
trade_dict_2["long_lost_total_pnl"] = long_lost_total_pnl
|
|
2371
|
+
trade_dict_2["long_lost_max_pnl"] = long_lost_max_pnl
|
|
2372
|
+
trade_dict_2["short_num"] = short_num
|
|
2373
|
+
trade_dict_2["short_win_num"] = short_win_num
|
|
2374
|
+
trade_dict_2["short_lost_num"] = short_lost_num
|
|
2375
|
+
trade_dict_2["short_total_pnl"] = short_total_pnl
|
|
2376
|
+
trade_dict_2["short_average_pnl"] = short_average_pnl
|
|
2377
|
+
trade_dict_2["short_win_total_pnl"] = short_win_total_pnl
|
|
2378
|
+
trade_dict_2["short_win_max_pnl"] = short_win_max_pnl
|
|
2379
|
+
trade_dict_2["short_lost_total_pnl"] = short_lost_total_pnl
|
|
2380
|
+
trade_dict_2["short_lost_max_pnl"] = short_lost_max_pnl
|
|
2381
|
+
|
|
2382
|
+
assert len(performance_dict) == len(trade_dict_2) == len(trade_dict_1)
|
|
2383
|
+
df00 = pd.DataFrame(index=range(18))
|
|
2384
|
+
df01 = pd.DataFrame([performance_dict]).T
|
|
2385
|
+
df01.columns = ["Performance indicator value"]
|
|
2386
|
+
df02 = pd.DataFrame([trade_dict_1]).T
|
|
2387
|
+
df02.columns = ["General trading indicator value"]
|
|
2388
|
+
df03 = pd.DataFrame([trade_dict_2]).T
|
|
2389
|
+
df03.columns = ["Long/short trading indicator value"]
|
|
2390
|
+
try:
|
|
2391
|
+
df00["Performance indicator"] = df01.index
|
|
2392
|
+
df00["Performance indicator value"] = [
|
|
2393
|
+
round(float(i), 4) for i in list(df01["Performance indicator value"])
|
|
2394
|
+
]
|
|
2395
|
+
df00["General trading indicator"] = df02.index
|
|
2396
|
+
df00["General trading indicator value"] = [
|
|
2397
|
+
round(float(i), 4) for i in list(df02["General trading indicator value"])
|
|
2398
|
+
]
|
|
2399
|
+
df00["Long/short trading indicator"] = df03.index
|
|
2400
|
+
df00["Long/short trading indicator value"] = [
|
|
2401
|
+
round(float(i), 4) for i in list(df03["Long/short trading indicator value"])
|
|
2402
|
+
]
|
|
2403
|
+
except Exception as e:
|
|
2404
|
+
logger.warning("plot:2398 fallback on Exception")
|
|
2405
|
+
traceback.format_exception(type(e), e, e.__traceback__)
|
|
2406
|
+
df00["Performance indicator"] = df01.index
|
|
2407
|
+
df00["Performance indicator value"] = df01["Performance indicator value"]
|
|
2408
|
+
df00["General trading indicator"] = df02.index
|
|
2409
|
+
df00["General trading indicator value"] = df02["General trading indicator value"]
|
|
2410
|
+
df00["Long/short trading indicator"] = df03.index
|
|
2411
|
+
df00["Long/short trading indicator value"] = df03[
|
|
2412
|
+
"Long/short trading indicator value"
|
|
2413
|
+
]
|
|
2414
|
+
|
|
2415
|
+
# Add table data
|
|
2416
|
+
table_data = [
|
|
2417
|
+
list(df00["Performance indicator"])[:9],
|
|
2418
|
+
list(df00["Performance indicator value"])[:9],
|
|
2419
|
+
list(df00["Performance indicator"])[9:],
|
|
2420
|
+
list(df00["Performance indicator value"])[9:],
|
|
2421
|
+
list(df00["General trading indicator"])[:9],
|
|
2422
|
+
list(df00["General trading indicator value"])[:9],
|
|
2423
|
+
list(df00["General trading indicator"])[9:],
|
|
2424
|
+
list(df00["General trading indicator value"])[9:],
|
|
2425
|
+
list(df00["Long/short trading indicator"])[:9],
|
|
2426
|
+
list(df00["Long/short trading indicator value"])[:9],
|
|
2427
|
+
list(df00["Long/short trading indicator"])[9:],
|
|
2428
|
+
list(df00["Long/short trading indicator value"])[9:],
|
|
2429
|
+
]
|
|
2430
|
+
fig = ff.create_table(table_data)
|
|
2431
|
+
# Add graph data
|
|
2432
|
+
# Add graph data
|
|
2433
|
+
trace1 = go.Scatter(
|
|
2434
|
+
x=list(df0.index),
|
|
2435
|
+
y=list(df0.total_value),
|
|
2436
|
+
xaxis="x2",
|
|
2437
|
+
yaxis="y2",
|
|
2438
|
+
name="total_value",
|
|
2439
|
+
mode="lines",
|
|
2440
|
+
)
|
|
2441
|
+
trace2 = go.Bar(
|
|
2442
|
+
x=list(df_return.index),
|
|
2443
|
+
y=[str(round(i, 3)) + "%" for i in list(df_return["return"])],
|
|
2444
|
+
xaxis="x2",
|
|
2445
|
+
yaxis="y3",
|
|
2446
|
+
name="year_profit",
|
|
2447
|
+
opacity=0.3,
|
|
2448
|
+
marker={"color": "#ffa631"},
|
|
2449
|
+
)
|
|
2450
|
+
# Add trace data to figure
|
|
2451
|
+
fig.add_traces([trace1, trace2])
|
|
2452
|
+
|
|
2453
|
+
# initialize xaxis2 and yaxis2
|
|
2454
|
+
fig["layout"]["xaxis2"] = {}
|
|
2455
|
+
fig["layout"]["yaxis2"] = {}
|
|
2456
|
+
fig["layout"]["yaxis3"] = {}
|
|
2457
|
+
|
|
2458
|
+
# Edit layout for subplots
|
|
2459
|
+
fig.layout.yaxis.update({"domain": [0.5, 1]})
|
|
2460
|
+
fig.layout.yaxis2.update({"domain": [0, 0.5]})
|
|
2461
|
+
fig.layout.yaxis3.update({"domain": [0, 0.5]})
|
|
2462
|
+
|
|
2463
|
+
# The graph's yaxis2 MUST BE anchored to the graph's xaxis2 and vice versa
|
|
2464
|
+
# fig.layout.yaxis3.update({'anchor': 'x2'})
|
|
2465
|
+
# # fig.layout.xaxis2.update({'anchor': 'y3'})
|
|
2466
|
+
# fig.layout.yaxis3.update({'title': 'year_profit'})
|
|
2467
|
+
# fig.layout.yaxis3.update({'overlaying':'y2', 'side':'right'})
|
|
2468
|
+
|
|
2469
|
+
fig.layout.yaxis2.update({"anchor": "x2"})
|
|
2470
|
+
fig.layout.xaxis2.update({"anchor": "y2"})
|
|
2471
|
+
fig.layout.yaxis2.update({"title": "total_value"})
|
|
2472
|
+
fig.layout.yaxis2.update({"type": "log"})
|
|
2473
|
+
|
|
2474
|
+
fig.layout.yaxis3.update({"anchor": "x2"})
|
|
2475
|
+
# fig.layout.xaxis2.update({'anchor': 'y3'})
|
|
2476
|
+
fig.layout.yaxis3.update({"title": "year_profit"})
|
|
2477
|
+
fig.layout.yaxis3.update({"overlaying": "y2", "side": "right"})
|
|
2478
|
+
|
|
2479
|
+
# Update the margins to add a title and see graph x-labels.
|
|
2480
|
+
fig.layout.margin.update({"t": 75, "l": 50})
|
|
2481
|
+
fig.layout.update(
|
|
2482
|
+
{
|
|
2483
|
+
"title": {
|
|
2484
|
+
"text": strategy.__name__ + params_str,
|
|
2485
|
+
"x": 0.5,
|
|
2486
|
+
"xanchor": "center",
|
|
2487
|
+
"yanchor": "middle",
|
|
2488
|
+
"font": {"family": "Arial", "color": "red"},
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
)
|
|
2492
|
+
|
|
2493
|
+
# Update the height because adding a graph vertically will interact with
|
|
2494
|
+
# the plot height calculated for the table
|
|
2495
|
+
fig.layout.update({"height": 800})
|
|
2496
|
+
|
|
2497
|
+
py.plot(fig, auto_open=auto_open, filename=result_path + strategy.__name__ + params_str)
|
|
2498
|
+
df00.to_csv(result_path + strategy.__name__ + params_str + ".csv", encoding="gbk")
|
|
2499
|
+
|
|
2500
|
+
return results
|