feat: add catlog command, agent flag reorder, and TUI polish
- Add `coinhunter catlog` with limit/offset pagination for audit logs - Optimize audit log reading with deque to avoid loading all history - Allow `-a/--agent` flag after subcommands - Fix upgrade spinner artifact and empty line issues - Render audit log TUI as timeline with low-saturation event colors - Convert audit timestamps to local timezone in TUI - Remove futures-related capabilities - Add conda environment.yml for development - Bump version to 2.0.9 and update README Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -57,7 +57,9 @@ def get_tickers(config: dict[str, Any], symbols: list[str], *, spot_client: Any)
|
||||
TickerView(
|
||||
symbol=normalize_symbol(ticker["symbol"]),
|
||||
last_price=float(ticker.get("lastPrice") or ticker.get("last_price") or 0.0),
|
||||
price_change_pct=float(ticker.get("priceChangePercent") or ticker.get("price_change_percent") or 0.0),
|
||||
price_change_pct=float(
|
||||
ticker.get("priceChangePercent") or ticker.get("price_change_percent") or 0.0
|
||||
),
|
||||
quote_volume=float(ticker.get("quoteVolume") or ticker.get("quote_volume") or 0.0),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -26,7 +26,9 @@ def _safe_pct(new: float, old: float) -> float:
|
||||
return (new - old) / old
|
||||
|
||||
|
||||
def _score_candidate(closes: list[float], volumes: list[float], ticker: dict[str, Any], weights: dict[str, float], concentration: float) -> tuple[float, dict[str, float]]:
|
||||
def _score_candidate(
|
||||
closes: list[float], volumes: list[float], ticker: dict[str, Any], weights: dict[str, float], concentration: float
|
||||
) -> tuple[float, dict[str, float]]:
|
||||
if len(closes) < 2 or not volumes:
|
||||
return 0.0, {
|
||||
"trend": 0.0,
|
||||
@@ -158,10 +160,7 @@ def scan_opportunities(
|
||||
top_n = int(opportunity_config.get("top_n", 10))
|
||||
quote = str(config.get("market", {}).get("default_quote", "USDT")).upper()
|
||||
held_positions = get_positions(config, spot_client=spot_client)["positions"]
|
||||
concentration_map = {
|
||||
normalize_symbol(item["symbol"]): float(item["notional_usdt"])
|
||||
for item in held_positions
|
||||
}
|
||||
concentration_map = {normalize_symbol(item["symbol"]): float(item["notional_usdt"]) for item in held_positions}
|
||||
total_held = sum(concentration_map.values()) or 1.0
|
||||
|
||||
universe = get_scan_universe(config, spot_client=spot_client, symbols=symbols)[:scan_limit]
|
||||
|
||||
@@ -40,7 +40,9 @@ def _default_dry_run(config: dict[str, Any], dry_run: bool | None) -> bool:
|
||||
return bool(config.get("trading", {}).get("dry_run_default", False))
|
||||
|
||||
|
||||
def _trade_log_payload(intent: TradeIntent, payload: dict[str, Any], *, status: str, error: str | None = None) -> dict[str, Any]:
|
||||
def _trade_log_payload(
|
||||
intent: TradeIntent, payload: dict[str, Any], *, status: str, error: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"market_type": intent.market_type,
|
||||
"symbol": intent.symbol,
|
||||
@@ -125,7 +127,9 @@ def execute_spot_trade(
|
||||
response_payload=response,
|
||||
)
|
||||
)
|
||||
audit_event("trade_filled", {**_trade_log_payload(intent, payload, status="DRY_RUN"), "response_payload": response})
|
||||
audit_event(
|
||||
"trade_filled", {**_trade_log_payload(intent, payload, status="DRY_RUN"), "response_payload": response}
|
||||
)
|
||||
return {"trade": result}
|
||||
|
||||
try:
|
||||
@@ -146,5 +150,7 @@ def execute_spot_trade(
|
||||
response_payload=response,
|
||||
)
|
||||
)
|
||||
audit_event("trade_filled", {**_trade_log_payload(intent, payload, status=result["status"]), "response_payload": response})
|
||||
audit_event(
|
||||
"trade_filled", {**_trade_log_payload(intent, payload, status=result["status"]), "response_payload": response}
|
||||
)
|
||||
return {"trade": result}
|
||||
|
||||
Reference in New Issue
Block a user