test_metals.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. from datetime import datetime, timezone, timedelta
  2. from src.metals_mcp import mcp_tools
  3. from src.metals_mcp import poller as poller_module
  4. from src.metals_mcp.mcp_tools import client, get_capabilities, get_candles, get_last_candle, get_market_snapshot, get_price, get_regime
  5. from src.metals_mcp.storage import init_db, last_candle, prune_candles_older_than, upsert_candle
  6. def test_capabilities():
  7. caps = get_capabilities()
  8. assert caps["server"] == "metals-mcp"
  9. def test_scaffold_tools(tmp_path, monkeypatch):
  10. db_path = tmp_path / "metals.sqlite3"
  11. init_db(db_path)
  12. monkeypatch.setattr(mcp_tools, "DB_PATH", db_path)
  13. assert get_price("XAU/USD")["symbol"] == "XAU/USD"
  14. assert get_candles("XAU/USD")["candles"] == []
  15. assert get_last_candle("XAU/USD")["candle"] is None
  16. def test_price_supports_counter_currency(monkeypatch):
  17. monkeypatch.setattr(client, "pair_is_supported", lambda symbol, counter_currency=None: True)
  18. class DummyQuote:
  19. mid = 4049.0
  20. timestamp = 1234567890
  21. monkeypatch.setattr(client, "fetch_quote", lambda pair: DummyQuote())
  22. quote = get_price("XAU", counter_currency="EUR")
  23. assert quote["pair"] == "XAU/EUR"
  24. assert quote["counter_currency"] == "EUR"
  25. assert quote["price"] == 4049.0
  26. def test_retention_prunes_old_candles(tmp_path):
  27. db_path = tmp_path / "metals.sqlite3"
  28. init_db(db_path)
  29. now_ms = int(datetime.now(timezone.utc).timestamp() * 1000)
  30. upsert_candle(
  31. db_path,
  32. {
  33. "symbol": "XAU/USD",
  34. "timeframe": "5m",
  35. "open": 4000.0,
  36. "high": 4010.0,
  37. "low": 3990.0,
  38. "close": 4005.0,
  39. "start_ts": now_ms - int(timedelta(days=40).total_seconds() * 1000),
  40. "end_ts": now_ms - int(timedelta(days=40).total_seconds() * 1000) + 300000,
  41. },
  42. )
  43. upsert_candle(
  44. db_path,
  45. {
  46. "symbol": "XAU/USD",
  47. "timeframe": "5m",
  48. "open": 4050.0,
  49. "high": 4060.0,
  50. "low": 4040.0,
  51. "close": 4055.0,
  52. "start_ts": now_ms,
  53. "end_ts": now_ms + 300000,
  54. },
  55. )
  56. deleted = prune_candles_older_than(db_path, 30)
  57. assert deleted == 1
  58. def test_poller_persists_current_candle(tmp_path, monkeypatch):
  59. db_path = tmp_path / "metals.sqlite3"
  60. init_db(db_path)
  61. monkeypatch.setattr(poller_module, "DB_PATH", db_path)
  62. monkeypatch.setattr(poller_module, "METALS_PAIRS", ["XAU/USD"])
  63. class DummyQuote:
  64. mid = 4050.0
  65. timestamp = int(datetime.now(timezone.utc).timestamp() * 1000)
  66. poller = poller_module.CandlePoller()
  67. monkeypatch.setattr(poller.client, "fetch_quote", lambda symbol: DummyQuote())
  68. poller.step()
  69. candle = last_candle(db_path, "XAU/USD", "5m")
  70. assert candle is not None
  71. assert candle["close"] == 4050.0
  72. def test_snapshot_and_regime_use_recent_candles(tmp_path, monkeypatch):
  73. db_path = tmp_path / "metals.sqlite3"
  74. init_db(db_path)
  75. monkeypatch.setattr(mcp_tools, "DB_PATH", db_path)
  76. base_ts = 1_700_000_000_000
  77. for idx, close in enumerate((4000.0, 4010.0, 4020.0, 4035.0, 4050.0)):
  78. upsert_candle(
  79. db_path,
  80. {
  81. "symbol": "XAU/USD",
  82. "timeframe": "5m",
  83. "open": close - 5,
  84. "high": close + 12,
  85. "low": close - 12,
  86. "close": close,
  87. "start_ts": base_ts + idx * 300000,
  88. "end_ts": base_ts + (idx + 1) * 300000,
  89. },
  90. )
  91. market_snapshot = get_market_snapshot("XAU/USD")
  92. assert market_snapshot["status"] == "ok"
  93. assert market_snapshot["components"]["trend_pct"] is not None
  94. regime = get_regime("XAU/USD")
  95. assert regime["status"] == "ok"
  96. assert regime["regime"] in {"bullish", "neutral", "compression"}
  97. rsi = mcp_tools.get_indicator("XAU/USD", "rsi", params={"period": 3})
  98. assert rsi["status"] == "ok"