set flag Reparse. replace real with double. replace Real with Double. replace price with float. sclass MPM3 { // MARKET (trading platform) // How much percentual slippage we have for each position on // average plus the fees for the position. // Obviously dependent on the market (trading site) we are using. settable double marketAdversity = 0.1; // SELF-IMPOSED LIMITS // How long a position can be held at the longest long maxPositionDuration = minutesToMS(60); // POSITIONS // An open(ed) position record Position(TickerSequence ticker, real openingTime, real direction) { real openingPrice() { ret ticker.priceAtTimestamp(openingTime); } real profitAtTime(real time) { ret (ticker.priceAtTimestamp(time)/openingPrice()*100-100)*direction - marketAdversity; } } record LivePosition(Position p) { settable Juicer juicer; // highest profit this position has reached settable real crest = -infinity(); settable real time; settable real profit; simplyCached long maxClosingTime() { ret min(p.openingTime+maxPositionDuration, p.ticker.endTime()); } bool shouldClose() { ret time >= maxClosingTime || profit < (profit < 0 ? -juicer.lossTolerance : crest-juicer.pullback); } } record ClosedPosition(Position p, real closingTime) { real profit = Double.NaN; MPM3 mpm() { ret MPM3.this; } TickerSequence ticker() { ret p.ticker; } real direction() { ret p.direction; } real openingTime() { ret p.openingTime; } real closingTime() { ret closingTime; } real openingPrice() { ret p.openingPrice(); } real closingPrice() { ret ticker().priceAtTimestamp(closingTime); } real duration() { ret closingTime()-openingTime(); } real profit() { if (isNaN(profit)) profit = p.profitAtTime(closingTime); ret profit; } } // BOT (Eye + Juicer) srecord Juicer(real lossTolerance, real pullback) {} abstract sclass Eye { abstract real adviseDirection(TickerPoint tickerPoint); } // time = ms to look back srecord SimpleEye(long lookbackTime, real minMove) extends Eye { real adviseDirection(TickerPoint tickerPoint) { real move = calculateMove(tickerPoint); if (!isNaN(move) && abs(move) >= minMove) ret (real) sign(move); ret 0; } real calculateMove(TickerPoint tickerPoint) { if (!tickerPoint.canLookback(lookbackTime)) ret Double.NaN; real currentPrice = tickerPoint.currentPrice(); real before = tickerPoint.lookback(lookbackTime); ret currentPrice/before*100-100; } } srecord TradingBot(Eye eye, Juicer juicer) {} ClosedPosition runJuicer(Position p, Juicer j) { TickerSequence ticker = p.ticker; long time = lround(p.openingTime); long maxClosingTime = min(time+maxPositionDuration, ticker.endTime()); real crest = -infinity(); while (time < maxClosingTime) { time = ticker.nextTimestamp(time); real profit = p.profitAtTime(time); crest = max(crest, profit); if (profit < (profit < 0 ? -j.lossTolerance : crest-j.pullback)) break; } ret new ClosedPosition(p, time); } record noeq Backtest(TickerSequence ticker, TradingBot bot) extends Convergent { new L closedPositions; long time; long latestAllowedOpeningTime() { ret ticker.endTime()-maxPositionDuration; } void step { if (time == 0) time = ticker.startTime(); if (time > latestAllowedOpeningTime()) ret with done = true; TickerPoint tickerPoint = new(ticker, time); real direction = bot.eye.adviseDirection(tickerPoint); if (direction == 0) // No position to open, move to next timestamp time = ticker.nextTimestamp(time); else { // We have a position to open var position = new Position(ticker, time, direction); // Ask juicer when to close position var closedPosition = runJuicer(position, bot.juicer); // Add to record of positions made closedPositions.add(closedPosition); // Move on to next timestamp time = ticker.nextTimestamp(closedPosition.closingTime); } } } }