refactor: rewrite to CoinHunter V2 flat architecture

Replace the V1 commands/services split with a flat, direct architecture:
- cli.py dispatches directly to service functions
- New services: account, market, trade, opportunity
- Thin Binance wrappers: spot_client, um_futures_client
- Add audit logging, runtime paths, and TOML config
- Remove legacy V1 code: commands/, precheck, review engine, smart executor
- Add ruff + mypy toolchain and fix edge cases in trade params

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 17:22:29 +08:00
parent 3819e35a7b
commit 52cd76a750
78 changed files with 2023 additions and 5407 deletions

View File

@@ -0,0 +1,80 @@
"""Account and market service tests."""
from __future__ import annotations
import unittest
from coinhunter.services import account_service, market_service
class FakeSpotClient:
def account_info(self):
return {
"balances": [
{"asset": "USDT", "free": "120.0", "locked": "0"},
{"asset": "BTC", "free": "0.01", "locked": "0"},
{"asset": "DOGE", "free": "1", "locked": "0"},
]
}
def ticker_price(self, symbols=None):
prices = {
"BTCUSDT": {"symbol": "BTCUSDT", "price": "60000"},
"DOGEUSDT": {"symbol": "DOGEUSDT", "price": "0.1"},
}
if not symbols:
return list(prices.values())
return [prices[symbol] for symbol in symbols]
def ticker_24h(self, symbols=None):
rows = [
{"symbol": "BTCUSDT", "lastPrice": "60000", "priceChangePercent": "4.5", "quoteVolume": "10000000", "highPrice": "61000", "lowPrice": "58000"},
{"symbol": "ETHUSDT", "lastPrice": "3000", "priceChangePercent": "3.0", "quoteVolume": "8000000", "highPrice": "3050", "lowPrice": "2900"},
{"symbol": "DOGEUSDT", "lastPrice": "0.1", "priceChangePercent": "1.0", "quoteVolume": "200", "highPrice": "0.11", "lowPrice": "0.09"},
]
if not symbols:
return rows
wanted = set(symbols)
return [row for row in rows if row["symbol"] in wanted]
def exchange_info(self):
return {"symbols": [{"symbol": "BTCUSDT", "status": "TRADING"}, {"symbol": "ETHUSDT", "status": "TRADING"}, {"symbol": "DOGEUSDT", "status": "BREAK"}]}
class FakeFuturesClient:
def balance(self):
return [{"asset": "USDT", "balance": "250.0", "availableBalance": "200.0"}]
def position_risk(self, symbol=None):
return [{"symbol": "BTCUSDT", "positionAmt": "0.02", "notional": "1200", "entryPrice": "59000", "markPrice": "60000", "unRealizedProfit": "20"}]
class AccountMarketServicesTestCase(unittest.TestCase):
def test_account_overview_and_dust_filter(self):
config = {
"market": {"default_quote": "USDT"},
"trading": {"dust_usdt_threshold": 10.0},
}
payload = account_service.get_overview(
config,
include_spot=True,
include_futures=True,
spot_client=FakeSpotClient(),
futures_client=FakeFuturesClient(),
)
self.assertEqual(payload["overview"]["spot_equity_usdt"], 720.1)
self.assertEqual(payload["overview"]["futures_equity_usdt"], 250.0)
symbols = {item["symbol"] for item in payload["positions"]}
self.assertNotIn("DOGEUSDT", symbols)
self.assertIn("BTCUSDT", symbols)
def test_market_tickers_and_scan_universe(self):
config = {
"market": {"default_quote": "USDT", "universe_allowlist": [], "universe_denylist": []},
"opportunity": {"min_quote_volume": 1000},
}
tickers = market_service.get_tickers(config, ["btc/usdt", "ETH-USDT"], spot_client=FakeSpotClient())
self.assertEqual([item["symbol"] for item in tickers["tickers"]], ["BTCUSDT", "ETHUSDT"])
universe = market_service.get_scan_universe(config, spot_client=FakeSpotClient())
self.assertEqual([item["symbol"] for item in universe], ["BTCUSDT", "ETHUSDT"])