concept CleanMoveStrategy extends G22TradingStrategy { // How clean the move must be settable double minCleanness = 2; // How profitable the move must be settable double minProfit = 0.5; // Factor for turning pullback into callback settable double callbackFactor = 1.5; // Max pullback of move settable double maxPullback = 1; // How often to start a new move analysis (seconds) settable double newMoveInterval = 30; Set ongoingMoves = syncLinkedHashSet(); settable long lastMoveStarted; S fieldsToReset() { ret lines(ll(super.fieldsToReset(), [[ongoingMoves lastMoveStarted]])); } persistable class Position extends G22TradingStrategy.Position { settable CallbackJuicer juicer; } srecord ProfitBeforeLeverageJuiceable(Position p) is Juiceable { public double juiceValue() { ret p.profitBeforeLeverage(); } } void price(double price) { if (currentPrice == price) ret; currentPrice = price; afterwards { afterStep(); change(); } if (startTime == 0) startTime = currentTime(); // Close positions for (p : openPositions()) { if (p.juicer != null) { var signals = p.juicer.calculateCloseSignals(); var strongest = highestBy(signals, s -> s.strength()); if (strongest != null && strongest.isTrigger()) p.close(strongest); } } // Start new move calculator var time = currentTime(); if (time >= lastMoveStarted+fromSeconds(newMoveInterval)) { lastMoveStarted = time; for (isShort : ll(false, true)) ongoingMoves.add(new LivePullbackToProfitAnalysis().isShort(isShort)); } // Update moves for (move : cloneList(ongoingMoves)) { move.feed(new PricePoint(time, price)); if (move.done()) ongoingMoves.remove(move); } // Check if we should start a position if (empty(openPositions())) { bool shouldShort = false, shouldLong = false; PullbackToProfit openReason = null; for (move : ongoingMoves) if (move.cleanness() >= minCleanness && move.profit() >= minProfit) { openReason = move.ptp(); if (openReason.isShort) shouldShort = true; else shouldLong = true; } if (shouldShort && shouldLong) print("Conflicting signal (long+short at once)"); else if (shouldShort || shouldLong) { int direction = shouldShort ? -1 : 1; new Position p; p.marginToUse = marginPerPosition; var callbackRate = openReason.pullback*callbackFactor; p.juicer = new CallbackJuicer(callbackRate); p.juicer.juiceable(new ProfitBeforeLeverageJuiceable(p)); openPosition(p, direction, openReason); } } } L openPositions() { ret (L) super.openPositions(); } LS status() { ret listCombine( "Ongoing moves: " + n2(ongoingMoves), super.status() ); } }