Files
strategy-lab/to_explore/Walk Forward Optimization - Vectorbt/custom_indicators.py
David Brazda e3da60c647 daily update
2024-10-21 20:57:56 +02:00

704 lines
22 KiB
Python

import numpy as np
import pandas as pd
import vectorbtpro as vbt
import talib
from numba import njit
# TREND FILTER INDICATOR
# Apply Method
def tf_apply(close, ema_period, shift_period, smooth_period, tick_val):
ma = vbt.nb.ma_1d_nb(close/tick_val, ema_period, wtype=2)
rocma = ma - vbt.nb.fshift_1d_nb(ma, shift_period)
tf = np.abs(vbt.nb.ma_1d_nb(rocma, smooth_period, wtype=2))
return tf
# Indicator Factor
TrendFilter = vbt.IF(
class_name='TrendFilter',
short_name='tf',
input_names=['close'],
param_names=['ema_period', 'shift_period', 'smooth_period', 'tick_val'],
output_names=['real']
).with_apply_func(
tf_apply,
takes_1d=True,
ema_period=200,
shift_period=1,
smooth_period=5,
tick_val=1e-4
)
# Adaptive Laguerre
# Helpers
@njit
def alag_loop_nb(p, l, warmup):
f = np.full(p.shape, np.nan)
L0 = np.full(p.shape, np.nan)
L1 = np.full(p.shape, np.nan)
L2 = np.full(p.shape, np.nan)
L3 = np.full(p.shape, np.nan)
dir_ = np.full(p.shape, 1)
d = np.full(l, np.nan)
for i in range(3, p.shape[0]):
if i < warmup:
f[i] = p[i]
L0[i] = p[i]
L1[i] = p[i-1]
L2[i] = p[i-2]
L3[i] = p[i-2]
else:
# Get Differences
mi = 0
mx = 0
a = 0
for j in range(l):
d[j] = p[i-j] - f[i-j-1]
mi = d[j] if d[j] < mi else mi
mx = d[j] if d[j] > mx else mx
# Min-Max Rescale
d = (d - mi)/(mx - mi)
a = np.nanmedian(d)
# Calculation
L0[i] = a*p[i] + (1-a)*L0[i-1]
L1[i] = -(1 - a) * L0[i] + L0[i-1] + (1 - a) * L1[i-1]
L2[i] = -(1 - a) * L1[i] + L1[i-1] + (1 - a) * L2[i-1]
L3[i] = -(1 - a) * L2[i] + L2[i-1] + (1 - a) * L3[i-1]
f[i] = (L0[i] + 2*L1[i] + 2*L2[i] + L3[i])/6
if f[i] < f[i-1]:
dir_[i] = -1
return f, dir_
def alag_apply(high, low, timeperiod):
# Hardcoded 3X Timeperiod Bar Warmup
warm = 3*timeperiod
# Average Price
av_price = talib.MEDPRICE(high, low)
av_price = vbt.nb.ffill_1d_nb(av_price)
# Filter
filter, direction = alag_loop_nb(av_price, timeperiod, warm)
# Warmup
filter[:warm] = np.nan
direction[:warm] = 0
# Trade Indicators
conf = direction > 0
long = vbt.nb.crossed_above_1d_nb(direction, np.full(high.shape, 0))
short = vbt.nb.crossed_below_1d_nb(direction, np.full(high.shape, 0))
return filter, direction, long, short, conf
AdaptiveLaguerre = vbt.IF(
class_name='AdaptiveLaguerre',
short_name='alag',
input_names=['high', 'low'],
param_names=['timeperiod'],
output_names=['filter', 'direction', 'long', 'short', 'conf']
).with_apply_func(
alag_apply,
takes_1d=True,
timeperiod=20,
)
class AdaptiveLaguerre(AdaptiveLaguerre):
from itertools import groupby, accumulate
def plot(self,
column=None,
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
fig=None,
**layout_kwargs):
l_kwargs = l_kwargs if l_kwargs else {}
s_kwargs = s_kwargs if s_kwargs else {}
filter = self.select_col_from_obj(self.filter, column)
direction = self.select_col_from_obj(self.direction, column)
# Coloring
long = pd.Series(np.nan, index=filter.index)
short = pd.Series(np.nan, index=filter.index)
ups = np.where(direction > 0)[0]
downs = np.where(direction < 0)[0]
# Back Shifting the Start of Each Sequence (proper coloring)
# ups
upstarts = ups[1:] - ups[:-1]
upstarts = np.insert(upstarts, 0, upstarts[0]) == 1
upstarts = ups[np.where(~upstarts & np.roll(upstarts,-1))[0]] - 1
ups = np.append(ups, upstarts)
# downs
downstarts = downs[1:] - downs[:-1]
downstarts = np.insert(downstarts, 0, downstarts[0]) == 1
downstarts = downs[np.where(~downstarts & np.roll(downstarts,-1))[0]] - 1
downs = np.append(downs, downstarts)
# Plot Lines
long[ups] = filter.iloc[ups]
short[downs] = filter.iloc[downs]
long.vbt.plot(fig=fig, **l_kwargs)
short.vbt.plot(fig=fig, **s_kwargs)
return fig
# AROON UP & DOWN
# Helpers
def aroon_apply(h, l, t):
u = 100*vbt.nb.rolling_argmax_1d_nb(h, t, local=True)/(t-1)
d = 100*vbt.nb.rolling_argmin_1d_nb(l, t, local=True)/(t-1)
u[:t] = np.nan
d[:t] = np.nan
# Trade Indicators
conf = u > d
long = vbt.nb.crossed_above_1d_nb(u, d)
short = vbt.nb.crossed_below_1d_nb(u, d)
return u, d, long, short, conf
Aroon = vbt.IF(
class_name='Aroon',
short_name='aroon',
input_names=['high', 'low'],
param_names=['timeperiod'],
output_names=['up', 'down', 'long', 'short', 'conf']
).with_apply_func(
aroon_apply,
takes_1d=True,
timeperiod=20,
)
# Class
class Aroon(Aroon):
from itertools import groupby, accumulate
def plot(self,
column=None,
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
fig=None,
**layout_kwargs):
l_kwargs = l_kwargs if l_kwargs else {}
s_kwargs = s_kwargs if s_kwargs else {}
up = self.select_col_from_obj(self.up, column).rename('Aroon(up)')
down = self.select_col_from_obj(self.down, column).rename('Aroon(down)')
up.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
down.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
return fig
# Elhers Two Pole Super Smoother
# Helpers
@njit
def tpss_loop(p, l, warmup):
f = np.full(p.shape, np.nan)
dir_ = np.full(p.shape, 1)
# Initialize
f[:warmup] = p[:warmup]
a1 = np.exp(-1.414*3.14159/l)
b1 = 2*a1*np.cos(np.deg2rad(1.414*180/l))
c2 = b1
c3 = -a1*a1
c1 = 1 - c2 - c3
for i in range(warmup, p.shape[0]):
f[i] = c1*p[i] + c2*f[i-1] + c3*f[i-2]
if f[i] - f[i-1] < 0:
dir_[i] = -1
return f, dir_
def tpss_apply(close, timeperiod):
# Hardcoded 3X Timeperiod Bar Warmup
warm = 3*timeperiod
# Average Price
av_price = (vbt.nb.fshift_1d_nb(close) + close)/2
av_price = vbt.nb.ffill_1d_nb(av_price)
# Filter
filter, direction = tpss_loop(av_price, timeperiod, warm)
# Warmup
filter[:warm] = np.nan
direction[:warm] = 0
# Trade Indicators
conf = direction > 0
long = vbt.nb.crossed_above_1d_nb(direction, np.full(close.shape, 0))
short = vbt.nb.crossed_below_1d_nb(direction, np.full(close.shape, 0))
return filter, direction, long, short, conf
SuperSmoother = vbt.IF(
class_name='SuperSmoother',
short_name='tpss',
input_names=['close'],
param_names=['timeperiod'],
output_names=['filter', 'direction', 'long', 'short', 'conf'],
attr_settings=dict(
filter=dict(dtype=np.float_),
direction=dict(dtype=np.float_),
long=dict(dtype=np.bool_),
short=dict(dtype=np.bool_),
conf=dict(dtype=np.bool_),
)
).with_apply_func(
tpss_apply,
takes_1d=True,
timeperiod=20
)
class SuperSmoother(SuperSmoother):
from itertools import groupby, accumulate
def plot(self,
column=None,
l_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
s_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
fig=None,
**layout_kwargs):
l_kwargs = l_kwargs if l_kwargs else {}
s_kwargs = s_kwargs if s_kwargs else {}
filter = self.select_col_from_obj(self.filter, column)
direction = self.select_col_from_obj(self.direction, column)
# Coloring
long = pd.Series(np.nan, index=filter.index)
short = pd.Series(np.nan, index=filter.index)
ups = np.where(direction > 0)[0]
downs = np.where(direction < 0)[0]
# Back Shifting the Start of Each Sequence (proper coloring)
# ups
upstarts = ups[1:] - ups[:-1]
upstarts = np.insert(upstarts, 0, upstarts[0]) == 1
upstarts = ups[np.where(~upstarts & np.roll(upstarts,-1))[0]] - 1
ups = np.append(ups, upstarts)
# downs
downstarts = downs[1:] - downs[:-1]
downstarts = np.insert(downstarts, 0, downstarts[0]) == 1
downstarts = downs[np.where(~downstarts & np.roll(downstarts,-1))[0]] - 1
downs = np.append(downs, downstarts)
# Plot Lines
long[ups] = filter.iloc[ups]
short[downs] = filter.iloc[downs]
long.vbt.plot(fig=fig, **l_kwargs)
short.vbt.plot(fig=fig, **s_kwargs)
return fig
# Kase Permission Stochastic
@njit
def kase_loop_nb(TripleK, pstX, alpha):
TripleDF = np.full(TripleK.shape[0], 0.0)
TripleDS = np.full(TripleK.shape[0], 0.0)
for i in range(pstX+1, TripleK.shape[0]):
TripleDF[i] = TripleDF[i-pstX] + alpha*(TripleK[i] - TripleDF[i-pstX])
TripleDS[i] = (2.0*TripleDS[i-pstX] + TripleDF[i]) / 3.0
return TripleDF, TripleDS
@njit
def kase_smooth_nb(price, length):
# Initialization
e0 = np.full(price.shape[0], 0.0)
e1 = np.full(price.shape[0], 0.0)
e2 = np.full(price.shape[0], 0.0)
e3 = np.full(price.shape[0], 0.0)
e4 = np.full(price.shape[0], 0.0)
alpha = 0.45*(length-1.0)/(0.45*(length-1.0)+2.0)
# Calculation
for i in range(price.shape[0]):
if i <= 2:
e0[i] = price[i]
e1[i] = price[i]
e2[i] = price[i]
e3[i] = price[i]
e4[i] = price[i]
else:
e0[i] = price[i] + alpha * (e0[i-1] - price[i])
e1[i] = (price[i] - e0[i]) * (1 - alpha) + alpha * e1[i-1]
e2[i] = e0[i] + e1[i]
e3[i] = e2[i] - e4[i-1] * (1-alpha)**2 + (alpha**2) * e3[i-1]
e4[i] = e3[i] + e4[i-1]
return e4
def kase_apply(h, l, c, pstLength, pstX, pstSmooth, smoothPeriod):
# Variables
lookback = pstLength*pstX
alpha = 2.0/(1.0 + pstSmooth)
# Calculations
hh = vbt.nb.rolling_max_1d_nb(h, lookback)
ll = vbt.nb.rolling_min_1d_nb(l, lookback)
# Triple K
TripleK = vbt.nb.fillna_1d_nb(100*(c - ll)/(hh-ll), 0.0)
TripleK[TripleK < 0] = 0.0
# Triple DF, DS
TripleDF, TripleDS = kase_loop_nb(TripleK, pstX, alpha)
# SMA of DF and DS
TripleDFs = talib.SMA(TripleDF, pstSmooth)
TripleDSs = talib.SMA(TripleDS, pstSmooth)
# Kase Smoothing
pst = kase_smooth_nb(TripleDFs, smoothPeriod)
pss = kase_smooth_nb(TripleDSs, smoothPeriod)
# Signals and Confirmation
long = vbt.nb.crossed_above_1d_nb(pst, pss)
short = vbt.nb.crossed_above_1d_nb(pss, pst)
conf = pst > pss
return pst, pss, long, short, conf
KasePermissionStochastic = vbt.IF(
class_name='KasePermissionStochastic',
short_name='KPSS',
input_names=['high', 'low', 'close'],
param_names=['pstLength', 'pstX', 'pstSmooth', 'smoothPeriod'],
output_names=['pst', 'pss', 'long', 'short', 'conf'],
attr_settings=dict(
pst=dict(dtype=np.float_),
pss=dict(dtype=np.float_),
long=dict(dtype=np.bool_),
short=dict(dtype=np.bool_),
conf=dict(dtype=np.bool_),
)
).with_apply_func(
kase_apply,
takes_1d=True,
pstLength=9,
pstX=5,
pstSmooth=3,
smoothPeriod=10
)
class KasePermissionStochastic(KasePermissionStochastic):
from itertools import groupby, accumulate
def plot(self,
column=None,
pst_kwargs=dict(trace_kwargs=dict(line_color='limegreen', connectgaps=False)),
pss_kwargs=dict(trace_kwargs=dict(line_color='red', connectgaps=False)),
fig=None,
**layout_kwargs):
pst_kwargs = pst_kwargs if pst_kwargs else {}
pss_kwargs = pss_kwargs if pss_kwargs else {}
pst = self.select_col_from_obj(self.pst, column).rename('KPSS-pst')
pss = self.select_col_from_obj(self.pss, column).rename('KPSS-pss')
long = self.select_col_from_obj(self.long, column).rename('Long')
short = self.select_col_from_obj(self.short, column).rename('Short')
pst.vbt.plot(fig=fig, **pst_kwargs, **layout_kwargs)
pss.vbt.plot(fig=fig, **pss_kwargs, **layout_kwargs)
long.vbt.signals.plot_as_entries(fig=fig, y=pst, **layout_kwargs)
short.vbt.signals.plot_as_exits(fig=fig, y=pss, **layout_kwargs)
return fig
## TREND LORD
def trend_lord_apply(c, period):
# Variables
sqrt_period = round(np.sqrt(period))
# Calculations
ma = vbt.nb.ma_1d_nb(c, period, wtype=vbt.enums.WType.Weighted)
tl = vbt.nb.ma_1d_nb(ma, sqrt_period, wtype=vbt.enums.WType.Weighted)
# Signals
dir = np.sign(tl - vbt.nb.fshift_1d_nb(tl))
long = vbt.nb.crossed_above_1d_nb(dir, np.full(c.shape[0], 0.0))
short = vbt.nb.crossed_below_1d_nb(dir, np.full(c.shape[0], 0.0))
conf = dir > 0
return tl, dir, long, short, conf
TrendLord = vbt.IF(
class_name='TrendLord',
short_name='tl',
input_names=['close'],
param_names=['timeperiod'],
output_names=['tl', 'direction', 'long', 'short', 'conf']
).with_apply_func(
trend_lord_apply,
takes_1d=True,
timeperiod=20
)
class TrendLord(TrendLord):
from itertools import groupby, accumulate
def plot(self,
column=None,
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
fig=None,
**layout_kwargs):
l_kwargs = l_kwargs if l_kwargs else {}
s_kwargs = s_kwargs if s_kwargs else {}
tl = self.select_col_from_obj(self.tl, column)
direction = self.select_col_from_obj(self.direction, column)
# Coloring
long = pd.Series(np.nan, index=tl.index)
short = pd.Series(np.nan, index=tl.index)
ups = np.where(direction > 0)[0]
downs = np.where(direction < 0)[0]
# Plot Lines
long[ups] = tl.iloc[ups]
short[downs] = tl.iloc[downs]
long.vbt.barplot(fig=fig, **l_kwargs, **layout_kwargs)
short.vbt.barplot(fig=fig, **s_kwargs, **layout_kwargs)
return fig
## Cyber Cycle
@njit
def cyber_cycle_apply(c, a):
# Variables
c = vbt.nb.ffill_1d_nb(c)
smooth = np.full(c.shape[0], 0.0)
cycle = np.full(c.shape[0], 0.0)
trigger = np.full(c.shape[0], 0.0)
# Calculations
for i in range(0, c.shape[0]):
if i < 4:
cycle[i] = (c[i] - 2*c[i-1] + c[i-2])/4
smooth[i] = (c[i] + 2*c[i-1] + 2*c[i-2] + c[i-3])/6
trigger[i] = cycle[i-1]
else:
smooth[i] = (c[i] + 2*c[i-1] + 2*c[i-2] + c[i-3])/6
cycle[i] = ((1 - 0.5*a)**2)*(smooth[i] - 2*smooth[i-1] + smooth[i-2]) + 2*(1-a)*cycle[i-1] - ((1-a)**2)*cycle[i-2]
trigger[i] = cycle[i-1]
# Remove Early Convergence Period
cycle[:30] = 0.0
trigger[:30] = 0.0
# Signals
long = vbt.nb.crossed_above_1d_nb(cycle, trigger)
short = vbt.nb.crossed_below_1d_nb(cycle, trigger)
conf = cycle > trigger
return cycle, trigger, long, short, conf
CyberCycle = vbt.IF(
class_name='CyberCycle',
short_name='cc',
input_names=['close'],
param_names=['alpha'],
output_names=['cycle', 'trigger', 'long', 'short', 'conf']
).with_apply_func(
cyber_cycle_apply,
takes_1d=True,
alpha=0.7
)
class CyberCycle(CyberCycle):
from itertools import groupby, accumulate
def plot(self,
column=None,
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
fig=None,
**layout_kwargs):
l_kwargs = l_kwargs if l_kwargs else {}
s_kwargs = s_kwargs if s_kwargs else {}
cycle = self.select_col_from_obj(self.cycle, column)
trigger = self.select_col_from_obj(self.trigger, column)
cycle.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
trigger.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
return fig
## Inverse Fisher of the Cyber Cycle
def icycle_apply(c, a, p, s, level=0.5):
# Calculate Cyber Cycle
cycle, _, _, _, _ = cyber_cycle_apply(c, a)
# Scaling
h = vbt.nb.rolling_max_1d_nb(cycle, p)
l = vbt.nb.rolling_min_1d_nb(cycle, p)
cycle = (2*5.0)*((cycle-l)/(h-l))-5.0
# Smoothing
cycle = talib.EMA(cycle, s)
# Inverse Fisher of the Cyber Cycle
icc = (np.exp(2*cycle)-1.0)/(np.exp(2*cycle)+1.0)
# Signals
long = vbt.nb.crossed_above_1d_nb(icc, np.full(cycle.shape[0], -1*level))
short = vbt.nb.crossed_below_1d_nb(icc, np.full(cycle.shape[0], level))
long_conf = icc < -1*level
short_conf = icc > level
return icc, long, short, long_conf, short_conf
iCyberCycle = vbt.IF(
class_name='iCyberCycle',
short_name='icc',
input_names=['close'],
param_names=['alpha', 'period', 'smoothing'],
output_names=['icycle', 'long', 'short', 'long_conf', 'short_conf']
).with_apply_func(
icycle_apply,
takes_1d=True,
alpha=0.7,
period=10,
smoothing=9
)
class iCyberCycle(iCyberCycle):
from itertools import groupby, accumulate
def plot(self,
column=None,
c_kwargs=dict(trace_kwargs=dict(marker=dict(color='yellow'))),
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
fig=None,
**layout_kwargs):
l_kwargs = l_kwargs if l_kwargs else {}
s_kwargs = s_kwargs if s_kwargs else {}
c_kwargs = c_kwargs if c_kwargs else {}
icycle = self.select_col_from_obj(self.icycle, column)
long_level = pd.DataFrame(np.full(len(icycle), -0.5), index=icycle.index)
short_level = pd.DataFrame(np.full(len(icycle), 0.5), index=icycle.index)
icycle.vbt.plot(fig=fig, **c_kwargs, **layout_kwargs)
long_level.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
short_level.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
return fig
# Trend Trigger Factor
def t3_ma_apply(c, l, a=0.7):
# Variables
c1 = -a**3
c2 = 3*a**2 + 3*a**3
c3 = -6*a**2 - 3*a - 3*a**3
c4 = 1 + 3*a + a**3 + 3*a**2
# Calculation
e1 = talib.EMA(c, l)
e2 = talib.EMA(e1, l)
e3 = talib.EMA(e2, l)
e4 = talib.EMA(e3, l)
e5 = talib.EMA(e4, l)
e6 = talib.EMA(e5, l)
T3 = c1*e6 + c2*e5 + c3*e4 + c4*e3
return T3
def ttf_apply(c, l, t3=True, t3_period=3, b=0.7):
# Fill Nan
c = vbt.nb.ffill_1d_nb(c)
# Variables
c_s = vbt.nb.fshift_1d_nb(c, l)
# Caluclations
buyPower = vbt.nb.rolling_max_1d_nb(c, l) - vbt.nb.rolling_min_1d_nb(c_s, l)
sellPower = vbt.nb.rolling_max_1d_nb(c_s, l) - vbt.nb.rolling_min_1d_nb(c, l)
ttf = 100*((buyPower-sellPower)/(0.5*(buyPower+sellPower)))
# T3 Smoothing
if t3:
ttf = t3_ma_apply(ttf, t3_period, a=b)
# Signnals
long = vbt.nb.crossed_above_1d_nb(ttf, np.full(c.shape[0], 0.0))
short = vbt.nb.crossed_below_1d_nb(ttf, np.full(c.shape[0], 0.0))
conf = ttf > 0
return ttf, long, short, conf
TrendTriggerFactor = vbt.IF(
class_name='TrendTriggerFactor',
short_name='ttf',
input_names=['close'],
param_names=['timeperiod', 't3', 't3_period', 'b'],
output_names=['ttf', 'long', 'short', 'conf']
).with_apply_func(
ttf_apply,
takes_1d=True,
timeperiod=15,
t3=True,
t3_period=3,
b=0.7
)
class TrendTriggerFactor(TrendTriggerFactor):
from itertools import groupby, accumulate
def plot(self,
column=None,
l_kwargs=dict(trace_kwargs=dict(marker=dict(color='limegreen'))),
s_kwargs=dict(trace_kwargs=dict(marker=dict(color='red'))),
n_kwargs=dict(trace_kwargs=dict(marker=dict(color='rgba(255, 255, 0, 0.1)'))),
fig=None,
signal=True,
**layout_kwargs):
l_kwargs = l_kwargs if l_kwargs else {}
s_kwargs = s_kwargs if s_kwargs else {}
n_kwargs = n_kwargs if n_kwargs else {}
ttf = self.select_col_from_obj(self.ttf, column)
if not signal:
ttf.vbt.plot(fig=fig, **n_kwargs, **layout_kwargs)
else:
long = pd.DataFrame(np.full(len(ttf), 100), index=ttf.index)
short = pd.DataFrame(np.full(len(ttf), -100), index=ttf.index)
neutral = pd.DataFrame(np.full(len(ttf), np.nan), index=ttf.index)
long_entry = self.select_col_from_obj(self.long, column).fillna(False)
short_entry = self.select_col_from_obj(self.short, column).fillna(False)
long_idx = long_entry
short_idx = short_entry
pre_long_idx = long_idx.shift(-1)
pre_short_idx = short_idx.shift(-1)
neutral[long_idx==True] = 100
neutral[pre_long_idx==True] = -100
neutral[short_idx==True] = -100
neutral[pre_short_idx==True] = 100
long[~self.select_col_from_obj(self.conf, column)] = np.nan
short[self.select_col_from_obj(self.conf, column)] = np.nan
long.vbt.plot(fig=fig, **l_kwargs, **layout_kwargs)
short.vbt.plot(fig=fig, **s_kwargs, **layout_kwargs)
neutral.vbt.plot(fig=fig, **n_kwargs, **layout_kwargs)
return fig