From a8fb0b81816c6bd3a0d14cf8b4f212ef30ec654f Mon Sep 17 00:00:00 2001 From: kingchenc Date: Fri, 22 May 2026 12:27:13 +0200 Subject: [PATCH] C12: fix falsy-value display and validate symbol/interval Two issues in the live_trading example: - The status line rendered indicator values with `... if snap.rsi else "--"`. A genuine reading of 0.0 is falsy, so an RSI / MACD histogram / Bollinger value of exactly zero was misreported as "--". Switch to explicit `is not None` checks. - --symbol and --interval were interpolated straight into the stream name and WebSocket URL with no checks. Add validate_args: the symbol must be strictly alphanumeric and the interval must be one Binance recognises. main() validates before connecting and exits with a clear error and code 2 otherwise; the strict values keep the URL well-formed without escaping. --- examples/python/live_trading.py | 47 ++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/examples/python/live_trading.py b/examples/python/live_trading.py index 810ad829..2c04d26d 100644 --- a/examples/python/live_trading.py +++ b/examples/python/live_trading.py @@ -20,6 +20,7 @@ import argparse import asyncio import json import logging +import re import signal import sys from dataclasses import dataclass @@ -36,6 +37,41 @@ except ImportError: BINANCE_WS = "wss://stream.binance.com:9443/stream" +# Kline intervals the public Binance WebSocket API recognises. +VALID_INTERVALS = frozenset( + { + "1s", "1m", "3m", "5m", "15m", "30m", + "1h", "2h", "4h", "6h", "8h", "12h", + "1d", "3d", "1w", "1M", + } +) + +# A Binance symbol is strictly alphanumeric (e.g. BTCUSDT). +_SYMBOL_RE = re.compile(r"^[A-Za-z0-9]+$") + + +def validate_args(symbol: str, interval: str) -> None: + """Reject a symbol or interval that is not safe to splice into the WS URL. + + Both values are interpolated straight into the stream name and URL, so an + unexpected value would otherwise produce a confusing connection failure. + Validating up front keeps the URL well-formed without needing to escape it. + + Raises: + ValueError: if ``symbol`` is not strictly alphanumeric, or ``interval`` + is not one Binance recognises. + """ + if not _SYMBOL_RE.match(symbol): + raise ValueError( + f"invalid --symbol {symbol!r}: expected only letters and digits, " + "e.g. BTCUSDT" + ) + if interval not in VALID_INTERVALS: + raise ValueError( + f"invalid --interval {interval!r}: expected one of " + + ", ".join(sorted(VALID_INTERVALS)) + ) + @dataclass class Snapshot: @@ -109,10 +145,10 @@ async def run(symbol: str, interval: str) -> None: "%s close=%.4f rsi=%s hist=%s bb=%s", "BAR" if is_closed else "tick", close, - f"{snap.rsi:.1f}" if snap.rsi else "--", - f"{snap.macd_hist:+.4f}" if snap.macd_hist else "--", + f"{snap.rsi:.1f}" if snap.rsi is not None else "--", + f"{snap.macd_hist:+.4f}" if snap.macd_hist is not None else "--", f"{snap.bb_lower:.2f}/{snap.bb_middle:.2f}/{snap.bb_upper:.2f}" - if snap.bb_upper + if snap.bb_upper is not None else "--", ) emit_signal(snap, log) @@ -128,6 +164,11 @@ def parse_args() -> argparse.Namespace: def main() -> int: args = parse_args() + try: + validate_args(args.symbol, args.interval) + except ValueError as exc: + print(f"error: {exc}", file=sys.stderr) + return 2 logging.basicConfig( level=logging.DEBUG if args.verbose else logging.INFO, format="%(asctime)s %(levelname)s %(message)s",