{
  "schema_version": "1.0.0",
  "bot_id": "2.14",
  "bot_name": "FeeSlippageEstimator",
  "slug": "fee_slippage_estimator",
  "layer": "Execution",
  "layer_key": "exec",
  "bot_class": "Execution Utility",
  "authority": [
    "Annotate"
  ],
  "status": "planned",
  "readiness": "Spec ready",
  "flagship": false,
  "is_reference": false,
  "public_export": false,
  "identity": {
    "layer": "Execution",
    "bot_class": "ExecutionUtility",
    "authority": "Annotate",
    "runs_before": "exec.smart_router, exec.order_shaper",
    "runs_after": "intel.orderflowanalyzer",
    "applies_to": "Per OrderIntent",
    "default_mode": "shadow",
    "user_visible": "Yes",
    "developer_owner": "Execution pod"
  },
  "purpose": "Stamps every approved OrderIntent with an explicit fee + slippage estimate before the order shaper sees it. The estimate is a structured object (maker_fee_bps, taker_fee_bps, expected_slippage_bps, total_cost_usd) that downstream tools and UIs all read instead of guessing.",
  "why_it_matters": [
    {
      "failure": "Hidden cost surprises",
      "consequence": "Without a per-order estimate, post-trade reports are the first place anyone learns the order was unprofitable after fees."
    },
    {
      "failure": "Inconsistent cost models",
      "consequence": "Each strategy inventing its own cost estimate produces inconsistent reports and dashboards."
    },
    {
      "failure": "Affiliate/builderCode accounting",
      "consequence": "The builder-fee component on V2 must be visible per order to track attribution correctly."
    }
  ],
  "polymarket_inputs": [
    {
      "input": "Live OrderBookSnapshot",
      "source": "intel.orderflowanalyzer",
      "required": true,
      "use": "Estimate slippage from depth profile."
    },
    {
      "input": "Polymarket fee schedule (maker/taker/builder)",
      "source": "Config",
      "required": true,
      "use": "Current tier fees."
    }
  ],
  "internal_inputs": [
    {
      "input": "OrderIntent",
      "source": "Strategy bot",
      "required": true,
      "use": "The order to estimate."
    },
    {
      "input": "Market microstructure prior",
      "source": "intel.liquidity_decay_monitor",
      "required": false,
      "use": "Improves slippage estimate when available."
    }
  ],
  "raw_params": [
    "maker_fee_bps \u00b7 0\u2013200",
    "taker_fee_bps \u00b7 0\u2013200",
    "builder_fee_bps \u00b7 0\u2013200",
    "slippage_safety_factor \u00b7 1.0\u20133.0"
  ],
  "parameters": [
    {
      "name": "maker_fee_bps",
      "default": 0,
      "warning": "\u2014",
      "hard": "\u2014",
      "controls": "Polymarket maker fee in basis points (0 today on V2 binary).",
      "why_default_matters": "Polymarket V2 currently does not charge a maker fee on binary markets.",
      "threshold_logic": [
        {
          "condition": "0",
          "action": "Default"
        }
      ],
      "dev_check": "fee += notional * p.maker_fee_bps/10000;",
      "user_facing": "(Internal.)"
    },
    {
      "name": "taker_fee_bps",
      "default": 200,
      "warning": "250",
      "hard": "400",
      "controls": "Polymarket taker fee in basis points.",
      "why_default_matters": "200 bps (2%) is the V2 binary taker default.",
      "threshold_logic": [
        {
          "condition": "200",
          "action": "Default"
        }
      ],
      "dev_check": "fee += notional * p.taker_fee_bps/10000;",
      "user_facing": "(Internal.)"
    },
    {
      "name": "builder_fee_bps",
      "default": 0,
      "warning": "\u2014",
      "hard": "\u2014",
      "controls": "Polytraders builderCode fee in basis points.",
      "why_default_matters": "Set by config; defaults to 0 until builderCode is registered.",
      "threshold_logic": [
        {
          "condition": "0",
          "action": "Until registered"
        }
      ],
      "dev_check": "fee += notional * p.builder_fee_bps/10000;",
      "user_facing": "(Internal.)"
    },
    {
      "name": "slippage_safety_factor",
      "default": 1.5,
      "warning": "1.0",
      "hard": "3.0",
      "controls": "Multiplier applied to the linear slippage estimate to account for adverse selection.",
      "why_default_matters": "1.5 is the empirical median across Polymarket binary markets.",
      "threshold_logic": [
        {
          "condition": "1.0",
          "action": "Optimistic"
        },
        {
          "condition": "1.5",
          "action": "Default"
        },
        {
          "condition": "2.0+",
          "action": "Defensive"
        }
      ],
      "dev_check": "slip_bps *= p.slippage_safety_factor;",
      "user_facing": "(Internal.)"
    }
  ],
  "default_config": {
    "maker_fee_bps": 0,
    "taker_fee_bps": 200,
    "builder_fee_bps": 0,
    "slippage_safety_factor": 1.5
  },
  "flow": "Receive OrderIntent \u2192 fetch live book \u2192 estimate marketable depth at intent.price \u2192 compute slippage as integral of (price - mid) over filled depth \u2192 multiply by safety factor \u2192 sum fees + slippage \u2192 annotate OrderIntent with cost_estimate sub-object.",
  "decision_logic": {
    "approve": "Compute marketable_depth at intent.price + side. Estimate fill VWAP and slippage_bps. Compute fee_bps from configured schedule. Stamp the OrderIntent and pass it through unchanged otherwise.",
    "reshape_required": "This bot does not reshape orders.",
    "reject": "No reject path defined for this bot \u2014 it is observe-only.",
    "warning_only": "No warn-only path defined."
  },
  "decision_output_example": {
    "intent_id": "intent_004",
    "cost_estimate": {
      "maker_fee_bps": 0,
      "taker_fee_bps": 200,
      "builder_fee_bps": 0,
      "slippage_bps": 35,
      "total_cost_bps": 235,
      "total_cost_usd": 2.35,
      "method": "linear_with_safety_factor"
    },
    "vote": "ANNOTATE"
  },
  "developer_log": "Per estimate: intent_id, market_id, size_usd, marketable_depth_usd, slippage_bps, fee_bps, total_cost_bps.",
  "user_explanations": [
    {
      "situation": "When this bot acts",
      "message": "Estimated total cost of this order, including fees and the price impact of moving the market."
    }
  ],
  "failure_modes": {
    "main_failure_mode": "Underestimating slippage for orders larger than top-of-book.",
    "false_positive_risk": "Over-conservative estimate suppresses orders that would have been profitable; mitigation: track realised vs estimated cost and re-tune safety factor weekly.",
    "false_negative_risk": "Stale fee schedule misses a fee change; mitigation: validate fee schedule against on-chain CTFExchangeV2 storage at startup.",
    "safe_fallback": "On any failure, stamp cost_estimate.method='conservative_default' with a generous flat estimate. Never strip the field."
  },
  "acceptance_tests": {
    "unit": [
      {
        "test": "For an order at top-of-book \u2264 depth, slippage estimate is < 5 bps.",
        "setup": "Synthetic fixture per template.",
        "expected": "Behaviour matches the rule described in the test name."
      },
      {
        "test": "For an order > 2x top-of-book depth, slippage estimate exceeds 50 bps.",
        "setup": "Synthetic fixture per template.",
        "expected": "Behaviour matches the rule described in the test name."
      }
    ],
    "integration": [
      {
        "test": "End-to-end: estimate vs realised cost on 100 historical orders agrees within \u00b125%.",
        "expected": "End-to-end behaviour matches the spec without manual intervention."
      }
    ],
    "property": [
      {
        "property": "Total cost is monotonically non-decreasing in size_usd for a fixed book.",
        "required": "Always true across all generated inputs."
      }
    ]
  },
  "reference_implementation": {
    "language": "pseudocode",
    "pseudocode": "depth = walk_book(book, intent.side, intent.price, intent.size_usd)\nslip_bps = (depth.vwap - book.mid) / book.mid * 10000 * p.slippage_safety_factor\nfee_bps = p.taker_fee_bps + p.builder_fee_bps\nstamp(intent, cost_estimate={maker:p.maker_fee_bps, taker:p.taker_fee_bps, builder:p.builder_fee_bps, slip:slip_bps, total_bps:fee_bps+slip_bps, total_usd: ...})"
  },
  "wire_examples": {
    "input": {
      "intent_id": "intent_004",
      "market_id": "0xabc",
      "size_usd": 500,
      "side": "BUY",
      "price": 0.62
    },
    "output": {
      "intent_id": "intent_004",
      "cost_estimate": {
        "slippage_bps": 35,
        "total_cost_bps": 235,
        "total_cost_usd": 11.75
      },
      "vote": "ANNOTATE"
    }
  },
  "reason_codes": [
    {
      "code": "EXEC_COST_ESTIMATED",
      "severity": "P2",
      "meaning": "Exec Cost Estimated",
      "action": "See decision output and developer log for context.",
      "user_message": "Estimated total cost of this order, including fees and the price impact of moving the market."
    },
    {
      "code": "EXEC_COST_FALLBACK",
      "severity": "P2",
      "meaning": "Exec Cost Fallback",
      "action": "See decision output and developer log for context.",
      "user_message": "Estimated total cost of this order, including fees and the price impact of moving the market."
    },
    {
      "code": "EXEC_COST_FEE_SCHEDULE_STALE",
      "severity": "P2",
      "meaning": "Exec Cost Fee Schedule Stale",
      "action": "See decision output and developer log for context.",
      "user_message": "Estimated total cost of this order, including fees and the price impact of moving the market."
    }
  ],
  "metrics": {
    "emitted": [
      {
        "name": "estimates_total",
        "type": "counter",
        "unit": "event",
        "labels": [
          "bot_id"
        ],
        "meaning": "Estimates total."
      },
      {
        "name": "estimated_vs_realised_bps_histogram",
        "type": "counter",
        "unit": "event",
        "labels": [
          "bot_id"
        ],
        "meaning": "Estimated vs realised bps histogram."
      },
      {
        "name": "fallback_estimates_total",
        "type": "counter",
        "unit": "event",
        "labels": [
          "bot_id"
        ],
        "meaning": "Fallback estimates total."
      }
    ],
    "alerts": [],
    "dashboards": [
      "2.14 overview dashboard"
    ]
  },
  "state": {
    "summary": "Stateless. Reads live book + config; no persistence required.",
    "stores": [
      {
        "name": "fee_slippage_estimator_state",
        "kind": "in-memory + fast KV mirror",
        "key": "bot_id",
        "value": "Stateless. Reads live book + config; no persistence required.",
        "ttl": "24h",
        "durability": "crash-safe via KV mirror"
      }
    ],
    "recovery": "Cold-start hydrates from fast KV; missing keys default to safe fallback.",
    "on_restart": "All in-flight decisions are re-evaluated; no bot decision is trusted across restart without re-emit."
  },
  "concurrency": {
    "execution_model": "Pure function over (book, intent, fees). Trivially concurrent.",
    "max_in_flight": 32,
    "idempotency_key": "order_intent_id",
    "replay_safe": true,
    "deduplication": "By idempotency_key within a 60s window.",
    "ordering_guarantees": "Per-market_id FIFO; cross-market unordered.",
    "timeout_ms": 250,
    "backpressure": "Bounded queue; oldest-dropped with metric increment when full.",
    "locking": "Per-market_id mutex; no global locks."
  },
  "dependencies": {
    "depends_on": [
      "intel.orderflowanalyzer"
    ],
    "emits_to": [
      "exec.smart_router",
      "exec.order_shaper"
    ]
  },
  "graph": {
    "requires": [
      "intel.orderflowanalyzer"
    ],
    "required_before": [
      "exec.smart_router",
      "exec.order_shaper"
    ],
    "consumes": [
      "OrderIntent",
      "OrderBookSnapshot"
    ],
    "emits": [
      "AnnotatedOrderIntent"
    ],
    "blocks": false
  },
  "mode_support": [
    "off",
    "shadow",
    "advisory",
    "enforced",
    "quarantine"
  ],
  "latency_budget_ms": {
    "p50": 4,
    "p99": 15
  },
  "data_freshness": {
    "max_market_data_age_ms": 2000,
    "max_orderbook_age_ms": 2000,
    "on_stale_data": "Stamp cost_estimate with method='conservative_default'."
  },
  "ownership": {
    "owner": "Execution pod",
    "on_call": "exec-oncall",
    "channel": "#polytraders-exec",
    "escalation": "Head of Execution",
    "severity_class": "P2"
  },
  "human_override": {
    "allowed": false,
    "who": "\u2014",
    "log_event": "\u2014",
    "time_bound": "\u2014",
    "scope": "\u2014",
    "second_approval": false
  },
  "security_surfaces": {
    "summary": "No external surface; reads internal config + live book.",
    "signing": "None \u2014 bot does not sign or submit.",
    "secrets": [],
    "contract_calls": [],
    "abuse_vectors": [],
    "mitigations": [
      "Rate-limit per source",
      "Audit-log every override",
      "Require role-based authz on admin paths"
    ]
  },
  "polymarket_v2_compat": {
    "clob_version": "V2",
    "collateral": "pUSD",
    "eip712_domain_version": "2",
    "builder_code_aware": true,
    "negrisk_aware": true,
    "multichain_ready": true,
    "sdk_used": "Polymarket CLOB V2 SDK",
    "settlement_contract": "CTFExchangeV2",
    "notes": "Reads V2 fee schedule (maker_fee_bps, taker_fee_bps, builder_fee_bps)."
  },
  "version": {
    "current": "0.1.0",
    "contract_version": "1.0.0",
    "last_breaking_change": "none",
    "deprecation_window_days": 30
  },
  "migration_history": [],
  "runbook": {
    "summary": "If estimated-vs-realised drifts > 25%, re-tune slippage_safety_factor on a weekly cadence.",
    "oncall_actions": [
      {
        "alert": "2.14_anomaly",
        "first_step": "Open the bot's reporting page and confirm the alert is real (not a metric hiccup).",
        "diagnosis": "Inspect developer log entries for the affected market_id over the last 30 minutes.",
        "mitigation": "Force-clear via Admin UI if the rule is clearly stale; otherwise leave engaged and notify owner.",
        "escalation": "Execution pod"
      }
    ],
    "manual_overrides": [
      {
        "command": "polytraders bot pause 2.14",
        "effect": "Disables the bot's enforcement layer; downstream consumers fall back to safe defaults."
      }
    ],
    "healthcheck": "GET /healthz/fee_slippage_estimator \u2192 200 if last successful evaluation < 60s ago."
  },
  "promotion_gates": {
    "to_shadow": [
      {
        "gate": "Stub",
        "how_measured": "deterministic on synthetic books.",
        "threshold": "Documented threshold met for the full window."
      }
    ],
    "to_limited_live": [
      {
        "gate": "Shadow",
        "how_measured": "14 days against historical orders.",
        "threshold": "Documented threshold met for the full window."
      },
      {
        "gate": "Advisory",
        "how_measured": "7 days; downstream tools may consume.",
        "threshold": "Documented threshold met for the full window."
      }
    ],
    "to_general_live": [
      {
        "gate": "Enforced",
        "how_measured": "required field on every OrderIntent.",
        "threshold": "Documented threshold met for the full window."
      }
    ]
  },
  "failure_injection": [
    {
      "scenario": "Strip the live book and assert fallback estimate is stamped, not absent",
      "how_to_inject": "Strip the live book and assert fallback estimate is stamped, not absent.",
      "expected_behavior": "Bot detects within its latency budget and emits the corresponding reason code.",
      "recovery": "Remove the injected fault; bot returns to healthy state within one debounce window."
    },
    {
      "scenario": "Inject a fee-schedule mismatch and assert the stale code fires",
      "how_to_inject": "Inject a fee-schedule mismatch and assert the stale code fires.",
      "expected_behavior": "Bot detects within its latency budget and emits the corresponding reason code.",
      "recovery": "Remove the injected fault; bot returns to healthy state within one debounce window."
    }
  ],
  "capital_impact": "Direct",
  "v3_status": {
    "phase": 5,
    "phase_name": "Execution rails",
    "docs": {
      "done": 27,
      "total": 27,
      "state": "done"
    },
    "impl": {
      "done": 0,
      "total": 15,
      "state": "pending"
    },
    "runtime": {
      "done": 0,
      "total": 8,
      "state": "pending"
    },
    "overall": "pending"
  }
}