{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://quantitizer.com/delivery/quantitizer.delivery.v1.schema.json",
  "title": "quantitizer.delivery/v1",
  "description": "One message delivered to a member's webhook. Fields are only ever added in v1, never renamed or repurposed; a breaking change is v2, so ignore any field you do not know. Prices are decimal strings: parse them as decimals, not floats. Only a message with opens_trade true describes a new trade: call.published (a house Intel call) and ticket.confirmed (a trade you drafted). Call updates, alerts and tests never do. A message with cancels_order true (call.expired, call.cancelled) means: cancel any unfilled entry order you placed for call.id.",
  "type": "object",
  "required": ["schema", "id", "type", "opens_trade", "cancels_order", "occurred_at", "sent_at", "test", "symbol", "headline", "call", "event", "alert", "url"],
  "properties": {
    "schema": { "const": "quantitizer.delivery/v1" },
    "id": { "type": "string", "description": "Stable across retries. Delivery is at least once, so a message can arrive more than once; a receiver that has seen this id should drop the message." },
    "type": {
      "enum": ["call.published", "call.entry_filled", "call.target_hit", "call.stopped", "call.expired", "call.cancelled", "alert.triggered", "ticket.confirmed"],
      "description": "call.expired: the stop traded before the entry, so the resting limit is void. call.cancelled: the thesis the call came from ended (expired, replaced by a newer call, or withdrawn) before the entry traded, so the resting limit is void. Neither ever closes a filled position."
    },
    "opens_trade": { "type": "boolean", "description": "True only for a new trade: call.published or ticket.confirmed, and never a test. A bot should open a position on this and nothing else. Call updates repeat the call's levels but are not new trades; an alert has no side and no levels. A call.published sent after the call stopped taking new entries (a late send or a retry) carries false." },
    "cancels_order": { "type": "boolean", "description": "Added 2026-10-05. True for call.expired and call.cancelled: cancel any unfilled entry order for call.id. These reach every destination subscribed to new calls or to call updates, and are never held back by the daily message limit. Never true for a filled position, which ends only at its stop or a target." },
    "occurred_at": { "type": "string", "format": "date-time", "description": "When the event happened." },
    "sent_at": { "type": "string", "format": "date-time", "description": "When this attempt was sent. Covered by the signature." },
    "test": { "type": "boolean", "description": "True for the Settings test button. Never trade a test message." },
    "symbol": { "type": "string", "description": "Quantitizer's canonical key, e.g. ETH. Map it to your venue's instrument yourself." },
    "headline": { "type": "string" },
    "call": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": ["id", "origin", "symbol", "name", "side", "order_type", "entry", "stop", "targets", "horizon", "published_at", "valid_until"],
          "properties": {
            "id": { "type": "string" },
            "origin": { "enum": ["house", "member"], "description": "house: a published Intel call. member: a trade you drafted and confirmed." },
            "symbol": { "type": "string" },
            "name": { "type": ["string", "null"] },
            "side": { "enum": ["long", "short"] },
            "order_type": { "enum": ["market", "limit", null] },
            "entry": { "$ref": "#/$defs/decimal_or_null" },
            "stop": { "$ref": "#/$defs/decimal_or_null" },
            "targets": { "type": "array", "items": { "$ref": "#/$defs/decimal" } },
            "horizon": { "type": ["string", "null"] },
            "published_at": { "type": ["string", "null"], "format": "date-time" },
            "valid_until": { "type": ["string", "null"], "format": "date-time", "description": "After this the call is stale and should not be entered." }
          }
        }
      ]
    },
    "event": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": ["price", "level", "level_index"],
          "properties": {
            "price": { "$ref": "#/$defs/decimal_or_null", "description": "The price that triggered the update." },
            "level": { "$ref": "#/$defs/decimal_or_null", "description": "The call level it reached." },
            "level_index": { "type": ["integer", "null"], "description": "For call.target_hit: which target, from 0." }
          }
        }
      ]
    },
    "alert": {
      "oneOf": [
        { "type": "null" },
        {
          "type": "object",
          "required": ["condition", "level", "price"],
          "properties": {
            "condition": { "enum": ["price", "band_flips", "verdict_changes"] },
            "level": { "$ref": "#/$defs/decimal_or_null" },
            "price": { "$ref": "#/$defs/decimal_or_null" }
          }
        }
      ]
    },
    "url": { "type": "string", "format": "uri", "description": "Where this is in the app." }
  },
  "allOf": [
    { "if": { "properties": { "opens_trade": { "const": true } } },
      "then": { "properties": { "type": { "enum": ["call.published", "ticket.confirmed"] }, "test": { "const": false }, "call": { "type": "object" } } } },
    { "if": { "properties": { "type": { "const": "alert.triggered" } } },
      "then": { "properties": { "opens_trade": { "const": false }, "call": { "type": "null" } } } },
    { "if": { "properties": { "test": { "const": true } } },
      "then": { "properties": { "opens_trade": { "const": false } } } },
    { "if": { "properties": { "cancels_order": { "const": true } }, "required": ["cancels_order"] },
      "then": { "properties": { "type": { "enum": ["call.expired", "call.cancelled"] }, "opens_trade": { "const": false }, "call": { "type": "object" } } } }
  ],
  "$defs": {
    "decimal": { "type": "string", "pattern": "^-?\\d+(\\.\\d+)?$" },
    "decimal_or_null": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/decimal" }] }
  }
}
