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.
This commit is contained in:
@@ -20,6 +20,7 @@ import argparse
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -36,6 +37,41 @@ except ImportError:
|
|||||||
|
|
||||||
BINANCE_WS = "wss://stream.binance.com:9443/stream"
|
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
|
@dataclass
|
||||||
class Snapshot:
|
class Snapshot:
|
||||||
@@ -109,10 +145,10 @@ async def run(symbol: str, interval: str) -> None:
|
|||||||
"%s close=%.4f rsi=%s hist=%s bb=%s",
|
"%s close=%.4f rsi=%s hist=%s bb=%s",
|
||||||
"BAR" if is_closed else "tick",
|
"BAR" if is_closed else "tick",
|
||||||
close,
|
close,
|
||||||
f"{snap.rsi:.1f}" if snap.rsi else "--",
|
f"{snap.rsi:.1f}" if snap.rsi is not None else "--",
|
||||||
f"{snap.macd_hist:+.4f}" if snap.macd_hist 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}"
|
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 "--",
|
else "--",
|
||||||
)
|
)
|
||||||
emit_signal(snap, log)
|
emit_signal(snap, log)
|
||||||
@@ -128,6 +164,11 @@ def parse_args() -> argparse.Namespace:
|
|||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
args = parse_args()
|
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(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG if args.verbose else logging.INFO,
|
level=logging.DEBUG if args.verbose else logging.INFO,
|
||||||
format="%(asctime)s %(levelname)s %(message)s",
|
format="%(asctime)s %(levelname)s %(message)s",
|
||||||
|
|||||||
Reference in New Issue
Block a user