{
  "schema_version": "1.0.0",
  "bot_id": "3.16",
  "bot_name": "CalendarCompression",
  "slug": "calendarcompression",
  "layer": "Strategy",
  "layer_key": "strat",
  "bot_class": "Alpha Strategy",
  "authority": [
    "Trade"
  ],
  "status": "planned",
  "readiness": "Spec started",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Strategy",
    "bot_class": "Alpha Strategy",
    "authority": "Trade",
    "runs_before": "Risk guardrail pipeline",
    "runs_after": "Resolution tracker / Market scanner",
    "applies_to": "Pairs of Polymarket markets that share the same underlying event and resolve on different calendar dates, where the time-decay between them creates a tradeable price gap",
    "default_mode": "shadow_only",
    "user_visible": "Advanced details only",
    "developer_owner": "Polytraders core \u2014 Strategy pod"
  },
  "purpose": "CalendarCompression identifies pairs of related Polymarket markets resolving on different dates (e.g. 'Will X happen by June?' vs 'Will X happen by December?') and trades the price differential when it deviates from the fair time-decay spread. The bot buys the underpriced near-expiry leg and/or sells the overpriced far-expiry leg to capture calendar compression.",
  "why_it_matters": [
    {
      "failure": "Resolution sources differ between pair legs",
      "consequence": "Even if markets look related, different oracle sources produce independent resolution paths; the calendar spread is not a true arbitrage."
    },
    {
      "failure": "Near-expiry market resolves unexpectedly early",
      "consequence": "A market may resolve before the expected calendar date (breaking news), collapsing the calendar spread before the far-expiry leg adjusts."
    },
    {
      "failure": "Both legs resolve YES simultaneously",
      "consequence": "If both legs are correlated, a calendar long/short pair may both move adversely on news, doubling losses instead of creating a hedged position."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "CLOB mid and depth for both legs",
      "source": "ws_market",
      "required": true,
      "use": "Measure calendar spread and available depth per leg."
    },
    {
      "input": "Market metadata (resolution date, source)",
      "source": "clob_public",
      "required": true,
      "use": "Identify calendar pairs and verify shared resolution source."
    },
    {
      "input": "Market status (open, resolved)",
      "source": "clob_public",
      "required": true,
      "use": "Skip resolved legs."
    }
  ],
  "internal_inputs": [
    {
      "input": "KillSwitch active flag",
      "source": "KillSwitch",
      "required": true,
      "use": "Abort all intent emission if KillSwitch active."
    },
    {
      "input": "Calendar pair catalog (approved near/far pairs)",
      "source": "internal config",
      "required": true,
      "use": "Restrict trading to pre-approved related-market pairs."
    },
    {
      "input": "Builder code bytes32",
      "source": "internal config",
      "required": true,
      "use": "Injected into builder field on every signed V2 OrderIntent."
    }
  ],
  "raw_params": [
    "min_gap_bps \u00b7 int",
    "max_days_to_resolve \u00b7 int",
    "require_same_source \u00b7 bool",
    "max_position_per_pair \u00b7 int"
  ],
  "parameters": [
    {
      "name": "min_gap_bps",
      "default": 150,
      "warning": 75,
      "hard": 25,
      "controls": "Minimum calendar spread gap in bps (beyond fair time-decay) required to emit an OrderIntent.",
      "why_default_matters": "150 bps provides margin after fees (~50 bps per leg) and residual timing uncertainty.",
      "threshold_logic": [
        {
          "condition": ">= 150 bps",
          "action": "EMIT calendar spread OrderIntent"
        },
        {
          "condition": "75\u2013150 bps",
          "action": "WARN CAL_GAP_MARGINAL; halve size"
        },
        {
          "condition": "< 25 bps",
          "action": "SKIP \u2014 CAL_NO_GAP"
        }
      ],
      "dev_check": "if gap_bps < params.hard: return skip('CAL_NO_GAP')",
      "user_facing": "The calendar spread was too small after fees to justify a trade."
    },
    {
      "name": "max_days_to_resolve",
      "default": 90,
      "warning": 180,
      "hard": 365,
      "controls": "Maximum days until the far-expiry leg resolves. Avoids committing capital in very long-dated illiquid pairs.",
      "why_default_matters": "90 days keeps capital engaged in reasonably liquid markets.",
      "threshold_logic": [
        {
          "condition": "<= 90 days",
          "action": "Normal pair trading"
        },
        {
          "condition": "180\u2013365 days",
          "action": "WARN CAL_LONG_DATED; halve size"
        },
        {
          "condition": "> 365 days",
          "action": "HARD_REJECT \u2014 CAL_PAIR_TOO_LONG_DATED"
        }
      ],
      "dev_check": "if days_to_far_expiry > params.hard: return skip('CAL_PAIR_TOO_LONG_DATED')",
      "user_facing": "The far-dated market leg is too far out to trade."
    },
    {
      "name": "require_same_source",
      "default": true,
      "warning": null,
      "hard": null,
      "controls": "Both legs of the calendar pair must share the same resolution source. Locked true.",
      "why_default_matters": "Without source parity the spread is not a hedged calendar trade.",
      "threshold_logic": [
        {
          "condition": "sources differ",
          "action": "HARD_REJECT CAL_SOURCE_MISMATCH"
        }
      ],
      "dev_check": "if legs[0].source != legs[1].source: return skip('CAL_SOURCE_MISMATCH')",
      "user_facing": "The two market legs resolve from different sources \u2014 calendar trade blocked."
    },
    {
      "name": "max_position_per_pair",
      "default": 400,
      "warning": 600,
      "hard": 800,
      "controls": "Maximum pUSD position per calendar pair (combined across both legs).",
      "why_default_matters": "400 pUSD limits single-pair exposure.",
      "threshold_logic": [
        {
          "condition": "<= 400 pUSD",
          "action": "Normal pair sizing"
        },
        {
          "condition": "> 800 pUSD",
          "action": "Reject config \u2014 PARAMETER_CHANGE_REQUIRES_APPROVAL"
        }
      ],
      "dev_check": "if params.max_position_per_pair > params.hard: raise ConfigError('PARAMETER_CHANGE_REQUIRES_APPROVAL')",
      "user_facing": "Position size was capped at the configured per-pair maximum."
    }
  ],
  "default_config": {
    "bot_id": "strat.calendarcompression",
    "version": "0.1.0",
    "mode": "shadow_only",
    "defaults": {
      "min_gap_bps": 150,
      "max_days_to_resolve": 90,
      "require_same_source": true,
      "max_position_per_pair": 400
    },
    "locked": {
      "require_same_source": {
        "value": true
      },
      "min_gap_bps": {
        "min": 25
      },
      "max_position_per_pair": {
        "max": 800
      }
    }
  },
  "implementation_flow": [
    "Check KillSwitch; if active, emit no OrderIntents.",
    "For each approved calendar pair: verify both legs share the same resolution source.",
    "FETCH clob_public metadata for both legs; check resolution dates and open status.",
    "Compute days_to_far_expiry; if > 365, skip CAL_PAIR_TOO_LONG_DATED.",
    "FETCH ws_market books for both legs; compute near_mid and far_mid.",
    "Compute fair_spread from time-decay model; gap_bps = (actual_spread - fair_spread) * 10000.",
    "IF gap_bps < hard (25 bps): skip CAL_NO_GAP.",
    "IF gap_bps < warning (75 bps): WARN CAL_GAP_MARGINAL; halve size.",
    "Compute per-leg size = min(max_position_per_pair/2, available_depth_per_leg).",
    "EMIT IOC OrderIntent on underpriced leg; EMIT DecisionReport reason=CAL_SPREAD_TRADE."
  ],
  "decision_logic": {
    "approve": "gap_bps >= min_gap_bps, sources match, within max_days_to_resolve, KillSwitch inactive.",
    "reshape_required": "Not applicable \u2014 reshaping handled by downstream Risk guardrail.",
    "reject": "gap_bps < 25 bps; source mismatch; far leg > 365 days; KillSwitch active.",
    "warning_only": "gap_bps 25\u201375 bps or far leg 180\u2013365 days triggers warning and size reduction."
  },
  "decision_output_schema": "OrderIntent",
  "decision_output_example": {
    "intent_id": "oi_01HCC0000001A",
    "market_id": "0xcc000000000000000000000000000000000000000000000000000000000000001",
    "outcome": "YES",
    "side": "buy",
    "price": "0.420",
    "size_pUSD": "200.00",
    "tif": "IOC",
    "post_only": false,
    "builder": {
      "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
      "fee_bps": 25
    },
    "negrisk_aware": false,
    "decision": {
      "gap_bps": 180.0,
      "pair_id": "cal_pair_001",
      "leg": "near",
      "reasons": [
        "CAL_SPREAD_TRADE"
      ]
    },
    "comment": "fees are operator-set at match time in V2 \u2014 feeRateBps is NOT on the signed order"
  },
  "developer_log": {
    "bot_id": "strat.calendarcompression",
    "pair_id": "cal_pair_001",
    "gap_bps": 180.0,
    "leg": "near",
    "intent_emitted": true,
    "reason": "CAL_SPREAD_TRADE",
    "emitted_at_ms": 1746790800000
  },
  "user_explanations": [
    {
      "situation": "Calendar spread trade placed",
      "message": "Two related markets with different expiry dates were priced inconsistently. A trade was placed on the underpriced leg."
    },
    {
      "situation": "Source mismatch \u2014 no trade",
      "message": "The two calendar legs resolve from different official sources; the trade was blocked."
    },
    {
      "situation": "Gap too small after fees",
      "message": "The calendar spread was too thin after fees to justify a trade."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Early resolution of the near leg: news causes the near-expiry market to resolve before the far leg adjusts, leaving an unhedged far-leg position.",
    "false_positive_risk": "Time-decay model underestimates the fair spread, treating a correctly priced pair as mispriced.",
    "false_negative_risk": "min_gap_bps too high misses genuine calendar compression opportunities.",
    "safe_fallback": "If ws_market stale for either leg, skip without emitting any OrderIntent.",
    "required_dependencies": [
      "ws_market",
      "clob_public",
      "internal calendar pair catalog",
      "KillSwitch",
      "internal builder code"
    ]
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "Emit IOC on near leg when gap=180 bps, sources match",
        "setup": "min_gap_bps=150, max_days_to_resolve=90",
        "expected": "IOC OrderIntent leg=near; reason=CAL_SPREAD_TRADE"
      },
      {
        "test": "Skip when source mismatch",
        "setup": "leg0.source='AP', leg1.source='Reuters'",
        "expected": "No OrderIntent; reason=CAL_SOURCE_MISMATCH"
      },
      {
        "test": "Skip when gap < 25 bps hard floor",
        "setup": "gap_bps=18",
        "expected": "No OrderIntent; sampled reason=CAL_NO_GAP"
      }
    ],
    "integration": [
      {
        "test": "Full cycle: pair identified \u2192 spread computed \u2192 IOC OrderIntent on Polygon testnet",
        "expected": "Order has builder.code, no feeRateBps, EIP-712 domain v2"
      }
    ],
    "property": [
      {
        "property": "Bot never trades when require_same_source=true and sources differ",
        "required": "Always true"
      },
      {
        "property": "feeRateBps never present on any signed OrderIntent",
        "required": "Always true"
      }
    ]
  },
  "checklist_overrides": {},
  "legacy_goal": "Trade the time-decay between markets resolving on different dates for the same underlying.",
  "legacy_pm_signals": [
    "Pairs of markets sharing source-of-truth, differing in resolution date",
    "Implied probability gap vs. days-to-resolution",
    "Historical compression behaviour for similar pairs"
  ],
  "legacy_external_feeds": [],
  "reporting_groups": [
    "strategy_decision"
  ],
  "reason_codes": [
    {
      "code": "CAL_SPREAD_TRADE",
      "severity": "INFO",
      "meaning": "gap_bps >= min_gap_bps, sources match, within date limits. IOC OrderIntent emitted.",
      "action": "Emit IOC OrderIntent.",
      "user_message": "A calendar spread trade was placed."
    },
    {
      "code": "CAL_NO_GAP",
      "severity": "INFO",
      "meaning": "gap_bps < 25 bps hard floor.",
      "action": "Skip; emit sampled DecisionReport.",
      "user_message": "The calendar spread was too small."
    },
    {
      "code": "CAL_GAP_MARGINAL",
      "severity": "WARN",
      "meaning": "gap_bps 25\u201375 bps; size halved.",
      "action": "Emit at 50% size; log warning.",
      "user_message": "A small calendar spread was found; reduced-size trade placed."
    },
    {
      "code": "CAL_SOURCE_MISMATCH",
      "severity": "HARD_REJECT",
      "meaning": "Calendar pair legs resolve from different sources.",
      "action": "Skip; no OrderIntent.",
      "user_message": "The two market legs have different resolution sources."
    },
    {
      "code": "CAL_PAIR_TOO_LONG_DATED",
      "severity": "HARD_REJECT",
      "meaning": "Far-expiry leg is beyond max_days_to_resolve hard limit.",
      "action": "Skip; no OrderIntent.",
      "user_message": "The far-dated leg is too distant to trade."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "polytraders_strat_calendarcompression_decisions_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "verdict",
          "reason_code"
        ],
        "meaning": "Total evaluation cycles by verdict and reason."
      },
      {
        "name": "polytraders_strat_calendarcompression_gap_bps",
        "type": "histogram",
        "unit": "basis_points",
        "labels": [],
        "meaning": "Distribution of calendar spread gap in bps."
      },
      {
        "name": "polytraders_strat_calendarcompression_intents_emitted_total",
        "type": "counter",
        "unit": "count",
        "labels": [
          "leg"
        ],
        "meaning": "Total IOC OrderIntents by leg (near/far)."
      },
      {
        "name": "polytraders_strat_calendarcompression_eval_latency_ms",
        "type": "histogram",
        "unit": "milliseconds",
        "labels": [],
        "meaning": "Latency from book tick to OrderIntent emit."
      }
    ],
    "alerts": [
      {
        "name": "CalendarCompressionStaleLeg",
        "condition": "rate(polytraders_strat_calendarcompression_decisions_total{reason_code='STALE_MARKET_DATA'}[5m]) > 0.1",
        "severity": "warn",
        "runbook": "#runbook-calcomp-stale"
      },
      {
        "name": "CalendarCompressionKillSwitch",
        "condition": "rate(polytraders_strat_calendarcompression_decisions_total{reason_code='KILL_SWITCH_ACTIVE'}[1m]) > 0",
        "severity": "page",
        "runbook": "#runbook-killswitch"
      }
    ]
  },
  "state": {
    "store": "redis",
    "shape": "Per pair: near_mid, far_mid, last_gap_bps; keyed by pair_id",
    "ttl": "30s per leg snapshot",
    "recovery": "On cold start, snapshots rebuilt from next ws_market tick for each leg.",
    "size_estimate": "~200 bytes per pair; < 500 KB total"
  },
  "concurrency": {
    "execution_model": "actor-per-pair",
    "max_in_flight": 20,
    "idempotency_key": "intent_id",
    "timeout_ms": 300,
    "backpressure": "drop oldest book update per pair when queue > 2",
    "locking": "per-pair_id mutex for spread state"
  },
  "dependencies": {
    "depends_on": [
      {
        "bot_id": "risk.kill_switch",
        "why": "Checked first; blocks all intent emission when active."
      }
    ],
    "emits_to": [
      {
        "bot_id": "risk.portfolio_guard",
        "what": "IOC OrderIntents for risk guardrail evaluation."
      },
      {
        "bot_id": "gov.builder_attribution",
        "what": "builder.code bytes32 on every OrderIntent."
      }
    ],
    "sibling": [],
    "external": [
      {
        "service": "Polymarket CLOB WebSocket (ws_market)",
        "sla": "best-effort",
        "fallback": "Skip pair evaluation on disconnect."
      },
      {
        "service": "Polymarket CLOB public API (market metadata)",
        "sla": "99.9%",
        "fallback": "Use cached metadata; if > 60s, skip source-match verification."
      }
    ]
  },
  "security_surfaces": {
    "signs_orders": true,
    "private_key_access": "signing-only",
    "abuse_vectors": [
      "Adversary manipulates one leg's metadata to appear as same-source pair",
      "Stale far-leg price creating phantom calendar gap"
    ],
    "mitigations": [
      "Resolution source comparison uses clob_public authoritative metadata",
      "Both leg snapshots must be fresh (< 10s) before spread computation"
    ]
  },
  "failure_injection": [
    {
      "scenario": "ONE_LEG_STALE",
      "how_to_inject": "Freeze ws_market for one calendar leg beyond 10s",
      "expected_behaviour": "STALE_MARKET_DATA; no OrderIntent for that pair",
      "recovery": "Automatic when feed recovers."
    },
    {
      "scenario": "SOURCE_MISMATCH",
      "how_to_inject": "Override test pair with mismatched sources",
      "expected_behaviour": "CAL_SOURCE_MISMATCH; no OrderIntent",
      "recovery": "Automatic when correct pair catalog is reloaded."
    },
    {
      "scenario": "KILL_SWITCH_ON",
      "how_to_inject": "Set killswitch.active=true",
      "expected_behaviour": "No OrderIntents emitted",
      "recovery": "Automatic on manual KillSwitch reset."
    }
  ],
  "runbook": {
    "summary": "CalendarCompression incidents are typically stale leg feeds or kill-switch activations. Source mismatches are expected for pairs that drifted out of configuration.",
    "oncall_actions": [
      {
        "alert": "CalendarCompressionStaleLeg",
        "first_action": "Check ws_market feed health for the affected market leg.",
        "escalate_to": "Infra on-call if feed lag > 60s."
      },
      {
        "alert": "CalendarCompressionKillSwitch",
        "first_action": "Confirm KillSwitch activation was intentional.",
        "escalate_to": "Risk pod lead immediately."
      }
    ],
    "manual_overrides": [
      {
        "name": "remove_pair",
        "how": "Remove pair from config.calendar_pairs",
        "when": "Pair resolution sources have diverged or one leg was unexpectedly resolved."
      }
    ],
    "healthcheck": "GET /internal/health/calendarcompression -> 200 if All active pair legs have snapshots < 10s; no source mismatches; KillSwitch inactive.. Red: Any leg stale > 60s or KillSwitch active.."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Unit tests pass including source-mismatch block and long-dated rejection",
        "how_measured": "CI test run",
        "threshold": "100% pass"
      }
    ],
    "to_limited_live": [
      {
        "gate": "p99 eval latency < 300ms over 24h shadow run",
        "how_measured": "polytraders_strat_calendarcompression_eval_latency_ms histogram",
        "threshold": "p99 < 300ms"
      }
    ],
    "to_general_live": [
      {
        "gate": "E2E: pair identified \u2192 calendar spread trade \u2192 IOC OrderIntent on Polygon testnet",
        "how_measured": "E2E test",
        "threshold": "Pass"
      }
    ]
  },
  "wire_examples": {
    "input": [
      {
        "label": "Calendar pair book tick \u2014 near leg at 0.42, far at 0.58",
        "source": "ws_market",
        "payload": {
          "pair_id": "cal_pair_001",
          "near_market_id": "0xcc000000000000000000000000000000000000000000000000000000000000001",
          "far_market_id": "0xcc000000000000000000000000000000000000000000000000000000000000002",
          "near_mid": "0.420",
          "far_mid": "0.580",
          "days_to_far": "62",
          "received_at_ms": 1746790800000
        }
      }
    ],
    "output": [
      {
        "label": "OrderIntent \u2014 calendar spread IOC near leg buy YES",
        "payload": {
          "intent_id": "oi_01HCC0000001A",
          "market_id": "0xcc000000000000000000000000000000000000000000000000000000000000001",
          "outcome": "YES",
          "side": "buy",
          "price": "0.420",
          "size_pUSD": "200.00",
          "tif": "IOC",
          "builder": {
            "code": "0x706f6c7974726164657273000000000000000000000000000000000000000000",
            "fee_bps": 25
          },
          "decision": {
            "gap_bps": 180.0,
            "reasons": [
              "CAL_SPREAD_TRADE"
            ]
          }
        }
      }
    ]
  },
  "reference_implementation": {
    "pseudocode": "FUNCTION onPairTick(pair_id, nearBook, farBook):\n  ks = FETCH internal.killswitch.status\n  IF ks.active: RETURN\n\n  // Source match check\n  nearMeta = FETCH clob_public.GET('/markets/' + pair_id.near_market)\n  farMeta  = FETCH clob_public.GET('/markets/' + pair_id.far_market)\n  IF nearMeta.resolution_source != farMeta.resolution_source:\n    EMIT DecisionReport(intent_emitted=false, reason='CAL_SOURCE_MISMATCH')\n    RETURN\n\n  // Date range check\n  daysToFar = (farMeta.end_date_ms - now_ms()) / 86400000\n  IF daysToFar > params.max_days_to_resolve_hard:  // 365\n    EMIT DecisionReport(intent_emitted=false, reason='CAL_PAIR_TOO_LONG_DATED')\n    RETURN\n\n  // Gap computation\n  nearMid = (nearBook.best_bid + nearBook.best_ask) / 2\n  farMid  = (farBook.best_bid  + farBook.best_ask)  / 2\n  fairSpread = computeFairCalendarSpread(nearMeta, farMeta)\n  gapBps = ((farMid - nearMid) - fairSpread) * 10000\n\n  IF gapBps < params.min_gap_bps_hard:  // 25 bps\n    IF random() < 0.01:\n      EMIT DecisionReport(intent_emitted=false, reason='CAL_NO_GAP')\n    RETURN\n\n  sizeMultiplier = 0.5 IF gapBps < params.min_gap_bps_warn ELSE 1.0\n  IF sizeMultiplier < 1.0: WARN('CAL_GAP_MARGINAL')\n\n  perLegSize = toPusdUnits(params.max_position_per_pair / 2 * sizeMultiplier)\n  EMIT OrderIntent(market=pair_id.near_market, outcome='YES', side='buy',\n                   price=nearMid, size_pUSD=perLegSize, tif='IOC', builder=code)\n  EMIT DecisionReport(intent_emitted=true, gap_bps=gapBps, reason='CAL_SPREAD_TRADE')",
    "sdk_calls": [
      "ws_market.subscribe('book', [near_market, far_market])",
      "fetchClobPublic('/markets/' + market_id)",
      "buildOrderTypedData(orderParams, {name:'CTFExchange', version:'2', chainId:137})"
    ],
    "complexity": "O(1) per pair tick"
  },
  "api_surface": [
    "clob_public",
    "clob_auth",
    "ws_market",
    "internal"
  ],
  "network": [
    "polygon"
  ],
  "version": {
    "spec": "2.0.0",
    "implementation": "0.1.0",
    "schema": "2",
    "released": null,
    "planned_release": "Q3-2026"
  },
  "migration_history": [
    {
      "date": "2026-04-28",
      "from": "n/a",
      "to": "v2-spec",
      "reason": "Spec drafted post-CLOB-V2 cutover; bot not yet implemented",
      "action_taken": "Designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain)"
    }
  ],
  "polymarket_v2_compat": {
    "clob_version": "v2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": true,
    "negrisk_aware": false,
    "multichain_ready": false,
    "sdk_used": "py-clob-client-v2",
    "settlement_contract": "CTFExchangeV2",
    "notes": "Bot not yet implemented; designed against V2 schema (pUSD, builder codes, V2 EIP-712 domain). feeRateBps not present on any signed OrderIntent."
  },
  "reporting": {
    "emits_kinds": [
      "DecisionReport"
    ],
    "topics": [
      "polytraders.reports.decision"
    ],
    "cadence": "every-event",
    "retention_class": "2y",
    "sampling_rule": "emit-every",
    "bus_failure_action": "fail-closed",
    "user_visible": "yes",
    "consumes_kinds": [
      "ObservationReport",
      "RiskVote"
    ]
  },
  "capital_impact": "Direct",
  "v3_status": {
    "phase": 8,
    "phase_name": "Additional strategies",
    "docs": {
      "done": 27,
      "total": 27,
      "state": "done"
    },
    "impl": {
      "done": 0,
      "total": 15,
      "state": "pending"
    },
    "runtime": {
      "done": 0,
      "total": 8,
      "state": "pending"
    },
    "overall": "pending"
  }
}