// Design notes. // // NG stands for "Non-Garbage" // // !Design notes end sclass NGSStrategy2 extends G22CandleBasedStrategyWithTrailingStop { settable int period1 = 100; // the "slow" MA settable int period2 = 5; // the fast MA (for opening) settable double maMinMove = 0.07; // MA trend threshold settable double maMoveHysteresis = 0.01; // MA trend non-oscillation threshold // per-run fields S fieldsToReset() { ret lineCombine(super.fieldsToReset(), [[ ma1 ma2 lastMA1 maMove movingAverage1_cache movingAverage2_cache maMoveHistory maDirection maDirectionHistory ]]); } settable double ma1; settable double ma2; settable double lastMA1 = Double.NaN; settable double maMove; settable int maDirection; Side longSide = new(1); Side shortSide = new(-1); gettable TickerSequence maMoveHistory = new TickerSequence("maMove"); gettable TickerSequence maDirectionHistory = new TickerSequence("maDirection"); // Side = logic for one side (long/short = direction 1/-1) record Side(int direction) { // Our condition is: // Price has to be in a band close to the MA first (band 1) // and then move into a band further away from the MA (band 2) // Are we allowed to open positions in this direction at all? bool enabled = true; // First band (close to MA) DoubleRange band1 = new(0.2, 0.6); // Second band DoubleRange band2 = new(0.6, 1.0); } void ngsStep aka ngsLogic() { double close = currentPrice(); double drift = drift(); } simplyCached MAIndicator movingAverage1() { ret new MAIndicator(period1); } simplyCached MAIndicator movingAverage2() { ret new MAIndicator(period2); } L indicators() { ret ll(movingAverage1(), movingAverage2()); } bool maRising() { ret maDirection > 0; } bool maFalling() { ret maDirection < 0; } double moveSignal() { ret maMove/maMinMove*100; } double maPlusPercent(dbl ma1 default ma1, double percent) { ret ma1*(1+percent/100); } double maMinusPercent(dbl ma1 default ma1, double percent) { ret ma1*(1-percent/100); } { granularity(30); onNewPrice(price -> ngsStep()); onCandleCompleted(candle -> { lastMA1(ma1); ma1(movingAverage1()!); ma2(movingAverage2()!); maMove((ma1/lastMA1-1)*100); maMoveHistory?.addIfPriceChanged(maMove, currentTime()); if (maMove >= maMinMove) maDirection(1); else if (maMove <= -maMinMove) maDirection(-1); else if (maDirection < 0 && maMove >= -maMinMove2() || maDirection > 0 && maMove <= maMinMove2()) maDirection(0); maDirectionHistory?.addIfPriceChanged(maDirection, currentTime()); ngsStep(); }); } double maMinMove2() { ret maMinMove-maMoveHysteresis; } LS status() { ret listCombine( "Min distance, MA min move: " + minDistance + " " + maMinMove + " (current move: " + formatDouble(maMove, 4) + ")", "MA periods: " + period1 + " " + period2, "Do: " + commaCombine(stringIf(doLongs(), "Longs"), stringIf(doShorts(), "Shorts")), "Moving averages: " + formatDouble_significant(ma1, 4) + ", " + formatDouble_significant(ma2, 4), super.status() ); } void afterStep :: before { } bool doShorts() { ret shortSide.enabled; } bool doLongs() { ret longSide.enabled; } }