abstract concept G22TradingStrategy extends ConceptWithChangeListeners { settable bool verbose; gettable S globalID = aGlobalID(); gettable transient Q q = startQ(); // link to market (optional) transient settable IFuturesMarket market; 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; // moved to archive (out of main list) settableWithVar bool archived; 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 = 0.2; // position options settableWithVar double leverage = 1; settableWithVar double marginPerPosition = 1; // in USDT swappable long currentTime() { ret now(); } // Note from price feeder settableWithVar S feedNote; void log(O o) { if (o != null) { log.add(print(str(o))); change(); } } 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() { double sum = 0; for (p : openPositions) sum += p.coinProfit(); ret sum; } L positionsInDirection(int direction) { ret filter(openPositions, p -> p.direction == direction); } L longPositions() { ret positionsInDirection(1); } L shortPositions() { ret positionsInDirection(-1); } L shortPositionsAtOrBelowPrice(double openingPrice) { ret filter(openPositions, p -> p.isShort() && p.openingPrice <= openingPrice); } L longPositionsAtOrAbovePrice(double openingPrice) { ret filter(openPositions, p -> p.isLong() && p.openingPrice >= openingPrice); } L 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 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 openingPrice; settable double direction; settable 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; G22TradingStrategy strategy() { ret G22TradingStrategy.this; } { if (!dynamicObjectIsLoading()) { openingStep = stepCount; openingTime = currentTime(); } } 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 openPositions; gettable new L closedPositions; 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 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); } void printPositions { print(colonCombine(n2(openPositions, "open position"), joinWithComma(openPositions))); } bool started() { ret !isNaN(startingPrice); } void prices(double... prices) { fOr (price : prices) price(price); } swappable PriceCells makePriceCells(double basePrice) { ret new GeometricPriceCells(basePrice, cellSize); } double digitizedPrice() { ret digitizer.digitizedPrice(); } double lastDigitizedPrice() { ret digitizer.lastDigitizedPrice(); } void handleNewPriceInQ(double price) { q.add(-> price(price)); } void nextStep { ++stepCount; stepSince(now()); } 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); } L shortPositionsAtOrBelowDigitizedPrice(double openingPrice) { ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() <= openingPrice); } L shortPositionsAtOrAboveDigitizedPrice(double openingPrice) { ret filter(openPositions, p -> p.isShort() && p.digitizedOpeningPrice() >= openingPrice); } L longPositionsAtOrAboveDigitizedPrice(double openingPrice) { ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() >= openingPrice); } L longPositionsAtOrBelowDigitizedPrice(double openingPrice) { ret filter(openPositions, p -> p.isLong() && p.digitizedOpeningPrice() <= openingPrice); } PriceCells cells() { ret digitizer?.cells; } S formatPriceX(double price) { if (isNaN(price)) ret "-"; double num = cells().priceToCellNumber(price); ret formatPrice(price) + " (C" + formatDouble2(num) + ")"; } abstract void price aka currentPrice(double price);

P openPosition(P p, int direction) { var price = digitizedPrice(); if (isNaN(price)) price = digitizer.digitizeIndividually(currentPrice()); p.openingPrice(currentPrice()); p.direction(direction); p.digitizedOpeningPrice(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; } LS status() { 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), "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) ); } void feed(TickerSequence ts) { if (ts == null) ret; for (pricePoint : ts.pricePoints()) { currentTime = -> pricePoint.timestamp; price(pricePoint.price); } } }