concept Corridor extends G22TradingStrategy { // how many cells the corridor has settableWithVar int capacity = 6; settableWithVar int maxPositionsPerDirection = 4; settableWithVar bool openTwoPositionsAtStart = true; settableWithVar bool alwaysOpenTwoPositions; // internal class Position extends G22TradingStrategy.Position { } Position openPosition(int direction) { if (l(positionsInDirection(direction)) >= maxPositionsPerDirection) { log("Not opening new position in direction " + direction + ", max reached"); null; } ret openPosition(new Position, direction); } 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 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); } swappable void step { double p1 = lastDigitizedPrice(); double p2 = digitizedPrice(); direction = sign(p2-p1); if (direction == 0) ret; // Find winning positions to close L toClose = new L; for (p : direction > 0 ? longPositionsAtOrBelowDigitizedPrice(p1) : shortPositionsAtOrAboveDigitizedPrice(p1)) { cast p to Position; 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 (p : toDismantle) { cast p to Position; 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(); } S baseToString() { ret colonCombine( _conceptID() == 0 ? "" : "Strategy " + _conceptID(), "Corridor"); } LS status() { var r = currentCorridorPriceRange(); ret listCombine( super.status(), "Max capacity: " + capacity, !started() ? null : "Current corridor: " + (r == null ? "-" : formatPrice(r.start) + " to " + formatPrice(r.end) + " (" + formatPercentage(currentCorridorSizeInPercent(), 3) + ")"), ); } // Corridor.toString toString { ret baseToString(); } double maxCorridorPercentSize() { ret capacity*cellSize; } 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); } 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; } Position openShort() { ret openPosition(-1); } Position openLong() { ret openPosition(1); } }