set flag Reparse. replace real with double. replace Real with Double. replace price with float. srecord noeq MPM2(price[] ticker, long[] timestamps) { // 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. srecord Market(real adversity) {} srecord Position(real openingTime, real direction) {} record ClosedPosition(Position p, real closingTime) { settable Ticker ticker; settable real profit; MPM2 mpm() { ret MPM2.this; } real openingPrice() { ret MPM2.this.ticker(p.openingTime); } real closingPrice() { ret MPM2.this.ticker(closingTime); } } srecord Juicer(real lossTolerance, real pullback) {} abstract sclass Eye { abstract real adviseDirection(Ticker ticker); } // only the first length items in data and timestamps are looked at srecord noeq Ticker(price[] data, long[] timestamps, int length, long currentTime) { real currentPrice() { ret data[length-1]; } Real lookback(real time) { int index = binarySearch_insertionPoint(wrapLongArrayAsList(timestamps), lceil(currentTime-time)); ret index >= 0 ? (real) data[index] : null; } } sclass LiveTicker extends Ticker { void add(price value, long timestamp) { data = addToArrayWithDoublingStrategy(data, length, value); timestamps = addToArrayWithDoublingStrategy(timestamps, length, timestamp); length++; } } Ticker ticker() { ret new Ticker(ticker, timestamps, l(timestamps), last(timestamps)); } srecord SimpleEye(real time, real minMove) extends Eye { real adviseDirection(Ticker ticker) { Real currentPrice = ticker.currentPrice(); if (currentPrice == null) ret 0; Real before = ticker.lookback(time); if (before == null) ret 0; real move = currentPrice/before*100-100; if (abs(move) >= minMove) ret (real) sign(move); ret 0; } } real ticker(real time) { ret ticker[iceil(time)]; } real openingPrice(Position p) { ret ticker(p.openingTime); } // all "profit" functions return a positive or negative percent value real profit(Position p, real time, Market m) { ret (ticker(time)/openingPrice(p)*100-100)*p.direction - m.adversity; } Real closingTime(Position p, Juicer j, Market m) { int time = iceil(p.openingTime); real openingPrice = ticker(time); real crest = -infinity(); while (time < ticker.length-1) { real profit = profit(p, time, m); crest = max(crest, profit); if (profit < (profit < 0 ? -j.lossTolerance : crest-j.pullback)) ret (real) time; ++time; } null; } Real profit(Position p, Juicer j, Market m) { Real closingTime = closingTime(p, j, m); ret closingTime == null ? null : profit(p, closingTime, m); } // How profitable is it to open a position of any direction // at a certain point in time? Real profitability(real time, Juicer j, Market m) { ret maxAllowingNull( profit(new Position(time, -1), j, m), profit(new Position(time, 1), j, m)); } // eye can be null, then we just test the juicer record noeq Backtest(Eye eye, Juicer j, Market m) extends Convergent { Iterator openingTimes = new WeightlessShuffledIterator(intRangeList(ticker.length)); new Average profitPerPosition; // TODO new Average averageHoldTime; new Average positionsOpenedPercentage; // percentage of positions closed before reaching the end of the ticker "tape" new Average positionsClosedPercentage; new L closedPositions; *(Juicer *j, Market *m) {} // internal, don't call from outside. Use Convergent/Iterator methods void step { if (!openingTimes.hasNext()) ret with done = true; int openingTime = openingTimes.next(); Real profit = null; ClosedPosition closedPosition = null; if (eye == null) // Test only juicer profit = profitability(openingTime, j, m); else { // Test eye + juicer real direction = eye.adviseDirection(new Ticker(ticker, timestamps, openingTime, timestamps[openingTime])); positionsOpenedPercentage.addSample(direction != 0 ? 100 : 0); if (direction != 0) { Position position = new Position(openingTime, direction); Real closingTime = closingTime(position, j, m); if (closingTime != null) { profit = profit(position, closingTime, m); closedPosition = new ClosedPosition(position, closingTime).ticker(ticker()).profit(profit); closedPositions.add(closedPosition); } } } positionsClosedPercentage.addSample(profit != null ? 100 : 0); if (profit != null) { profitPerPosition.addSample(profit); //averageHoldTime.addSample(TODO); } else // Profit for unclosed positions = 0 profitPerPosition.addSample(0); value = profitPerPosition!; } } }