// Design notes. // // NG stands for "Non-Garbage" // // !Design notes end sclass NGSStrategy extends G22CandleBasedStrategyWithTrailingStop { settable double minDistance = 0.4; settable double maxDistance = 50; 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 double unblockDistance = 0.8; // per-run fields S fieldsToReset() { ret lineCombine(super.fieldsToReset(), [[ ma1 ma2 lastMA1 longCondition shortCondition blockShort blockLong maMove movingAverage1_cache movingAverage2_cache ]]); } 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; void ngsStep aka ngsLogic() { double close = currentPrice(); double drift = drift(); longCondition(doLongs && close > upperBand() && close < upperMaxDistanceBand() && maRising()); shortCondition(doShorts & close < lowerBand() && close > lowerMaxDistanceBand() && maFalling()); if (ma2 > ma1 || close > lowerReturnBand()) unblockShort(); if (ma2 < ma1 || close < upperReturnBand()) unblockLong(); 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 (drift < 0 && close > upperBand()) closeAllPositions("Above band"); } 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 moveSignal() { ret maMove/maMinMove*100; } double upperBand(dbl ma1 default ma1) { ret ma1*(1+minDistance/100); } double lowerBand(dbl ma1 default ma1) { ret ma1*(1-minDistance/100); } double upperMaxDistanceBand() { ret ma1*(1+maxDistance/100); } double lowerMaxDistanceBand() { ret ma1*(1-maxDistance/100); } double upperReturnBand() { ret ma1*(1+unblockDistance/100); } double lowerReturnBand() { ret ma1*(1-unblockDistance/100); } { granularity(30); onNewPrice(price -> ngsStep()); onCandleCompleted(candle -> { lastMA1(ma1); ma1(movingAverage1()!); ma2(movingAverage2()!); maMove((ma1/lastMA1-1)*100); ngsStep(); }); } 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), stringIf(blockLong, "Long blocked"), stringIf(longCondition, "Long condition"), stringIf(blockShort, "Short blocked"), stringIf(shortCondition, "Short condition"), super.status() ); } }