Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

221
LINES

< > BotCompany Repo | #1036572 // NGSStrategy2 - "NGS" Strategy v2 #themoneymakingstrategy (not anymore though - now we have the Mystery Strategy etc)

JavaX fragment (include) [tags: use-pretranspiled]

Libraryless. Click here for Pure Java version (31812L/204K).

// Design notes.
//
// NG stands for "Non-Garbage"
//
// !Design notes end

sclass NGSStrategy2 extends G22CandleBasedStrategyWithTrailingStop is IntegrityCheckable {
  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

  settable Side longSide = new(1);
  settable Side shortSide = new(-1);
  
    // false = only manual openLong() and openShort(),
  // not based on indicators.
  // true = auto-open positions indefinitely depending on
  // indicators.
  settable bool autoPositions = true;
  
  // temporal fields follow
  
  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;
  
  // Too big, disabling this by default now
  gettable TickerSequence maMoveHistory/* = new TickerSequence("maMove")*/;
  gettable TickerSequence maDirectionHistory = new TickerSequence("maDirection");
  
  settable transient O visualizer;
  
  selfType emptyClone() {
    selfType clone = cast super.emptyClone();
    clone.longSide(longSide.emptyClone(clone));
    clone.shortSide(shortSide.emptyClone(clone));
    ret clone;
  }
  
  // Side = logic for one side (long/short = direction 1/-1)
  record noeq 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?
    settable bool enabled = true;
    
    // First band (close to MA)
    settable DoubleRange band1 = new(0.2, 0.6);
    
    // Second band
    settable DoubleRange band2 = new(0.6, 1.0);
    
    // Percentage to close the position at (below band/above band)
    settable double closeLevel = 0;
    
    // Do we require the MA to go in our direction?
    // (If no, we just require it to not go the other direction.)
    settable bool needMADirection = true;
    
    // temporal fields follow
    
    selfType emptyClone(NGSStrategy2 stratClone) {
      ret copyFields(this, stratClone.new Side(),
        identifiers("direction enabled band1 band2 closeLevel"));
    }
    
    // Has price visited band1 recently?
    settable bool wasInBand1;
    
    // convert percent band to current price range
    DoubleRange instantiateBand(DoubleRange band, double ma1 default ma1) {
      ret doubleRange(
        maPlusPercent(ma1, direction*band.start),
        maPlusPercent(ma1, direction*band.end)).sort();
    }
    
    double priceToPercent(double price, double ma1 default ma1) {
      ret (price/ma1-1)*direction*100;
    }
    
    double relativePrice() {
      ret priceToPercent(currentPrice());
    }
    
    void step {
      var relativePrice = relativePrice();
      
      // close if too close to band/beyond band
      if (relativePrice < closeLevel && sign(drift()) == direction) {
        closeAllPositions(direction > 0 ? "Below band" : "Above band");
      }
      
      // price on wrong side => reset
      if (relativePrice < 0) wasInBand1(false);
      
      // Above band2 => reset
      if (relativePrice > band2.end) wasInBand1(false);
      
      // We hit band 1
      if (band1.contains(relativePrice)) wasInBand1(true);
      
      if (autoPositions && enabled && wasInBand1 && band2.contains(relativePrice)
        && ma2*direction < currentPrice()*direction
        && (needMADirection ? maDirection == direction : maDirection != -direction)) {
        log("Trigger " + this);
        wasInBand1(false);
        if (drift() == 0 && !inCooldown())
          openPosition(direction);
      }
    }
 
    // "short" or "long"   
    toString { ret trading_directionToPositionType(direction); }
    
    LS status() {
      ret listCombine(
        enabled ? null : "disabled",
        "Relative price: "+ formatDouble1(relativePrice()),
        !wasInBand1 ? null : "Was in band 1",
        "Band 1: " + band1,
        "Band 2: " + band2,
        "Close level: " + closeLevel
      );
    }
  } // end of Side

  void ngsStep aka ngsLogic() {
    //double close = currentPrice();
    //double drift = drift();
    
    for (side : sides())
      side.step();
      
    change();
  }
  
  simplyCached MAIndicator movingAverage1() {
    ret new MAIndicator(period1);
  }
  
  simplyCached MAIndicator movingAverage2() {
    ret new MAIndicator(period2);
  }

  L<CandleBasedIndicator> 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: " + maMinMove + " (current move: " + formatDouble(maMove, 4) + ", direction: " + maDirection + ")",
      "MA periods: " + period1 + " " + period2,
      "Do: " + (!autoPositions ? "Only manual positions" : commaCombine(stringIf(doLongs(), "Longs"), stringIf(doShorts(), "Shorts"))),
      "Moving averages: " + formatDouble_significant(ma1, 4) + ", " + formatDouble_significant(ma2, 4),
      map(sides(), side -> firstToUpper(str(side)) + " side: " + commaCombine(side.status())),
      super.status()
    );
  }
  
  void afterStep :: before {
  }
  
  bool doShorts() { ret shortSide.enabled; }
  bool doLongs() { ret longSide.enabled; }
  
  L<Side> sides() {
    ret ll(longSide, shortSide);
  }
  
  int candlesNeededBeforeOperational() { ret period1; }
  
  bool usingCells() { false; }
  
  public void integrityCheck {
    movingAverage1().integrityCheck();
    movingAverage2().integrityCheck();
  }
}

Author comment

Began life as a copy of #1036558

download  show line numbers  debug dex  old transpilations   

Travelled to 2 computer(s): mqqgnosmbjvj, wnsclhtenguj

No comments. add comment

Snippet ID: #1036572
Snippet name: NGSStrategy2 - "NGS" Strategy v2 #themoneymakingstrategy (not anymore though - now we have the Mystery Strategy etc)
Eternal ID of this version: #1036572/42
Text MD5: 8171b475658e80e436a42215660863c9
Transpilation MD5: 92e0f187d3228019ac821e357ef1d816
Author: stefan
Category: javax / trading
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2023-04-29 00:33:17
Source code size: 7045 bytes / 221 lines
Pitched / IR pitched: No / No
Views / Downloads: 169 / 348
Version history: 41 change(s)
Referenced in: [show references]