Files
strategy-lab/to_explore/notebooks/MTFAnalysis.ipynb

36 KiB

MTF analysis

In [ ]:
from vectorbtpro import *
# whats_imported()

vbt.settings.set_theme("dark")

Data

In [ ]:
# h1_data = vbt.BinanceData.pull(
#     "BTCUSDT", 
#     start="2020-01-01 UTC", 
#     end="2021-01-01 UTC",
#     timeframe="1h"
# )

# h1_data.to_hdf()
In [ ]:
h1_data = vbt.HDFData.pull('BinanceData.h5')
In [ ]:
h1_data.wrapper.index
In [ ]:
h1_resampler = h1_data.wrapper.get_resampler("1h")
h1_resampler.index_difference(reverse=True)
In [ ]:
h1_data.wrapper.columns
In [ ]:
h1_ohlcv_data = h1_data[["Open", "High", "Low", "Close", "Volume"]]
In [ ]:
h4_ohlcv = h1_ohlcv_data.get().resample("4h").agg({
    "Open": "first",
    "High": "max",
    "Low": "min",
    "Close": "last",
    "Volume": "sum"
})
h4_ohlcv
In [ ]:
print(h1_ohlcv_data.get().iloc[:4])
In [ ]:
print(h4_ohlcv.iloc[[0]])
In [ ]:
print(vbt.prettify(vbt.BinanceData.feature_config))
In [ ]:
h1_data.use_feature_config_of(vbt.BinanceData)

h4_data = h1_data.resample("4h")
d1_data = h1_data.resample("1d")
In [ ]:
print(d1_data.get().iloc[[0, -1]])
In [ ]:
print(vbt.BinanceData.pull(
    "BTCUSDT", 
    start="2020-01-01 UTC", 
    end="2021-01-01 UTC",
    timeframe="1d"
).get().iloc[[0, -1]])

Alignment

Pandas

In [ ]:
h1_close = h1_data.get("Close")
h4_close = h4_data.get("Close")
In [ ]:
h1_close.iloc[:4]
In [ ]:
h4_close.iloc[:1]
In [ ]:
h1_h4_ratio = h1_close / h4_close
h1_h4_ratio.iloc[:4]
In [ ]:
h4_close_shifted = h4_close.shift()
h1_h4_ratio = h1_close / h4_close_shifted
h1_h4_ratio.iloc[:8]
In [ ]:
h1_h4_ratio.shift(-1).iloc[:8]
In [ ]:
h4_h1_close = h4_close.shift(1).resample("1h").last().shift(-1).ffill()
h4_h1_close.iloc[:8]
In [ ]:
fig = h1_close.rename("H1").iloc[:16].vbt.plot()
h4_h1_close.rename("H4_H1").iloc[:16].vbt.plot(fig=fig).show_svg()
In [ ]:
h1_h4_ratio = h1_close / h4_h1_close
h1_h4_ratio
In [ ]:
h1_open = h1_data.get("Open")
h4_open  = h4_data.get("Open")

h1_open.iloc[:8]
In [ ]:
h4_h1_open = h4_open.resample("1h").first().ffill()
h4_h1_open.iloc[:8]

VBT

In [ ]:
h4_close.vbt.realign_closing("1h")
In [ ]:
h4_open.vbt.realign_opening("1h")

Resampler

In [ ]:
h4_h1_resampler = h4_close.vbt.wrapper.get_resampler("1h")
h4_h1_resampler.source_index
In [ ]:
h4_h1_resampler.target_index
In [ ]:
h4_h1_resampler.source_freq
In [ ]:
h4_h1_resampler.target_freq
In [ ]:
pd_resampler = h4_close.resample("1h")
vbt.Resampler.from_pd_resampler(pd_resampler)
In [ ]:
resampler = vbt.Resampler.from_date_range(
    source_index=h4_close.index,
    source_freq="4h",
    start="2020-01-01 10:00:00",
    end="2020-01-01 22:00:00",
    freq="1h",
)
In [ ]:
h4_close.vbt.realign_closing(resampler)

Custom index

In [ ]:
target_index = pd.Index([
    "2020-01-01",
    "2020-02-01",
    "2020-03-01",
    "2020-04-01",
    "2020-05-01",
    "2020-06-01",
    "2020-07-01",
    "2020-08-01",
    "2020-09-01",
    "2020-10-01",
    "2020-11-01",
    "2020-12-01",
    "2021-01-01"
])
resampler = vbt.Resampler(h4_close.index, target_index, target_freq=False)
h4_close.vbt.realign_closing(resampler)
In [ ]:
h4_close[h4_close.index < "2020-09-01"].iloc[-1]
In [ ]:
h4_open.vbt.realign_opening(resampler)
In [ ]:
h4_open[h4_open.index <= "2020-08-01"].iloc[-1]
In [ ]:
target_index = pd.Index([
    "2020-01-01",
    "2020-02-01",
])
resampler = vbt.Resampler(h4_close.index, target_index, target_freq=False)
h4_close.vbt.realign_closing(resampler)
In [ ]:
resampler = vbt.Resampler(h4_close.index, target_index, target_freq="30d")
h4_close.vbt.realign_closing(resampler)
In [ ]:
h4_open.vbt.realign("2020-06-07 12:15:00")
In [ ]:
h4_close.vbt.realign(
    "2020-06-07 12:15:00", 
    source_rbound=True
)
In [ ]:
h4_high = h4_data.get("High")
h4_high.vbt.realign(
    target_index, 
    source_rbound=True
)
In [ ]:
h4_high.index[h4_high.index < "2020-02-01"][-1]
In [ ]:
h4_high.vbt.realign(
    target_index, 
    source_rbound=True,
    target_rbound=True
)
In [ ]:
resampler = vbt.Resampler(h4_high.index, target_index)
resampler.target_rbound_index
In [ ]:
resampler = vbt.Resampler(
    h4_high.index, 
    target_index, 
    target_freq=pd.offsets.MonthBegin(1))
resampler.target_rbound_index
In [ ]:
h4_high.vbt.realign(
    resampler.replace(
        target_index=resampler.target_rbound_index, 
        target_freq=False
    ), 
    wrap_kwargs=dict(index=target_index)
)
In [ ]:
h4_high.vbt.realign(
    target_index, 
    freq=pd.offsets.MonthBegin(1),
    target_rbound="pandas"
)
In [ ]:
h4_high[h4_high.index < "2020-03-01"].resample(vbt.offset("M")).last()

Numeric index

In [ ]:
resampler = vbt.Resampler(
    source_index=np.arange(len(h4_high)),
    target_index=np.arange(len(h4_high))[::6],
    source_freq=1,
    target_freq=6
)
h4_high.vbt.realign(
    resampler, 
    source_rbound=True,
    target_rbound=True
)

Forward filling

In [ ]:
min5_index = vbt.date_range(start="2020", freq="5min", periods=3)
min1_index = vbt.date_range(start="2020", freq="1min", periods=15)
min5_mask = pd.Series(False, index=min5_index)
min5_mask.iloc[0] = True
min5_mask.iloc[2] = True

resampler = vbt.Resampler(min5_index, min1_index)
min1_mask = min5_mask.vbt.realign_closing(resampler)
min1_mask
In [ ]:
min1_mask = min5_mask.vbt.realign_closing(resampler, ffill=False)
min1_mask
In [ ]:
min1_mask = min1_mask.fillna(False).astype(bool)
min1_mask

Indicators

In [ ]:
h4_sma = vbt.talib("SMA").run(h4_data.get("Close"), skipna=True).real
d1_sma = vbt.talib("SMA").run(d1_data.get("Close"), skipna=True).real

h4_sma = h4_sma.ffill()
d1_sma = d1_sma.ffill()
In [ ]:
resampler = vbt.Resampler(
    d1_sma.index,
    h4_sma.index,
    source_freq="1d",
    target_freq="4h"
)
d1_h4_sma = d1_sma.vbt.realign_closing(resampler)
In [ ]:
d1_sma["2020-12-30":]
In [ ]:
d1_h4_sma["2020-12-30":]
In [ ]:
entries = h4_sma.vbt.crossed_above(d1_h4_sma)
exits = h4_sma.vbt.crossed_below(d1_h4_sma)

def plot_date_range(date_range):
    fig = h4_sma[date_range].rename("H4").vbt.plot()
    d1_h4_sma[date_range].rename("D1_H4").vbt.plot(fig=fig)
    entries[date_range].rename("Entry").vbt.signals.plot_as_entries(
        y=h4_sma[date_range], fig=fig)
    exits[date_range].rename("Exit").vbt.signals.plot_as_exits(
        y=h4_sma[date_range], fig=fig)
    return fig

plot_date_range(slice("2020-02-01", "2020-03-01")).show_svg()
In [ ]:
d1_open_sma = vbt.talib("SMA").run(
    d1_data.get("Open"), 
    skipna=True
).real
d1_open_sma = d1_open_sma.ffill()

d1_h4_open_sma = d1_open_sma.vbt.realign(
    resampler, 
    source_rbound=False,
    target_rbound=True,
)
In [ ]:
d1_open_sma["2020-12-30":]
In [ ]:
d1_h4_open_sma["2020-12-30":]
In [ ]:
def generate_bandwidths(freqs):
    bandwidths = []
    for freq in freqs:
        close = h1_data.resample(freq).get("Close")
        bbands = vbt.talib("BBANDS").run(close, skipna=True)
        upperband = bbands.upperband.ffill()
        middleband = bbands.middleband.ffill()
        lowerband = bbands.lowerband.ffill()
        bandwidth = (upperband - lowerband) / middleband
        bandwidths.append(bandwidth.vbt.realign_closing("1h"))
    df = pd.concat(bandwidths, axis=1, keys=pd.Index(freqs, name="timeframe"))
    return df.ffill()

bandwidths = generate_bandwidths(["1h", "4h", "1d", "7d"])
print(bandwidths)
In [ ]:
bandwidths.loc[:, ::-1].vbt.ts_heatmap().show_svg()
In [ ]:
>>> bbands = vbt.talib("BBANDS").run(
...     h1_data.get("Close"), 
...     skipna=True, 
...     timeframe=["1h", "4h", "1d", "7d"],
...     broadcast_kwargs=dict(wrapper_kwargs=dict(freq="1h"))
... )
>>> bandwidth = (bbands.upperband - bbands.lowerband) / bbands.middleband
>>> print(bandwidths)

Testing

In [ ]:
def generate_signals(data, freq, fast_window, slow_window):
    open_price = data.get("Open").resample(freq).first()
    fast_sma = vbt.talib("SMA")\
        .run(
            open_price, 
            fast_window, 
            skipna=True, 
            short_name="fast_sma"
        )\
        .real.ffill()\
        .vbt.realign(data.wrapper.index)
    slow_sma = vbt.talib("SMA")\
        .run(
            open_price, 
            slow_window, 
            skipna=True, 
            short_name="slow_sma"
        )\
        .real.ffill()\
        .vbt.realign(data.wrapper.index)
    entries = fast_sma.vbt.crossed_above(slow_sma)
    exits = fast_sma.vbt.crossed_below(slow_sma)
    return entries, exits

fast_window = [10, 20]
slow_window = [20, 30]
h1_entries, h1_exits = generate_signals(h1_data, "1h", fast_window, slow_window)
h4_entries, h4_exits = generate_signals(h1_data, "4h", fast_window, slow_window)
d1_entries, d1_exits = generate_signals(h1_data, "1d", fast_window, slow_window)

entries = pd.concat(
    (h1_entries, h4_entries, d1_entries), 
    axis=1, 
    keys=pd.Index(["1h", "4h", "1d"], name="timeframe")
)
exits = pd.concat(
    (h1_exits, h4_exits, d1_exits), 
    axis=1, 
    keys=pd.Index(["1h", "4h", "1d"], name="timeframe")
)
In [ ]:
(entries.astype(int) - exits.astype(int))\
    .resample("1d").sum()\
    .vbt.ts_heatmap(
        trace_kwargs=dict(
            colorscale=["#ef553b", "rgba(0, 0, 0, 0)", "#17becf"],
            colorbar=dict(
                tickvals=[-1, 0, 1], 
                ticktext=["Exit", "", "Entry"]
            )
        )
    ).show_svg()
In [ ]:
pf = vbt.Portfolio.from_signals(
    h1_data,
    entries,
    exits,
    sl_stop=0.1,
    freq="1h"
)

pf.orders.count()
In [ ]:
pf.sharpe_ratio

Aggregation

In [ ]:
ms_data = h1_data.resample("M")
ms_data.get("Low") / ms_data.get("High") - 1
In [ ]:
h1_high = h1_data.get("High")
h1_low = h1_data.get("Low")
ms_high = h1_high.resample(vbt.offset("M")).max()
ms_low = h1_low.resample(vbt.offset("M")).min()
ms_low / ms_high - 1
In [ ]:
ms_high = h1_high.vbt.resample_apply("M", vbt.nb.max_reduce_nb)
ms_low = h1_low.vbt.resample_apply("M", vbt.nb.min_reduce_nb)
ms_low / ms_high - 1

Custom index

Using target index

In [ ]:
target_index = pd.Index([
    "2020-01-01",
    "2020-02-01",
])
h1_high.vbt.resample_to_index(
    target_index, 
    vbt.nb.max_reduce_nb
)
In [ ]:
target_rbound_index = vbt.Resampler.get_rbound_index(
    target_index, 
    pd.offsets.MonthBegin(1)
)
h1_high.vbt.resample_to_index(
    target_index.append(target_rbound_index[[-1]]), 
    vbt.nb.max_reduce_nb
).iloc[:-1]
In [ ]:
h1_high[:"2020-03-01"].resample(vbt.offset("M")).max().iloc[:-1]

Using group-by

In [ ]:
pd_resampler = h1_high.resample(vbt.offset("M"))
ms_high = h1_high.vbt.groupby_apply(pd_resampler, vbt.nb.max_reduce_nb)
ms_low = h1_low.vbt.groupby_apply(pd_resampler, vbt.nb.min_reduce_nb)
ms_low / ms_high - 1
In [ ]:
target_lbound_index = pd.Index([
    "2020-01-01",
    "2020-02-01",
])
target_rbound_index = pd.Index([
    "2020-02-01",
    "2020-03-01",
])
h1_high.vbt.resample_between_bounds(
    target_lbound_index, 
    target_rbound_index,
    vbt.nb.max_reduce_nb
)
In [ ]:
h1_high.vbt.resample_between_bounds(
    "2020-01-01", 
    vbt.date_range("2020-01-02", "2021-01-01", freq="M", inclusive="both"),
    vbt.nb.max_reduce_nb
)
In [ ]:
h1_high.expanding().max().resample(vbt.offset("M")).max()

Meta methods

In [ ]:
@njit
def mdd_nb(from_i, to_i, col, high, low):
    highest = np.nanmax(high[from_i:to_i, col])
    lowest = np.nanmin(low[from_i:to_i, col])
    return lowest / highest - 1

vbt.pd_acc.resample_apply(
    "M",
    mdd_nb,
    vbt.Rep("high"),
    vbt.Rep("low"),
    broadcast_named_args=dict(
        high=h1_high,
        low=h1_low
    )
)
In [ ]:
h1_high.iloc[0:744]
In [ ]:
h1_low.iloc[0:744].min() / h1_high.iloc[0:744].max() - 1
In [ ]:
>>> target_lbound_index = vbt.date_range("2020-01-01", "2020-12-01", freq="M", tz="UTC", inclusive="both")
>>> target_rbound_index = vbt.date_range("2020-02-01", "2021-01-01", freq="M", tz="UTC", inclusive="both")
>>> vbt.pd_acc.resample_between_bounds(
...     target_lbound_index,
...     target_rbound_index,
...     mdd_nb,
...     vbt.Rep("high"),
...     vbt.Rep("low"),
...     broadcast_named_args=dict(
...         high=h1_high,
...         low=h1_low
...     )
... )

Numba

In [ ]:
>>> from vectorbtpro.base.resampling.nb import map_bounds_to_source_ranges_nb

>>> range_starts, range_ends = map_bounds_to_source_ranges_nb(
...     source_index=h1_high.index.values,
...     target_lbound_index=target_lbound_index.values,
...     target_rbound_index=target_rbound_index.values,
...     closed_lbound=True,
...     closed_rbound=False,
... )
>>> np.column_stack((range_starts, range_ends))
In [ ]:
>>> ms_mdd_arr = vbt.nb.reduce_index_ranges_meta_nb(
...     1,
...     range_starts,
...     range_ends,
...     mdd_nb,
...     vbt.to_2d_array(h1_high),
...     vbt.to_2d_array(h1_low)
... )
>>> ms_mdd_arr
In [ ]:
>>> pd.Series(ms_mdd_arr[:, 0], index=target_lbound_index)

Caveats

In [ ]:
h4_close_2d = h4_close.iloc[:12]
h4_close_2d
In [ ]:
h4_close_2d.resample("1d").last()
In [ ]:
h5_close = h1_close.resample("5h").last()
h5_close_2d = h5_close.iloc[:10]
h5_close_2d
In [ ]:
h5_close_2d.resample("1d").last()
In [ ]:
vbt.timedelta("1d") % vbt.timedelta("1h")
In [ ]:
vbt.timedelta("1d") % vbt.timedelta("4h")
In [ ]:
vbt.timedelta("1d") % vbt.timedelta("5h")
In [ ]:
h5_close_time = h5_close_2d.index.shift() - pd.Timedelta(nanoseconds=1)
h5_close_time.name = "Close time"
h5_close_2d.index = h5_close_time
h5_close_2d
In [ ]:
h5_close_2d.resample("1d").last()

Portfolio

In [ ]:
fast_sma = vbt.talib("SMA").run(h1_close, timeperiod=vbt.Default(10))
slow_sma = vbt.talib("SMA").run(h1_close, timeperiod=vbt.Default(20))
entries = fast_sma.real_crossed_above(slow_sma.real)
exits = fast_sma.real_crossed_below(slow_sma.real)

pf = vbt.Portfolio.from_signals(h1_close, entries, exits)
pf.plot().show_svg()
In [ ]:
ms_pf = pf.resample("M")
ms_pf.plot().show_svg()
In [ ]:
pf.total_return
In [ ]:
ms_pf.total_return
In [ ]:
(1 + pf.returns).resample(vbt.offset("M")).apply(lambda x: x.prod() - 1)
In [ ]:
ms_pf.returns
In [ ]:
ms_pf.trades.pnl.to_pd(reduce_func_nb="sum")
In [ ]: