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

498
LINES

< > BotCompany Repo | #1036257 // Corridor - backup used in the real-money experiment

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

Libraryless. Click here for Pure Java version (25749L/159K).

concept Corridor extends G22TradingStrategy {
  // how many percent the crypto price needs to move
  // for one profitable trade
  settableWithVar double cellSize = 0.2;
  
  // how many cells the corridor has
  settableWithVar int capacity = 6;
  
  settableWithVar int maxPositionsPerDirection = 4;
  
  settableWithVar bool openTwoPositionsAtStart = true;
  settableWithVar bool alwaysOpenTwoPositions;
  
  // position options
  settableWithVar double leverage = 1;
  settableWithVar double marginPerPosition = 1; // in USDT
  
  // internal
  
  // primed = started but waiting for first price
  settableWithVar bool primed;
  
  settableWithVar PriceDigitizer2 digitizer;
  settableWithVar int direction;
  
  settableWithVar double epsilon = 1e-6;
  
  // maximum debt seen
  settableWithVar double maxDebt;
  //settableWithVar double minCoinProfit;
  //settableWithVar double maxBoundCoin;

  record noeq Position(double openingPrice, double direction, double digitizedOpeningPrice) {
    double closingPrice = Double.NaN;
    long openingStep, closingStep;
    gettable long openingTime;
    gettable long closingTime;
    double leverage;
    settable double cryptoAmount;
    settable O openError;
    settable O closeReason;
    settable O closeError;
    gettable double margin;
    settable bool openedOnMarket;
    settable bool closedOnMarket;
    
    Corridor strategy() { ret Corridor.this; }
    
    {
      // Corridor.this is not set when unstructuring
      if (!dynamicObjectIsLoading()) {
        openingStep = stepCount;
        leverage = Corridor.this.leverage;
        openingTime = currentTime();
      }
    }
    
    double digitizedOpeningPrice() { ret digitizedOpeningPrice; }

    bool isLong() { ret direction > 0; }
    bool isShort() { ret direction < 0; }
    
    bool closed() { ret !isNaN(closingPrice); }
    S type() { ret trading_directionToPositionType(direction); }
    
    double profitAtPrice(double price) {
      ret ((price-openingPrice)/openingPrice*direction*100-adversity)*leverage;
    }
    
    double profit() {
      ret profitAtPrice(closed() ? closingPrice : currentPrice());
    }
    
    double coinProfit() {
      ret profit()/100*margin();
    }
    
    void close(O closeReason) {
      if (closed()) fail("Can't close again");
      if (closeReason != null) closeReason(closeReason);
      openPositions.remove(this);
      closingPrice = currentPrice();
      closingTime = currentTime();
      closingStep = stepCount;
      closedPositions.add(this);
      realizedProfit += profit();
      realizedCoinProfit += coinProfit();
      closeOnMarket();
      //print(this);
      //printPositions();
    }
    
    // Position.toString
    toString {
      ret commaCombine(
        spaceCombine(
          closed() ? "Closed " + formatLocalDateWithSeconds(closingTime()) : null,
          formatDouble1(leverage) + "X " + upper(type())
        ),
        "opened " + formatLocalDateWithSeconds(openingTime()),
        !closed() ? null : or(closeReason, "unknown reason"),
        "profit: " + formatProfit(profit()) + "% (" + marginCoin + " " + plusMinusFix(formatPrice(coinProfit())) + ")",
        "margin: " + marginCoin + " " + formatPrice(margin()),
        "crypto: " + formatPrice(cryptoAmount),
        "opening price: " + formatPriceX(openingPrice)
           + " @ step " + openingStep,
        !closed() ? null : "closing price: " + formatPriceX(closingPrice)
          + " @ step " + closingStep,
        //"digitized: " + formatPrice(digitizedOpeningPrice()),
        );
    }
    
    void closeOnMarket {
      try {
        if (market != null) {
          log("Closing on market: " + this);
          market.closePosition(new IFuturesMarket.CloseOrder()
            .holdSide(HoldSide.fromInt(direction))
            .cryptoAmount(cryptoAmount));
          closedOnMarket(true);
          change();
        }
      } catch e {
        closeError = toPersistableThrowable(e);
      }
    }
    
    void open {
      margin = cryptoAmount*openingPrice/leverage;
      openPositions.add(this);
    }
    
    void openOnMarket {
      try {
        if (market != null) {
          log("Opening on market: " + this);
          market.openPosition(new IFuturesMarket.OpenOrder()
            .holdSide(HoldSide.fromInt(direction))
            .cryptoAmount(cryptoAmount)
            .leverage(leverage)
            .isCross(true));
          openedOnMarket(true);
          change();
        }
      } catch e {
        openError(toPersistableThrowable(e));
        log("Open error:" + e);
        close("Open error");
      }
    }
  }
      
  gettable double currentPrice = 0;
  
  gettable double oldPrice = Double.NaN;
  gettable double startingPrice = Double.NaN;
  gettable int lastDirection;
  gettable double realizedProfit;
  gettable double realizedCoinProfit;
  settableWithVar long stepCount;
  settableWithVar long stepSince;
  gettable new LinkedHashSet<Position> openPositions;
  gettable new L<Position> closedPositions;

  Position openPosition(int direction) {
    if (l(positionsInDirection(direction)) >= maxPositionsPerDirection) {
      log("Not opening new position in direction " + direction + ", max reached");
      null;
    }
    
    var price = digitizedPrice();
    if (isNaN(price)) price = digitizer.digitizeIndividually(currentPrice());
    var p = new Position(currentPrice(), direction, price);
    double cryptoAmount = marginPerPosition/price*leverage;
    cryptoAmount = roundTo(cryptoStep, cryptoAmount);
    p.cryptoAmount = max(minCrypto, cryptoAmount);
    p.open();
    //print("Opening " + p);
    //printPositions();
    p.openOnMarket();
    ret p;
  }
  
  bool hasPosition(double price, double direction) {
    ret findPosition(price, direction) != null;
  }
  
  Position closePosition(double price, double direction, O closeReason) {
    var p = findPosition(price, direction);
    p?.close(closeReason);
    ret p;
  }
  
  void closePositions(Cl<Position> positions, O closeReason default null) {
    forEach(positions, -> .close(closeReason));
  }
  
  Position findPosition(double digitizedPrice, double direction) {
    ret firstThat(openPositions(), p ->
      diffRatio(p.digitizedOpeningPrice(), digitizedPrice) <= epsilon() && p.direction == direction);
  }
  
  Position openShort() { ret openPosition(-1); }
  Position openLong() { ret openPosition(1); }
  
  void printPositions {
    print(colonCombine(n2(openPositions, "open position"),
      joinWithComma(openPositions)));
  }
  
  bool started() { ret !isNaN(startingPrice); }
  
  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);
    //digitizer.init(startingPrice);
    
    print("Starting CORRIDOR at " + startingPrice + " +/- " + cellSize);
    
    if (openTwoPositionsAtStart) {
      nextStep();
      openPosition(1);
      openPosition(-1);
      afterStep();
    }
  }
  
  void prices(double... prices) {
    fOr (price : prices)
      price(price);
  }
  
  swappable PriceCells makePriceCells(double basePrice) {
    //ret new RegularPriceCells(basePrice, cellSize);
    ret new GeometricPriceCells(basePrice, cellSize);
  }
  
  double digitizedPrice() { ret digitizer.digitizedPrice(); }
  double lastDigitizedPrice() { ret digitizer.lastDigitizedPrice(); }
  
  void handleNewPriceInQ(double price) {
    q.add(-> price(price));
  }

  void currentPrice aka price(double price) {
    if (currentPrice == price) ret;
    currentPrice = price;
    
    if (!started()) {
      if (primed) {
        primed(false);
        start();
      }
      ret;
    }
    
    digitizer.digitize(price);
    direction = sign(digitizedPrice()-lastDigitizedPrice());
    
    // No cell move?
    if (direction == 0) ret with afterStep();

    nextStep();
    //printWithPrecedingNL("New digitized price: " + digitizedPrice());

    if (started())
      step();
      
    print(this);
  }
  
  void nextStep {
    ++stepCount;
    stepSince(now());
  }
  
  swappable void step {
    double p1 = lastDigitizedPrice();
    double p2 = digitizedPrice();
    direction = sign(p2-p1);
    if (direction == 0) ret;
    
    // Find winning positions to close
    
    new L<Position> toClose;
    for (Position p : direction > 0
      ? longPositionsAtOrBelowDigitizedPrice(p1)
      : shortPositionsAtOrAboveDigitizedPrice(p1)) {
      p.closeReason("WIN");
      log("Closing winner position at " + currentPrice + " (" + p1 + " -> " + p2 + "): " + p);
      toClose.add(p);
    }
    
    // Dismantle corridor if too large
    double limit = fromCellNumber(toCellNumber(digitizedPrice())-capacity*direction);
    var toDismantle = direction > 0
      ? shortPositionsAtOrBelowDigitizedPrice(limit)
      : longPositionsAtOrAboveDigitizedPrice(limit);
    print("DISMANTLE LIMIT: " + formatPriceX(limit));
    for (Position p : toDismantle) {
      p.closeReason("DISMANTLE");
      toClose.add(p);
    }
      
    //printVars("step", +p1, +p2, +direction, +closed, +opened);
    
    if (nempty(toClose))
      closePositions(toClose);

    // Open backward position here (if not there yet)
    if (!hasPosition(p2, -direction))
      openPosition(-direction);
      
    // Open forward position if configured as such
    if (alwaysOpenTwoPositions && !hasPosition(p2, direction))
      openPosition(direction);
      
    afterStep();
  }
  
  void afterStep {
    maxDebt = max(maxDebt, debt());
    //minCoinProfit = min(minCoinProfit, coinProfit());
    //maxBoundCoin = max(maxBoundCoin, boundCoin());
    maxInvestment = max(maxInvestment, investment());
    change();
  }
  
  double investment() {
    ret boundCoin()-realizedCoinProfit;
  }
  
  double boundCoin() {
    ret max(openMargins(), max(0, -coinProfit()));
  }
  
  double fromCellNumber(double cellNumber) { ret cells().fromCellNumber(cellNumber); }
  double toCellNumber(double price) { ret cells().toCellNumber(price); }
  
  double unrealizedProfit() {
    double sum = 0;
    for (p : openPositions)
      sum += p.profit();
    ret sum;
  }
  
  double openMargins() {
    double sum = 0;
    for (p : openPositions)
      sum += p.margin();
    ret sum;
  }
  
  double unrealizedCoinProfit() {
    double sum = 0;
    for (p : openPositions)
      sum += p.coinProfit();
    ret sum;
  }
  
  L<Position> positionsInDirection(int direction) {
    ret filter(openPositions, p -> p.direction == direction);
  }
  
  L<Position> longPositions() { ret positionsInDirection(1); }
  L<Position> shortPositions() { ret positionsInDirection(-1); }
  
  L<Position> shortPositionsAtOrBelowPrice(double openingPrice) {
    ret filter(openPositions, p -> p.isShort() && p.openingPrice <= openingPrice);
  }
  
  L<Position> longPositionsAtOrAbovePrice(double openingPrice) {
    ret filter(openPositions, p -> p.isLong() && p.openingPrice >= openingPrice);
  }
  
  L<Position> shortPositionsAtOrBelowDigitizedPrice(double openingPrice) {
    ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() <= openingPrice);
  }
  
  L<Position> shortPositionsAtOrAboveDigitizedPrice(double openingPrice) {
    ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() >= openingPrice);
  }
  
  L<Position> longPositionsAtOrAboveDigitizedPrice(double openingPrice) {
    ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() >= openingPrice);
  }
  
  L<Position> longPositionsAtOrBelowDigitizedPrice(double openingPrice) {
    ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() <= openingPrice);
  }
  
  L<Position> negativePositions() {
    ret filter(openPositions, p -> p.profit() < 0);
  }
  
  double debt() {
    ret max(0, -unrealizedCoinProfit());
  }
  
  double profit() {
    ret realizedProfit+unrealizedProfit();
  }
  
  double coinProfit() {
    ret realizedCoinProfit+unrealizedCoinProfit();
  }
  
  S baseToString() {
    ret colonCombine(
      _conceptID() == 0 ? "" : "Strategy " + _conceptID(),
      "Corridor");
  }
  
  LS status() {
    var r = currentCorridorPriceRange();
    ret llNonNulls(
      //baseToString(),
      !primed() ? null : "Primed",
      !started() ? null : "Started. current price: " + formatPriceX(currentPrice) + ", digitized: " + formatPriceX(digitizedPrice()),
      "Leverage: " + leverage + ", margin per position: " + marginCoin+ " " + formatPrice(marginPerPosition),
      "Cell size: " + formatPercentage(cellSize, 3) + ", max capacity: " + capacity,
      !started() ? null : "Current corridor: " + (r == null ? "-" : formatPrice(r.start) + " to " + formatPrice(r.end) + " (" + formatPercentage(currentCorridorSizeInPercent(), 3) + ")"),
      "Step " + n2(stepCount) + (stepSince == 0 ? "" : " since " + formatHoursMinutesColonSeconds(currentTime()-stepSince)),
      "Profit: " + marginCoin + " " + plusMinusFix(formatMarginPrice(coinProfit())),
      "Realized profit: " + marginCoin + " " + formatProfit(realizedCoinProfit) + " from " + n2(closedPositions, "closed position"),
      "Unrealized profit: " + marginCoin + " " + formatProfit(unrealizedCoinProfit()) + " in " + n2(openPositions, "open position"),
      "Debt: " + marginCoin + " " + formatProfit(debt()) + " (max seen: " + formatProfit(maxDebt) + ")",
      "Investment used: " + marginCoin + " " + formatMarginPrice(maxInvestment()),
    );
  }
  
  LS fullStatus() {
    ret listCombine(
      status(),
      "",
      n2(openPositions, "open position") + ":",
      reversed(openPositions),
      "",
      n2(closedPositions, "closed position") + ":",
      reversed(closedPositions)
    );
  }
  
  S formatProfit(double x) {
    ret plusMinusFix(formatDouble1(x));
  }
  
  // Corridor.toString
  toString { ret baseToString(); } 
  
  double maxCorridorPercentSize() {
    ret capacity*cellSize;
  }
  
  PriceCells cells() {
    ret digitizer?.cells;
  }
  
  DoubleRange currentCorridorPriceRange() {
    double[] openingPrices = mapToDoubleArray(openPositions, ->.openingPrice);
    ret doubleRange(doubleMin(openingPrices), doubleMax(openingPrices));
  }
  
  double currentCorridorSizeInPercent() {
    DoubleRange r = currentCorridorPriceRange();
    ret r == null ? 0 : asPercentIncrease(r.end, r.start);
  }

  S formatPriceX(double price) {
    if (isNaN(price)) ret "-";
    double num = cells().priceToCellNumber(price);
    ret formatPrice(price) + " (C" + formatDouble2(num) + ")";
  }
  
  void reset {
    resetFields(this, [[
      digitizer maxDebt direction oldPrice startingPrice              lastDirection realizedProfit realizedCoinProfit
      stepCount
      openPositions closedPositions
    ]]);
    change();
  }
  
  Corridor emptyClone() {
    //var clone = shallowClone(this);
    var clone = shallowCloneToUnlistedConcept(this);
    clone.reset();
    ret clone;
  }
}

Author comment

Began life as a copy of #1036196

download  show line numbers  debug dex  old transpilations   

Travelled to 2 computer(s): mqqgnosmbjvj, wnsclhtenguj

No comments. add comment

Snippet ID: #1036257
Snippet name: Corridor - backup used in the real-money experiment
Eternal ID of this version: #1036257/2
Text MD5: a8ece345d4f92a456d8983d17960f1ee
Transpilation MD5: 66ca43f9d3185ce1fd51b7e67bc4f158
Author: stefan
Category: javax / gazelle 22
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-11-05 20:13:22
Source code size: 15569 bytes / 498 lines
Pitched / IR pitched: No / No
Views / Downloads: 60 / 88
Version history: 1 change(s)
Referenced in: [show references]