using System; using System.Collections.Generic; using System.Text; using System.Drawing; using WealthLab; using WealthLab.Indicators;namespace WealthLab.Strategies { public class GoldenTriangleStrategy : WealthScript { private StrategyParameter paramRiseBars; private StrategyParameter paramWhiteSpace; private StrategyParameter paramMinRise; private StrategyParameter paramSMA; private StrategyParameter paramMom; private StrategyParameter paramHi; private StrategyParameter paramProximity; private StrategyParameter paramPullback; private StrategyParameter paramRecovery; private StrategyParameter paramTimeout; private StrategyParameter paramVolConf; private StrategyParameter paramMaxBuy; private StrategyParameter paramTrail; public GoldenTriangleStrategy() { paramRiseBars = CreateParameter("Rise: X bars", 50, 10, 100, 10); paramWhiteSpace = CreateParameter("White space %", 50, 10, 100, 10); paramMinRise = CreateParameter("Min. rise %", 10, 5, 200, 5); paramSMA = CreateParameter("SMA period", 50, 10, 200, 10); paramMom = CreateParameter("Momentum period", 10, 2, 30, 2); paramHi = CreateParameter("Highest period", 20, 10, 100, 10); paramProximity = CreateParameter("Within SMA %", 2, 1, 5, 1); paramPullback = CreateParameter("Pullback %", 2, 2, 18, 2); paramRecovery = CreateParameter("Approaching %", 2, 2, 6, 2); paramTimeout = CreateParameter("Expires after Y bars", 20, 5, 40, 5); paramVolConf = CreateParameter("Volume confirmation?", 0, 0, 1, 1); paramMaxBuy = CreateParameter("Max buy price", 5, 1, 10, 1); paramTrail = CreateParameter("Trailing low exit", 40, 10, 80, 10); } protected override void Execute() { bool pivot = false; int pivotBar = -1; bool pullback = false; int pullbackBar = -1; bool recovery = false; int recoveryBar = -1; bool volConfirm = paramVolConf.ValueInt == 1; double ws = paramWhiteSpace.Value / 100d; double within = paramProximity.Value; double minRise = paramMinRise.Value; double risePct = 0.0, pivotPrice = 0.0, dipPrice = 0.0; int riseBars = paramRiseBars.ValueInt, ba = 0; SMA sma = SMA.Series(Close,paramSMA.ValueInt); MomentumPct mom = MomentumPct.Series(Close,paramMom.ValueInt); Highest hi = Highest.Series(High,paramHi.ValueInt); DataSeries whiteSpace = new DataSeries(Bars,"WhiteSpace"); Color blue = Color.FromArgb(50,Color.Blue); LineStyle ls = LineStyle.Solid; PlotSeries(PricePane,sma,Color.Red,ls,1); for(int bar = Math.Max(riseBars, GetTradingLoopStartBar(paramSMA.ValueInt)); bar < Bars.Count; bar++) { // "White space": percentage of bars above 50-day SMA for (int i = bar - riseBars; i <= bar; i++) { ba = (Low[i] > sma[i]) ? ba += 1 : 0; whiteSpace[bar] = ba / (double)riseBars; } if (IsLastPositionActive) { SellAtStop( bar+1, LastPosition, Lowest.Series(Low,paramTrail.ValueInt)[bar] ); } else { // 1. Detecting pivot if( !pivot ) { // Uptrend: price > SMA, momentum % > 100, "white space" at or exceeds 50%, hit new 50-day high if( mom[bar] >= 100 && whiteSpace[bar] > ws && High[bar] >= hi[bar] ) { // Rise over X bars (default) risePct = (High[bar] - High[bar - riseBars]) / High[bar] * 100.0; // Pivot detected: price rise exceeds predefined % threshold if( risePct > minRise ) { pivot = true; pivotBar = bar; pivotPrice = Close[pivotBar]; SetBackgroundColor( pivotBar, blue ); } } } // 2. Looking for pullback if( pivot ) { // Pullback is valid until it times out if( bar <= pivotBar + paramTimeout.ValueInt ) { if( !pullback ) { // Pullback detected: price dove within N% of SMA bool priceNearSMA = Close[bar] > (sma[bar] * 1 - (within / 100d)) && Close[bar] < (sma[bar] * 1 + (within / 100d)); if( priceNearSMA ) { pullback = true; pullbackBar = bar; dipPrice = Close[pullbackBar]; SetBackgroundColor( pullbackBar, Color.FromArgb(30, Color.Red) ); } } // 3. Looking for recovery if( pullback ) { // Rebound is valid until it times out if( bar <= pullbackBar + paramTimeout.ValueInt ) { if( !recovery ) { // Recovery started: current price is above both the 50-day SMA and Pullback price // but current high is still below the Pivot price if( (Close[bar] > sma[bar]) && (Close[bar] > dipPrice) && (High[bar] <= High[pivotBar]) ) { recovery = true; recoveryBar = bar; SetBackgroundColor( recoveryBar, Color.FromArgb(50, Color.Orange) ); } } // 4. Looking to enter if( recovery ) { // 4.a Price confirmation if( Close[bar] > sma[bar] ) { // 4.b Volume confirmation (if enabled) if( !volConfirm || (volConfirm && Volume[bar] > SMA.Series(Volume,50)[bar]) ) { // Enter: price below Max Buy price double maxBuyPrice = (1 - paramMaxBuy.Value / 100d) * High[pivotBar]; if( Close[bar] < maxBuyPrice ) { // Eugene: buy at stop half-way between the Pivot and Pullback prices (or higher) if( BuyAtStop( bar + 1, (dipPrice + (pivotPrice - dipPrice) / 2), Bars.FormatValue(risePct) ) != null ) // Alternative: //if( BuyAtMarket( bar + 1, Bars.FormatValue(risePct) ) != null ) { DrawLine( PricePane, pivotBar, pivotPrice, pullbackBar, dipPrice, blue, ls, 2 ); DrawLine( PricePane, pullbackBar, dipPrice, bar, High[pivotBar], blue, ls, 2 ); DrawLine( PricePane, pivotBar, High[pivotBar], bar, High[pivotBar], blue, LineStyle.Dashed, 2 ); pivot = false; pullback = false; recovery = false; LastPosition.Priority = -Close[bar]; } else // reset if setup has timed out recovery = bar + 1 - recoveryBar < paramTimeout.ValueInt; } } } } } else { pullback = false; SetBackgroundColor( pullbackBar, Color.Transparent ); } } } else { pivot = false; SetBackgroundColor( pivotBar, Color.Transparent ); } } } } } } }