concept Corridor extends G22TradingStrategy { // how many cells the corridor has settableWithVar int capacity = 3; settableWithVar int maxPositionsPerDirection = 100; settableWithVar bool instantRestart = true; // initial position behavior settableWithVar bool openTwoPositionsAtStart; settableWithVar bool openRandomPositionAtStart = true; settableWithVar int openFixedDirectionAtStart; // 1 or -1 or 0 // later position opening behavior settableWithVar bool alwaysOpenTwoPositions; // Pre-run to check if coin is in corridor mode (dev.) //settableWithVar bool usePreRun; //settableWithVar AbstractJuicer preRunJuicer; settableWithVar bool endStrategyWhenCapacityExceeded = true; settableWithVar bool useStrategyJuicer; !include #1036390 // MultiPullbackJuicer maker include settableWithVar SignalWithStrength closeSignal; // internal persistable class Position extends G22TradingStrategy.Position { } // direction: -1 or 1 for normal short or long // To change size of of position, use -2, 2 etc Position openPosition(int direction) { if (l(positionsInDirection(direction)) >= maxPositionsPerDirection) { log("Not opening new position in direction " + direction + ", max reached"); null; } new Position p; p.marginToUse = marginPerPosition*abs(direction); ret openPosition(p, direction); } void start { if (started()) fail("Already started"); if (currentPrice() == 0) ret with primed(true); // Save starting price, create price cells & digitizer startingPrice = currentPrice(); startTime = currentTime(); var priceCells = makePriceCells(startingPrice); print("Made price cells: " + priceCells); digitizer = new PriceDigitizer2(priceCells); digitizer.verbose(verbose); //digitizer.init(startingPrice); makeStrategyJuicer(); log("Starting CORRIDOR at " + startingPrice + " +/- " + cellSize); startAction(); } swappable void startAction { if (openTwoPositionsAtStart) { nextStep(); openPosition(1); openPosition(-1); afterStep(); } else if (openRandomPositionAtStart) { nextStep(); log("Opening random initial position."); openPosition(securelyTossCoin() ? 1 : - 1); afterStep(); } else if (openFixedDirectionAtStart != 0) { nextStep(); log("Opening fixed initial position."); openPosition(sign(openFixedDirectionAtStart)); afterStep(); } } void price(double price) { if (currentPrice == price) ret; currentPrice = price; if (hasClosedItself()) ret; if (!started()) { if (primed) { primed(false); start(); } else ret; } digitizer.digitize(price); direction = sign(digitizedPrice()-lastDigitizedPrice()); if (applyStrategyJuicer()) ret; // No cell move? if (direction == 0) ret with afterStep(); nextStep(); //printWithPrecedingNL("New digitized price: " + digitizedPrice()); if (started()) step(); print(this); } // true if exit bool applyStrategyJuicer() { // Check strategy juicer closeSignal(strongestSignal(strategyJuicer?.calculateCloseSignals())); if (closeSignal != null && closeSignal.isTrigger()) { log("Juicer close signal! " + closeSignal); closeOrRestart(); afterStep(); true; } false; } 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 " + formatPriceX(currentPrice) + " (" + p1 + " -> " + p2 + "): " + p); toClose.add(p); } // Dismantle/close corridor if too large double limit = fromCellNumber(toCellNumber(digitizedPrice())-capacity*direction); var toDismantle = direction > 0 ? shortPositionsAtOrBelowDigitizedPrice(limit) : longPositionsAtOrAboveDigitizedPrice(limit); if (logToPrint) printVars(dismantleLimit := formatPriceX(limit), +direction, +capacity, +digitizedPrice(), +toDismantle); if (nempty(toDismantle)) { if (endStrategyWhenCapacityExceeded) { log("Capacity exceeded!"); closeOrRestart(); afterStep(); ret; } else { 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); // Apply take profits etc BEFORE possibly opening a new position afterStep(); openNewPositionAfterMove(); // Apply take profits, stop loss etc afterStep(); } void closeOrRestart { if (instantRestart) restart(); else closeMyself(); } void restart { closeMyself(); active(true); closedItself(0); startAction(); } swappable void openNewPositionAfterMove { // Open backward position here (if not there yet) openPositionIfNotThere(-direction); // Open forward position if configured as such if (alwaysOpenTwoPositions) openPositionIfNotThere(direction); } Position openPositionIfNotThere(int direction) { if (!hasPosition(digitizedPrice(), direction)) ret openPosition(direction); else null; } swappable S mainDesc() { try answer renderCustomName(); ret capacity + "x" + formatCellSize(cellSize) + " Corridor"; } S baseToString() { ret colonCombine( _conceptID() == 0 ? "" : "Strategy " + _conceptID(), spaceCombine( squareBracketUnlessEmpty(areaDesc()), mainDesc(), nempty(cryptoCoin) ? "on " + cryptoCoin : "" ), ); } 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) + ")"), "Profit unit: " + formatMarginPrice(profitUnitBeforeAdversity()) + "/" + marginCoin + " " + formatMarginPrice(profitUnitAfterAdversity()) + ", loss unit: " + marginCoin + " " + formatMarginPrice(lossUnit()), closeSignal == null ?: "Close signal: " + closeSignal, ); } // Corridor.toString toString { ret baseToString(); } S renderCustomName() { ret empty(customName) ? null : simpleQuoteOpt(trim(customName)); } 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); } Position openShort() { ret openPosition(-1); } Position openLong() { ret openPosition(1); } { cellSize(3.0); } double positionSize() { ret marginPerPosition*leverage; } double profitUnitBeforeAdversity() { ret cellSize/100*positionSize(); } double profitUnitAfterAdversity() { ret (cellSize-adversity)/100*positionSize(); } double lossUnit() { ret (cellSize+adversity)/100*positionSize(); } // Uses profit calculated in profit units // (could alternatively use investment units, but like this the // loss ratio should stay more consistent between coins [=leverage values]) class ProfitUnitsJuiceable is Juiceable { public double juiceValue() { ret coinProfit()/profitUnitBeforeAdversity(); } } // Update strategy juicer if it exists void updateStrategyJuicer { if (strategyJuicer != null) { makeStrategyJuicer(); applyStrategyJuicer(); } } // Can also update existing juicer with new params void makeStrategyJuicer { if (!useStrategyJuicer) ret with strategyJuicer(null); var j = makeJuicer(); j.copyTransientValuesFrom(strategyJuicer); j.juiceable(new ProfitUnitsJuiceable); strategyJuicer(j); } S fieldsToReset() { ret lines(ll(super.fieldsToReset(), [[closeSignal strategyJuicer]])); } double currentCellNumber() { ret toCellNumber(currentPrice()); } // Cell we're in int currentDigitizedCell() { ret iround(toCellNumber(digitizedPrice())); } int priceDirection() { ret sign(currentCellNumber()-currentDigitizedCell()); } // Cell we're currently moving to int targetCell() { ret currentDigitizedCell()+priceDirection(); } double targetPrice() { ret fromCellNumber(targetCell()); } // Coin profit when next target is reached double targetProfit() { ret coinProfitAtPrice(targetPrice()); } // Is profit currently going up or down int profitDirection() { ret sign(targetProfit()-coinProfit()); } // progress to next target in percent double progressInProfitDirection() { ret absDiff(currentCellNumber(), currentDigitizedCell())*100; } }