S&P500混合季节性的交易策略是一种利用季节性规律进行股票交易的量化策略。该策略结合了增强型买入持有系统、技术指标条件以及资金流量指标,实现了在一年中较好和较差交易月份之间进行轮动。
策略的交易信号和规则主要如下:
该策略利用了股票市场在一年内表现不均的规律,在历史统计上表现较好的10-4月份做多,在历史上表现较差的5-9月份止盈或做空,进行反向交易。同时,策略加入了一些技术指标条件,在市场大幅波动时暂缓交易,有助于规避风险。
S&P500混合季节性的交易策略具有以下优势:
S&P500混合季节性的交易策略也存在一些潜在风险:
可通过风险管理、指标组合、参数调整、引入机器学习等方式进一步强化策略,解决上述风险。
S&P500混合季节性的交易策略可从以下方面进行进一步优化:
S&P500混合季节性的交易策略综合运用了成熟的季节性规律、技术指标条件和资金流量指标。该策略回避了股票市场表现最差的几个月,在一年中较好的交易月份进行配置,并内置了有效的市场波动过滤机制,可产生稳定的超额收益。同时,策略易于测试、优化和调整,也为量化交易者提供了一个可参考和二次开发的框架。通过引入更多数据、止损措施、参数调整和组合等方法,有望进一步增强策略的效果。
/*backtest
start: 2023-12-01 00:00:00
end: 2023-12-31 23:59:59
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/
// TASC Issue: April 2022 - Vol. 40, Issue 4
// Article: Sell In May? Stock Market Seasonality
// Article By: Markos Katsanos
// Language: TradingView's Pine Script v5
// Provided By: PineCoders, for tradingview.com
//@version=5
strategy(title = "TASC 2022.04 S&P500 Hybrid Seasonal System",
shorttitle = "HSS v2.0",
overlay = true,
default_qty_type = strategy.percent_of_equity,
default_qty_value = 10,
initial_capital = 100000,
currency = currency.USD,
commission_type = strategy.commission.percent,
commission_value = 0.01
)
// Helper Functions:
// @function Returns the ratio to max/min of a sample period
// @param src float, data source.
// @param length int, period of the sample.
// @returns [float, float] tuple.
volatility (float src, int length) =>
[(src / ta.highest(src, length)[1] - 1.0) * 100.0,
(src / ta.lowest (src, length)[1] - 1.0) * 100.0]
// @function Volume Flow Indicator.
// @param Period int, period of the data sample.
// @param VCoef float, Volume Volatility Coefficient.
// @param Coef float, Cutoff Coefficient.
// @returns float.
// ref: https://mkatsanos.com/volume-flow-vfi-indicator/
vfi (int Period = 130, float VCoef = 2.5, float Coef = 0.2) =>
lastHLC3 = nz(hlc3[1], hlc3)
MF = hlc3 - lastHLC3
Vinter = ta.stdev(math.log(hlc3) - math.log(lastHLC3), 30)
Vave = ta.sma(volume, Period)[1]
Cutoff = Coef * close * Vinter
VC = math.min(volume, Vave * VCoef)
VCP = MF > Cutoff ? VC :
MF < -Cutoff ? -VC : 0.0
VFI1 = nz(math.sum(VCP, Period) / Vave)
VFI = ta.ema(VFI1, 3)
// inputs:
// optional strategy obserservation window parameters:
string ig_ow = 'Observation Window:'
bool i_Sdate = input( title = 'Start date:',
defval = timestamp('2021-01-01'),
inline = 'Sdate',
group = ig_ow
) < time //
bool i_useSdate = input.bool( title = '',
defval = false,
group = ig_ow,
inline = 'Sdate',
tooltip = 'Optional start date to clamp strategy observation window.'
) //
bool i_Edate = input( title = 'End date:',
defval = timestamp('2022-01-01'),
inline = 'Edate',
group = ig_ow
) > time //
bool i_useEdate = input.bool( title = '',
defval = false,
group = ig_ow,
inline = 'Edate',
tooltip = 'Optional end date to clamp strategy observation window.'
) //
//
string ig_ro = 'Lookback Options:'
int i_lback = input.int( title = 'Lookback Shift:',
defval = 0, minval = 0,
group = ig_ro,
tooltip = 'Optional, inspect previous signal values.'
) //
//
string ig_so = 'Signal Options:'
bool i_onlyL = input.bool( title = 'Long Only:',
defval = true,
group = ig_so,
tooltip = 'If switched off, short entries are initiated by sell signals.'
) //
int i_sMonth = input.int( title = 'Sell Month:',
defval = 8, minval = 1, maxval = 12, step = 1,
group = ig_so,
tooltip = 'The worst performing month, originally clamped between months 5 and 8.'
) //
int i_maxVI = input.int( title = 'Max VIX up:',
defval = 60, minval = 50, maxval = 60, step = 5,
group = ig_so,
tooltip = 'Volatility maximum threshold.'
) //
int i_critVFI = input.int( title = 'Critical VFI Sell:',
defval = -20, minval = -20, maxval = -15, step = 5,
group = ig_so,
tooltip = 'Critical money float (VFI) threshold for sell signal.'
) //
float i_K = input.float( title = 'ATR/VIX Ratio:',
defval = 1.5, minval = 1.3, maxval = 1.7, step = 0.2,
group = ig_so,
tooltip = 'ATR to VIX ratio for sell signal.'
) //
//
string i_VIticker = input( title = 'Volatility Index:',
defval = 'VIX',
group = ig_so,
tooltip = 'Volatility Index Ticker.'
) //
string i_VItf = input.timeframe( title = '',
defval = 'D',
group = ig_so,
tooltip = 'Volatility Index Timeframe.'
) //
int i_VIiperiod = input.int( title = 'Implied Volatility period:',
defval = 25,
group = ig_so
) //
int i_VIhperiod = input.int( title = 'Historical Volatility period:',
defval = 15,
group = ig_so
) //
//
int i_VFIperiod = input.int( title = 'VFI period:',
defval = 130,
group = ig_so, inline = 'VFI1'
) //
int i_VFIMperiod = input.int( title = 'MA:',
defval = 10,
group = ig_so, inline = 'VFI1',
tooltip = 'VFI and Moving Average sampling period.'
) //
float i_VFIcoef = input.float( title = 'VFI Coef Cuttoff:',
defval = 0.2,
group = ig_so, inline = 'VFI2'
) //
float i_VFIvcoef = input.float( title = 'Volat.:',
defval = 2.5,
group = ig_so, inline = 'VFI2',
tooltip = 'VFI Cutoff and Volatility coefficient.'
) //
int i_ATRperiod = input.int( title = 'ATR length:',
defval = 15,
group = ig_so, inline = 'ATR',
tooltip = 'ATR length.'
) //
//
string ig_to = 'Table Options:'
bool i_showT = input.bool( title = 'Show Table:',
defval = false,
group = ig_to,
tooltip = 'Optional toggle.'
) //
string i_Tpos = input.string(title = 'Position:',
defval = position.middle_right,
options = [ position.top_left, position.top_center, position.top_right,
position.middle_left, position.middle_center, position.middle_right,
position.bottom_left, position.bottom_center, position.bottom_right ],
group = ig_to) //
int i_Ttransp = input.int( title = 'Transparency:',
defval = 0, minval = 1, maxval = 99,
group = ig_to
) //
//
color i_Tcframe = input.color( title = 'Table Colors:',
defval = #000000,
group = ig_to, inline = 'table color'
) //
color i_Tcrowe = input.color( title = '',
defval = #d6dae3,
group = ig_to, inline = 'table color'
) //
color i_Tcrowo = input.color( title = '',
defval = #cccccc,
group = ig_to, inline = 'table color',
tooltip = 'Table background colors, in order: frame, even row, odd row.'
) //
string i_Ttsize = input.string(title = 'Table Text:',
defval = size.small,
options = [size.auto, size.huge, size.large, size.normal, size.small, size.tiny],
group = ig_to, inline = 'table text'
) //
color i_Tcdeft = input.color( title = 'Text Colors:',
defval = #000000,
group = ig_to, inline = 'table text'
) //
color i_Tcsigt = input.color( title = '',
defval = color.red,
group = ig_to, inline = 'table text'
) //
color i_Tctitt = input.color( title = '',
defval = color.navy,
group = ig_to, inline = 'table text',
tooltip = 'Table text size and colors, in order: default, short signal, title.'
) //
// Comparison Index
float VIX = request.security(i_VIticker, i_VItf, close)
[VIdn, VIup] = volatility(VIX, i_VIiperiod) // Implied
[ATRdn, ATRup] = volatility(ta.atr(i_VIhperiod), i_VIiperiod) // Historical
float VFI = vfi(i_VFIperiod, i_VFIvcoef, i_VFIcoef)
float VFI10 = ta.sma(VFI, i_VFIMperiod)
//
bool VFIatCrit = VFI > i_critVFI
bool lowVolat = (VIup < i_maxVI) or (ATRup < (i_K * i_maxVI))
bool VolatC = VFIatCrit ? lowVolat : false
bool Long = ((month >= 10) or (month < i_sMonth)) and VolatC[1]
bool Sseasonal = month == i_sMonth // SEASONAL EXIT/SHORT
bool Svol = VIup > (2.0 * i_maxVI) // VOLATILITY EXIT/SHORT
bool Scrit = ta.cross(i_critVFI, VFI) and (VFI10 < VFI10[1]) // VFI EXIT/SHORT
bool Short = Sseasonal or Svol[1] or Scrit[1]
bool withinObsWindow = true
//
if withinObsWindow and strategy.equity > 0
_L = strategy.long
_S = strategy.short
strategy.entry('L' , direction = _L, when = Long )
if i_onlyL
strategy.close('L', comment = 'EXIT SEASONAL' , when = Sseasonal )
strategy.close('L', comment = 'EXIT VOLATILITY', when = Svol[1] )
strategy.close('L', comment = 'EXIT MF' , when = Scrit[1] )
else
strategy.entry('S Seasonal' , direction = _S, when = Sseasonal )
strategy.entry('S Volatility', direction = _S, when = Svol[1] )
strategy.entry('S MF Crit.' , direction = _S, when = Scrit[1] )
else
strategy.close_all()
string SIGNAL = switch
(Long) => 'Long Seasonal'
(Sseasonal and i_onlyL) => 'Exit Seasonal'
(Svol[1] and i_onlyL) => 'Exit Volatility'
(Scrit[1] and i_onlyL) => 'Exit Money Flow'
(Sseasonal and not i_onlyL) => 'Short Seasonal'
(Svol[1] and not i_onlyL) => 'Short Volatility'
(Scrit[1] and not i_onlyL) => 'Short Money Flow Bearish'
=> 'none'
string date = str.format(
'{0,number,0000}-{1,number,00}-{2,number,00}',
year, month, dayofmonth
)
var table dTable = table.new(position = i_Tpos,
columns = 2,
rows = 17,
frame_color = color.new(#000000, i_Ttransp),
frame_width = 4
) //
// @function Helper to populate the table rows.
tRow(tableId, idx, left, right, tcol=0) =>
color _bg = color.new(idx % 2 ? i_Tcrowo : i_Tcrowe, i_Ttransp)
color _tx = switch (tcol)
(1) => color.new(i_Tcsigt, i_Ttransp)
(2) => color.new(i_Tctitt, i_Ttransp)
=> color.new(i_Tcdeft, i_Ttransp)
// table.cell( table_id=tableId,
// column=0, row=idx,
// text=left, text_color=_tx, text_halign=text.align_right, text_size=i_Ttsize,
// bgcolor=_bg) //
// table.cell( table_id=tableId,
// column=1, row=idx,
// text=str.tostring(right), text_color=_tx, text_halign=text.align_left, text_size=i_Ttsize,
// bgcolor=_bg) //
if i_showT
float _atr10 = ta.atr(10)[i_lback]
string _nf = '0.00'
string _aru = '🔼 ', string _ard = '🔽 '
// id | idx | left label | right label | conditional color |
tRow(dTable, 00, 'S&P500 Hybrid Seasonal ' , '' , 2 )
tRow(dTable, 01, 'Created By: Markos Katsanos' , '' , 2 )
tRow(dTable, 02, 'Date:' , date[i_lback] )
tRow(dTable, 03, 'Signal:' , SIGNAL[i_lback] )
tRow(dTable, 04, 'Price:' , open[i_lback] )
tRow(dTable, 05, 'VIX:' , str.tostring( VIX[i_lback], _nf) )
tRow(dTable, 06, 'VFI:' , str.tostring( VFI[i_lback], _nf) , VFIatCrit ? 1 : 0 )
tRow(dTable, 07, 'ATR:' , str.tostring( _atr10, _nf) )
tRow(dTable, 08, 'VIup%:' , str.tostring( VIup[i_lback], _nf) , VIup > i_maxVI ? 1 : 0 )
tRow(dTable, 09, 'ATRup%:' , str.tostring(ATRup[i_lback], _nf) , ATRup > i_K * i_maxVI ? 1 : 0 )
tRow(dTable, 10, 'VIdn%:' , str.tostring( VIdn[i_lback], _nf) )
tRow(dTable, 11, 'ATRdn%:' , str.tostring(ATRdn[i_lback], _nf) )
tRow(dTable, 12, _aru + 'Long Seasonal:' , Long[i_lback] )
tmp = 12
if not i_onlyL
tmp := 13
tRow(dTable, 13, _ard + 'Short:' , Short[i_lback] , Short[i_lback] ? 1 : 0 )
tRow(dTable, tmp+1, _ard + 'Seasonal:' , Sseasonal[i_lback] , Sseasonal[i_lback] ? 1 : 0 )
tRow(dTable, tmp+2, _ard + 'Volatility:' , Svol[1+i_lback] , Svol[1 + i_lback] ? 1 : 0 )
tRow(dTable, tmp+3, _ard + 'Money Flow:' , Scrit[i_lback] , Scrit[i_lback] ? 1 : 0 )