- 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>
96 lines
3.2 KiB
Python
96 lines
3.2 KiB
Python
"""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 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,
|
|
spot_client=FakeSpotClient(),
|
|
)
|
|
self.assertEqual(payload["overview"]["total_equity_usdt"], 720.1)
|
|
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"])
|