How to structure the participant-facing package that ML engineers use to join your competition.
The challenge package is the public-facing repository that ML engineers (Crunchers) use to
participate in your competition. When you scaffold a workspace with crunch-node init, the
challenge/ directory contains a complete working example.Your challenge package should include:
Model interface — The base class that all participant models must implement
Scoring function — Lets participants evaluate their models locally
Quickstarter examples — Working models that participants can copy and adapt
Backtest harness — Lets participants run their models against historical data
The end goal is to publish this as a PyPI package that any Cruncher can pip install to get
started.
The TrackerBase class in
tracker.py
defines the contract between your Crunch Node and every Cruncher submission:
Copy
class TrackerBase: """Base class for participant models. Subclass this and implement predict() to compete. tick() receives market data on every feed update — use it to maintain internal state (indicators, history, etc.). """ def tick(self, data: dict[str, Any]) -> None: """Receive latest market data. Override to maintain state.""" subject_key = data.get("symbol", "_default") if isinstance(data, dict) else "_default" self._latest_data_by_subject[subject_key] = data def predict(self, subject: str, resolve_horizon_seconds: int, step_seconds: int) -> dict[str, Any]: """Return a prediction for the given scope. Args: subject: Asset being predicted (e.g. "BTC"). resolve_horizon_seconds: How far ahead ground truth is resolved. step_seconds: Time step between predictions. Returns: Dict matching InferenceOutput fields. Default expects {"value": float} — positive means bullish, negative means bearish. """ raise NotImplementedError("Implement predict() in your model")
Key design decisions:
tick() receives market data on every feed update — models use it to maintain internal state
(e.g., price history, indicators)
predict() returns a prediction for a specific scope — the return format must match your
InferenceOutput type
The MODEL_BASE_CLASSNAME in your Crunch Node environment must match this class. For the
default scaffold, it’s tracker.TrackerBase.
The scaffold includes a scoring function in scoring.py that participants can use for local
testing:
Copy
def score_prediction(prediction, ground_truth): """Score a single prediction against ground truth. Return a dict matching your contract's ScoreResult shape. """ return {"value": 0.0, "success": True, "failed_reason": None}
Replace this with your actual evaluation logic. This same function is used by the score worker in
production (via the SCORING_FUNCTION environment variable).
The challenge package includes a backtest harness so participants can evaluate their models against
historical data before submitting:
Copy
from starter_challenge.backtest import BacktestRunnerfrom my_model import MyTrackerresult = BacktestRunner(model=MyTracker()).run( start="2026-01-01", end="2026-02-01")result.predictions_df # DataFrame of all predictionsresult.metrics # Rolling windows + multi-metric evaluationresult.summary() # Formatted output
The backtest harness:
Auto-fetches data from the Coordinator and caches locally on first run
Uses the same tick() → predict() loop as production
Applies the same scoring function and multi-metric evaluation as the live leaderboard