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:
@@ -29,17 +29,52 @@ class FakeSpotClient:
|
||||
|
||||
def ticker_24h(self, symbols=None):
|
||||
rows = {
|
||||
"BTCUSDT": {"symbol": "BTCUSDT", "lastPrice": "60000", "priceChangePercent": "5", "quoteVolume": "9000000", "highPrice": "60200", "lowPrice": "55000"},
|
||||
"ETHUSDT": {"symbol": "ETHUSDT", "lastPrice": "3000", "priceChangePercent": "3", "quoteVolume": "8000000", "highPrice": "3100", "lowPrice": "2800"},
|
||||
"SOLUSDT": {"symbol": "SOLUSDT", "lastPrice": "150", "priceChangePercent": "8", "quoteVolume": "10000000", "highPrice": "152", "lowPrice": "130"},
|
||||
"DOGEUSDT": {"symbol": "DOGEUSDT", "lastPrice": "0.1", "priceChangePercent": "1", "quoteVolume": "100", "highPrice": "0.11", "lowPrice": "0.09"},
|
||||
"BTCUSDT": {
|
||||
"symbol": "BTCUSDT",
|
||||
"lastPrice": "60000",
|
||||
"priceChangePercent": "5",
|
||||
"quoteVolume": "9000000",
|
||||
"highPrice": "60200",
|
||||
"lowPrice": "55000",
|
||||
},
|
||||
"ETHUSDT": {
|
||||
"symbol": "ETHUSDT",
|
||||
"lastPrice": "3000",
|
||||
"priceChangePercent": "3",
|
||||
"quoteVolume": "8000000",
|
||||
"highPrice": "3100",
|
||||
"lowPrice": "2800",
|
||||
},
|
||||
"SOLUSDT": {
|
||||
"symbol": "SOLUSDT",
|
||||
"lastPrice": "150",
|
||||
"priceChangePercent": "8",
|
||||
"quoteVolume": "10000000",
|
||||
"highPrice": "152",
|
||||
"lowPrice": "130",
|
||||
},
|
||||
"DOGEUSDT": {
|
||||
"symbol": "DOGEUSDT",
|
||||
"lastPrice": "0.1",
|
||||
"priceChangePercent": "1",
|
||||
"quoteVolume": "100",
|
||||
"highPrice": "0.11",
|
||||
"lowPrice": "0.09",
|
||||
},
|
||||
}
|
||||
if not symbols:
|
||||
return list(rows.values())
|
||||
return [rows[symbol] for symbol in symbols]
|
||||
|
||||
def exchange_info(self):
|
||||
return {"symbols": [{"symbol": "BTCUSDT", "status": "TRADING"}, {"symbol": "ETHUSDT", "status": "TRADING"}, {"symbol": "SOLUSDT", "status": "TRADING"}, {"symbol": "DOGEUSDT", "status": "TRADING"}]}
|
||||
return {
|
||||
"symbols": [
|
||||
{"symbol": "BTCUSDT", "status": "TRADING"},
|
||||
{"symbol": "ETHUSDT", "status": "TRADING"},
|
||||
{"symbol": "SOLUSDT", "status": "TRADING"},
|
||||
{"symbol": "DOGEUSDT", "status": "TRADING"},
|
||||
]
|
||||
}
|
||||
|
||||
def klines(self, symbol, interval, limit):
|
||||
curves = {
|
||||
@@ -50,7 +85,18 @@ class FakeSpotClient:
|
||||
}[symbol]
|
||||
rows = []
|
||||
for index, close in enumerate(curves[-limit:]):
|
||||
rows.append([index, close * 0.98, close * 1.01, close * 0.97, close, 100 + index * 10, index + 1, close * (100 + index * 10)])
|
||||
rows.append(
|
||||
[
|
||||
index,
|
||||
close * 0.98,
|
||||
close * 1.01,
|
||||
close * 0.97,
|
||||
close,
|
||||
100 + index * 10,
|
||||
index + 1,
|
||||
close * (100 + index * 10),
|
||||
]
|
||||
)
|
||||
return rows
|
||||
|
||||
|
||||
@@ -85,7 +131,9 @@ class OpportunityServiceTestCase(unittest.TestCase):
|
||||
|
||||
def test_scan_is_deterministic(self):
|
||||
with patch.object(opportunity_service, "audit_event", return_value=None):
|
||||
payload = opportunity_service.scan_opportunities(self.config | {"opportunity": self.config["opportunity"] | {"top_n": 2}}, spot_client=FakeSpotClient())
|
||||
payload = opportunity_service.scan_opportunities(
|
||||
self.config | {"opportunity": self.config["opportunity"] | {"top_n": 2}}, spot_client=FakeSpotClient()
|
||||
)
|
||||
self.assertEqual([item["symbol"] for item in payload["recommendations"]], ["SOLUSDT", "BTCUSDT"])
|
||||
|
||||
def test_score_candidate_handles_empty_klines(self):
|
||||
|
||||
Reference in New Issue
Block a user