// "First non-garbage strategy" (NGS) sclass NGSStrategy extends G22CandleBasedStrategyWithTrailingStop { settable double minDistance = 0.4; // input.float(0.4, minval=0, step=0.02) 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 bool doLongs = true; settable bool doShorts = true; settable bool unblockAfterCrossingBand; settable bool unblockAfterTouchingBand = true; // per-run fields settable double ma1; settable double ma2; settable double lastMA1 = Double.NaN; settable bool longCondition; settable bool shortCondition; settable bool blockLong; settable bool blockShort; settable double maMove; S fieldsToReset() { ret lineCombine(super.fieldsToReset(), [[ma1 ma2 lastMA1 longCondition shortCondition blockShort blockLong maMove movingAverage1_cache movingAverage2_cache]]); } simplyCached MAIndicator movingAverage1() { ret new MAIndicator(period1); } simplyCached MAIndicator movingAverage2() { ret new MAIndicator(period2); } L indicators() { ret ll(movingAverage1(), movingAverage2()); } void unblockLong { if (blockLong) { log("Long unblock"); blockLong(false); } } void unblockShort { if (blockShort) { log("Short unblock"); blockShort(false); } } bool maRising() { ret maMove >= maMinMove; } bool maFalling() { ret maMove <= -maMinMove; } double upperBand() { ret ma1*(1+minDistance/100); } double lowerBand() { ret ma1*(1-minDistance/100); } { granularity(30); onCandleCompleted(candle -> { double close = candle.close(); lastMA1(ma1); ma1(movingAverage1()!); ma2(movingAverage2()!); maMove((ma1/lastMA1-1)*100); longCondition(doLongs && ma2 > upperBand() && maRising()); shortCondition(doShorts & ma2 < lowerBand() && maFalling()); if (ma2 > ma1) unblockShort(); if (ma2 < ma1) unblockLong(); double drift = drift(); if (drift <= 0 && longCondition && !blockLong) { openLong(); blockLong(true); } if (drift >= 0 && shortCondition && !blockShort) { openShort(); blockShort(true); } if (drift > 0 && close < lowerBand()) { closeAllPositions("Below band"); if (unblockAfterCrossingBand) unblockLong(); } if (drift < 0 && close > upperBand()) { closeAllPositions("Above band"); if (unblockAfterCrossingBand) unblockShort(); } if (unblockAfterTouchingBand && close < upperBand()) unblockLong(); if (unblockAfterTouchingBand && close > lowerBand()) unblockShort(); }); } 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")), unblockAfterTouchingBand ? "Unblock after touching band" : unblockAfterCrossingBand ? "Unblock after crossing band" : null, "Moving averages: " + formatDouble_significant(ma1, 4) + ", " + formatDouble_significant(ma2, 4), stringIf(blockLong, "Long blocked"), stringIf(longCondition, "Long condition"), stringIf(shortLong, "Short blocked"), stringIf(shortCondition, "Short condition"), super.status() ); } }