Skip to content

Analysis

Expanding trade metrics

  • Regular metrics such as MAE and MFE represent only the final point of each trade, but what if we would like to see their development during each trade? You can now analyze expanding trade metrics as DataFrames!
Visualize the expanding MFE using projections
>>> data = vbt.YFData.pull("BTC-USD")
>>> pf = vbt.PF.from_random_signals(data, n=50, tp_stop=0.5)
>>> pf.trades.plot_expanding_mfe_returns().show()

Trade signals

  • New method for plotting trades that categorizes entry and exit trades into long entries, long exits, short entries, and short exits. Supports different styles for positions.
Plot trade signals of a Bollinger Bands strategy
>>> data = vbt.YFData.pull("BTC-USD")
>>> bb = data.run("bbands")
>>> long_entries = data.hlc3.vbt.crossed_above(bb.upper) & (bb.bandwidth < 0.1)
>>> long_exits = data.hlc3.vbt.crossed_below(bb.upper) & (bb.bandwidth > 0.5)
>>> short_entries = data.hlc3.vbt.crossed_below(bb.lower) & (bb.bandwidth < 0.1)
>>> short_exits = data.hlc3.vbt.crossed_above(bb.lower) & (bb.bandwidth > 0.5)
>>> pf = vbt.PF.from_signals(
...     data, 
...     long_entries=long_entries, 
...     long_exits=long_exits, 
...     short_entries=short_entries, 
...     short_exits=short_exits
... )
>>> pf.plot_trade_signals().show()

Edge ratio

  • Edge ratio is a unique way to quantify entry profitability. Unlike most performance metrics, the edge ratio takes both open profits and losses into account. This can help you determine optimal trade exits.
Compare the edge ratio of an EMA crossover to a random strategy
>>> data = vbt.YFData.pull("BTC-USD")
>>> fast_ema = data.run("ema", 10, hide_params=True)
>>> slow_ema = data.run("ema", 20, hide_params=True)
>>> entries = fast_ema.real_crossed_above(slow_ema)
>>> exits = fast_ema.real_crossed_below(slow_ema)
>>> pf = vbt.PF.from_signals(data, entries, exits, direction="both")
>>> rand_pf = vbt.PF.from_random_signals(data, n=pf.orders.count() // 2)  # (1)!
>>> fig = pf.trades.plot_running_edge_ratio(
...     trace_kwargs=dict(line_color="limegreen", name="Edge Ratio (S)")
... )
>>> fig = rand_pf.trades.plot_running_edge_ratio(
...     trace_kwargs=dict(line_color="mediumslateblue", name="Edge Ratio (R)"),
...     fig=fig
... )
>>> fig.show()
  1. Random strategy should have a similar number of orders to be comparable

Trade history

  • Trade history is a human-readable DataFrame that lists orders and extends them with valuable information on entry trades, exit trades, and positions.
Get the trade history of a random portfolio with one signal
>>> data = vbt.YFData.pull(["BTC-USD", "ETH-USD"], missing_index="drop")
>>> pf = vbt.PF.from_random_signals(
...     data, 
...     n=1,
...     run_kwargs=dict(hide_params=True),
...     tp_stop=0.5, 
...     sl_stop=0.1
... )
>>> pf.trade_history
   Order Id   Column              Signal Index            Creation Index  \
0         0  BTC-USD 2016-02-20 00:00:00+00:00 2016-02-20 00:00:00+00:00   
1         1  BTC-USD 2016-02-20 00:00:00+00:00 2016-06-12 00:00:00+00:00   
2         0  ETH-USD 2019-05-25 00:00:00+00:00 2019-05-25 00:00:00+00:00   
3         1  ETH-USD 2019-05-25 00:00:00+00:00 2019-07-15 00:00:00+00:00   

                 Fill Index  Side    Type Stop Type      Size       Price  \
0 2016-02-20 00:00:00+00:00   Buy  Market      None  0.228747  437.164001   
1 2016-06-12 00:00:00+00:00  Sell  Market        TP  0.228747  655.746002   
2 2019-05-25 00:00:00+00:00   Buy  Market      None  0.397204  251.759872   
3 2019-07-15 00:00:00+00:00  Sell  Market        SL  0.397204  226.583885   

   Fees   PnL  Return Direction  Status  Entry Trade Id  Exit Trade Id  \
0   0.0  50.0     0.5      Long  Closed               0             -1   
1   0.0  50.0     0.5      Long  Closed              -1              0   
2   0.0 -10.0    -0.1      Long  Closed               0             -1   
3   0.0 -10.0    -0.1      Long  Closed              -1              0   

   Position Id  
0            0  
1            0  
2            0  
3            0  

Patterns

  • Patterns are the distinctive formations created by the movements of security prices on a chart and are the foundation of technical analysis. There are new designated functions and classes for detecting patterns of any complexity in any time series data. The concept is simple: fit a pattern to match the scale and period of the selected data window, and compute their element-wise distance to each other to derive a single similarity score. You can then tweak the threshold for this score above which a data window should be marked as "matched". Thanks to Numba, this operation can be done hundreds of thousands of times per second! 🔎

Tutorial

Learn more in the Patterns and projections tutorial.

Find and plot a descending triangle pattern
>>> data = vbt.YFData.pull("BTC-USD")
>>> data.hlc3.vbt.find_pattern(
...     pattern=[5, 1, 3, 1, 2, 1],
...     window=100,
...     max_window=700,
... ).loc["2017":"2019"].plot().show()

Projections

  • There are more clean ways of analyzing events and their impact on the price than conventional backtesting. Meet projections! 👋 Not only they can allow you to assess the performance of events visually and quantitatively, but they also can enable you to project events into the future for assistance in trading. That is possible by extracting the price range after each event, putting all the price ranges into a multidimensional array, and deriving the confidence intervals and other meaningful statistics from that array. Combined with patterns, these are a quantitative analyst's dream tools! 🌠

Tutorial

Learn more in the Patterns and projections tutorial.

Find occurrences of the price moving similarly to the last week and project them
>>> data = vbt.YFData.pull("ETH-USD")
>>> pattern_ranges = data.hlc3.vbt.find_pattern(
...     pattern=data.close.iloc[-7:],
...     rescale_mode="rebase"
... )
>>> delta_ranges = pattern_ranges.with_delta(7)
>>> fig = data.iloc[-7:].plot(plot_volume=False)
>>> delta_ranges.plot_projections(fig=fig)
>>> fig.show()

MAE and MFE

  • Maximum Adverse Excursion (MAE) helps you to identify what the maximum loss was during a trade. This is also known as maximum drawdown of the position. Maximum Favorable Excursion (MFE), on the other hand, shows you what the highest profit was during a trade. Analyzing MAE and MFE statistics can help you optimize your exit strategies.
Analyze the MAE of a random portfolio without SL
>>> data = vbt.YFData.pull("BTC-USD")
>>> pf = vbt.PF.from_random_signals(data, n=50)
>>> pf.trades.plot_mae_returns().show()

Analyze the MAE of a random portfolio with SL
>>> pf = vbt.PF.from_random_signals(data, n=50, sl_stop=0.1)
>>> pf.trades.plot_mae_returns().show()

OHLC-native classes

  • Previously, OHLC was used for simulation but only the close price was used for analysis purposes. With this update, most classes will allow you to keep track of the entire OHLC data for a more accurate quantitative and qualitative analysis.
Plot trades of a random portfolio
>>> data = vbt.YFData.pull("BTC-USD", start="2020-01", end="2020-03")
>>> pf = vbt.PF.from_random_signals(
...     open=data.open,
...     high=data.high,
...     low=data.low,
...     close=data.close,
...     n=10
... )
>>> pf.trades.plot().show()

Benchmark

  • Benchmark can be easily set for the entire portfolio.
Compare Microsoft to S&P 500
>>> data = vbt.YFData.pull(["SPY", "MSFT"], start="2010", missing_columns="drop")

>>> pf = vbt.PF.from_holding(
...     close=data.data["MSFT"]["Close"],
...     bm_close=data.data["SPY"]["Close"]
... )
>>> pf.plot_cum_returns().show()