Overview
The interval tracking system manages the lifecycle of 5-minute trading intervals:- IntervalTracker: State machine that detects epoch transitions, captures prediction snapshots, evaluates outcomes, and triggers callbacks
- HistoryStore: Persistence layer for interval records (JSON file on disk)
IntervalTracker
Interval lifecycle manager - tracks 5-minute interval transitions, closes finished intervals with results, and starts new ones. Source:src/tracker/interval.js:58
Constructor
Configuration options (all optional)
Called with the closed IntervalRecord when an interval ends. Can be async. Use this to persist records, feed outcomes to the prediction engine, etc.
Called with
(epochTimestamp: number, index: number) when a new interval begins. Use this to reset momentum, log transitions, etc.Methods
tick()
Called every second from the main loop. Detects epoch transitions to close the previous interval and open a new one. Between transitions, keeps the current interval’s prediction and strike price up to date.Tick parameters
Latest BTC price (null when WebSocket not connected yet)
Strike from Vatic (null when API failed or hasn’t responded yet)
Post-filter prediction:
{ probability: number, direction: 'UP'|'DOWN' } or null. Used for trading data (bet size, EV capture).Current 5-minute epoch boundary (seconds, divisible by 300)
Seconds left in interval
Polymarket price for this market (0-1)
EV calculation result:
{ ev, edge, margin, bestSide } from calculateEV()Bet sizing result:
{ bet, fullKelly, alpha, side, capped } from PositionSizerRisk level:
'green' | 'yellow' | 'red' | 'critical' from DrawdownTrackerModel output before trading filters (for recording even when filters abstain):
{ probability, direction, volatility }Reason for trading abstention (null if trading)
Full state from DrawdownTracker.getState():
{ bankroll, drawdownPct, coldStreak }Async - waits for onIntervalClose callback to complete when intervals transition
-
Epoch transition detection: When
epochTimestampchanges:- Close previous interval (if exists and
currentPriceis available):- Sets
finalPrice = currentPrice - Determines
result:'UP'ifcurrentPrice > strikePrice, else'DOWN' - Evaluates
predictionCorrectandearlyPredictionCorrect - Calculates
priceDeltaandpriceMovePct - Sets
closedAttimestamp (ISO string) - Adds to history
- Calls
onIntervalClose(record)(awaits completion)
- Sets
- Start new interval:
- Creates fresh IntervalRecord with
index = nextIndex++ - Initializes all fields to null
- Calls
onIntervalStart(epochTimestamp, index)
- Creates fresh IntervalRecord with
- Close previous interval (if exists and
-
Between transitions:
- Updates
livePredictionevery tick (for display) - Back-fills
strikePricewhen it arrives late - Early capture (once at ≤60s remaining):
- Stores
earlyPredictionsnapshot (usesrawPredictionto avoid filter blocking) - Records model factors:
volatility,momentum,reversion,calibrated - Records market data:
qMarket,evAtCapture,edge,margin,evSide - Records bet details:
betSize,fullKelly,alpha,betSide,betCapped(only whenpredictionexists) - Records risk state:
drawdownLevel,bankroll,drawdownPct,coldStreak - Records
abstentionReasonandtimeRemainingAtCapture
- Stores
- Final capture (once at ≤30s remaining):
- Stores
predictionsnapshot for final evaluation
- Stores
- Updates
Why two prediction snapshots?
earlyPrediction(60s): Captured early for recording/calibration (usesrawPredictionbefore filters)prediction(30s): Captured closer to expiry for outcome evaluation
getCurrentInterval()
Get the currently active interval record.The currently active IntervalRecord, or null if no tick has been processed yet. See IntervalRecord structure for complete field reference.
getHistory()
Get all closed intervals from this session.Array of all closed IntervalRecord objects accumulated this session. Empty array if no intervals have closed yet.
loadHistory()
Restores previously persisted records into the tracker. Use this on startup to load saved history from disk so that indices continue from where they left off.Array of IntervalRecord objects loaded from HistoryStore
No return value. Updates internal state.
- Replaces internal history array
- Sets next index to
max(record.index) + 1to continue sequential numbering
HistoryStore
Persistence layer for interval history records (JSON file on disk). Source:src/tracker/history.js:7
Constructor
Configuration options
Path to the JSON file where interval records are persisted. Defaults to
'data/history.json'. Parent directories are created automatically.Methods
load()
Reads the persisted history from disk.Array of IntervalRecord objects. Returns an empty array when the file does not exist yet.
save()
Overwrites the history file with the given records array.Full array of IntervalRecord objects to persist
Async - completes when file is written
- Creates parent directory tree if it doesn’t exist (recursive)
- Overwrites existing file
- Writes formatted JSON with 2-space indentation
append()
Convenience method: loads existing records, appends one, and saves.A single IntervalRecord to append
Async - completes when file is updated
Integration Example
Complete setup with engine feedback and persistence:Performance Considerations
File I/O
- Read latency: ~1-5ms for typical history files (< 1MB)
- Write latency: ~5-20ms depending on disk speed
- Append operation: Full read-modify-write (O(n) where n = record count)
Memory Usage
- ~1KB per IntervalRecord (60 fields, mostly nulls)
- 1 day = 288 intervals = ~300KB
- 1 month = ~9MB
- 1 year = ~105MB
Optimization Tips
- Use append() for real-time: It’s designed for onIntervalClose callbacks
- Batch saves for bulk operations: Use
save()directly when importing/exporting - Archive old data: Move records older than 30 days to separate files
- Consider SQLite: For >100K records, switch to a database for better performance
See Also
- Data Model - Complete IntervalRecord field reference
- PredictionEngine - How to feed outcomes back to the engine
- Configuration - Storage path settings