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

331
LINES

< > BotCompany Repo | #1036259 // Chaser

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

Transpiled version (30028L) is out of date.

concept Chaser extends G22TradingStrategy {
  // Juicer parameters
  
  // Maximum duration of a position
  settable double maxPositionHours = 4;
  
  // Max loss per position before leverage
  settable double maxLoss = 0.6;
  
  // Ordinary pullback (fixed price percentage)
  settable double pullback = 0.4;
  
  // Relative pullback (percentage of profit)
  settable double relativePullback; // e.g. 20
  settable double minPullback; // e.g. 0.1
  settable double maxPullback; // e.g. 4
  
  settable Opener opener;
  
  // internal
  
  settableWithVar int lastDirection;
  new RunLengthCounter<Int> rlc;
  OpenSignal openSignal;
    
  // trigger = 0 to 1
  record noeq CloseSignal(S reason, double trigger) {
    double trigger() { ret trigger; }
    toString { ret "Close signal " + reason + " (" + iround(trigger*100) + "%)"; }
  }

  // trigger = 0 to 1
  record noeq OpenSignal(S reason, double trigger, int direction) {
    double trigger() { ret trigger; }
    toString { ret "Open signal " + reason + " (" + iround(trigger*100) + "%)"; }
  }
  
  persistable class BaseJuicer {
    settable double maxLoss;
    
    L<CloseSignal> closeSignals(Position p) {
      new L<CloseSignal> signals;
      double profit = p.profitBeforeLeverage();
      
      // How close are we to the max closing time?
      if (p.maxClosingTime() < infinity())
        signals.add(new CloseSignal("Age", transformToZeroToOne(currentTime(), p.openingTime(), p.maxClosingTime())));
        
      // How close are we to our loss limit?
      if (profit < 0)
        signals.add(new CloseSignal("Loss", doubleRatio(-profit, maxLoss));
        
      ret signals;
    }
  }
  
  // Juicer with fixed pullback
  persistable class Juicer extends BaseJuicer {
    settable double pullback;
    
    *(double *maxLoss, double *pullback) {}
    
    toString { ret renderRecord("Juicer", +maxLoss, +pullback); }
    
    L<CloseSignal> closeSignals(Position p) {
      L<CloseSignal> signals = super.closeSignals(p);
      
      double profit = p.profitBeforeLeverage();
      
      // How close are we to the pullback limit?
      if (profit >= 0)
        signals.add(new CloseSignal("Profit", transformToZeroToOne(profit,
          p.crest, p.crest-pullback));
          
      ret signals;
    }
  }
  
  // Juicer with relative pullback
  persistable class RelativePullbackJuicer extends BaseJuicer {
    settable double relativePullback; // e.g. 20
    settable double minPullback; // e.g. 0.1
    settable double maxPullback; // e.g. 4
    
    toString { ret renderRecord("RelativePullbackJuicer", +maxLoss, +relativePullback, +minPullback, +maxPullback); }
    
    L<CloseSignal> closeSignals(Position p) {
      L<CloseSignal> signals = super.closeSignals(p);
      
      double profit = p.profitBeforeLeverage();
      
      // How close are we to the pullback limit?
      if (profit >= 0)
        signals.add(new CloseSignal("Profit", transformToZeroToOne(profit,
          p.crest, p.crest-calculatePullback(p.crest)));
          
      ret signals;
    }
    
    double calculatePullback(double crest) {
      ret clamp(relativePullback/100*crest, minPullback, maxPullback);
    }
  }
  
  abstract sclass Opener {
    abstract OpenSignal openSignal();
    abstract Opener cloneInto(Chaser strat);
  }
  
  record PreRunOpener(int minRunUp, int minRunDown) extends Opener {
    Opener cloneInto(Chaser strat) {
      ret strat.new PreRunOpener(minRunUp, minRunDown);
    }
    
    OpenSignal openSignal() {
      int direction = Chaser.this.direction;
      if (direction == 0) null;
      int preRun = direction > 1 ? minRunUp : minRunDown;
      int runLength = toInt(rlc.runLength());
      
      /*printConcat(
        "[",
        formatLocalDateWithSeconds(currentTime()),
        "] ",
        "Runlength ",
        runLength,
        " price: ",
        formatPriceX(currentPrice())
      );*/
      
      // TODO: even more precise signal
      //if (runLength >= preRun) print("Pre-run signal!");
      ret new OpenSignal("RunLength " + runLength, doubleRatio(runLength, preRun), direction);
    }
    
    toString { ret "PreRunOpener up " + minRunUp + ", down " + minRunDown; }
  }
  
  persistable class Position extends G22TradingStrategy.Position {
    settable OpenSignal openSignal;
    settable BaseJuicer juicer;
  
    // highest profit this position has reached
    double crest = -infinity();
        
    double maxClosingTime() {
      ret usingLiveData ? infinity() : openingTime()+hoursToMS(maxPositionHours);
    }
    
    L<CloseSignal> closeSignals() {
      ret juicer == null ?: juicer.closeSignals(this);
    }
    
    // >= 1 to close
    // otherwise keep open
    double closeSignal() {
      CloseSignal strongestSignal = closeSignalWithDescription();
      ret strongestSignal == null ? 0 : strongestSignal.trigger;
    }
    
    CloseSignal closeSignalWithDescription() {
      ret highestBy(closeSignals(), signal -> signal.trigger);
    }
    
    void update {
      crest = max(crest, profitBeforeLeverage());
    }
    
    toString {
      var closeSignal = closed() ? null : closeSignalWithDescription();
      ret commaCombine(super.toString(),
        closeSignal
      );
    }
  }
  
  LS status() {
    ret listCombine(
      commaCombine(
        "Max loss: " + formatDouble3(maxLoss) + "%",
        relativePullback == 0
          ? "pullback: " + formatDouble3(pullback) + "%"
          : commaCombine(
            "relative pullback: " + formatDouble3(relativePullback) + "%",
            "min pullback: " + formatDouble3(minPullback) + "%",
            "max pullback: " + formatDouble3(maxPullback) + "%",
          )
      ), 
      "Opener: " + opener,
      "Run length: " + signToUpDown(unnull(rlc.value())) + " " + rlc.runLength(),
      openSignal,
      super.status()
    );
  }
  
  Position openPosition(OpenSignal openSignal) {
    int direction = openSignal.direction;
    if (l(positionsInDirection(direction)) > 0) {
      //log("Not opening a second position in direction " + direction);
      null;
    }
    
    nextStep();
    new Position p;
    p.openSignal(openSignal);
    p.juicer(makeJuicer());
    ret openPosition(p, direction);
  }
  
  BaseJuicer makeJuicer() {
    if (relativePullback != 0)
      ret new RelativePullbackJuicer()
        .relativePullback(relativePullback)
        .minPullback(minPullback)
        .maxPullback(maxPullback)
        .maxLoss(maxLoss);
    else
      ret new Juicer(maxLoss, pullback);
  }

  void start {
    if (started()) fail("Already started");
    if (currentPrice() == 0)
      ret with primed(true);
    
    // Save starting price, create price cells & digitizer
    
    startingPrice = currentPrice();
    var priceCells = makePriceCells(startingPrice);
    digitizer = new PriceDigitizer2(priceCells);
    digitizer.verbose(verbose);

    log("Starting CHASER at " + startingPrice + " +/- " + cellSize);
  }
  
  void price(double price) {
    if (currentPrice == price) ret;
    currentPrice = price;
    
    if (!started()) {
      if (primed) {
        primed(false);
        start();
        nextStep();
        beforeStep();
        afterStep();
      }
      ret;
    }
    
    digitizer.digitize(price);
    direction = sign(digitizedPrice()-lastDigitizedPrice());
    if (direction != 0) lastDirection = direction;
    
    if (started())
      step();
  }
  
  swappable void step {
    // Update positions with new price
    
    for (p : openPositions()) {
      cast p to Position;
      p.update();
    }
    
    // Find positions to close
    
    L<G22TradingStrategy.Position> toClose = new L;
    for (p : openPositions()) {
      cast p to Position;
      CloseSignal signal = p.closeSignalWithDescription();
      if (signal != null && signal.trigger() >= 1) {
        p.closeReason(signal);
        log("Closing position because " + signal + ": " + p);
        toClose.add(p);
      }
    }
    
    if (nempty(toClose)) {
      nextStep();
      closePositions(toClose);
      afterStep();
    }
    
    beforeStep();
    double p1 = lastDigitizedPrice();
    double p2 = digitizedPrice();
    direction = sign(p2-p1);
    
    if (direction != 0)
      rlc.add(direction);

    // Don't open anything while we're feeding back data
    if (haveBackData()) {
      openSignal = makeOpenSignal();
      if (openSignal != null && openSignal.trigger >= 1)
        openPosition(openSignal);
        
      afterStep();
    }
  }
  
  swappable OpenSignal makeOpenSignal() {
    ret opener == null ?: opener.openSignal();
  }
  
  swappable void beforeStep() {}

  S baseToString() {
    ret colonCombine(
      _conceptID() == 0 ? "" : "Strategy " + _conceptID(),
      "Chaser profit " + formatMarginProfit(coinProfit()));
  }
  
  S fieldsToReset() {
    ret lines(ll(super.fieldsToReset(), [[lastDirection rlc openSignal]]));
  }
  
  Chaser emptyClone() {
    Chaser strat = cast super.emptyClone();
    ret strat.opener(opener?.cloneInto(strat));
  }
  
  // Chaser.toString
  toString { ret baseToString(); } 
  
  selfType preRunOpener(int minRunUp, int minRunDown) {
    ret opener(new PreRunOpener(minRunUp, minRunDown));
  }
}

Author comment

Began life as a copy of #1036196

download  show line numbers  debug dex  old transpilations   

Travelled to 2 computer(s): elmgxqgtpvxh, mqqgnosmbjvj

No comments. add comment

Snippet ID: #1036259
Snippet name: Chaser
Eternal ID of this version: #1036259/64
Text MD5: eb33884196d87a21882b1ab93d00af52
Author: stefan
Category: javax / gazelle 22
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2023-03-16 16:38:40
Source code size: 9527 bytes / 331 lines
Pitched / IR pitched: No / No
Views / Downloads: 207 / 536
Version history: 63 change(s)
Referenced in: [show references]