@scriptmasterlabs/mcp-x402 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/.env.example +35 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/keepalive.yml +31 -0
  4. package/.well-known/agentcard.json +34 -0
  5. package/CONTRIBUTING.md +76 -0
  6. package/Dockerfile +19 -0
  7. package/LICENSE +21 -0
  8. package/README.md +304 -0
  9. package/agents.json +67 -0
  10. package/dist/lib/chains/base.d.ts +10 -0
  11. package/dist/lib/chains/base.d.ts.map +1 -0
  12. package/dist/lib/chains/base.js +73 -0
  13. package/dist/lib/chains/base.js.map +1 -0
  14. package/dist/lib/chains/solana.d.ts +10 -0
  15. package/dist/lib/chains/solana.d.ts.map +1 -0
  16. package/dist/lib/chains/solana.js +49 -0
  17. package/dist/lib/chains/solana.js.map +1 -0
  18. package/dist/lib/chains/xrpl.d.ts +10 -0
  19. package/dist/lib/chains/xrpl.d.ts.map +1 -0
  20. package/dist/lib/chains/xrpl.js +55 -0
  21. package/dist/lib/chains/xrpl.js.map +1 -0
  22. package/dist/lib/credit/bureau.d.ts +10 -0
  23. package/dist/lib/credit/bureau.d.ts.map +1 -0
  24. package/dist/lib/credit/bureau.js +58 -0
  25. package/dist/lib/credit/bureau.js.map +1 -0
  26. package/dist/lib/sml-api/agentcard.d.ts +17 -0
  27. package/dist/lib/sml-api/agentcard.d.ts.map +1 -0
  28. package/dist/lib/sml-api/agentcard.js +30 -0
  29. package/dist/lib/sml-api/agentcard.js.map +1 -0
  30. package/dist/lib/sml-api/backtest.d.ts +22 -0
  31. package/dist/lib/sml-api/backtest.d.ts.map +1 -0
  32. package/dist/lib/sml-api/backtest.js +28 -0
  33. package/dist/lib/sml-api/backtest.js.map +1 -0
  34. package/dist/lib/sml-api/brokers.d.ts +40 -0
  35. package/dist/lib/sml-api/brokers.d.ts.map +1 -0
  36. package/dist/lib/sml-api/brokers.js +128 -0
  37. package/dist/lib/sml-api/brokers.js.map +1 -0
  38. package/dist/lib/sml-api/copytrader.d.ts +11 -0
  39. package/dist/lib/sml-api/copytrader.d.ts.map +1 -0
  40. package/dist/lib/sml-api/copytrader.js +30 -0
  41. package/dist/lib/sml-api/copytrader.js.map +1 -0
  42. package/dist/lib/sml-api/crawl.d.ts +20 -0
  43. package/dist/lib/sml-api/crawl.d.ts.map +1 -0
  44. package/dist/lib/sml-api/crawl.js +32 -0
  45. package/dist/lib/sml-api/crawl.js.map +1 -0
  46. package/dist/lib/sml-api/echo.d.ts +10 -0
  47. package/dist/lib/sml-api/echo.d.ts.map +1 -0
  48. package/dist/lib/sml-api/echo.js +23 -0
  49. package/dist/lib/sml-api/echo.js.map +1 -0
  50. package/dist/lib/sml-api/forge.d.ts +11 -0
  51. package/dist/lib/sml-api/forge.d.ts.map +1 -0
  52. package/dist/lib/sml-api/forge.js +29 -0
  53. package/dist/lib/sml-api/forge.js.map +1 -0
  54. package/dist/lib/sml-api/ftd.d.ts +18 -0
  55. package/dist/lib/sml-api/ftd.d.ts.map +1 -0
  56. package/dist/lib/sml-api/ftd.js +43 -0
  57. package/dist/lib/sml-api/ftd.js.map +1 -0
  58. package/dist/lib/sml-api/ghost.d.ts +13 -0
  59. package/dist/lib/sml-api/ghost.d.ts.map +1 -0
  60. package/dist/lib/sml-api/ghost.js +29 -0
  61. package/dist/lib/sml-api/ghost.js.map +1 -0
  62. package/dist/lib/sml-api/launchpad.d.ts +20 -0
  63. package/dist/lib/sml-api/launchpad.d.ts.map +1 -0
  64. package/dist/lib/sml-api/launchpad.js +31 -0
  65. package/dist/lib/sml-api/launchpad.js.map +1 -0
  66. package/dist/lib/sml-api/leviathan.d.ts +22 -0
  67. package/dist/lib/sml-api/leviathan.d.ts.map +1 -0
  68. package/dist/lib/sml-api/leviathan.js +33 -0
  69. package/dist/lib/sml-api/leviathan.js.map +1 -0
  70. package/dist/lib/sml-api/nexus.d.ts +18 -0
  71. package/dist/lib/sml-api/nexus.d.ts.map +1 -0
  72. package/dist/lib/sml-api/nexus.js +40 -0
  73. package/dist/lib/sml-api/nexus.js.map +1 -0
  74. package/dist/lib/sml-api/proof402.d.ts +6 -0
  75. package/dist/lib/sml-api/proof402.d.ts.map +1 -0
  76. package/dist/lib/sml-api/proof402.js +30 -0
  77. package/dist/lib/sml-api/proof402.js.map +1 -0
  78. package/dist/lib/sml-api/rails.d.ts +12 -0
  79. package/dist/lib/sml-api/rails.d.ts.map +1 -0
  80. package/dist/lib/sml-api/rails.js +29 -0
  81. package/dist/lib/sml-api/rails.js.map +1 -0
  82. package/dist/lib/sml-api/shadow.d.ts +15 -0
  83. package/dist/lib/sml-api/shadow.d.ts.map +1 -0
  84. package/dist/lib/sml-api/shadow.js +27 -0
  85. package/dist/lib/sml-api/shadow.js.map +1 -0
  86. package/dist/lib/sml-api/squeezeos.d.ts +21 -0
  87. package/dist/lib/sml-api/squeezeos.d.ts.map +1 -0
  88. package/dist/lib/sml-api/squeezeos.js +97 -0
  89. package/dist/lib/sml-api/squeezeos.js.map +1 -0
  90. package/dist/lib/sml-api/xdeo.d.ts +13 -0
  91. package/dist/lib/sml-api/xdeo.d.ts.map +1 -0
  92. package/dist/lib/sml-api/xdeo.js +34 -0
  93. package/dist/lib/sml-api/xdeo.js.map +1 -0
  94. package/dist/lib/sml-api/xmit.d.ts +13 -0
  95. package/dist/lib/sml-api/xmit.d.ts.map +1 -0
  96. package/dist/lib/sml-api/xmit.js +34 -0
  97. package/dist/lib/sml-api/xmit.js.map +1 -0
  98. package/dist/server/health.d.ts +16 -0
  99. package/dist/server/health.d.ts.map +1 -0
  100. package/dist/server/health.js +39 -0
  101. package/dist/server/health.js.map +1 -0
  102. package/dist/server/index.d.ts +3 -0
  103. package/dist/server/index.d.ts.map +1 -0
  104. package/dist/server/index.js +193 -0
  105. package/dist/server/index.js.map +1 -0
  106. package/dist/server/payments/ap2.d.ts +17 -0
  107. package/dist/server/payments/ap2.d.ts.map +1 -0
  108. package/dist/server/payments/ap2.js +75 -0
  109. package/dist/server/payments/ap2.js.map +1 -0
  110. package/dist/server/payments/receipt.d.ts +28 -0
  111. package/dist/server/payments/receipt.d.ts.map +1 -0
  112. package/dist/server/payments/receipt.js +60 -0
  113. package/dist/server/payments/receipt.js.map +1 -0
  114. package/dist/server/payments/router.d.ts +23 -0
  115. package/dist/server/payments/router.d.ts.map +1 -0
  116. package/dist/server/payments/router.js +69 -0
  117. package/dist/server/payments/router.js.map +1 -0
  118. package/dist/server/payments/wallet.d.ts +18 -0
  119. package/dist/server/payments/wallet.d.ts.map +1 -0
  120. package/dist/server/payments/wallet.js +107 -0
  121. package/dist/server/payments/wallet.js.map +1 -0
  122. package/dist/server/payments/x402.d.ts +29 -0
  123. package/dist/server/payments/x402.d.ts.map +1 -0
  124. package/dist/server/payments/x402.js +122 -0
  125. package/dist/server/payments/x402.js.map +1 -0
  126. package/dist/server/registry/catalog.d.ts +12 -0
  127. package/dist/server/registry/catalog.d.ts.map +1 -0
  128. package/dist/server/registry/catalog.js +55 -0
  129. package/dist/server/registry/catalog.js.map +1 -0
  130. package/dist/server/registry/discovery.d.ts +16 -0
  131. package/dist/server/registry/discovery.d.ts.map +1 -0
  132. package/dist/server/registry/discovery.js +33 -0
  133. package/dist/server/registry/discovery.js.map +1 -0
  134. package/dist/server/registry/pricing.d.ts +10 -0
  135. package/dist/server/registry/pricing.d.ts.map +1 -0
  136. package/dist/server/registry/pricing.js +66 -0
  137. package/dist/server/registry/pricing.js.map +1 -0
  138. package/dist/server/security/acl.d.ts +28 -0
  139. package/dist/server/security/acl.d.ts.map +1 -0
  140. package/dist/server/security/acl.js +36 -0
  141. package/dist/server/security/acl.js.map +1 -0
  142. package/dist/server/security/audit.d.ts +15 -0
  143. package/dist/server/security/audit.d.ts.map +1 -0
  144. package/dist/server/security/audit.js +77 -0
  145. package/dist/server/security/audit.js.map +1 -0
  146. package/dist/server/security/rate-limit.d.ts +12 -0
  147. package/dist/server/security/rate-limit.d.ts.map +1 -0
  148. package/dist/server/security/rate-limit.js +72 -0
  149. package/dist/server/security/rate-limit.js.map +1 -0
  150. package/dist/server/security/sandbox.d.ts +7 -0
  151. package/dist/server/security/sandbox.d.ts.map +1 -0
  152. package/dist/server/security/sandbox.js +42 -0
  153. package/dist/server/security/sandbox.js.map +1 -0
  154. package/dist/server/tools/agentcard.d.ts +3 -0
  155. package/dist/server/tools/agentcard.d.ts.map +1 -0
  156. package/dist/server/tools/agentcard.js +118 -0
  157. package/dist/server/tools/agentcard.js.map +1 -0
  158. package/dist/server/tools/backtest.d.ts +3 -0
  159. package/dist/server/tools/backtest.d.ts.map +1 -0
  160. package/dist/server/tools/backtest.js +112 -0
  161. package/dist/server/tools/backtest.js.map +1 -0
  162. package/dist/server/tools/brokers.d.ts +3 -0
  163. package/dist/server/tools/brokers.d.ts.map +1 -0
  164. package/dist/server/tools/brokers.js +223 -0
  165. package/dist/server/tools/brokers.js.map +1 -0
  166. package/dist/server/tools/copytrader.d.ts +3 -0
  167. package/dist/server/tools/copytrader.d.ts.map +1 -0
  168. package/dist/server/tools/copytrader.js +90 -0
  169. package/dist/server/tools/copytrader.js.map +1 -0
  170. package/dist/server/tools/crawl.d.ts +3 -0
  171. package/dist/server/tools/crawl.d.ts.map +1 -0
  172. package/dist/server/tools/crawl.js +60 -0
  173. package/dist/server/tools/crawl.js.map +1 -0
  174. package/dist/server/tools/discovery.d.ts +3 -0
  175. package/dist/server/tools/discovery.d.ts.map +1 -0
  176. package/dist/server/tools/discovery.js +188 -0
  177. package/dist/server/tools/discovery.js.map +1 -0
  178. package/dist/server/tools/echo.d.ts +3 -0
  179. package/dist/server/tools/echo.d.ts.map +1 -0
  180. package/dist/server/tools/echo.js +48 -0
  181. package/dist/server/tools/echo.js.map +1 -0
  182. package/dist/server/tools/forge.d.ts +3 -0
  183. package/dist/server/tools/forge.d.ts.map +1 -0
  184. package/dist/server/tools/forge.js +77 -0
  185. package/dist/server/tools/forge.js.map +1 -0
  186. package/dist/server/tools/ftd.d.ts +3 -0
  187. package/dist/server/tools/ftd.d.ts.map +1 -0
  188. package/dist/server/tools/ftd.js +70 -0
  189. package/dist/server/tools/ftd.js.map +1 -0
  190. package/dist/server/tools/ghost.d.ts +3 -0
  191. package/dist/server/tools/ghost.d.ts.map +1 -0
  192. package/dist/server/tools/ghost.js +83 -0
  193. package/dist/server/tools/ghost.js.map +1 -0
  194. package/dist/server/tools/index.d.ts +3 -0
  195. package/dist/server/tools/index.d.ts.map +1 -0
  196. package/dist/server/tools/index.js +44 -0
  197. package/dist/server/tools/index.js.map +1 -0
  198. package/dist/server/tools/launchpad.d.ts +3 -0
  199. package/dist/server/tools/launchpad.d.ts.map +1 -0
  200. package/dist/server/tools/launchpad.js +151 -0
  201. package/dist/server/tools/launchpad.js.map +1 -0
  202. package/dist/server/tools/leviathan.d.ts +3 -0
  203. package/dist/server/tools/leviathan.d.ts.map +1 -0
  204. package/dist/server/tools/leviathan.js +73 -0
  205. package/dist/server/tools/leviathan.js.map +1 -0
  206. package/dist/server/tools/nexus.d.ts +3 -0
  207. package/dist/server/tools/nexus.d.ts.map +1 -0
  208. package/dist/server/tools/nexus.js +65 -0
  209. package/dist/server/tools/nexus.js.map +1 -0
  210. package/dist/server/tools/proof402.d.ts +3 -0
  211. package/dist/server/tools/proof402.d.ts.map +1 -0
  212. package/dist/server/tools/proof402.js +74 -0
  213. package/dist/server/tools/proof402.js.map +1 -0
  214. package/dist/server/tools/rails.d.ts +3 -0
  215. package/dist/server/tools/rails.d.ts.map +1 -0
  216. package/dist/server/tools/rails.js +82 -0
  217. package/dist/server/tools/rails.js.map +1 -0
  218. package/dist/server/tools/shadow.d.ts +3 -0
  219. package/dist/server/tools/shadow.d.ts.map +1 -0
  220. package/dist/server/tools/shadow.js +114 -0
  221. package/dist/server/tools/shadow.js.map +1 -0
  222. package/dist/server/tools/squeezeos.d.ts +3 -0
  223. package/dist/server/tools/squeezeos.d.ts.map +1 -0
  224. package/dist/server/tools/squeezeos.js +231 -0
  225. package/dist/server/tools/squeezeos.js.map +1 -0
  226. package/dist/server/tools/xdeo.d.ts +3 -0
  227. package/dist/server/tools/xdeo.d.ts.map +1 -0
  228. package/dist/server/tools/xdeo.js +58 -0
  229. package/dist/server/tools/xdeo.js.map +1 -0
  230. package/dist/server/tools/xmit.d.ts +3 -0
  231. package/dist/server/tools/xmit.d.ts.map +1 -0
  232. package/dist/server/tools/xmit.js +59 -0
  233. package/dist/server/tools/xmit.js.map +1 -0
  234. package/docker-compose.yml +50 -0
  235. package/llms.txt +70 -0
  236. package/package.json +77 -0
  237. package/render.yaml +39 -0
  238. package/sdk/mcp-x402-sdk/package.json +18 -0
  239. package/sdk/mcp-x402-sdk/src/index.ts +118 -0
  240. package/sdk/mcp-x402-sdk/tsconfig.json +14 -0
  241. package/server.json +60 -0
  242. package/services/backtest_service.py +176 -0
  243. package/src/lib/chains/base.ts +77 -0
  244. package/src/lib/chains/solana.ts +59 -0
  245. package/src/lib/chains/xrpl.ts +63 -0
  246. package/src/lib/credit/bureau.ts +65 -0
  247. package/src/lib/sml-api/agentcard.ts +40 -0
  248. package/src/lib/sml-api/backtest.ts +47 -0
  249. package/src/lib/sml-api/brokers.ts +160 -0
  250. package/src/lib/sml-api/copytrader.ts +33 -0
  251. package/src/lib/sml-api/crawl.ts +44 -0
  252. package/src/lib/sml-api/echo.ts +28 -0
  253. package/src/lib/sml-api/forge.ts +33 -0
  254. package/src/lib/sml-api/ftd.ts +53 -0
  255. package/src/lib/sml-api/ghost.ts +35 -0
  256. package/src/lib/sml-api/launchpad.ts +43 -0
  257. package/src/lib/sml-api/leviathan.ts +49 -0
  258. package/src/lib/sml-api/nexus.ts +50 -0
  259. package/src/lib/sml-api/proof402.ts +27 -0
  260. package/src/lib/sml-api/rails.ts +34 -0
  261. package/src/lib/sml-api/shadow.ts +35 -0
  262. package/src/lib/sml-api/squeezeos.ts +95 -0
  263. package/src/lib/sml-api/xdeo.ts +40 -0
  264. package/src/lib/sml-api/xmit.ts +40 -0
  265. package/src/server/health.ts +52 -0
  266. package/src/server/index.ts +206 -0
  267. package/src/server/payments/ap2.ts +99 -0
  268. package/src/server/payments/receipt.ts +85 -0
  269. package/src/server/payments/router.ts +110 -0
  270. package/src/server/payments/wallet.ts +123 -0
  271. package/src/server/payments/x402.ts +162 -0
  272. package/src/server/registry/catalog.ts +61 -0
  273. package/src/server/registry/discovery.ts +39 -0
  274. package/src/server/registry/pricing.ts +76 -0
  275. package/src/server/security/acl.ts +42 -0
  276. package/src/server/security/audit.ts +94 -0
  277. package/src/server/security/rate-limit.ts +84 -0
  278. package/src/server/security/sandbox.ts +40 -0
  279. package/src/server/tools/agentcard.ts +134 -0
  280. package/src/server/tools/backtest.ts +119 -0
  281. package/src/server/tools/brokers.ts +250 -0
  282. package/src/server/tools/copytrader.ts +104 -0
  283. package/src/server/tools/crawl.ts +70 -0
  284. package/src/server/tools/discovery.ts +202 -0
  285. package/src/server/tools/echo.ts +58 -0
  286. package/src/server/tools/forge.ts +87 -0
  287. package/src/server/tools/ftd.ts +88 -0
  288. package/src/server/tools/ghost.ts +93 -0
  289. package/src/server/tools/index.ts +42 -0
  290. package/src/server/tools/launchpad.ts +173 -0
  291. package/src/server/tools/leviathan.ts +81 -0
  292. package/src/server/tools/nexus.ts +76 -0
  293. package/src/server/tools/proof402.ts +87 -0
  294. package/src/server/tools/rails.ts +92 -0
  295. package/src/server/tools/shadow.ts +128 -0
  296. package/src/server/tools/squeezeos.ts +312 -0
  297. package/src/server/tools/xdeo.ts +67 -0
  298. package/src/server/tools/xmit.ts +68 -0
  299. package/tests/integration/e2e.test.ts +51 -0
  300. package/tests/unit/payments.test.ts +49 -0
  301. package/tests/unit/security.test.ts +92 -0
  302. package/tests/unit/tools.test.ts +42 -0
  303. package/tsconfig.json +21 -0
  304. package/vitest.config.ts +20 -0
@@ -0,0 +1,176 @@
1
+ """
2
+ Backtest microservice — wraps backtester-mcp + yfinance
3
+ POST /backtest { ticker, strategy_signals, lookback_days, fees, slippage }
4
+ POST /validate { ticker, lookback_days, train_ratio } — walk-forward split
5
+ GET /health
6
+ """
7
+
8
+ from __future__ import annotations
9
+ import os
10
+ import json
11
+ import numpy as np
12
+ from datetime import datetime, timedelta
13
+ from typing import Any
14
+
15
+ from flask import Flask, request, jsonify
16
+ import backtester_mcp as bt
17
+
18
+ # yfinance is the free price source — no API key required
19
+ try:
20
+ import yfinance as yf
21
+ YF_AVAILABLE = True
22
+ except ImportError:
23
+ YF_AVAILABLE = False
24
+
25
+ app = Flask(__name__)
26
+
27
+ # ── helpers ──────────────────────────────────────────────────────────────────
28
+
29
+ def _fetch_prices(ticker: str, days: int) -> np.ndarray:
30
+ if not YF_AVAILABLE:
31
+ raise RuntimeError("yfinance not installed")
32
+ end = datetime.utcnow()
33
+ start = end - timedelta(days=days + 30) # buffer for weekends/holidays
34
+ df = yf.download(ticker, start=start.strftime("%Y-%m-%d"),
35
+ end=end.strftime("%Y-%m-%d"), progress=False, auto_adjust=True)
36
+ if df.empty:
37
+ raise ValueError(f"No price data for {ticker}")
38
+ return df["Close"].dropna().values[-days:]
39
+
40
+
41
+ def _momentum_signals(prices: np.ndarray, window: int = 10, threshold: float = 0.001) -> np.ndarray:
42
+ """Default long-only momentum signal for validation."""
43
+ returns = np.diff(np.log(prices))
44
+ mom = np.convolve(returns, np.ones(window) / window, mode="same")
45
+ signals = np.zeros(len(prices))
46
+ signals[1:] = np.where(mom[:-1] > threshold, 1, 0)
47
+ return signals
48
+
49
+
50
+ def _run_backtest(prices: np.ndarray, signals: np.ndarray,
51
+ fees: float, slippage: float) -> dict[str, Any]:
52
+ result = bt.backtest(prices, signals, fees=fees, slippage=slippage)
53
+ m = result.metrics
54
+ return {
55
+ "sharpe": round(m["sharpe"], 3),
56
+ "sortino": round(m["sortino"], 3),
57
+ "cagr": round(m["cagr"], 4),
58
+ "total_return": round(m["total_return"], 4),
59
+ "max_drawdown": round(m["max_drawdown"], 4),
60
+ "max_drawdown_duration_days": int(m["max_drawdown_duration"]),
61
+ "win_rate": round(m["win_rate"], 4),
62
+ "profit_factor": round(m["profit_factor"], 3),
63
+ "calmar": round(m["calmar"], 3),
64
+ "volatility": round(m["volatility"], 4),
65
+ "num_trades": int(m["num_trades"]),
66
+ }
67
+
68
+
69
+ # ── routes ───────────────────────────────────────────────────────────────────
70
+
71
+ @app.get("/health")
72
+ def health():
73
+ return jsonify({
74
+ "status": "ok",
75
+ "engine": f"backtester-mcp v{bt.__version__}",
76
+ "yfinance": YF_AVAILABLE,
77
+ "timestamp": datetime.utcnow().isoformat() + "Z",
78
+ })
79
+
80
+
81
+ @app.post("/backtest")
82
+ def backtest():
83
+ body = request.get_json(force=True)
84
+ ticker: str = body.get("ticker", "").upper()
85
+ custom_signals: list | None = body.get("signals") # optional float array
86
+ lookback: int = int(body.get("lookback_days", 252))
87
+ fees: float = float(body.get("fees", 0.001))
88
+ slippage: float = float(body.get("slippage", 0.0005))
89
+ window: int = int(body.get("momentum_window", 10))
90
+ threshold: float = float(body.get("momentum_threshold", 0.001))
91
+
92
+ if not ticker:
93
+ return jsonify({"error": "ticker required"}), 400
94
+
95
+ try:
96
+ prices = _fetch_prices(ticker, lookback)
97
+ except Exception as e:
98
+ return jsonify({"error": str(e)}), 422
99
+
100
+ if custom_signals:
101
+ signals = np.array(custom_signals, dtype=float)
102
+ if len(signals) != len(prices):
103
+ return jsonify({"error": f"signals length {len(signals)} != prices length {len(prices)}"}), 400
104
+ else:
105
+ signals = _momentum_signals(prices, window=window, threshold=threshold)
106
+
107
+ try:
108
+ metrics = _run_backtest(prices, signals, fees, slippage)
109
+ except Exception as e:
110
+ return jsonify({"error": str(e)}), 500
111
+
112
+ verdict = "ROBUST" if metrics["sharpe"] > 1.5 and metrics["max_drawdown"] > -0.25 else \
113
+ "MODERATE" if metrics["sharpe"] > 0.8 else \
114
+ "WEAK" if metrics["sharpe"] > 0 else "OVERFITTED"
115
+
116
+ return jsonify({
117
+ "ticker": ticker,
118
+ "lookback_days": len(prices),
119
+ "fees": fees,
120
+ "slippage": slippage,
121
+ "metrics": metrics,
122
+ "verdict": verdict,
123
+ "engine": f"backtester-mcp v{bt.__version__}",
124
+ })
125
+
126
+
127
+ @app.post("/validate")
128
+ def walk_forward():
129
+ """Walk-forward OOS validation — splits data into train/test."""
130
+ body = request.get_json(force=True)
131
+ ticker: str = body.get("ticker", "").upper()
132
+ lookback: int = int(body.get("lookback_days", 504))
133
+ train_ratio: float = float(body.get("train_ratio", 0.7))
134
+ fees: float = float(body.get("fees", 0.001))
135
+ slippage: float = float(body.get("slippage", 0.0005))
136
+
137
+ if not ticker:
138
+ return jsonify({"error": "ticker required"}), 400
139
+
140
+ try:
141
+ prices = _fetch_prices(ticker, lookback)
142
+ except Exception as e:
143
+ return jsonify({"error": str(e)}), 422
144
+
145
+ split = int(len(prices) * train_ratio)
146
+ train_prices = prices[:split]
147
+ oos_prices = prices[split:]
148
+
149
+ train_signals = _momentum_signals(train_prices)
150
+ oos_signals = _momentum_signals(oos_prices)
151
+
152
+ try:
153
+ train_metrics = _run_backtest(train_prices, train_signals, fees, slippage)
154
+ oos_metrics = _run_backtest(oos_prices, oos_signals, fees, slippage)
155
+ except Exception as e:
156
+ return jsonify({"error": str(e)}), 500
157
+
158
+ # Deflated Sharpe — penalize if OOS degrades significantly
159
+ sharpe_degradation = train_metrics["sharpe"] - oos_metrics["sharpe"]
160
+ oos_verdict = "PASS" if oos_metrics["sharpe"] > 0.5 and sharpe_degradation < 1.5 else "FAIL"
161
+
162
+ return jsonify({
163
+ "ticker": ticker,
164
+ "train_days": len(train_prices),
165
+ "oos_days": len(oos_prices),
166
+ "train_metrics": train_metrics,
167
+ "oos_metrics": oos_metrics,
168
+ "sharpe_degradation": round(sharpe_degradation, 3),
169
+ "oos_verdict": oos_verdict,
170
+ "engine": f"backtester-mcp v{bt.__version__}",
171
+ })
172
+
173
+
174
+ if __name__ == "__main__":
175
+ port = int(os.environ.get("BACKTEST_PORT", 8300))
176
+ app.run(host="0.0.0.0", port=port, debug=False)
@@ -0,0 +1,77 @@
1
+ import { createWalletClient, createPublicClient, http, parseUnits } from 'viem';
2
+ import { base, baseSepolia } from 'viem/chains';
3
+ import { privateKeyToAccount } from 'viem/accounts';
4
+ import { mnemonicToSeedSync } from 'bip39';
5
+ import HDKey from 'hdkey';
6
+ import { WalletManager } from '../../server/payments/wallet.js';
7
+ import type { RouteParams } from '../../server/payments/router.js';
8
+
9
+ // USDC contract on Base mainnet
10
+ const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
11
+ // USDC contract on Base Sepolia
12
+ const USDC_BASE_SEPOLIA = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
13
+
14
+ const ERC20_TRANSFER_ABI = [
15
+ {
16
+ name: 'transfer',
17
+ type: 'function',
18
+ inputs: [
19
+ { name: 'to', type: 'address' },
20
+ { name: 'amount', type: 'uint256' },
21
+ ],
22
+ outputs: [{ name: '', type: 'bool' }],
23
+ stateMutability: 'nonpayable',
24
+ },
25
+ ] as const;
26
+
27
+ export class BaseChain {
28
+ private static instance: BaseChain;
29
+ private readonly testnet: boolean;
30
+
31
+ private constructor() {
32
+ this.testnet = process.env['TESTNET'] === 'true';
33
+ }
34
+
35
+ static getInstance(): BaseChain {
36
+ if (!BaseChain.instance) {
37
+ BaseChain.instance = new BaseChain();
38
+ }
39
+ return BaseChain.instance;
40
+ }
41
+
42
+ async sendPayment(params: RouteParams): Promise<string> {
43
+ const chain = this.testnet ? baseSepolia : base;
44
+ const rpcUrl = this.testnet
45
+ ? (process.env['BASE_SEPOLIA_RPC_URL'] ?? 'https://sepolia.base.org')
46
+ : (process.env['BASE_RPC_URL'] ?? 'https://mainnet.base.org');
47
+
48
+ const privateKey = await this.getPrivateKey();
49
+ const account = privateKeyToAccount(privateKey);
50
+
51
+ const walletClient = createWalletClient({ account, chain, transport: http(rpcUrl) });
52
+ const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
53
+
54
+ const usdcAddress = this.testnet ? USDC_BASE_SEPOLIA : USDC_BASE;
55
+ const amount = parseUnits(params.amount, 6); // USDC has 6 decimals
56
+
57
+ const hash = await walletClient.writeContract({
58
+ address: usdcAddress,
59
+ abi: ERC20_TRANSFER_ABI,
60
+ functionName: 'transfer',
61
+ args: [params.to as `0x${string}`, amount],
62
+ });
63
+
64
+ // Wait for confirmation
65
+ await publicClient.waitForTransactionReceipt({ hash });
66
+ return hash;
67
+ }
68
+
69
+ private async getPrivateKey(): Promise<`0x${string}`> {
70
+ const mnemonic = await WalletManager.getInstance().getSeed();
71
+ const seed = mnemonicToSeedSync(mnemonic);
72
+ const hdkey = HDKey.fromMasterSeed(seed);
73
+ const child = hdkey.derive("m/44'/60'/0'/0/0");
74
+ if (!child.privateKey) throw new Error('Key derivation failed');
75
+ return `0x${child.privateKey.toString('hex')}`;
76
+ }
77
+ }
@@ -0,0 +1,59 @@
1
+ import {
2
+ Connection,
3
+ Keypair,
4
+ PublicKey,
5
+ Transaction,
6
+ sendAndConfirmTransaction,
7
+ } from '@solana/web3.js';
8
+ import { createTransferInstruction, getAssociatedTokenAddress } from '@solana/spl-token';
9
+ import { mnemonicToSeedSync } from 'bip39';
10
+ import HDKey from 'hdkey';
11
+ import { WalletManager } from '../../server/payments/wallet.js';
12
+ import type { RouteParams } from '../../server/payments/router.js';
13
+
14
+ // USDC mint on Solana mainnet
15
+ const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
16
+
17
+ export class SolanaChain {
18
+ private static instance: SolanaChain;
19
+ private readonly rpcUrl: string;
20
+
21
+ private constructor() {
22
+ this.rpcUrl = process.env['SOLANA_RPC_URL'] ?? 'https://api.mainnet-beta.solana.com';
23
+ }
24
+
25
+ static getInstance(): SolanaChain {
26
+ if (!SolanaChain.instance) {
27
+ SolanaChain.instance = new SolanaChain();
28
+ }
29
+ return SolanaChain.instance;
30
+ }
31
+
32
+ async sendPayment(params: RouteParams): Promise<string> {
33
+ const connection = new Connection(this.rpcUrl, 'confirmed');
34
+ const payer = await this.getKeypair();
35
+ const destination = new PublicKey(params.to);
36
+
37
+ const fromATA = await getAssociatedTokenAddress(USDC_MINT, payer.publicKey);
38
+ const toATA = await getAssociatedTokenAddress(USDC_MINT, destination);
39
+
40
+ const lamports = Math.round(parseFloat(params.amount) * 1_000_000); // USDC 6 decimals
41
+
42
+ const tx = new Transaction().add(
43
+ createTransferInstruction(fromATA, toATA, payer.publicKey, BigInt(lamports)),
44
+ );
45
+
46
+ const sig = await sendAndConfirmTransaction(connection, tx, [payer]);
47
+ return sig;
48
+ }
49
+
50
+ private async getKeypair(): Promise<Keypair> {
51
+ const mnemonic = await WalletManager.getInstance().getSeed();
52
+ const seed = mnemonicToSeedSync(mnemonic);
53
+ const hdkey = HDKey.fromMasterSeed(seed);
54
+ // BIP-44 for Solana: m/44'/501'/0'/0'
55
+ const child = hdkey.derive("m/44'/501'/0'/0'");
56
+ if (!child.privateKey) throw new Error('Solana key derivation failed');
57
+ return Keypair.fromSeed(child.privateKey.slice(0, 32));
58
+ }
59
+ }
@@ -0,0 +1,63 @@
1
+ import { Client, Wallet, xrpToDrops } from 'xrpl';
2
+ import { WalletManager } from '../../server/payments/wallet.js';
3
+ import type { RouteParams } from '../../server/payments/router.js';
4
+
5
+ export class XRPLChain {
6
+ private static instance: XRPLChain;
7
+ private readonly rpcUrl: string;
8
+
9
+ private constructor() {
10
+ this.rpcUrl = process.env['XRPL_RPC_URL'] ?? 'wss://xrplcluster.com';
11
+ }
12
+
13
+ static getInstance(): XRPLChain {
14
+ if (!XRPLChain.instance) {
15
+ XRPLChain.instance = new XRPLChain();
16
+ }
17
+ return XRPLChain.instance;
18
+ }
19
+
20
+ async sendPayment(params: RouteParams): Promise<string> {
21
+ const client = new Client(this.rpcUrl);
22
+ await client.connect();
23
+
24
+ try {
25
+ const wallet = await this.getWallet();
26
+
27
+ // RLUSD on XRPL or XRP-denominated drop equivalent
28
+ const tx = await client.submitAndWait(
29
+ {
30
+ TransactionType: 'Payment',
31
+ Account: wallet.address,
32
+ Destination: params.to,
33
+ Amount:
34
+ params.currency === 'RLUSD'
35
+ ? {
36
+ currency: 'USD',
37
+ issuer: process.env['RLUSD_ISSUER'] ?? 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
38
+ value: params.amount,
39
+ }
40
+ : xrpToDrops(params.amount),
41
+ },
42
+ { wallet },
43
+ );
44
+
45
+ if (tx.result.meta && typeof tx.result.meta === 'object' && 'TransactionResult' in tx.result.meta) {
46
+ const meta = tx.result.meta as { TransactionResult: string };
47
+ if (meta.TransactionResult !== 'tesSUCCESS') {
48
+ throw new Error(`XRPL transaction failed: ${meta.TransactionResult}`);
49
+ }
50
+ }
51
+
52
+ const txResult = tx.result as unknown as { hash?: string };
53
+ return txResult.hash ?? 'unknown';
54
+ } finally {
55
+ await client.disconnect();
56
+ }
57
+ }
58
+
59
+ private async getWallet(): Promise<Wallet> {
60
+ const mnemonic = await WalletManager.getInstance().getSeed();
61
+ return Wallet.fromMnemonic(mnemonic, { mnemonicEncoding: 'bip39' });
62
+ }
63
+ }
@@ -0,0 +1,65 @@
1
+ interface ScoreCache {
2
+ score: number;
3
+ fetchedAt: number;
4
+ }
5
+
6
+ const SCORE_CACHE_TTL = 300_000; // 5 minutes
7
+
8
+ export class CreditBureau {
9
+ private static instance: CreditBureau;
10
+ private readonly cache = new Map<string, ScoreCache>();
11
+ private readonly baseUrl: string;
12
+
13
+ private constructor() {
14
+ this.baseUrl = process.env['PROOF402_URL'] ?? 'https://four02proof.onrender.com';
15
+ }
16
+
17
+ static getInstance(): CreditBureau {
18
+ if (!CreditBureau.instance) {
19
+ CreditBureau.instance = new CreditBureau();
20
+ }
21
+ return CreditBureau.instance;
22
+ }
23
+
24
+ async getScore(wallet: string): Promise<number> {
25
+ const now = Date.now();
26
+ const cached = this.cache.get(wallet);
27
+
28
+ if (cached && now - cached.fetchedAt < SCORE_CACHE_TTL) {
29
+ return cached.score;
30
+ }
31
+
32
+ try {
33
+ const res = await fetch(`${this.baseUrl}/v1/score/${wallet}`, {
34
+ signal: AbortSignal.timeout(5000),
35
+ });
36
+
37
+ if (res.ok) {
38
+ const body = (await res.json()) as { score: number };
39
+ this.cache.set(wallet, { score: body.score, fetchedAt: now });
40
+ return body.score;
41
+ }
42
+ } catch {
43
+ // Fall through to default
44
+ }
45
+
46
+ // Default score for new/unknown agents
47
+ const defaultScore = 300;
48
+ this.cache.set(wallet, { score: defaultScore, fetchedAt: now });
49
+ return defaultScore;
50
+ }
51
+
52
+ async incrementScore(wallet: string, delta: number): Promise<void> {
53
+ try {
54
+ await fetch(`${this.baseUrl}/v1/score/${wallet}/increment`, {
55
+ method: 'POST',
56
+ headers: { 'Content-Type': 'application/json' },
57
+ body: JSON.stringify({ delta }),
58
+ });
59
+ // Invalidate cache
60
+ this.cache.delete(wallet);
61
+ } catch {
62
+ // Non-fatal
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,40 @@
1
+ const AGENTCARD_BASE = process.env['AGENTCARD_URL'] ?? 'https://agentcard.onrender.com';
2
+
3
+ async function acGet(path: string): Promise<unknown> {
4
+ const res = await fetch(`${AGENTCARD_BASE}${path}`, {
5
+ headers: { 'Accept': 'application/json' },
6
+ signal: AbortSignal.timeout(10_000),
7
+ });
8
+ if (!res.ok) throw new Error(`AgentCard GET ${path}: HTTP ${res.status}`);
9
+ return res.json();
10
+ }
11
+
12
+ export interface AgentCardMintParams {
13
+ walletAddress: string;
14
+ name: string;
15
+ did?: string;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+
19
+ export interface AgentCardVerifyParams {
20
+ walletAddress: string;
21
+ message: string;
22
+ signature: string;
23
+ }
24
+
25
+ async function acPost(path: string, body: unknown): Promise<unknown> {
26
+ const res = await fetch(`${AGENTCARD_BASE}${path}`, {
27
+ method: 'POST',
28
+ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
29
+ body: JSON.stringify(body),
30
+ signal: AbortSignal.timeout(15_000),
31
+ });
32
+ if (!res.ok) throw new Error(`AgentCard POST ${path}: HTTP ${res.status}`);
33
+ return res.json();
34
+ }
35
+
36
+ export const AgentCardAPI = {
37
+ lookup: (walletOrDid: string) => acGet(`/v1/card/${encodeURIComponent(walletOrDid)}`),
38
+ verify: (params: AgentCardVerifyParams) => acPost('/v1/verify', params),
39
+ mint: (params: AgentCardMintParams) => acPost('/v1/mint', params),
40
+ };
@@ -0,0 +1,47 @@
1
+ // backtester-mcp microservice client
2
+ // Wraps the Python backtest_service.py running at BACKTEST_BASE_URL
3
+
4
+ const BASE = process.env.BACKTEST_BASE_URL ?? 'http://localhost:8300';
5
+
6
+ export interface BacktestParams {
7
+ ticker: string;
8
+ lookback_days?: number;
9
+ signals?: number[];
10
+ fees?: number;
11
+ slippage?: number;
12
+ momentum_window?: number;
13
+ momentum_threshold?: number;
14
+ }
15
+
16
+ export interface ValidateParams {
17
+ ticker: string;
18
+ lookback_days?: number;
19
+ train_ratio?: number;
20
+ fees?: number;
21
+ slippage?: number;
22
+ }
23
+
24
+ async function post(path: string, body: object): Promise<unknown> {
25
+ const res = await fetch(`${BASE}${path}`, {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json' },
28
+ body: JSON.stringify(body),
29
+ signal: AbortSignal.timeout(30_000),
30
+ });
31
+ if (!res.ok) {
32
+ const err = await res.json().catch(() => ({ error: res.statusText }));
33
+ throw new Error((err as { error?: string }).error ?? res.statusText);
34
+ }
35
+ return res.json();
36
+ }
37
+
38
+ export const BacktestAPI = {
39
+ health: async (): Promise<unknown> => {
40
+ const res = await fetch(`${BASE}/health`, { signal: AbortSignal.timeout(5_000) });
41
+ return res.json();
42
+ },
43
+
44
+ backtest: (params: BacktestParams): Promise<unknown> => post('/backtest', params),
45
+
46
+ walkForward: (params: ValidateParams): Promise<unknown> => post('/validate', params),
47
+ };
@@ -0,0 +1,160 @@
1
+ // Broker execution rails — Robinhood + Tradier
2
+ // Robinhood: OAuth2 Bearer token (ROBINHOOD_ACCESS_TOKEN env)
3
+ // Tradier: Bearer token (TRADIER_API_KEY env, TRADIER_ENV=sandbox|production)
4
+
5
+ const TRADIER_BASE = process.env.TRADIER_ENV === 'production'
6
+ ? 'https://api.tradier.com/v1'
7
+ : 'https://sandbox.tradier.com/v1';
8
+
9
+ const ROBINHOOD_BASE = 'https://api.robinhood.com';
10
+
11
+ // ── Shared fetch helpers ────────────────────────────────────────────────────
12
+
13
+ async function tradierGet(path: string, params?: Record<string, string>): Promise<unknown> {
14
+ const token = process.env.TRADIER_API_KEY;
15
+ if (!token) throw new Error('TRADIER_API_KEY not configured');
16
+ const url = new URL(`${TRADIER_BASE}${path}`);
17
+ if (params) Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
18
+ const res = await fetch(url.toString(), {
19
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
20
+ signal: AbortSignal.timeout(10_000),
21
+ });
22
+ if (!res.ok) throw new Error(`Tradier ${res.status}: ${res.statusText}`);
23
+ return res.json();
24
+ }
25
+
26
+ async function tradierPost(path: string, body: Record<string, string>): Promise<unknown> {
27
+ const token = process.env.TRADIER_API_KEY;
28
+ if (!token) throw new Error('TRADIER_API_KEY not configured');
29
+ const res = await fetch(`${TRADIER_BASE}${path}`, {
30
+ method: 'POST',
31
+ headers: {
32
+ Authorization: `Bearer ${token}`,
33
+ Accept: 'application/json',
34
+ 'Content-Type': 'application/x-www-form-urlencoded',
35
+ },
36
+ body: new URLSearchParams(body).toString(),
37
+ signal: AbortSignal.timeout(10_000),
38
+ });
39
+ if (!res.ok) throw new Error(`Tradier ${res.status}: ${res.statusText}`);
40
+ return res.json();
41
+ }
42
+
43
+ async function robinhoodGet(path: string): Promise<unknown> {
44
+ const token = process.env.ROBINHOOD_ACCESS_TOKEN;
45
+ if (!token) throw new Error('ROBINHOOD_ACCESS_TOKEN not configured');
46
+ const res = await fetch(`${ROBINHOOD_BASE}${path}`, {
47
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
48
+ signal: AbortSignal.timeout(10_000),
49
+ });
50
+ if (!res.ok) throw new Error(`Robinhood ${res.status}: ${res.statusText}`);
51
+ return res.json();
52
+ }
53
+
54
+ async function robinhoodPost(path: string, body: object): Promise<unknown> {
55
+ const token = process.env.ROBINHOOD_ACCESS_TOKEN;
56
+ if (!token) throw new Error('ROBINHOOD_ACCESS_TOKEN not configured');
57
+ const res = await fetch(`${ROBINHOOD_BASE}${path}`, {
58
+ method: 'POST',
59
+ headers: {
60
+ Authorization: `Bearer ${token}`,
61
+ Accept: 'application/json',
62
+ 'Content-Type': 'application/json',
63
+ },
64
+ body: JSON.stringify(body),
65
+ signal: AbortSignal.timeout(10_000),
66
+ });
67
+ if (!res.ok) throw new Error(`Robinhood ${res.status}: ${res.statusText}`);
68
+ return res.json();
69
+ }
70
+
71
+ // ── Tradier API ─────────────────────────────────────────────────────────────
72
+
73
+ export interface TradierQuoteParams { symbols: string }
74
+ export interface TradierOrderParams {
75
+ account_id: string;
76
+ symbol: string;
77
+ side: 'buy' | 'sell';
78
+ quantity: number;
79
+ type: 'market' | 'limit' | 'stop' | 'stop_limit';
80
+ duration: 'day' | 'gtc' | 'pre' | 'post';
81
+ price?: number;
82
+ stop?: number;
83
+ }
84
+
85
+ export const TradierAPI = {
86
+ quote: (params: TradierQuoteParams) =>
87
+ tradierGet('/markets/quotes', { symbols: params.symbols, greeks: 'false' }),
88
+
89
+ optionChain: (symbol: string, expiration: string) =>
90
+ tradierGet('/markets/options/chains', { symbol, expiration, greeks: 'true' }),
91
+
92
+ order: (params: TradierOrderParams) => {
93
+ const body: Record<string, string> = {
94
+ class: 'equity',
95
+ symbol: params.symbol,
96
+ side: params.side,
97
+ quantity: String(params.quantity),
98
+ type: params.type,
99
+ duration: params.duration,
100
+ };
101
+ if (params.price !== undefined) body.price = String(params.price);
102
+ if (params.stop !== undefined) body.stop = String(params.stop);
103
+ return tradierPost(`/accounts/${params.account_id}/orders`, body);
104
+ },
105
+
106
+ positions: (account_id: string) =>
107
+ tradierGet(`/accounts/${account_id}/positions`),
108
+
109
+ balances: (account_id: string) =>
110
+ tradierGet(`/accounts/${account_id}/balances`),
111
+
112
+ orderStatus: (account_id: string, order_id: string) =>
113
+ tradierGet(`/accounts/${account_id}/orders/${order_id}`),
114
+ };
115
+
116
+ // ── Robinhood API ────────────────────────────────────────────────────────────
117
+
118
+ export interface RobinhoodOrderParams {
119
+ symbol: string;
120
+ side: 'buy' | 'sell';
121
+ quantity: number;
122
+ type: 'market' | 'limit';
123
+ time_in_force: 'gfd' | 'gtc' | 'ioc' | 'opg';
124
+ price?: number;
125
+ }
126
+
127
+ export const RobinhoodAPI = {
128
+ quote: async (symbol: string) => {
129
+ const data = await robinhoodGet(`/quotes/?symbols=${symbol}`) as { results?: unknown[] };
130
+ return data.results?.[0] ?? data;
131
+ },
132
+
133
+ instruments: async (symbol: string) => {
134
+ const data = await robinhoodGet(`/instruments/?symbol=${symbol}`) as { results?: { url: string }[] };
135
+ return data.results?.[0];
136
+ },
137
+
138
+ order: async (params: RobinhoodOrderParams) => {
139
+ const instrument = await RobinhoodAPI.instruments(params.symbol) as { url?: string } | undefined;
140
+ if (!instrument?.url) throw new Error(`Instrument not found: ${params.symbol}`);
141
+ const body: Record<string, unknown> = {
142
+ account: `/accounts/${process.env.ROBINHOOD_ACCOUNT_ID}/`,
143
+ instrument: instrument.url,
144
+ symbol: params.symbol,
145
+ side: params.side,
146
+ quantity: params.quantity,
147
+ type: params.type,
148
+ time_in_force: params.time_in_force,
149
+ trigger: 'immediate',
150
+ };
151
+ if (params.price !== undefined) body.price = String(params.price);
152
+ return robinhoodPost('/orders/', body);
153
+ },
154
+
155
+ portfolio: () => robinhoodGet('/portfolios/'),
156
+
157
+ positions: () => robinhoodGet('/positions/?nonzero=true'),
158
+
159
+ orderHistory: () => robinhoodGet('/orders/?page_size=20'),
160
+ };