test_reporting_tools.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. from __future__ import annotations
  2. import json
  3. import pytest
  4. from exec_mcp import repo, storage
  5. from exec_mcp import services_orders
  6. from exec_mcp.server import report_balance_snapshots, report_recent_trades
  7. @pytest.fixture()
  8. def temp_db(tmp_path, monkeypatch):
  9. db_path = tmp_path / "exec.sqlite3"
  10. monkeypatch.setattr(storage, "DB_PATH", db_path)
  11. storage.init_db()
  12. return db_path
  13. def _create_account() -> str:
  14. repo.create_account(display_name="strategy", venue="bitstamp", venue_account_ref="ref-1", api_key="k", api_secret="s")
  15. return repo.list_accounts(enabled_only=False)[0]["id"]
  16. def test_report_recent_trades_defaults_to_finished_and_sorts_newest_first(temp_db):
  17. account_id = _create_account()
  18. with storage.get_connection() as conn:
  19. rows = [
  20. ("rec-1", "finished", "client-a", "1994292738961401", "2026-04-09T08:20:50+00:00"),
  21. ("rec-2", "cancelled", "client-a", "1994292738961402", "2026-04-09T09:20:50+00:00"),
  22. ("rec-3", "finished", "client-b", "1994292738961403", "2026-04-09T10:20:50+00:00"),
  23. ("rec-4", "open", "client-a", "1994292738961404", "2026-04-09T11:20:50+00:00"),
  24. ("rec-5", "new", "client-a", "1994292738961405", "2026-04-09T12:20:50+00:00"),
  25. ]
  26. for record_id, status, client_id, order_id, stamp in rows:
  27. conn.execute(
  28. """
  29. INSERT INTO order_records
  30. (id, account_id, market, side, order_type, amount, price, expire_time, status, bitstamp_order_id, client_id, client_order_id, raw_json, created_at, updated_at)
  31. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  32. """,
  33. (
  34. record_id,
  35. account_id,
  36. "xrpusd",
  37. "buy",
  38. "limit",
  39. "10",
  40. "2.00",
  41. None,
  42. status,
  43. order_id,
  44. client_id,
  45. None,
  46. json.dumps({"id": order_id, "status": status}),
  47. stamp,
  48. stamp,
  49. ),
  50. )
  51. conn.commit()
  52. result = report_recent_trades(account_id=account_id)
  53. assert result["ok"] is True
  54. assert result["status"] == "finished"
  55. assert result["count"] == 2
  56. assert [row["bitstamp_order_id"] for row in result["orders"]] == ["1994292738961403", "1994292738961401"]
  57. def test_report_recent_trades_supports_open_status_and_client_filter(temp_db):
  58. account_id = _create_account()
  59. with storage.get_connection() as conn:
  60. for idx, (status, client_id, stamp) in enumerate(
  61. [
  62. ("open", "client-a", "2026-04-09T08:20:50+00:00"),
  63. ("new", "client-a", "2026-04-09T09:20:50+00:00"),
  64. ("partially_filled", "client-b", "2026-04-09T10:20:50+00:00"),
  65. ("cancelled", "client-a", "2026-04-09T11:20:50+00:00"),
  66. ],
  67. start=1,
  68. ):
  69. order_id = f"19942927389614{idx}"
  70. conn.execute(
  71. """
  72. INSERT INTO order_records
  73. (id, account_id, market, side, order_type, amount, price, expire_time, status, bitstamp_order_id, client_id, client_order_id, raw_json, created_at, updated_at)
  74. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  75. """,
  76. (
  77. f"rec-{idx}",
  78. account_id,
  79. "xrpusd",
  80. "sell",
  81. "limit",
  82. "10",
  83. "2.00",
  84. None,
  85. status,
  86. order_id,
  87. client_id,
  88. None,
  89. json.dumps({"id": order_id, "status": status}),
  90. stamp,
  91. stamp,
  92. ),
  93. )
  94. conn.commit()
  95. result = report_recent_trades(account_id=account_id, status="open", client_id="client-a", limit=10)
  96. assert result["ok"] is True
  97. assert result["status"] == "open"
  98. assert result["client_id"] == "client-a"
  99. assert [row["status"] for row in result["orders"]] == ["new", "open"]
  100. def test_report_recent_trades_supports_all_statuses_and_client_filter(temp_db):
  101. account_id = _create_account()
  102. with storage.get_connection() as conn:
  103. for idx, (status, client_id, stamp) in enumerate(
  104. [
  105. ("finished", "client-a", "2026-04-09T08:20:50+00:00"),
  106. ("cancelled", "client-a", "2026-04-09T09:20:50+00:00"),
  107. ("finished", "client-b", "2026-04-09T10:20:50+00:00"),
  108. ],
  109. start=1,
  110. ):
  111. order_id = f"19942927389614{idx}"
  112. conn.execute(
  113. """
  114. INSERT INTO order_records
  115. (id, account_id, market, side, order_type, amount, price, expire_time, status, bitstamp_order_id, client_id, client_order_id, raw_json, created_at, updated_at)
  116. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  117. """,
  118. (
  119. f"rec-all-{idx}",
  120. account_id,
  121. "xrpusd",
  122. "sell",
  123. "limit",
  124. "10",
  125. "2.00",
  126. None,
  127. status,
  128. order_id,
  129. client_id,
  130. None,
  131. json.dumps({"id": order_id, "status": status}),
  132. stamp,
  133. stamp,
  134. ),
  135. )
  136. conn.commit()
  137. result = report_recent_trades(account_id=account_id, status="all", client_id="client-a", limit=10)
  138. assert result["ok"] is True
  139. assert result["status"] == "all"
  140. assert result["client_id"] == "client-a"
  141. assert [row["status"] for row in result["orders"]] == ["cancelled", "finished"]
  142. def test_report_balance_snapshots_returns_history_newest_first(temp_db):
  143. account_id = _create_account()
  144. repo.save_account_balance_snapshot(
  145. account_id=account_id,
  146. source_kind="order_finished",
  147. source_ref="order-1",
  148. snapshot={"balances": [{"asset_code": "BTC"}], "total_value_usd": 11250.0},
  149. )
  150. repo.save_account_balance_snapshot(
  151. account_id=account_id,
  152. source_kind="order_finished",
  153. source_ref="order-2",
  154. snapshot={"balances": [{"asset_code": "USD"}], "total_value_usd": 500.0},
  155. )
  156. result = report_balance_snapshots(account_id=account_id)
  157. assert len(result) == 2
  158. assert [row["source_ref"] for row in result] == ["order-2", "order-1"]
  159. assert [row["snapshot"]["balances"][0]["asset_code"] for row in result] == ["USD", "BTC"]
  160. def test_record_order_status_update_persists_raw_json_and_triggers_snapshot_once(temp_db, monkeypatch):
  161. account_id = _create_account()
  162. with storage.get_connection() as conn:
  163. conn.execute(
  164. """
  165. INSERT INTO order_records
  166. (id, account_id, market, side, order_type, amount, price, expire_time, status, bitstamp_order_id, client_id, client_order_id, raw_json, created_at, updated_at)
  167. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  168. """,
  169. (
  170. "rec-1",
  171. account_id,
  172. "xrpusd",
  173. "buy",
  174. "limit",
  175. "10",
  176. "2.00",
  177. None,
  178. "open",
  179. "1994292738961401",
  180. "client-a",
  181. None,
  182. json.dumps({"status": "open"}),
  183. "2026-04-09T08:20:50+00:00",
  184. "2026-04-09T08:20:50+00:00",
  185. ),
  186. )
  187. conn.commit()
  188. snapshot_calls: list[dict] = []
  189. monkeypatch.setattr(
  190. services_orders,
  191. "capture_account_balance_snapshot",
  192. lambda account_id, *, source_kind, source_ref="": snapshot_calls.append(
  193. {"account_id": account_id, "source_kind": source_kind, "source_ref": source_ref}
  194. ),
  195. )
  196. services_orders._record_order_status_update(
  197. bitstamp_order_id="1994292738961401",
  198. status="finished",
  199. raw_json={"id": "1994292738961401", "status": "finished"},
  200. account_id=account_id,
  201. )
  202. with storage.get_connection() as conn:
  203. row = conn.execute(
  204. "SELECT status, raw_json FROM order_records WHERE bitstamp_order_id = ?",
  205. ("1994292738961401",),
  206. ).fetchone()
  207. assert row["status"] == "finished"
  208. assert json.loads(row["raw_json"]) == {"id": "1994292738961401", "status": "finished"}
  209. assert snapshot_calls == [
  210. {"account_id": account_id, "source_kind": "order_finished", "source_ref": "1994292738961401"}
  211. ]
  212. services_orders._record_order_status_update(
  213. bitstamp_order_id="1994292738961401",
  214. status="finished",
  215. raw_json={"id": "1994292738961401", "status": "finished"},
  216. account_id=account_id,
  217. )
  218. assert len(snapshot_calls) == 1