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

709
LINES

< > BotCompany Repo | #1036209 // G22TradingStrategy

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

Transpiled version (29169L) is out of date.

abstract concept G22TradingStrategy extends ConceptWithChangeListeners is Comparable<G22TradingStrategy>, Juiceable {
  S fieldsToReset() {
    ret [[
      globalID active
      q
      archived primed usingLiveData 
      backDataFed startTime deactivated closedItself
      log currentTime currentPrice feedNote
      direction
      digitizer maxDebt direction oldPrice startingPrice
      maxInvestment
      realizedProfit realizedCoinProfit
      realizedWins realizedCoinWins
      realizedLosses realizedCoinLosses
      stepCount stepSince
      openPositions closedPositions positionsThatFailedToOpen
      drift driftSystem
    ]];
  }
  
  settable bool verbose;
  
  gettable S globalID = aGlobalID();
  
  // user-given name, overriding the technical name
  settable S customName;
  
  // user-given comment
  settable S comment;

  gettable transient Q q = startQ();
  
  // link to market (optional)
  transient settable IFuturesMarket market;
  
  settable bool mergePositionsForMarket;
  
  settableWithVar S cryptoCoin;
  settableWithVar S marginCoin = "USDT";
  
  // primed = started but waiting for first price
  settableWithVar bool primed;
  
  settableWithVar bool usingLiveData;
  settableWithVar bool doingRealTrades;
  settableWithVar bool demoCoin;
  settableWithVar bool active;
  
  // set to date when strategy ended itself
  settableWithVar long closedItself;
  
  // set to date when strategy was deactivated (by itself or by the user)
  settableWithVar long deactivated;
  
  // moved to archive (out of main list)
  settableWithVar bool archived;
  
  // area where this strategy is held, e.g. "Candidates"
  settableWithVar S area;
  
  settableWithVar double adversity = 0.2;
  
  settableWithVar new LS log;
  
  settableWithVar double epsilon = 1e-6;
  
  // increment of crypto we can bet on
  settableWithVar double cryptoStep = 0.0001;
  
  // minimum crypto we need to bet on
  settableWithVar double minCrypto = 0.0001;
  
  settableWithVar double maxInvestment;
  
  settableWithVar double cellSize = 1;
  
  // position options
  settableWithVar double leverage = 1;
  settableWithVar double marginPerPosition = 1; // in USDT
  
  settableWithVar bool showClosedPositionsBackwards = true;
  
  // End strategy when this margin coin profit is reached.
  settableWithVar double takeCoinProfit = infinity();
  settableWithVar bool takeCoinProfitEnabled;
  
  // How many hours of recent back data this algorithm likes
  // to be fed with prior to running live.
  settableWithVar double backDataHoursWanted;
  
  // Did we feed the back data?
  settableWithVar bool backDataFed;
  
  // Juicer that is used to stop the whole strategy depending
  // on its profit (optional)
  settableWithVar AbstractJuicer strategyJuicer;
  
  swappable long currentTime() { ret now(); }
  
  settable transient bool logToPrint = true;
  
  // Note from price feeder
  settableWithVar S feedNote;
  
  // optimization to save fees
  settableWithVar bool useDriftSystem;
  
  // The drift system we are connected to
  settableWithVar G22DriftSystem driftSystem;
  
  void log(O o) {
    if (o != null) {
      log.add(printIf(logToPrint, "[" + formatLocalDateWithSeconds(currentTime()) + "] " + str(o)));
      change();
    }
  }
  
  void logVars(O... o) {
    log(renderVars(o));
  }
  
  LS activationStatus() {
    ret llNempties(
      active ? "Active" : "Inactive",
      empty(cryptoCoin) ? null
        : "coin: " + cryptoCoin + " over " + marginCoin
          + stringIf(demoCoin, " (demo coin)"),
      stringIf(market != null, "connected to market"),
      stringIf(usingLiveData, "using live data"),
      stringIf(doingRealTrades, "real trades")
    );
  }
  
  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() {
    ret unrealizedCoinProfitAtPrice(currentPrice);
  }
  
  double unrealizedCoinProfitAtPrice(double price) {
    double sum = 0;
    for (p : openPositions)
      sum += p.coinProfitAtPrice(price);
    ret sum;
  }
  
  L<Position> positionsInDirection(int direction) {
    ret filter(openPositions, p -> sign(p.direction) == sign(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> negativePositions() {
    ret filter(openPositions, p -> p.profit() < 0);
  }
  
  double debt() {
    ret max(0, -unrealizedCoinProfit());
  }
  
  // TODO: remove
  double profit() {
    ret realizedProfit+unrealizedProfit();
  }
  
  double coinProfit() {
    ret realizedCoinProfit+unrealizedCoinProfit();
  }
  
  double coinProfitAtPrice(double price) {
    ret realizedCoinProfit+unrealizedCoinProfitAtPrice(price);
  }
  
  S formatProfit(double x) {
    ret plusMinusFix(formatDouble1(x));
  }
  
  settableWithVar PriceDigitizer2 digitizer;
  settableWithVar int direction;
  
  // maximum debt seen
  settableWithVar double maxDebt;
  //settableWithVar double minCoinProfit;
  //settableWithVar double maxBoundCoin;

  class Position {
    settable double marginToUse;
    settable double openingPrice;
    settable double direction;
    settable double digitizedOpeningPrice;
    gettable 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;
    settable bool dontCloseOnMarket;

    G22TradingStrategy strategy() { ret G22TradingStrategy.this; }
    
    {
      if (!dynamicObjectIsLoading()) {
        marginToUse = marginPerPosition;
        openingStep = stepCount;
        leverage = G22TradingStrategy.this.leverage;
        openingTime = currentTime();
      }
    }
    
    bool isLong() { ret direction > 0; }
    bool isShort() { ret direction < 0; }
    
    bool closed() { ret !isNaN(closingPrice); }
    S type() { ret trading_directionToPositionType(direction); }
    
    long closingOrCurrentTime() { ret closed() ? closingTime() : currentTime(); }
    
    long duration() { ret closingOrCurrentTime()-openingTime(); }
    
    double profitAtPrice(double price) {
      ret profitAtPriceBeforeLeverage(price)*leverage;
    }
    
    double profitAtPriceBeforeLeverage(double price) {
      ret ((price-openingPrice)/openingPrice*direction*100-adversity);
    }
    
    double workingPrice() {
      ret closed() ? closingPrice : currentPrice();
    }
    
    double profit() {
      ret profitAtPrice(workingPrice());
    }
    
    double profitBeforeLeverage() {
      ret profitAtPriceBeforeLeverage(workingPrice());
    }
    
    double coinProfit() {
      ret coinProfitAtPrice(workingPrice());
    }
    
    double coinProfitAtPrice(double price) {
      ret profitAtPrice(price)/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);
      double cp = coinProfit();
      realizedProfit += profit();
      realizedCoinProfit += cp;
      if (cp > 0) {
        realizedWins++;
        realizedCoinWins += cp;
      } else {
        realizedLosses++;
        realizedCoinLosses += cp;
      }
      change();
      closeOnMarket();
      //print(this);
      //printPositions();
    }
    
    // Position.toString
    toString {
      ret commaCombine(
        spaceCombine(
          !closed() ? null :
            "Closed " + formatLocalDateWithSeconds(closingTime())
              + ", duration " + formatHoursMinutesColonSeconds(duration()),
          formatDouble1(leverage) + "X " + upper(type())
        ),
        "opened " + formatLocalDateWithSeconds(openingTime()),
        !closed() ? null : or(closeReason, "unknown reason"),
        "profit: " + formatProfit(profit()) + "% (" + marginCoin + " " + formatMarginProfit(coinProfit()) + ")",
        "before leverage: " + formatProfit(profitBeforeLeverage()) + "%",
        "margin: " + marginCoin + " " + formatMarginPrice(margin()),
        "crypto: " + formatPrice(cryptoAmount),
        "opening price: " + formatPriceX(openingPrice)
           + " (digitized: " + formatPrice(digitizedOpeningPrice())
           + ") @ step " + openingStep,
        !closed() ? null : "closing price: " + formatPriceX(closingPrice)
          + " @ step " + closingStep,
        );
    }
    
    void closeOnMarket {
      if (dontCloseOnMarket) ret;
      if (openError != null) ret with log("Not closing because open error: " + this);
      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);
      change();
    }
    
    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);
        positionsThatFailedToOpen.add(this);
        close("Open error");
      }
    }
  }
      
  gettable double currentPrice = 0;
  
  gettable double oldPrice = Double.NaN;
  gettable double startingPrice = Double.NaN;
  settable long startTime;
  gettable double realizedProfit;
  gettable double realizedCoinProfit;
  gettable int realizedWins;
  gettable double realizedCoinWins;
  gettable int realizedLosses;
  gettable double realizedCoinLosses;
  settableWithVar long stepCount;
  settableWithVar long stepSince;
  gettable new LinkedHashSet<Position> openPositions;
  gettable new L<Position> closedPositions;
  gettable new L<Position> positionsThatFailedToOpen;
  
  void closeOnMarketMerged(Cl<? extends Position> positions) {
    if (market == null) ret;
    closeOnMarketMerged_oneDirection(filter(positions, -> .isShort()));
    closeOnMarketMerged_oneDirection(filter(positions, -> .isLong()));
  }
  
  // all positions must have the same direction
  void closeOnMarketMerged_oneDirection(L<? extends Position> positions) {
    if (empty(positions)) ret;
    
    double direction = first(positions).direction;
    double cryptoAmount = doubleSum(positions, -> .cryptoAmount);
    log("Closing on market: " + positions);
    
    try {
      market.closePosition(new IFuturesMarket.CloseOrder()
        .holdSide(HoldSide.fromInt(direction))
        .cryptoAmount(cryptoAmount);
        
      for (p : positions)
        p.closedOnMarket(true);
      change();
    } catch e {
      var e2 = toPersistableThrowable(e);
      for (p : positions)
        p.closeError(e2);
    }
  }

  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<? extends Position> positions, O closeReason default null) {
    if (mergePositionsForMarket) {
      for (p : positions)
        p.dontCloseOnMarket(true).close(closeReason);
      closeOnMarketMerged(positions);
    } else
      forEach(positions, -> .close(closeReason));
  }
  
  Position findPosition(double digitizedPrice, double direction) {
    ret firstThat(openPositions(), p ->
      diffRatio(p.digitizedOpeningPrice(), digitizedPrice) <= epsilon() && sign(p.direction) == sign(direction));
  }
  
  void printPositions {
    print(colonCombine(n2(openPositions, "open position"),
      joinWithComma(openPositions)));
  }
  
  bool started() { ret !isNaN(startingPrice); }
  void prices(double... prices) {
    fOr (price : prices) {
      if (!active()) ret;
      price(price);
    }
  }
  
  swappable PriceCells makePriceCells(double basePrice) {
    ret new GeometricPriceCells(basePrice, cellSize);
  }
  
  double digitizedPrice() { ret digitizer == null ? Double.NaN : digitizer.digitizedPrice(); }
  double lastDigitizedPrice() { ret digitizer == null ? Double.NaN : digitizer.lastDigitizedPrice(); }
  int digitizedCellNumber() { ret digitizer == null ? 0 : digitizer.cellNumber(); }
  
  void handleNewPriceInQ(double price) {
    q.add(-> price(price));
  }

  void nextStep {
    ++stepCount;
    stepSince(currentTime());
  }
  
  void afterStep {
    maxDebt(max(maxDebt, debt()));
    //minCoinProfit = min(minCoinProfit, coinProfit());
    //maxBoundCoin = max(maxBoundCoin, boundCoin());
    maxInvestment(max(maxInvestment, investment()));
    if (takeCoinProfitEnabled() && coinProfit() >= takeCoinProfit) {
      log("Taking coin profit.");
      closeMyself();
    }
  }
  
  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); }
  
  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);
  }
  
  PriceCells cells aka priceCells() {
    ret digitizer?.cells;
  }
  
  S formatPriceX(double price) {
    if (isNaN(price)) ret "-";
    S s = formatPrice(price);
    if (cells() == null) ret s;
    double num = cells().priceToCellNumber(price);
    ret s + " (C" + formatDouble2(num) + ")";
  }
  
  abstract void price aka currentPrice(double price);
  
  <P extends Position> P openPosition(P p, int direction) {
    var price = digitizedPrice();
    var realPrice = currentPrice();
    logVars("openPosition", +realPrice, +price, +digitizer);
    if ((isNaN(price) || price == 0) && digitizer != null) {
      price = digitizer.digitizeIndividually(currentPrice());
      print("digitized individually: " + price);
    }
    p.openingPrice(realPrice);
    p.direction(direction);
    p.digitizedOpeningPrice(price);
    double cryptoAmount = p.marginToUse/realPrice*leverage;
    cryptoAmount = roundTo(cryptoStep, cryptoAmount);
    log(renderVars("openPosition", +marginPerPosition, +realPrice, +leverage, +cryptoAmount, +cryptoStep));
    p.cryptoAmount = max(minCrypto, cryptoAmount);
    p.open();
    //print("Opening " + p);
    //printPositions();
    p.openOnMarket();
    ret p;
  }
  
  LS status() {
    double mulProf = multiplicativeProfit();
    ret llNonNulls(
      empty(comment) ? null : "Comment: " + comment,
      "Profit: " + marginCoin + " " + plusMinusFix(formatMarginPrice(coinProfit())),
      "Realized profit: " + marginCoin + " " + formatMarginProfit(realizedCoinProfit) + " from " + n2(closedPositions, "closed position")
        + " (" + formatMarginProfit(realizedCoinWins) + " from " + n2(realizedWins, "win")
        + ", " + formatMarginProfit(realizedCoinLosses) + " from " + n2(realizedLosses, "loss", "losses") + ")",
      "Unrealized profit: " + marginCoin + " " + formatMarginProfit(unrealizedCoinProfit()) + " in " + n2(openPositions, "open position"),
      isNaN(mulProf) ? null : "Multiplicative profit: " + formatProfit(mulProf) + "%",
      //baseToString(),
      !primed() ? null : "Primed",
      !started() ? null : "Started. current price: " + formatPriceX(currentPrice) + ", digitized: " + formatPriceX(digitizedPrice()),
      "Leverage: " + leverage + ", margin per position: " + marginCoin + " " + formatPrice(marginPerPosition),
      "Cell size: " + formatCellSize(cellSize),
      spaceCombine("Step " + n2(stepCount), renderStepSince()),
      //"Debt: " + marginCoin + " " + formatMarginProfit(debt()) + " (max seen: " + formatMarginProfit(maxDebt) + ")",
      "Investment used: " + marginCoin + " " + formatMarginPrice(maxInvestment()),
      strategyJuicer == null ?: "Strategy juicer: " + strategyJuicer,
      "Drift: " + cryptoCoin + " " + plusMinusFix(formatCryptoAmount(drift())),
    );
  }
  
  S renderStepSince() {
    if (stepSince == 0) ret "";
    ret "since " + (active()
      ? formatHoursMinutesColonSeconds(currentTime()-stepSince)
      : formatLocalDateWithSeconds(stepSince));
  }
  
  LS fullStatus() {
    ret listCombine(
      status(),
      "",
      n2(openPositions, "open position") + ":",
      reversed(openPositions),
      "",
      n2(closedPositions, "closed position") + ":",
      showClosedPositionsBackwards ? reversed(closedPositions) : closedPositions,
    );
  }
  
  void feed(PricePoint pricePoint) {
    if (!active()) ret;
    setTime(pricePoint.timestamp);
    price(pricePoint.price);
  }
  
  void feed(TickerSequence ts) {
    if (!active()) ret;
    if (ts == null) ret;
    for (pricePoint : ts.pricePoints())
      feed(pricePoint);
  }
  
  public int compareTo(G22TradingStrategy s) {
    ret s == null ? 1 : cmp(coinProfit(), s.coinProfit());
  }
  
  void closeAllPositions {
    closePositions(cloneList(openPositions()), "User close");
  }
  
  void closeMyself() {
    closedItself(currentTime());
    closeAllPositionsAndDeactivate();
  }
  
  void closeAllPositionsAndDeactivate {
    deactivate();
    closeAllPositions();
  }
  
  void deactivate {
    if (!active) ret;
    active(false);
    deactivated(currentTime());
    log("Strategy deactivated.");
  }
  
  void reset {
    resetFields(this, fieldsToReset());
    change();
  }
  
  selfType emptyClone() {
    var clone = shallowCloneToUnlistedConcept(this);
    clone.reset();
    ret clone;
  }
  
  L<Position> allPositions() {
    ret concatLists(openPositions, closedPositions);
  }
  
  L<Position> sortedPositions() {
    var allPositions = allPositions();
    ret sortedByCalculatedField(allPositions, -> .openingTime());
  }
  
  bool positionsAreNonOverlapping() {
    for (a, b : unpair overlappingPairs(sortedPositions()))
      if (b.openingTime() < a.closingTime())
        false;
    true;
  }
  
  // Profit when applying all positions (somewhat theoretical because you
  // might go below platform limits)
  // Also only works when positions are linear
  double multiplicativeProfit() {
    if (!positionsAreNonOverlapping()) ret Double.NaN;
    double profit = 1;
    for (p : sortedPositions())
      profit *= 1+p.profit()/100;
    ret (profit-1)*100;
  }
  
  bool haveBackData() { ret backDataHoursWanted == 0 | backDataFed; }
  
  bool didRealTrades() {
    ret any(allPositions(), p -> p.openedOnMarket() || p.closedOnMarket());
  }
  
  S formatCellSize(double cellSize) {
    ret formatPercentage(cellSize, 3);
  }
  
  S areaDesc() {
    if (eq(area, "Candidates")) ret "Candidate";
    ret nempty(area) ? area : archived ? "Archived" : "";
  }
  
  selfType setTime(long time) {
    int age = ifloor(ageInHours());
    long lastMod = mod(currentTime()-startTime, hoursToMS(1));
    currentTime = -> time;
    if (ifloor(ageInHours()) > age)
      log("Hourly profit log: " + formatMarginProfit(coinProfit()));
    this;
  }
  
  double ageInHours() { ret startTime == 0 ? 0 : msToHours(currentTime()-startTime); }
  
  // only use near start of strategy, untested otherwise
  selfType changeCellSize(double newCellSize) {
    double oldCellSize = cellSize();
    if (oldCellSize == newCellSize) this;
    cellSize(newCellSize);
    if (digitizer != null)
      digitizer.swapPriceCells(makePriceCells(priceCells().basePrice()));
    log("Changed cell size from " + oldCellSize + " to " + newCellSize);
    this;
  }
  
  bool hasClosedItself() { ret closedItself != 0; }
  
  public double juiceValue() { ret coinProfit(); }
  
  double drift() {
    double drift = 0;
    for (p : cloneList(openPositions()))
      drift += p.cryptoAmount()*p.direction();
    ret drift;
  }
  
  S formatCryptoAmount(double amount) {
    ret formatDouble3(amount);
  }
}

download  show line numbers  debug dex  old transpilations   

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

No comments. add comment

Snippet ID: #1036209
Snippet name: G22TradingStrategy
Eternal ID of this version: #1036209/159
Text MD5: 5cbce31519034e70a14715f5902bf735
Author: stefan
Category: javax / trading
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-12-08 03:14:17
Source code size: 22212 bytes / 709 lines
Pitched / IR pitched: No / No
Views / Downloads: 332 / 931
Version history: 158 change(s)
Referenced in: [show references]