// 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 double maMinMove2 = 0.05; // MA trend threshold 2 (hysteresis) 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 maDirection maDirectionHistory maMoveHistory blockLongHistory blockShortHistory longConditionHistory shortConditionHistory ]]); } 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; settable int maDirection; gettable TickerSequence maMoveHistory = new TickerSequence("maMove"); gettable TickerSequence blockLongHistory = new TickerSequence("blockLong"); gettable TickerSequence blockShortHistory = new TickerSequence("blockShort"); gettable TickerSequence longConditionHistory = new TickerSequence("longCondition"); gettable TickerSequence shortConditionHistory = new TickerSequence("shortCondition"); gettable TickerSequence maDirectionHistory = new TickerSequence("maDirection"); void ngsStep aka ngsLogic() { double close = currentPrice(); double drift = drift(); longCondition(doLongs && close > upperBand() && close < upperMaxDistanceBand() && maRising()); shortCondition(doShorts & close < lowerBand() && close > lowerMaxDistanceBand() && maFalling()); longConditionHistory?.addIfPriceChanged(boolToInt(longCondition), currentTime()); shortConditionHistory?.addIfPriceChanged(boolToInt(shortCondition), currentTime()); if (ma2 > ma1 || close > lowerReturnBand()) unblockShort(); if (ma2 < ma1 || close < upperReturnBand()) unblockLong(); if (!inCooldown() && drift == 0) { if (longCondition && !blockLong) { openLong(); blockLong(true); } if (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 maDirection > 0; } bool maFalling() { ret maDirection < 0; } 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); 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(); }); } 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), valueOrNull(blockLong, "Long blocked"), valueOrNull(longCondition, "Long condition"), valueOrNull(blockShort, "Short blocked"), valueOrNull(shortCondition, "Short condition"), super.status() ); } void afterStep :: before { blockLongHistory?.addIfPriceChanged(boolToInt(blockLong), currentTime()); blockShortHistory?.addIfPriceChanged(boolToInt(blockShort), currentTime()); } }