混合性的交易策略

Author: ChaoZhang, Date: 2024-01-22 11:59:58
Tags:

混合性的交易策略

概述

S&P500混合季节性的交易策略是一种利用季节性规律进行股票交易的量化策略。该策略结合了增强型买入持有系统、技术指标条件以及资金流量指标,实现了在一年中较好和较差交易月份之间进行轮动。

策略原理

策略的交易信号和规则主要如下:

  1. 每年10月第一个交易日开盘时做多入场。
  2. 当VIX高于60%或15日ATR高于90%时,暂缓季节性的交易,等待市场波动平息后再入场。
  3. 每年8月第一个交易日开盘时平仓。
  4. 当VIX超过120%或资金流量指标VFI跌破-20且10日平均线向下时,也会发出平仓信号。
  5. 可选加入做空交易。

该策略利用了股票市场在一年内表现不均的规律,在历史统计上表现较好的10-4月份做多,在历史上表现较差的5-9月份止盈或做空,进行反向交易。同时,策略加入了一些技术指标条件,在市场大幅波动时暂缓交易,有助于规避风险。

优势分析

S&P500混合季节性的交易策略具有以下优势:

  1. 利用成熟和稳定的季节性规律。该策略奠基在S&P500指数一年内有明显不同表现月份的事实基础之上。
  2. 结合多种过滤条件。策略加入了VIX、ATR、VFI等多个条件,可有效过滤 Noise 并发出更可靠的交易信号。
  3. 可配置的交易规则。策略可选加入做多或做空,交易月份也可根据需要调整,易于测试和优化。
  4. 内置风险规避机制。如VIX和ATR的波动度检测,可有效回避市场剧烈波动的影响。
  5. 资金流量指标辅助判断。VFI可反映市场参与者的资金流向,为策略决策提供额外依据。

风险分析

S&P500混合季节性的交易策略也存在一些潜在风险:

  1. 历史规律失效的风险。股票市场运行有较强不确定性,历史规律不一定永远有效。
  2. 技术指标发出错误信号的风险。VIX、ATR和VFI等指标也可能出现误判。
  3. 参数优化不完善风险。策略参数可以进一步测试和优化,现有参数可能并非最优。
  4. 做空带来的额外风险。可选的做空交易会带来无限亏损的风险。

可通过风险管理、指标组合、参数调整、引入机器学习等方式进一步强化策略,解决上述风险。

优化方向

S&P500混合季节性的交易策略可从以下方面进行进一步优化:

  1. 测试更长的历史数据训练。可用更多历史数据重新测试和优化策略参数。
  2. 增加止损机制。可设置浮动止损或时间止损,有效控制单笔亏损。
  3. 优化技术指标参数。可调整VIX、ATR和VFI的参数,寻找最优参数组合。
  4. 引入机器学习模型。使用神经网络或决策树进行参数自适应优化。
  5. 策略组合。可测试与其他策略进行组合,利用非相关性降低市场系统性风险。

总结

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          )


更多内容