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

282
LINES

< > BotCompany Repo | #1036185 // MPM3 - backup before extensions

JavaX fragment (include)

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;
    
    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; }
  }
  
  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(); }
  }
  
  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.openEnded() ? 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 {
    real adviseDirection(TickerPoint tickerPoint) {
      real move = calculateMove(tickerPoint);
      if (isNaN(move)) ret 0;
      real relativeMove = doubleRatio(move, minMove);
      ret clamp(relativeMove, -1, 1);
    }
    
    real calculateMove(TickerPoint tickerPoint) {
      if (!tickerPoint.canLookback(lookbackTime)) ret Double.NaN;
      real currentPrice = tickerPoint.currentPrice();
      real before = tickerPoint.lookback(lookbackTime);
      ret currentPrice/before*100-100;
    }
  }
  
  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);
        
        // 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 #1036164

download  show line numbers  debug dex  old transpilations   

Travelled to 1 computer(s): mqqgnosmbjvj

No comments. add comment

Snippet ID: #1036185
Snippet name: MPM3 - backup before extensions
Eternal ID of this version: #1036185/1
Text MD5: 5aaf46284f2bf0769f160f52001b4b95
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-10 15:30:12
Source code size: 9101 bytes / 282 lines
Pitched / IR pitched: No / No
Views / Downloads: 58 / 59
Referenced in: [show references]