changedOutput schema / (root)
Previous value: -nullNew value: +{
+ "properties": {
+ "checked_at": {
+ "description": "When we produced this answer (ISO 8601, UTC). The only ISO timestamp in the payload; every other timestamp is unix seconds.",
+ "format": "date-time",
+ "type": "string"
+ },
+ "count": {
+ "description": "Number of items in opportunities[].",
+ "type": "integer"
+ },
+ "disclaimer": {
+ "description": "Our product boundary, verbatim in every paid advice response: market intelligence only, no execution, no custody, no position sizing, and opportunity_score is a heuristic rather than a calibrated win probability.",
+ "type": "string"
+ },
+ "next_check_after": {
+ "description": "Only present when opportunities[] is empty — unix seconds before which scanning again is pointless (now + 900).",
+ "type": "integer"
+ },
+ "opportunities": {
+ "description": "Compact items sorted by opportunity_score descending; an empty array is a valid paid answer meaning nothing is published right now.",
+ "items": {
+ "description": "Compact opportunity — enough to rank and pick, not enough to act on; call get_crypto_signal for the full advice.",
+ "properties": {
+ "classification": {
+ "description": "Spot-long classification derived from opportunity_score (shorts are never advised). Bands: 80-100 STRONG_LONG, 65-79 LONG, 50-64 CONDITIONAL_LONG, 35-49 SPECULATIVE_LONG (concrete entry setup) or WATCH (no concrete setup, so entry_zone/stop_loss/take_profit are null), below 35 AVOID (actively negative signals) or NEUTRAL. EXIT is a fresh strong exit condition. Candidates with internal execution blockers are capped upstream at CONDITIONAL_LONG.",
+ "enum": [
+ "STRONG_LONG",
+ "LONG",
+ "CONDITIONAL_LONG",
+ "SPECULATIVE_LONG",
+ "WATCH",
+ "NEUTRAL",
+ "AVOID",
+ "EXIT"
+ ],
+ "type": "string"
+ },
+ "coin": {
+ "description": "Product id of the market, e.g. 'BTC-EUR'.",
+ "type": "string"
+ },
+ "entry_valid_until": {
+ "description": "Unix seconds — end of the 30-minute new-entry window; after it this is no longer a current entry advice.",
+ "type": "integer"
+ },
+ "freshness_seconds": {
+ "description": "Age in seconds at scan time (now minus published_at); lower is fresher.",
+ "type": "integer"
+ },
+ "opportunity_score": {
+ "description": "0-100 heuristic ranking of setup quality (trend alignment, EMA separation, momentum, volume, breakout/pullback strength, efficiency ratio, regime direction, ML horizon scores, estimated net edge, data/spread quality). NOT a calibrated win probability; the measured win rate lives in get_crypto_evidence.",
+ "maximum": 100,
+ "minimum": 0,
+ "type": "integer"
+ },
+ "opportunity_type": {
+ "description": "Setup shape: PULLBACK enters on a retracement inside an existing trend, BREAKOUT enters on a move through resistance.",
+ "enum": [
+ "PULLBACK",
+ "BREAKOUT"
+ ],
+ "type": "string"
+ },
+ "publication_status": {
+ "description": "Publication state of the advice. PUBLISHED = live and current, STALE = retrievable history whose entry window closed, INVALIDATED = an invalidation condition triggered. Expired ids stay retrievable with their real status.",
+ "enum": [
+ "PUBLISHED",
+ "STALE",
+ "INVALIDATED"
+ ],
+ "type": "string"
+ },
+ "published_at": {
+ "description": "Unix seconds — when this version was published.",
+ "type": "integer"
+ },
+ "score_type": {
+ "description": "Which scheme produced opportunity_score. 'heuristic_v1' is a deterministic, unit-tested heuristic and explicitly signals that the score is not a calibrated probability. Scores are only comparable within one score_type: a 70 of heuristic_v1 says nothing about a 70 of a future heuristic_v2, so any ranking, threshold or history you build resets when this value changes.",
+ "enum": [
+ "heuristic_v1"
+ ],
+ "type": "string"
+ },
+ "signal_id": {
+ "description": "Stable id of the opportunity across versions; the input for every other call in the chain.",
+ "pattern": "^sig_[0-9a-f]{12}$",
+ "type": "string"
+ },
+ "version": {
+ "description": "Version of this advice; starts at 1 and increments on every material change.",
+ "type": "integer"
+ }
+ },
+ "type": "object"
+ },
+ "type": "array"
+ },
+ "query": {
+ "description": "The validated input echoed back; this tool takes no input, so it is always an empty object.",
+ "type": "object"
+ },
+ "recommended_next_call": {
+ "description": "Machine-readable next step in the chain (scan → signal → validate → evidence → check), so you can follow the workflow without documentation. null means there is explicitly nothing useful to call now; next_check_after then says when to come back.",
+ "properties": {
+ "arguments": {
+ "description": "Arguments to pass verbatim, e.g. {\"signal_id\":\"sig_a1b2c3d4e5f6\"}, plus known_version for check_crypto_signal.",
+ "type": "object"
+ },
+ "reason": {
+ "description": "Why this is the useful next step, in plain language.",
+ "type": "string"
+ },
+ "tool": {
+ "description": "Name of the MCP tool to call next.",
+ "enum": [
+ "get_crypto_signal",
+ "validate_crypto_signal",
+ "get_crypto_evidence",
+ "check_crypto_signal"
+ ],
+ "type": "string"
+ }
+ },
+ "type": [
+ "object",
+ "null"
+ ]
+ },
+ "source_as_of": {
+ "description": "Unix seconds — freshness of the upstream advice system's answer.",
+ "type": "integer"
+ },
+ "source_disclaimer": {
+ "description": "Verbatim disclaimer of the upstream advice system, passed through unedited.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "query",
+ "checked_at",
+ "source_as_of",
+ "count",
+ "opportunities",
+ "recommended_next_call",
+ "disclaimer"
+ ],
+ "type": "object"
+}