Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,4 @@ broker schema quote.snapshot

- `--help` is available on all command groups
- output is machine-first JSON in a stable envelope (`ok/data/error/meta`)
- `--strict` enables stricter empty-result validation where supported (`history`, `chain`)
- when daemon is down, run `broker daemon status` and `broker daemon start`
2 changes: 0 additions & 2 deletions cli/src/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ def build_meta(
"request_id": request_id or str(uuid.uuid4()),
"timestamp": datetime.now(UTC).isoformat(),
}
if strict is not None:
meta["strict"] = strict
return meta


Expand Down
7 changes: 1 addition & 6 deletions cli/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,9 @@
@app.callback()
def root(
ctx: typer.Context,
strict: bool = typer.Option(
False,
"--strict/--no-strict",
help="Enable strict mode for commands that can treat empty market payloads as errors.",
),
) -> None:
cfg = load_config()
ctx.obj = CLIState(config=cfg, json_output=True, strict=strict)
ctx.obj = CLIState(config=cfg, json_output=True, strict=False)


def run() -> None:
Expand Down
22 changes: 4 additions & 18 deletions cli/src/market.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,20 +180,13 @@ def chain(
"--fields",
help="Comma-separated entry fields to include. Defaults to all.",
),
strict: bool | None = typer.Option(
None,
"--strict/--no-strict",
help="Treat empty chain results as errors.",
),
) -> None:
state = get_state(ctx)
command = "market.chain"
strict_mode = state.strict if strict is None else strict
params: dict[str, object] = {
"symbol": symbol,
"limit": limit,
"offset": offset,
"strict": strict_mode,
}
if expiry:
params["expiry"] = expiry
Expand All @@ -212,10 +205,10 @@ def chain(
json_output=state.json_output,
command=command,
request_id=result.request_id,
strict=strict_mode,
strict=state.strict,
)
except BrokerError as exc:
handle_error(exc, json_output=state.json_output, command=command, strict=strict_mode)
handle_error(exc, json_output=state.json_output, command=command, strict=state.strict)


@app.command("history", help="Fetch historical bars for a symbol.")
Expand All @@ -225,15 +218,9 @@ def history(
period: HistoryPeriod = typer.Option(..., "--period", case_sensitive=False, help="1d, 5d, 30d, 90d, 1y"),
bar: BarSize = typer.Option(..., "--bar", case_sensitive=False, help="1m, 5m, 15m, 1h, 1d"),
rth_only: bool = typer.Option(False, "--rth-only", help="Restrict to regular trading hours."),
strict: bool | None = typer.Option(
None,
"--strict/--no-strict",
help="Treat empty history responses as errors.",
),
) -> None:
state = get_state(ctx)
command = "market.history"
strict_mode = state.strict if strict is None else strict
try:
result = run_async(
daemon_request(
Expand All @@ -244,7 +231,6 @@ def history(
"period": period.value,
"bar": bar.value,
"rth_only": rth_only,
"strict": strict_mode,
},
)
)
Expand All @@ -253,10 +239,10 @@ def history(
json_output=state.json_output,
command=command,
request_id=result.request_id,
strict=strict_mode,
strict=state.strict,
)
except BrokerError as exc:
handle_error(exc, json_output=state.json_output, command=command, strict=strict_mode)
handle_error(exc, json_output=state.json_output, command=command, strict=state.strict)


@app.command("capabilities", help="Show detected market-data capabilities for the connected provider.")
Expand Down
23 changes: 0 additions & 23 deletions daemon/src/broker_daemon/daemon/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,13 +345,6 @@ async def _dispatch(self, request: Request) -> dict[str, Any]:
bar=bar,
rth_only=bool(p.get("rth_only", False)),
)
if bool(p.get("strict", False)) and not bars:
raise BrokerError(
ErrorCode.INVALID_SYMBOL,
f"no historical bars returned for symbol '{symbol}'",
details={"symbol": symbol, "period": period, "bar": bar},
suggestion="Use a valid symbol or disable strict mode with --no-strict.",
)
return {"bars": [b.model_dump(mode="json") for b in bars]}

if cmd == "market.chain":
Expand Down Expand Up @@ -383,19 +376,6 @@ async def _dispatch(self, request: Request) -> dict[str, Any]:
if selected_fields:
all_entries = [{field: entry.get(field) for field in selected_fields} for entry in all_entries]
entries = all_entries[offset : offset + limit]
if bool(p.get("strict", False)) and not entries:
raise BrokerError(
ErrorCode.INVALID_SYMBOL,
f"no option contracts matched filters for '{symbol}'",
details={
"symbol": symbol,
"expiry": p.get("expiry"),
"strike_range": raw_strike_range,
"offset": offset,
"limit": limit,
},
suggestion="Relax filters, increase --limit, or disable strict mode with --no-strict.",
)
payload["entries"] = entries
payload["pagination"] = {
"total_entries": len(all_entries),
Expand Down Expand Up @@ -982,7 +962,6 @@ def _cli_envelope_schema() -> dict[str, Any]:
"command": {"type": "string"},
"request_id": {"type": "string"},
"timestamp": {"type": "string", "format": "date-time"},
"strict": {"type": "boolean"},
},
"required": ["schema_version", "command", "request_id", "timestamp"],
},
Expand Down Expand Up @@ -1055,7 +1034,6 @@ def _command_schema_registry() -> dict[str, dict[str, Any]]:
"period": {"enum": ["1d", "5d", "30d", "90d", "1y"]},
"bar": {"enum": ["1m", "5m", "15m", "1h", "1d"]},
"rth_only": {"type": "boolean"},
"strict": {"type": "boolean"},
},
"required": ["symbol"],
},
Expand All @@ -1079,7 +1057,6 @@ def _command_schema_registry() -> dict[str, dict[str, Any]]:
"limit": {"type": "integer", "minimum": 1},
"offset": {"type": "integer", "minimum": 0},
"fields": {"type": "array", "items": {"enum": sorted(OPTION_CHAIN_FIELDS)}},
"strict": {"type": "boolean"},
},
"required": ["symbol"],
},
Expand Down
20 changes: 0 additions & 20 deletions daemon/tests/test_daemon/test_dispatch_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,26 +86,6 @@ async def test_dispatch_market_capabilities_returns_payload(tmp_path) -> None:
assert "cache_age_ms" in data["cache"]


@pytest.mark.asyncio
async def test_dispatch_history_strict_raises_on_empty(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None:
server = DaemonServer(_test_config(tmp_path))
server._provider.capabilities["history"] = True # noqa: SLF001

async def fake_history(**_: object) -> list[object]:
return []

monkeypatch.setattr(server._provider, "history", fake_history) # noqa: SLF001

req = Request(
command="market.history",
params={"symbol": "INVALID", "period": "5d", "bar": "1d", "strict": True},
)

with pytest.raises(BrokerError) as exc:
await server._dispatch(req) # noqa: SLF001

assert exc.value.code == ErrorCode.INVALID_SYMBOL


@pytest.mark.asyncio
async def test_dispatch_chain_applies_limit_offset_and_fields(monkeypatch: pytest.MonkeyPatch, tmp_path) -> None:
Expand Down
7 changes: 2 additions & 5 deletions sdk/python/src/broker_sdk/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,10 @@ async def history(
period: HistoryPeriod,
bar: BarSize,
rth_only: bool = False,
*,
strict: bool = False,
) -> list[dict[str, Any]]:
data = await self._request(
"market.history",
{"symbol": symbol, "period": period, "bar": bar, "rth_only": rth_only, "strict": strict},
{"symbol": symbol, "period": period, "bar": bar, "rth_only": rth_only},
)
return data.get("bars", [])

Expand All @@ -192,9 +190,8 @@ async def chain(
limit: int = 200,
offset: int = 0,
fields: list[ChainField] | None = None,
strict: bool = False,
) -> dict[str, Any]:
params: dict[str, Any] = {"symbol": symbol, "limit": limit, "offset": offset, "strict": strict}
params: dict[str, Any] = {"symbol": symbol, "limit": limit, "offset": offset}
if expiry:
params["expiry"] = expiry
if strike_range:
Expand Down
7 changes: 1 addition & 6 deletions sdk/typescript/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,14 +175,12 @@ export class Client {
period: HistoryPeriod,
bar: BarSize,
rthOnly = false,
strict = false
): Promise<MarketHistoryResponse> {
return this.request("market.history", {
symbol,
period,
bar,
rth_only: rthOnly,
strict
});
}

Expand All @@ -191,7 +189,7 @@ export class Client {
expiry?: string,
strikeRange?: string,
optionType?: OptionType,
options: { limit?: number; offset?: number; fields?: ChainField[]; strict?: boolean } = {}
options: { limit?: number; offset?: number; fields?: ChainField[] } = {}
): Promise<OptionChainResponse> {
const params: CommandParams<"market.chain"> = { symbol };
if (expiry) {
Expand All @@ -212,9 +210,6 @@ export class Client {
if (options.fields) {
params.fields = options.fields;
}
if (options.strict !== undefined) {
params.strict = options.strict;
}
return this.request("market.chain", params);
}

Expand Down
2 changes: 0 additions & 2 deletions sdk/typescript/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ export interface CommandMap {
period: HistoryPeriod;
bar: BarSize;
rth_only?: boolean;
strict?: boolean;
},
MarketHistoryResponse
>;
Expand All @@ -73,7 +72,6 @@ export interface CommandMap {
limit?: number;
offset?: number;
fields?: ChainField[];
strict?: boolean;
},
OptionChainResponse
>;
Expand Down
5 changes: 1 addition & 4 deletions website/app/reference/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ export default function ReferencePage() {
Complete reference for every broker-cli command. Every response uses a stable JSON envelope:
<code className="mx-1 text-[var(--foreground)]">{`{ok,data,error,meta}`}</code>
with request IDs for tracing and audit correlation.
Use global <code className="mx-1 text-[var(--foreground)]">--strict</code> (or command-level strict flags where available)
when agents should treat empty market payloads as errors.
Use supported flags where available and note command-specific behavior in each section.
</p>
</div>

Expand Down Expand Up @@ -261,7 +260,6 @@ export default function ReferencePage() {
{ flag: "--limit INT", description: "Max entries returned after filtering", default: "200" },
{ flag: "--offset INT", description: "Offset into filtered entries", default: "0" },
{ flag: "--fields LIST", description: "Comma-separated entry fields (e.g. strike,expiry,bid,ask)" },
{ flag: "--strict / --no-strict", description: "Error if no entries match filters" },
]}
example={`$ broker chain AAPL --type call --strike-range 0.95:1.05 --limit 5 --fields strike,expiry,bid,ask\n{"ok":true,"data":{"symbol":"AAPL","underlying_price":263.3,"entries":[{"strike":252.5,"expiry":"2026-02-20","bid":null,"ask":null}],"pagination":{"total_entries":80,"offset":0,"limit":5,"returned_entries":5},"fields":["strike","expiry","bid","ask"]},"error":null,"meta":{"schema_version":"v1","command":"market.chain","request_id":"...","timestamp":"..."}}`}
/>
Expand All @@ -273,7 +271,6 @@ export default function ReferencePage() {
{ flag: "--period 1d|5d|30d|90d|1y", description: "Lookback period" },
{ flag: "--bar 1m|5m|15m|1h|1d", description: "Bar size" },
{ flag: "--rth-only", description: "Restrict to regular trading hours" },
{ flag: "--strict / --no-strict", description: "Error when no bars are returned" },
]}
example={`$ broker history AAPL --period 5d --bar 1h\n{"ok":true,"data":[{"symbol":"AAPL","time":"2026-02-18T00:00:00","open":265.1,"close":263.29}],"error":null,"meta":{"schema_version":"v1","command":"market.history","request_id":"...","timestamp":"..."}}`}
notes="Available on Interactive Brokers only."
Expand Down