set flag Reparse. sclass Corridor { // how many percent the crypto price needs to move // for one profitable trade settable double cellSize = 0.2; // how many cells the corridor has settable int capacity = 6; settable PriceDigitizer digitizer; settable bool openTwoPositionsAtStart = true; settable bool verbose; record noeq Position(double openingPrice, double direction, double digitizedOpeningPrice) { double closingPrice = Double.NaN; 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)*direction; } double profit() { ret profitAtPrice(closed() ? closingPrice : currentPrice()); } void close { if (closed()) fail("Can't close again"); openPositions.remove(this); closingPrice = currentPrice(); closedPositions.add(this); realizedProfit += profit(); print(this); printPositions(); } toString { ret renderFunctionCall( spaceCombine( closed() ? "Closed" : null, type() ), openingPrice + " [" + digitizedOpeningPrice() + "]", "profit=" + profit()); } } gettable double currentPrice = 0; gettable double oldPrice = Double.NaN; gettable double startingPrice = Double.NaN; gettable int lastDirection; gettable double realizedProfit; gettable int stepCount; gettable new LinkedHashSet openPositions; gettable new L closedPositions; Position openPosition(double direction) { var p = new Position(currentPrice(), direction, digitizedPrice()); openPositions.add(p); print("Opening " + p); printPositions(); ret p; } bool hasPosition(double price, double direction) { ret findPosition(price, direction) != null; } Position closePosition(double price, double direction) { var p = findPosition(price, direction); p?.close(); ret p; } void closePositions(Cl positions) { forEach(positions, -> .close()); } Position findPosition(double digitizedPrice, double direction) { ret firstThat(openPositions(), p -> p.digitizedOpeningPrice() == digitizedPrice && 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"); // Save starting price, create price cells & digitizer startingPrice = currentPrice(); var priceCells = makePriceCells(startingPrice); digitizer = new PriceDigitizer(priceCells); digitizer.verbose(verbose); print("Starting CORRIDOR at " + startingPrice + " +/- " + cellSize); if (openTwoPositionsAtStart) { openPosition(1); openPosition(-1); } } 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 currentPrice aka price(double price) { currentPrice = price; if (digitizer == null) ret; digitizer.digitize(price); if (isNaN(digitizedPrice())) // Haven't crossed a cell limit yet ret; int direction = sign(digitizedPrice()-lastDigitizedPrice()); if (direction == 0) ret; // No cell move ++stepCount; printWithPrecedingNL("New digitized price: " + digitizedPrice()); if (started()) step(); print(this); } swappable void step { double p1 = lastDigitizedPrice(); double p2 = digitizedPrice(); int direction = sign(p2-p1); if (direction == 0) ret; // Close forward position "behind us" Position closed = closePosition(p1, direction); // Open backward position here (if not there yet) Position opened = null; if (!hasPosition(p2, -direction)) opened = openPosition(-direction); printVars("step", +p1, +p2, +direction, +closed, +opened); // Dismantle corridor if too large Cl toClose; if (direction > 0) toClose = shortPositionsAtOrBelowPrice(digitizer.cells.nCellLimitsDown(digitizedPrice(), capacity)); else toClose = longPositionsAtOrAbovePrice(digitizer.cells.nCellLimitsUp(digitizedPrice(), capacity)); if (nempty(toClose)) { for (p : toClose) print("Dismantling corridor of capacity " + capacity + ": " + p); closePositions(toClose); } } double unrealizedProfit() { ret doubleSum(map(openPositions, ->.profit())); } 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 doubleSum(map(negativePositions(), ->.profit())); } double profit() { ret realizedProfit+unrealizedProfit(); } LS status() { ret ll( "Step " + n2(stepCount), "Profit: " + profit(), "Realized profit: " + realizedProfit + " from " + n2(closedPositions, "closed position"), "Unrealized profit: " + unrealizedProfit() + " in " + n2(openPositions, "open position"), "Debt: " + debt(), ); } toString { ret commaCombine(status()); } double maxCorridorPercentSize() { ret capacity*cellSize; } }