この記事では,Simple Moving Average (SMA) とAdaptive Moving Average (ALMA) を組み合わせた定量的な取引戦略を紹介しています.この戦略は複数の技術指標を組み込み,異なるパラメータ設定に基づいて取引信号を生成します.
I. 戦略原則
この戦略の核心は,異なるパラメータ設定を持つSMAとALMAの組み合わせである.SMAは,特定の期間における閉値の算術平均を計算することによって,トレンドの方向性と勢いを示す非常に一般的なトレンドフォロー指標である.ALMAは,歴史的な価格を平均する点でSMAに似ているが,SMAよりも市場の変化により敏感になる2つの調整可能なパラメータ,αと σを追加している.
この戦略は,まず,それぞれ短期,中期,長期のトレンドを表す3つのSMAを計算する.同時に,異なるタイムフレームにおける移動平均を表現するために3つのALMAを計算する.SMAとALMAのクロスオーバーは複数の指標を構成する.短期SMAが中期SMAを横切ると,購入信号が生成される.短期SMAが中期SMAを下回ると,販売信号が生成される.ALMAの調整可能なパラメータにより,信号はより早く市場に反応することができる.
さらに,相対強度指数 (RSI) は,過剰購入と過剰販売の条件を特定するのに役立つように導入されています.RSIが過剰購入の限界値を超えると,市場は過剰購入とみなされます.この場合,SMAとALMAが購入信号を生成しても,誤解を招く可能性があります.同様に,RSIが過剰販売線を下回ると,指標からの販売信号はリバウンドを見逃す可能性があります.したがって,RSIの補助判断は,特定の罠リスクを回避することができます.
SMA,ALMA,RSIのパラメータ設定と異なるパラメータの指標間のクロスコンビネーションを総合的に利用することで,比較的敏感な取引戦略信号が形成できます.また,RSIからの過剰購入および過剰販売判断は,エントリータイミングをさらに最適化し,トラップの可能性を減らすことができます.
戦略の利点
この戦略の最大の利点は,指標パラメータの柔軟な組み合わせと適用である.SMAとALMAの両方が,異なるタイプの移動平均を表現するためにパラメータを調整するのに柔軟である.RSIはまた,パラメータを調整することによって信号の周波数を制御することができる.これらの指標の組み合わせは互いを補完し,エントリのタイミングを最適化できる取引信号を形成する.
単一のSMA指標と比較して,ALMAは市場の変化に対する感受性を高め,トレンド逆転に迅速に対応することができる.また,補助RSI判断は,移動平均値からのシグナルを盲目的にフォローすることをさらに避けます.したがって,この戦略は全体的に比較的強い適応性と最適化能力を備えています.
また,戦略のシグナルソースの多様性も利点である.異なるタイムフレームにおけるSMAとALMAの相互作用は,戦略のための多層の参照を提供します.これは,ランダムな市場ノイズを一定程度フィルタリングし,シグナルをより信頼性のあるものにすることができます.
一般的にこの戦略は柔軟なパラメータを持ち,安定した信号を生成し,異なる製品間のアルゴリズム取引に適しています.
III.潜在的なリスク
この戦略には一定の利点があるが,実際での適用にはいくつかのリスクがある.
まず,指標設定による過剰最適化問題である.SMA,ALMA,RSIは自由に調整できるが,不適切な調節は過剰最適化や市場の長期的な構造変化に適応できない状態につながる.これは,短期的な結果を追求するのではなく,異なる製品の特徴に基づいて慎重なパラメータ設定を必要とする.
2つ目は,戦略信号が遅れることがある.ALMAはSMAよりも早く反応するが,依然として一定の遅れがある.急速に変化する市場で,これは最適なエントリータイミングを逃す結果になる可能性がある.ここで,最適化するためにいくつかの主要な指標を組み合わせることを検討する.
最後に,複数の指標からの矛盾するシグナルに注意する必要があります.特定のタイミングで,異なる指標が矛盾する指示を与える可能性があります.これを解決するには,経験に基づいた明確な優先規則が必要です.
概要すると,この戦略は完璧ではなく,実用的には継続的な調整と最適化が必要です.しかし,柔軟なパラメータ設定と複数の指標組み合わせの利点により,長期的に実行可能なアルゴリズム取引システムになります.
IV.要約
この記事では,SMA,ALMA,およびRSIを組み合わせた定量的な取引戦略を詳細に紹介しました.指標の柔軟な組み合わせを通じて,市場に敏感な信号を形成します.単一の指標と比較して,より強い適応性とノイズフィルタリング能力を持っています.しかし,過剰最適化,信号遅延,判断誤差などの潜在的な問題にも注意する必要があります.全体として,この戦略は合理的に構築されており,継続的な最適化によって安定したアルゴリズム取引信号を生成することができます.
/*backtest start: 2023-09-06 00:00:00 end: 2023-09-13 00:00:00 period: 5m basePeriod: 1m exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}] */ //The plotchar UP/DOWN Arrows is the crossover of the fastest MA and fastest IIR MAs // //The dots at the bottom are the two simple averages crossing over // //The count over/under the candles is the count of bars that the SMAs on their //respective resolution are fanning out. // //The colored background indicates a squeeze, lime=kinda tight : green=very tight squeeze. based on the 3 IIRs // //To answer my own question in a forum, looking at the code, i couldn't figure out how to get it from another timeframe //and run the same calculations with the same results. My answer in the end was to scale the chosen MA length //in the corresponding CurrentPeriod/ChosenMAPeriod proportion. This results in the same line in the same place when browsing through the //different time resolutions. Somebody might find this invaluable // //The counts are for MA's fanning out, or going parabolic. Theres IIRs, Almas, one done of the other. A lot. //The arrows above and below bars are from standard RSI numbers for OB/OS // //The IIRs changes color depending on their slope, which can be referenced easily with a variable. // //The backgrond on a bar-by-bar basis is colored when 2 sets of moving averages are in a squeeze, aka //when price is consolidating. // //This aims to help the trader combine conditions and entry criteria of the trade and explore these options visually. //They detail things from all time-frames on the current one. I prefer it because of the fractal nature of price-action, both large and small, //either yesterday or last year. For best results, go long in short-term trades when the long-term trend is also up. //and other profitable insights. This is also a great example of an automation algorith. // //The pretty ribbon is my script called 'Trading With Colors'. Use them together for fanciest results. 55/233 is my Fib Cross (golden/death) Compare it to the classic 50/200 if //you get bored. I believe it simply works better, at least for Crypto. // //Evidently, I am a day-trader. But this yields higher profits on larger time-frames anyways, so do play around with it. Find what works for you. //Thanks and credit for code snippets goes to: //matryskowal //ChrisMoody, probably twice //Alex Orekhov (everget) //author=LucF and midtownsk8rguy, for PineCoders //If you use code from this, real quick search for perhaps the original and give them a shoutout too. I may have missed something //Author: Sean Duffy //@version=4 strategy(title = "Combination Parabolic MA/IIR/ALMA Strategy", shorttitle = "MA-QuickE", overlay = true, backtest_fill_limits_assumption = 0, default_qty_type = strategy.cash, default_qty_value = 1000, initial_capital = 1000, currency = currency.USD, linktoseries = true) // calc_on_order_fills = true, // calc_on_every_tick = true, // Input Variables showFIBMAs = input(false, type=input.bool, title="═══════════════ Show Fibby MAs ═══════════════") maRes = input(960, type=input.integer, title="MA-Cross Resolution") mal1 = input(8, type=input.integer, title="MA#1 Length") mal2 = input(13, type=input.integer, title="MA#2 Length") mal3 = input(34, type=input.integer, title="MA#3 Length") loosePercentClose = input(1.1, type=input.float, title="SMA LooseSqueeze Percent") showIIRs = input(false, type=input.bool, title="═══════════════════ Show IIRs ═══════════════════") iirRes = input(60, type=input.integer, title="IIR Resolution") percentClose = input(title="IIR Squeeze PercentClose", type=input.float, defval=.8) iirlength1 = input(title="IIR Length 1", type=input.integer, defval=34) iirlength2 = input(title="IIR Length 2", type=input.integer, defval=144)//input(title="ATR Period", type=input.integer, defval=1) iirlength3 = input(title="IIR Length 3", type=input.integer, defval=720)//input(title="ATR Period", type=input.integer, defval=1) showIIR1 = input(true, type=input.bool, title="Show IIR1") showIIR2 = input(true, type=input.bool, title="Show IIR2") showIIR3 = input(true, type=input.bool, title="Show IIR3") showCounts = input(true, type=input.bool, title="═════════════ Show Parabolic MA Counts ════════════") showSignals = input(true, type=input.bool, title="══════════════ Show Buy/Sell Signals ══════════════") showBackground = input(true, type=input.bool, title="══════════════ Show Background Colors ══════════════") //runStrategy = input(true, type=input.bool, title="══════════════ Run Strategy ══════════════") debug = input(false, type=input.bool, title="══════════════ Show Debug ══════════════") barLookbackPeriod = input(title="══ Bar Lookback Period ══", type=input.integer, defval=5) percentageLookbackPeriod = input(title="══ Percentage Lookback Period ══", type=input.integer, defval=1) bullcolor = color.green bearcolor = color.red color bgcolor = na var bool slope1Green = na var bool slope2Green = na var bool slope3Green = na var bool buySignal = na var bool sellSignal = na var bool bigbuySignal = na var bool bigsellSignal = na bool smbuySignal = false bool smsellSignal = false var bool insqueeze = na var bool intightsqueeze = na var bool infastsqueeze = na var bool awaitingEntryIn = false // My counting variables var int count1 = 0 var float madist1 = 0 var int count2 = 0 var float madist2 = 0 var int sinceSmSignal = 0 var entryPrice = 0.0 var entryBarIndex = 0 var stopLossPrice = 0.0 // var updatedEntryPrice = 0.0 // var alertOpenPosition = false // var alertClosePosition = false // var label stopLossPriceLabel = na // var line stopLossPriceLine = na positionType = "LONG" // Strategy type, and the only current option hasOpenPosition = strategy.opentrades != 0 hasNoOpenPosition = strategy.opentrades == 0 strategyClose() => if (hasOpenPosition) if positionType == "LONG" strategy.close("LONG", when=true) else strategy.close("SHORT", when=true) strategyOpen() => if (hasNoOpenPosition) if positionType == "LONG" strategy.entry("LONG", strategy.long, when=true) else strategy.entry("SHORT", strategy.short, when=true) checkEntry() => buysignal = false if (hasNoOpenPosition) strategyOpen() buysignal := true // if (slope1Green and (trend1Green or trend2Green) and awaitingEntryIn and hasNoOpenPosition) // strategyOpen() // buysignal := true buysignal checkExit() => sellsignal = false // if (trend1Green == false and trend2Green == false) // to later have quicker exit strategy // sellsignal := true // strategyClose() if (hasOpenPosition) sellsignal := true strategyClose() sellsignal multiplier(_adjRes, _adjLength) => // returns adjusted length multiplier = _adjRes/timeframe.multiplier round(_adjLength*multiplier) //reset the var variables before new calculations buySignal := false sellSignal := false smbuySignal := false smsellSignal := false bigbuySignal := false bigsellSignal := false ma1 = sma(close, multiplier(maRes, mal1)) ma2 = sma(close, multiplier(maRes, mal2)) ma3 = sma(close, multiplier(maRes, mal3)) madist1 := abs(ma1 - ma2) madist2 := abs(ma1 - ma3) // check if MA's are fanning/going parabolic if (ma1 >= ma2 and ma2 >= ma3 and madist1[0] > madist1[1]) //and abs(dataB - dataC >= madist2) // dataA must be higher than b, and distance between gaining, same with C count1 := count1 + 1 else count1 := 0 if (ma1 <= ma2 and ma2 <= ma3 and madist1[0] > madist1[1]) //<= madist2 and dataB <= dataC) //and abs(dataB - dataC >= madist2) // dataA must be higher than b, and distance between gaining, same with C count2 := count2 + 1 else count2 := 0 crossoverAB = crossover(ma1, ma2) crossunderAB = crossunder(ma1, ma2) plot(showFIBMAs ? ma1 : na, linewidth=3) plot(showFIBMAs ? ma2 : na) plot(showFIBMAs ? ma3 : na) // Fast Squeese Check WORK IN PROGRESS // float singlePercent = close / 100 if max(madist1, madist2) <= singlePercent*loosePercentClose bgcolor := color.yellow infastsqueeze := true else infastsqueeze := false // IIR MOVING AVERAGE f(a) => a[0] // fixes mutable error iirma(iirlength, iirsrc) => cf = 2*tan(2*3.14159*(1/iirlength)/2) a0 = 8 + 8*cf + 4*pow(cf,2) + pow(cf,3) a1 = -24 - 8*cf + 4*pow(cf,2) + 3*pow(cf,3) a2 = 24 - 8*cf - 4*pow(cf,2) + 3*pow(cf,3) a3 = -8 + 8*cf - 4*pow(cf,2) + pow(cf,3) //---- c = pow(cf,3)/a0 d0 = -a1/a0 d1 = -a2/a0 d2 = -a3/a0 //---- out = 0. out := nz(c*(iirsrc + iirsrc[3]) + 3*c*(iirsrc[1] + iirsrc[2]) + d0*out[1] + d1*out[2] + d2*out[3],iirsrc) f(out) iirma1 = iirma(multiplier(iirRes, iirlength1), close) iirma2 = iirma(multiplier(iirRes, iirlength2), close) iirma3 = iirma(multiplier(iirRes, iirlength3), close) // adjusts length for current resolution now, length is lengthened/shortened accordingly, upholding exact placement of lines // iirmaD1 = security(syminfo.tickerid, tostring(iirRes), iirma1, barmerge.gaps_on, barmerge.lookahead_on) // iirmaD2 = security(syminfo.tickerid, tostring(iirRes), iirma2, barmerge.gaps_on, barmerge.lookahead_on) // iirmaD3 = security(syminfo.tickerid, tostring(iirRes), iirma3, barmerge.gaps_on, barmerge.lookahead_on) slope1color = slope1Green ? color.lime : color.blue slope2color = slope2Green ? color.lime : color.blue slope3color = slope3Green ? color.lime : color.blue plot(showIIR1 and showIIRs ? iirma1 : na, title="IIR1", color=slope1color, linewidth=2, transp=30) plot(showIIR2 and showIIRs ? iirma2 : na, title="IIR2", color=slope2color, linewidth=3, transp=30) plot(showIIR3 and showIIRs ? iirma3 : na, title="IIR3", color=slope3color, linewidth=4, transp=30) // checks slope of IIRs to create a boolean variable and and color it differently if (iirma1[0] >= iirma1[1]) slope1Green := true else slope1Green := false if (iirma2[0] >= iirma2[1]) slope2Green := true else slope2Green := false if (iirma3[0] >= iirma3[1]) slope3Green := true else slope3Green := false // calculate space between IIRs and then if the price jumps above both //float singlePercent = close / 100 // = a single percent var float distIIR1 = na var float distIIR2 = na distIIR1 := abs(iirma1 - iirma2) distIIR2 := abs(iirma1 - iirma3) if (distIIR1[0] < percentClose*singlePercent and close[0] >= iirma1[0]) if close[0] >= iirma2[0] and close[0] >= iirma3[0] bgcolor := color.green insqueeze := true intightsqueeze := true else bgcolor := color.lime insqueeze := true intightsqueeze := false else insqueeze := false intightsqueeze := false // if (true)//sinceSmSignal > 0) // cutting down on fastest MAs noise // sinceSmSignal := sinceSmSignal + 1 // if (crossoverAB) // //checkEntry() // //smbuySignal := true // sinceSmSignal := 0 // if (crossunderAB) // and all NOT greennot (slope1Green and slope2Green and slope3Green) // //checkExit() // //smsellSignal := true // sinceSmSignal := 0 // else // sinceSmSignal := sinceSmSignal + 1 f_draw_infopanel(_x, _y, _line, _text, _color)=> _rep_text = "" for _l = 0 to _line _rep_text := _rep_text + "\n" _rep_text := _rep_text + _text var label _la = na label.delete(_la) _la := label.new( x=_x, y=_y, text=_rep_text, xloc=xloc.bar_time, yloc=yloc.price, color=color.black, style=label.style_labelup, textcolor=_color, size=size.normal) posx = timenow + round(change(time)*60) posy = highest(50) // CONSTRUCTION ZONE // TODO: program way to eliminate noise and false signals // MAYBEDO: program it to differentiate between a moving average bump and a cross // I think the best way would be to calculate the tangent line... OR // Take the slope of both going back a couple bars and if it's close enough, its a bounce off // and an excellent entry signal // program in quickest exit, 2 bars next to eachother both closing under, as to avoid a single wick from // prompting to close the trade // Some other time, have it move SMA up or down depending on whether trending up or down. Then use those MA crosses //THIS CHECKS THE SLOPE FROM CURRENT PRICE TO BACK 10 BARS checkSlope(_series) => (_series[0]/_series[10])*100 // it now returns it as a percentage doNewX = input(true, type=input.bool, title="══════════ Show misc MA Cross Strategy ══════════") iirX = input(13, title="IIRx Length: ", type=input.integer) iirXperiod = input(21, title="IIRx Period/TF: ", type=input.integer) iirX2 = input(144, title="IIRx2 Length: ", type=input.integer) iirX2period = input(233, title="IIRx2 Period/TF: ", type=input.integer) //15 almaXperiod = input(defval=21, title="Alma of IIR1 Period: ", type=input.integer) almaXalpha = input(title="Alma Alpha Value: ", defval=.99, maxval=.99, type=input.float) almaXsigma = input(title="Alma Sigma Value: ", defval=8, type=input.float) iirmaOTF = iirma(multiplier(iirXperiod, iirX), close) iirma2OTF = iirma(multiplier(iirX2period, iirX2), close) smaOTF = alma(iirmaOTF, almaXperiod, almaXalpha, almaXsigma) // maybe dont touch, its precise // I took the ALMA of the IIRMA, and i hope thats not cheating ;) // I could have removed this. the multiplier function adjusts the length to fit the current timeframe while displaying the same // smaXOTF = security(syminfo.tickerid, smaXperiod, smaOTF, barmerge.gaps_on, barmerge.lookahead_on) // iirmaXOTF = security(syminfo.tickerid, iirXperiod, iirmaOTF, barmerge.gaps_on, barmerge.lookahead_on) // iirmaX2OTF = security(syminfo.tickerid, iirX2period, iirma2OTF, barmerge.gaps_on, barmerge.lookahead_on) plot(doNewX ? smaOTF : na, title="FastMA X-Over : ", color=color.blue, linewidth=1, transp=40) plot(doNewX ? iirmaOTF : na, title="IIR MAx : ", color=color.purple, linewidth=1, transp=30) plot(doNewX ? iirma2OTF : na, title="IIR MAx : ", color=color.purple, linewidth=2, transp=20) iirma2Up = iirma2OTF[0] > iirma2OTF[1] // just another slope up/down variable. //calculate spaces between averages distiiralma = abs(iirmaOTF - smaOTF) crossoverFast = crossover(iirmaOTF[0], smaOTF[0]) // and (iirmaOTF[1] <= smaOTF[1]) crossunderFast = crossunder(iirmaOTF[0], smaOTF[0]) // and (iirmaOTF[1] >= smaOTF[1]) if (crossoverFast and iirma2Up == true) // and (count1 != 0))// or close[0] < (lowest(barLookbackPeriod) + singlePercent*3))) // must be at most a few percent up from a recent low. Avoid buying highs :P buySignal := true strategyOpen() // if (slope1Green and slope2Green and slope3Green and infastsqueeze == false) // checkEntry() if (crossunderFast) sellSignal := true checkExit() // I feel like I didn't cite the OG author for this panel correctly. I hope I did, but there are extentions of his/her work in multiple places. // I could have gotten it confused. if (debug) f_draw_infopanel(posx, posy, 18, "distiiralma from IIR: " + tostring(distiiralma), color.lime) //f_draw_infopanel(posx, posy, 16, "distiirs: " + tostring(distiirX1), color.lime) f_draw_infopanel(posx, posy, 14, "Value of iirmaOTF: " + tostring(iirmaOTF), color.lime) f_draw_infopanel(posx, posy, 6, "slope X: " + tostring(abs(100 - checkSlope(iirmaOTF))), color.lime) f_draw_infopanel(posx, posy, 12, "value of smaOTF: " + tostring(smaOTF), color.lime) f_draw_infopanel(posx, posy, 6, "slopeAlma: " + tostring(abs(100 - checkSlope(smaOTF))), color.lime) f_draw_infopanel(posx, posy, 2, "slopeIIR2 " + tostring(abs(100 - checkSlope(iirma2OTF))), color.lime) f_draw_infopanel(posx, posy, 2, "slopeIIR2 " + tostring(abs(100 - checkSlope(iirma2OTF))), color.lime) // I kept this separate because it discludes the calculations. Its hard to hold a train of thought while fishing for the right section bgcolor(showBackground ? bgcolor : na) plotshape(showSignals ? buySignal : na, location=location.bottom, style=shape.circle, text="", size=size.tiny, color=color.blue, transp=60) plotshape(showSignals ? sellSignal : na, location=location.bottom, style=shape.circle, text="", size=size.tiny, color=color.red, transp=60) plotchar(showSignals and smbuySignal, title="smBuy", location=location.belowbar, char='↑', size=size.tiny, color=color.green, transp=0) plotchar(showSignals and smsellSignal, title="smSell", location=location.abovebar, char='↓', size=size.tiny, color=color.orange, transp=0) // can not display a variable. Can only match the count to a corresponding plotchar // to display a non-constant variable, use the debug box, which was so kindly offered up by our community. plotchar(showCounts and count1==1, title='', char='1', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==2, title='', char='2', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==3, title='', char='3', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==4, title='', char='4', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==5, title='', char='5', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==6, title='', char='6', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==7, title='', char='7', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==8, title='', char='8', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1==9, title='', char='9', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count1>=10, title='', char='$', location=location.belowbar, color=#2c9e2c, transp=0) plotchar(showCounts and count2==1, title='', char='1', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==2, title='', char='2', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==3, title='', char='3', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==4, title='', char='4', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==5, title='', char='5', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==6, title='', char='6', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==7, title='', char='7', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==8, title='', char='8', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2==9, title='', char='9', location=location.abovebar, color=#e91e63, transp=0) plotchar(showCounts and count2>=10, title='', char='$', location=location.abovebar, color=#e91e63, transp=0) showRSIind = input(true, type=input.bool, title="═══════════════════ Show RSI Arrows ═══════════════════") // Get user input rsiSource = input(title="RSI Source", type=input.source, defval=close) rsiLength = input(title="RSI Length", type=input.integer, defval=14) rsiOverbought = input(title="RSI Overbought Level", type=input.integer, defval=80) rsiOversold = input(title="RSI Oversold Level", type=input.integer, defval=20) // Get RSI value rsiValue = rsi(rsiSource, rsiLength) isRsiOB = rsiValue >= rsiOverbought isRsiOS = rsiValue <= rsiOversold // Plot signals to chart plotshape(isRsiOB, title="Overbought", location=location.abovebar, color=color.red, transp=0, style=shape.triangledown) plotshape(isRsiOS, title="Oversold", location=location.belowbar, color=color.green, transp=0, style=shape.triangleup) //reset the var variables before new calculations buySignal := false sellSignal := false smbuySignal := false smsellSignal := false bigbuySignal := false bigsellSignal := false