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

342
LINES

< > BotCompany Repo | #1036164 // MPM3 - Trading Bot v3

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

Transpiled version (13269L) is out of date.

set flag Reparse.
replace real with double.
replace Real with Double.
replace price with float.

// Persistence:
// MPM3.BotInAction is persistable which in turn means
// MPM3, Position, Juicer and other classes are also persistable.
// We don't persist Position.ticker for space reasons though.

persistable sclass MPM3 {
  // MARKET (trading platform)
  
  // How much percentual slippage we have for each position on
  // average plus the fees for the position.
  // Obviously dependent on the market (trading site) we are using.
  settable real marketAdversity = 0.12 + 0.08;
  
  // SELF-IMPOSED LIMITS
  
  // How long a position can be held at the longest
  // - only for backtesting!
  gettable real maxPositionDuration = minutesToMS(60);
  
  real minimalMaxDuration() { ret minutesToMS(1); }
  
  // correct user if they are stupid
  selfType maxPositionDuration(real whatUserWants) {
    maxPositionDuration = max(whatUserWants, minimalMaxDuration());
    this;
  }
  
  // POSITIONS

  // An open(ed) position  
  persistable record Position(transient TickerSequence ticker, real openingTime, real direction) {
  
    // Set this to a non-null value if this is a real position on a real market
    settable O irlInfo;
    
    // Eye that opened this position
    settable Eye eye;
    
    real openingPrice() { ret ticker.priceAtTimestamp(openingTime); }
    
    real profitAtTime(real time) {
      ret (ticker.priceAtTimestamp(time)/openingPrice()*100-100)*direction - marketAdversity;
    }
    
    real openingTime() { ret openingTime; }
    
    S directionString() { ret direction > 0 ? "long" : direction  < 0 ? "short" : ""; }
    
    void restoreTicker(TickerSequence ticker) { this.ticker = ticker; }
    
    TickerPoint openingTickerPoint() { ret new TickerPoint(ticker, lround(openingTime)); }
  }
  
  macro forwardToPosition {
    TickerSequence ticker() { ret p.ticker; }
    void restoreTicker(TickerSequence ticker) { p.restoreTicker(ticker); }
    real direction() { ret p.direction; }
    S directionString() { ret p.directionString(); }
    real openingTime() { ret p.openingTime; }
    real openingPrice() { ret p.openingPrice(); }
    Eye eye() { ret p.eye(); }
  }
  
  persistable record LivePosition(Position p) {
    settable Juicer juicer;
    
    // highest profit this position has reached
    settable real crest = -infinity();
    
    settable real time;   // time we last updated this position
    settable real profit; // expected profit at that time
    
    forwardToPosition
    
    // return true if any change
    bool update(real time) {
      if (time <= this.time) false;
      this.time = time;
      profit = p.profitAtTime(time);
      crest = max(crest, profit);
      true;
    }
    
    transient simplyCached real maxClosingTime() {
      ret p.ticker.live() ? infinity()
        : min(p.openingTime+maxPositionDuration, p.ticker.endTime());
    }
    
    bool shouldClose() {
      ret time >= maxClosingTime()
        || profit < (profit < 0 ? -juicer.lossTolerance : crest-juicer.pullback);
    }
    
    // >= 1 to close
    // otherwise keep open
    real closeSignal() {
      CloseSignal strongestSignal = closeSignalWithDescription();
      ret strongestSignal == null ? 0 : strongestSignal.trigger;
    }
    
    CloseSignal closeSignalWithDescription() {
      ret highestBy(closeSignals(), signal -> signal.trigger);
    }
    
    // trigger = 0 to 1
    record noeq CloseSignal(S reason, real trigger) {}

    L<CloseSignal> closeSignals() {
      new L<CloseSignal> signals;
      
      // How close are we to the max closing time?
      if (maxClosingTime() < infinity())
        signals.add(new CloseSignal("Age", transformToZeroToOne(time, p.openingTime(), maxClosingTime())));
        
      // How close are we to our loss limit?
      if (profit < 0)
        signals.add(new CloseSignal("Loss", doubleRatio(-profit, juicer.lossTolerance));
      else
        // How close are we to the pullback limit?
        signals.add(new CloseSignal("Profit", transformToZeroToOne(profit,
          crest, crest-juicer.pullback));
          
      ret signals;
    }
  }
  
  record ClosedPosition(Position p, real closingTime) {
    real profit = Double.NaN;
    
    MPM3 mpm() { ret MPM3.this; }

    forwardToPosition
    real closingTime() { ret closingTime; }
    real closingPrice() { ret ticker().priceAtTimestamp(closingTime); }
    
    real duration() { ret closingTime()-openingTime(); }
    
    real profit() {
      if (isNaN(profit))
        profit = p.profitAtTime(closingTime);
      ret profit;
    }
  }
  
  // Wrappers for L<LivePosition> and L<ClosedPosition>
  // (maybe not needed?)
  
  sclass LivePositions {
    L<LivePosition> positions = syncL();
  }
  
  sclass ClosedPositions {
    L<ClosedPosition> positions = syncL();
  }
  
  // BOT (Eye + Juicer)
  
  persistable srecord Juicer(real lossTolerance, real pullback) {}

  abstract sclass Eye {
    // returns -1 to open short, 1 to open long
    // and anything in between to do nothing
    abstract real adviseDirection(TickerPoint tickerPoint);
  }
  
  // time = ms to look back
  srecord SimpleEye(long lookbackTime, real minMove) extends Eye {
    // 0 to 1
    settable real relativeVerificationTime/* = 0.33*/;
    
    // 0 to 1 (roughly)
    settable real verificationThreshold/* = 0.5*/;
    
    // in ms
    real verificationTime() {
      ret lookbackTime*relativeVerificationTime;
    }
    
    // in price percent
    real verificationMoveThreshold() {
      ret minMove*relativeVerificationTime*verificationThreshold;
    }
    
    // in price percent
    real verificationMove(TickerPoint tickerPoint) {
      ret calculateMove(tickerPoint, verificationTime());
    }
    
    // in price percent, scaled up to lookbackTime
    real relativeVerificationMove(TickerPoint tickerPoint) {
      ret doubleRatio(verificationMove(tickerPoint)*lookbackTime, verificationTime());
    }
    
    real verificationSignal(TickerPoint tickerPoint) {
      ret doubleRatio(verificationMove(tickerPoint), verificationMoveThreshold());
    }
    
    bool verificationEnabled() {
      ret relativeVerificationTime != 0;
    }
    
    real adviseDirection(TickerPoint tickerPoint) {
      real move = calculateMove(tickerPoint);
      if (isNaN(move)) ret 0;
      real relativeMove = doubleRatio(move, minMove);
      int sign = sign(relativeMove);
      real absRelativeMove = abs(relativeMove);
      
      if (verificationEnabled()) {
        real verification = max(0, sign*verificationSignal(tickerPoint));
        absRelativeMove = min(absRelativeMove, verification);
      }
      
      // No more clamping - allow trade signals > 100%
      ret sign*absRelativeMove;
    }
    
    real calculateMove(TickerPoint tickerPoint, real lookbackTime default this.lookbackTime) {
      if (!tickerPoint.canLookback(lround(lookbackTime))) ret Double.NaN;
      real currentPrice = tickerPoint.currentPrice();
      real before = tickerPoint.lookback(lround(lookbackTime));
      ret currentPrice/before*100-100;
    }
  }
  
  /*persistable srecord TwoEyes extends Eye {
    settable Eye eye1;
    settable Eye eye2;
    
    *(Eye *eye1, Eye *eye2) {}
    
    real adviseDirection(TickerPoint tickerPoint) {
    }
  }*/
  
  srecord TradingBot(Eye eye, Juicer juicer) {
    *(real lookbackMinutes, real minMove, real maxLoss, real pullback) {
      eye = new SimpleEye(minutesToMS(lookbackMinutes), minMove);
      juicer = new Juicer(maxLoss, pullback);
    }
  }
  
  class BotInAction extends MetaWithChangeListeners {
    settable TradingBot botConfiguration;
    transient TickerSequence ticker;
    
    // Both open and closed positions may be purely simulated or
    // mirrors of positions actually made IRL (see Position.irlInfo).
    
    new ListWithChangeListeners<LivePosition> openPositions;
    new ListWithChangeListeners<ClosedPosition> closedPositions;
    
    LivePosition openPosition(TickerPoint tickerPoint, real direction) {
      var p = new Position(tickerPoint.ticker(), tickerPoint.time(), direction);
      var livePosition = new LivePosition(p).juicer(botConfiguration.juicer);
      livePosition.update(tickerPoint.time());
      openPositions.add(livePosition);
      change();
      ret livePosition;
    }
    
    void closePosition(LivePosition position) {
      if (openPositions.remove(position)) {
        // TODO: closedPositions.add...
        change();
      }
    }
    
    void restoreTicker(TickerSequence ticker) {
      if (ticker == this.ticker) ret;
      this.ticker = ticker;
      ticker.onPricePointAdded(time -> updatePositions(time));
      for (p : openPositions) p.restoreTicker(ticker);
      for (p : closedPositions) p.restoreTicker(ticker);
    }
    
    void updatePositions(real time) {
      bool anyChange;
      for (p : openPositions) anyChange |= p.update(time);
      if (anyChange) change();
    }
  }

  ClosedPosition runJuicer(Position p, Juicer j) {
    TickerSequence ticker = p.ticker;
    
    long time = lround(p.openingTime);
    double maxClosingTime = min(time+maxPositionDuration, ticker.endTime());
    real crest = -infinity();
    
    while (time < maxClosingTime) {
      time = ticker.nextTimestamp(time);
      real profit = p.profitAtTime(time);
      crest = max(crest, profit);
      if (profit < (profit < 0 ? -j.lossTolerance : crest-j.pullback))
        break;
    }
    
    ret new ClosedPosition(p, time);
  }

  record noeq Backtest(TickerSequence ticker, TradingBot bot) extends Convergent {
    new L<ClosedPosition> closedPositions;
  
    long time;
    
    double latestAllowedOpeningTime() {
      ret ticker.endTime()-maxPositionDuration;
    }
    
    void step {
      if (time == 0) time = ticker.startTime();
      if (time > latestAllowedOpeningTime())
        ret with done = true;
      
      TickerPoint tickerPoint = new(ticker, time);
      real direction = bot.eye.adviseDirection(tickerPoint);
      if (abs(direction) < 1)
        // No position to open, move to next timestamp
        time = ticker.nextTimestamp(time);
      else {
        // We have a position to open
        var position = new Position(ticker, time, direction)
          .eye(bot.eye);
        
        // Ask juicer when to close position
        var closedPosition = runJuicer(position, bot.juicer);
        
        // Add to record of positions made
        closedPositions.add(closedPosition);
        
        // Move on to next timestamp
        time = ticker.nextTimestamp(closedPosition.closingTime);
      }
    }
  }
}

Author comment

Began life as a copy of #1036148

download  show line numbers  debug dex  old transpilations   

Travelled to 3 computer(s): elmgxqgtpvxh, mqqgnosmbjvj, wnsclhtenguj

No comments. add comment

Snippet ID: #1036164
Snippet name: MPM3 - Trading Bot v3
Eternal ID of this version: #1036164/95
Text MD5: 60307496b119b8815b3b635d9ce80fc8
Author: stefan
Category: javax / trading bot
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-10-30 18:05:14
Source code size: 10946 bytes / 342 lines
Pitched / IR pitched: No / No
Views / Downloads: 280 / 702
Version history: 94 change(s)
Referenced in: #1003674 - Standard Classes + Interfaces (LIVE continued in #1034167)
#1036185 - MPM3 - backup before extensions