set flag Reparse. sclass Corridor { record noeq Position(/*settable*/ double openingPrice, /*settable*/ double direction) { /*settable*/ double closingPrice = Double.NaN; 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(); } toString { ret renderFunctionCall( spaceCombine( closed() ? "Closed" : null, type() ), openingPrice, "profit=" + profit()); } } record noeq Threshold(/*settable*/ double price, /*settable*/ double direction, Runnable action) { void trigger { print("Triggered at " + price + " " + direction + ": " + action); callF(action); } } settable double epsilon = 0.001; settable double ladderStep = 1; gettable double currentPrice = 0; gettable double lastDirection; gettable double realizedProfit; new L loops; new TreeMultiMap thresholds; new LinkedHashSet openPositions; new L closedPositions; record noeq Loop(/*settable*/ double startingPrice, /*settable*/ double direction) { Position position; Loop successor; class Open is Runnable { run { if (position != null && !position.closed()) fail("Trying to re-open position: " + this); print("Opening position at " + currentPrice() + " by loop " + this); position = openPosition(direction); if (successor == null) successor = new Loop(startingPrice+ladderStep, direction).init(); } toString { ret spaceCombine("Open", startingPrice, direction); } } class Close is Runnable { run { if (position != null) { print("Closing position at " + currentPrice() + " by loop " + this); position.close(); } } toString { ret spaceCombine("Close", startingPrice, direction); } } selfType init() { loops.add(this); addThreshold(startingPrice, -direction, new Open); addThreshold(startingPrice+ladderStep, direction, new Close); this; } toString { ret formatRecordVars("Loop", +startingPrice, +direction, +position, succ := successor == null ?: "t" ); } } Position openPosition(double direction) { ret addAndReturn(openPositions, new Position(currentPrice(), direction)); } Threshold addThreshold(double price, double direction, Runnable action) { var t = new Threshold(price, direction, action); thresholds.put(t.price, t); bool triggered = (lastDirection == 0 || lastDirection == direction) && absDiff(currentPrice, price) < epsilon; printVars addThreshold(+currentPrice, +price, +direction, +lastDirection, +triggered); if (triggered) t.trigger(); ret t; } void start { double price = currentPrice(); print("Starting CORRIDOR at " + price + " +/- " + ladderStep); lastDirection = 0; new Loop(price, 1).init(); new Loop(price, -1).init(); } selfType currentPrice aka price(double price) { double oldPrice = currentPrice; if (oldPrice == price) this; currentPrice = price; int direction = sign(price-oldPrice); lastDirection = direction; NavigableMap> map = thresholds.innerMap(); map = direction > 0 ? map.subMap(oldPrice, false, price, true) : map.subMap(price, true, oldPrice, false); var keys = keys(map); LL thresholdsCrossed = valuesList(map); if (direction < 0) reverseInPlace(thresholdsCrossed); new L toExecute; for (actions : thresholdsCrossed) for (t : actions) if (t.direction == direction) toExecute.add(t); printWithPrecedingNL(commaCombine( "New price: " + currentPrice, "direction: " + direction, "Thresholds crossed: " + keys, n2(toExecute, "trigger"))); for (t : toExecute) t.trigger(); this; } double unrealizedProfit() { ret doubleSum(map(openPositions, ->.profit())); } double profit() { ret realizedProfit+unrealizedProfit(); } LS status() { double up = unrealizedProfit(); ret ll( "Profit: " + realizedProfit+up, "Realized profit: " + realizedProfit + " from " + n2(closedPositions, "closed position"), "Unrealized profit: " + up + " in " + n2(openPositions, "open position"), //n2(loops, "loop"), //n2(keysSize(thresholds), "threshold"), //n2(l(thresholds), "threshold action"), ); } toString { ret commaCombine(status()); } }