amdk62 commited on
Commit
501e37c
·
verified ·
1 Parent(s): 9180d94

deploy: publish THEGOD static frontend

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +0 -9
  2. assets/{index-DKO8x-qK.js → index-Bh0tM6mi.js} +0 -0
  3. bun.lock +0 -831
  4. firebase-applet-config.json +0 -11
  5. index.html +1 -1
  6. metadata.json +0 -7
  7. package.json +0 -36
  8. server.cjs +0 -0
  9. server.cjs.map +0 -0
  10. server.ts +0 -40
  11. server/ai/quantAnalyst.ts +0 -42
  12. server/backtest/historicalBacktest.ts +0 -104
  13. server/backtest/historicalDataService.ts +0 -192
  14. server/confluence/confluenceEngine.ts +0 -123
  15. server/confluence/index.ts +0 -2
  16. server/confluence/marketAnalysisService.ts +0 -60
  17. server/derivatives/derivativesAnalysis.ts +0 -46
  18. server/derivatives/derivativesClient.ts +0 -116
  19. server/derivatives/historicalFundingService.ts +0 -151
  20. server/derivatives/index.ts +0 -2
  21. server/divergence/divergenceEngine.ts +0 -99
  22. server/divergence/index.ts +0 -2
  23. server/gann/gannEngine.ts +0 -61
  24. server/gann/index.ts +0 -2
  25. server/indicators/index.ts +0 -2
  26. server/indicators/technicalIndicators.ts +0 -103
  27. server/market/OKX_FIX_PENDING.md +0 -1
  28. server/market/candleService.ts +0 -78
  29. server/market/exchangeClient.okx-fix-notes.md +0 -1
  30. server/market/exchangeClient.ts +0 -123
  31. server/market/marketCache.ts +0 -61
  32. server/market/marketSnapshotService.ts +0 -19
  33. server/market/timeframeService.ts +0 -40
  34. server/paper/paperTradingEngine.ts +0 -32
  35. server/paper/paperTradingLoop.ts +0 -25
  36. server/quant/monteCarloAnalysis.ts +0 -116
  37. server/quant/multiRegimeAnalytics.ts +0 -223
  38. server/quant/parameterValidation.ts +0 -75
  39. server/quant/quantitativeLab.ts +0 -31
  40. server/quant/regimeAnalytics.ts +0 -27
  41. server/quant/robustParameterSelection.ts +0 -47
  42. server/quant/robustnessAnalysis.ts +0 -82
  43. server/quant/statisticalAnalysis.ts +0 -137
  44. server/quant/stressTest.ts +0 -60
  45. server/quant/timeSeriesAnalysis.ts +0 -64
  46. server/quant/walkForwardAnalysis.ts +0 -113
  47. server/realMarketData.ts +0 -1018
  48. server/signal/index.ts +0 -1
  49. server/signal/signalEngine.ts +0 -188
  50. server/signal/signalRoute.ts +0 -28
.env.example DELETED
@@ -1,9 +0,0 @@
1
- # GEMINI_API_KEY: Required for Gemini AI API calls.
2
- # AI Studio automatically injects this at runtime from user secrets.
3
- # Users configure this via the Secrets panel in the AI Studio UI.
4
- GEMINI_API_KEY="MY_GEMINI_API_KEY"
5
-
6
- # APP_URL: The URL where this applet is hosted.
7
- # AI Studio automatically injects this at runtime with the Cloud Run service URL.
8
- # Used for self-referential links, OAuth callbacks, and API endpoints.
9
- APP_URL="MY_APP_URL"
 
 
 
 
 
 
 
 
 
 
assets/{index-DKO8x-qK.js → index-Bh0tM6mi.js} RENAMED
The diff for this file is too large to render. See raw diff
 
bun.lock DELETED
@@ -1,831 +0,0 @@
1
- {
2
- "lockfileVersion": 2,
3
- "configVersion": 1,
4
- "workspaces": {
5
- "": {
6
- "name": "react-example",
7
- "dependencies": {
8
- "@google/genai": "^2.4.0",
9
- "@tailwindcss/vite": "^4.1.14",
10
- "@vitejs/plugin-react": "^5.0.4",
11
- "dotenv": "^17.2.3",
12
- "express": "^4.21.2",
13
- "firebase": "^12.18.0",
14
- "lucide-react": "^0.546.0",
15
- "motion": "^12.23.24",
16
- "react": "^19.0.1",
17
- "react-dom": "^19.0.1",
18
- "vite": "^6.2.3",
19
- },
20
- "devDependencies": {
21
- "@types/express": "^4.17.21",
22
- "@types/node": "^22.14.0",
23
- "autoprefixer": "^10.4.21",
24
- "esbuild": "^0.25.0",
25
- "tailwindcss": "^4.1.14",
26
- "tsx": "^4.21.0",
27
- "typescript": "~5.8.2",
28
- "vite": "^6.2.3",
29
- },
30
- },
31
- },
32
- "packages": {
33
- "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
34
-
35
- "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
36
-
37
- "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
38
-
39
- "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="],
40
-
41
- "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
42
-
43
- "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
44
-
45
- "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
46
-
47
- "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
48
-
49
- "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
50
-
51
- "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
52
-
53
- "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
54
-
55
- "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
56
-
57
- "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
58
-
59
- "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="],
60
-
61
- "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="],
62
-
63
- "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="],
64
-
65
- "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
66
-
67
- "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="],
68
-
69
- "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="],
70
-
71
- "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
72
-
73
- "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
74
-
75
- "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
76
-
77
- "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
78
-
79
- "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
80
-
81
- "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
82
-
83
- "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
84
-
85
- "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
86
-
87
- "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
88
-
89
- "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
90
-
91
- "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
92
-
93
- "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
94
-
95
- "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
96
-
97
- "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
98
-
99
- "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
100
-
101
- "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
102
-
103
- "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
104
-
105
- "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
106
-
107
- "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
108
-
109
- "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
110
-
111
- "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
112
-
113
- "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
114
-
115
- "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
116
-
117
- "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
118
-
119
- "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
120
-
121
- "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
122
-
123
- "@firebase/ai": ["@firebase/ai@2.15.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.5", "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-types": "0.x" } }, "sha512-Aj7TbFdAIWZdkX8JfdDStERpR35g6WNs+7XhNPtFLFOizUotj6k4N/D8HJkR45165HhnMJBe4hjOeeaxnnn56Q=="],
124
-
125
- "@firebase/analytics": ["@firebase/analytics@0.10.24", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/installations": "0.6.24", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-OfIAcIIwoqXjBzS+DnUQpOZ7i3ePaNFg83KPpDWjBZUKJmqMwzzAtLJqmKZOkQnW8im6vFR6f2I3M/9hxVd+QA=="],
126
-
127
- "@firebase/analytics-compat": ["@firebase/analytics-compat@0.2.30", "", { "dependencies": { "@firebase/analytics": "0.10.24", "@firebase/analytics-types": "0.8.5", "@firebase/component": "0.7.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-uVZEKlLaW4AHAhv8zoN9cTmveX6v86AVqcJ4LCaRCMTChTH8/NsjbisTq1lpBfWCLWS1spqwSHB4vol/YSCdMA=="],
128
-
129
- "@firebase/analytics-types": ["@firebase/analytics-types@0.8.5", "", {}, "sha512-kdnooE7Bis2jEnsqcerRwn/UQpH5D3uvHpku7OdUM9TJN3omlu6iYtbyAQ6XkAJRgJtO0aNf9OOkW8J6D59oCA=="],
130
-
131
- "@firebase/app": ["@firebase/app@0.16.1", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "idb": "7.1.1", "tslib": "^2.1.0" } }, "sha512-tjUEorFyKrurH7PbLWv9zDHRuU4mLefgD/yY38D584ziorIRlQz/PVjx4c/SCGyCuUlmyzt72mK+Lh+COAywNQ=="],
132
-
133
- "@firebase/app-check": ["@firebase/app-check@0.13.1", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-l8y3dmnhodXks/APAwx4tWqRl3tk8b9874KF1FJyKg1DIU+kMD0l52iz7S9/MW+eK8k6cjJg0G0iJ5KQAsQpow=="],
134
-
135
- "@firebase/app-check-compat": ["@firebase/app-check-compat@0.4.7", "", { "dependencies": { "@firebase/app-check": "0.13.1", "@firebase/app-check-types": "0.5.5", "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-fBb/xSMyIKv1nDmccfzz3IAGBzx/cQOxmZai66rJzifb1hMMkztf824Xx7LhpTUe7HNUbXwY90IvJ66fi8q6yg=="],
136
-
137
- "@firebase/app-check-interop-types": ["@firebase/app-check-interop-types@0.3.5", "", {}, "sha512-qId34pVZ2CXTmtu4ofW5leiI93DvnOvIcr/5GT7MZPw5WXAi6nZoU+g9cNRgl7K90UJSbK0cXxten4meYMPyZg=="],
138
-
139
- "@firebase/app-check-types": ["@firebase/app-check-types@0.5.5", "", {}, "sha512-+DF4gzFrlwGFyky38o4T/YN/r51l70yCtJ2HTkwmRj8FCMwPjm4hLH4fQbj6BUvzkiFqliBkitFsRpf1/YZkWA=="],
140
-
141
- "@firebase/app-compat": ["@firebase/app-compat@0.5.17", "", { "dependencies": { "@firebase/app": "0.16.1", "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" } }, "sha512-5GdJWobqs6jbNYOnaQiSa4Ng8gnFerhqr7nY+v5jlnT7o9QbEeIZRLPQpnLe0TxYSWfRV2lAMbyR0kbXeIos9Q=="],
142
-
143
- "@firebase/app-types": ["@firebase/app-types@0.9.6", "", { "dependencies": { "@firebase/logger": "0.5.2" } }, "sha512-yPLahy7Esfu2w/yme3msVK4xTkDXQqq6szfQn8yVOQpCKiT5GVFjqNpLbuz6NkX0WuTwUidkmPewW3r4xkvpeg=="],
144
-
145
- "@firebase/auth": ["@firebase/auth@1.13.5", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@react-native-async-storage/async-storage": "^2.2.0 || ^3.0.0" }, "optionalPeers": ["@react-native-async-storage/async-storage"] }, "sha512-1AXoBJqBVD8WL8FZYo3S2GmJF9YUoom6Y6ngMxOSkzzhW5sT83pLchb6TGFgxes91dfXx8s/VYc5VrLDNqpLog=="],
146
-
147
- "@firebase/auth-compat": ["@firebase/auth-compat@0.6.10", "", { "dependencies": { "@firebase/auth": "1.13.5", "@firebase/auth-types": "0.13.2", "@firebase/component": "0.7.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-Bvklg2nL7BrBFCAqdsleW8fse9Jh0fARqJPlJmA40uermfWx1bJiO7dnKyZN3J39d2TFVd4VJXtg3i6Wg92/YQ=="],
148
-
149
- "@firebase/auth-interop-types": ["@firebase/auth-interop-types@0.2.6", "", {}, "sha512-FgwZqDrBqgK0BHI70QTv1v/5wmuDF9f8fsJFvKSxoRlK2PPfSQtZAk2jhXu4pe9BZzFi/e4UYusU/bEQHdf6Qg=="],
150
-
151
- "@firebase/auth-types": ["@firebase/auth-types@0.13.2", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-OU+miuoSxIWYN7GT291d2ylVJBL3k/OePo7/JDSoQtkFQoEiGBc4AHuMjj/seqFIsHrqnjrAfRCk4pfufoJolQ=="],
152
-
153
- "@firebase/component": ["@firebase/component@0.7.5", "", { "dependencies": { "@firebase/util": "1.15.3", "tslib": "^2.1.0" } }, "sha512-vuFDcL91Q+2ZuBJkyOh86T4q0B4ffNTDjc/A38tybO56odQABxRTFLTIowCWqAKeIcgo37GowWMVgFF73gD8Qw=="],
154
-
155
- "@firebase/data-connect": ["@firebase/data-connect@0.7.4", "", { "dependencies": { "@firebase/auth-interop-types": "0.2.6", "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-su1aGWlzhxb+xtggCUSsufJn1FDa06SBDK71y+fpQ+g2zMhMExik6FUv/odkKzOF/xfWVjabCbWBcjJY3MceDA=="],
156
-
157
- "@firebase/database": ["@firebase/database@1.1.5", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.5", "@firebase/auth-interop-types": "0.2.6", "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "faye-websocket": "0.11.4", "tslib": "^2.1.0" } }, "sha512-/JGpvszLoNXNgzilRXocigGfFF4hbcPA9wN1i1kjJx6oKkXgkHteZYl3lQs1lJX3ETDf6bv7zI15lPMVUp4sAQ=="],
158
-
159
- "@firebase/database-compat": ["@firebase/database-compat@2.1.7", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/database": "1.1.5", "@firebase/database-types": "1.0.22", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" }, "optionalPeers": ["@firebase/app", "@firebase/app-compat"] }, "sha512-lBq9sJm8MnJINKJnkAKSOj2MbC66xGoSVCcGkPtstl+lkoKEBtD46qHLbuDgW/WbLPoKVm17RIN8BxHj+Z2F2w=="],
160
-
161
- "@firebase/database-types": ["@firebase/database-types@1.0.22", "", { "dependencies": { "@firebase/app-types": "0.9.6", "@firebase/util": "1.15.3" } }, "sha512-YAZNXsjY9EQQ+pKw/3ax8n5FgolHC7Qew7EY5RceYdl0R2ZP+kCv1O0DkKlcceue8uQCOreHCNN7PWVFpY1Nug=="],
162
-
163
- "@firebase/firestore": ["@firebase/firestore@4.17.1", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "@firebase/webchannel-wrapper": "1.0.7", "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.8", "re2js": "^2.8.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-8lqPNf2w10CtYG+tayVjZO1pSyQpnhztQRudeD109VtXDzNbASTaYdO43sj5PMsDcWq0aOYY3RmJOUlXu9++jw=="],
164
-
165
- "@firebase/firestore-compat": ["@firebase/firestore-compat@0.4.13", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/firestore": "4.17.1", "@firebase/firestore-types": "3.0.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-l9dCewxMzzLOIhcwTjERCKxrWOn1kZ9JvwOQq9zZNq4I/Nbvb/B9njT230V61uvQ9qZ3/qpUG758D8DDTEFXnw=="],
166
-
167
- "@firebase/firestore-types": ["@firebase/firestore-types@3.0.5", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-dbdMAQkMd5dwWc48eupz/Y6/E9ruat3+gY5lhVKscvvT/HnDBEEMzJW38zdKhgdnglZHGk2vUsJMOHAphMHGMA=="],
168
-
169
- "@firebase/functions": ["@firebase/functions@0.14.0", "", { "dependencies": { "@firebase/app-check-interop-types": "0.3.5", "@firebase/auth-interop-types": "0.2.6", "@firebase/component": "0.7.5", "@firebase/messaging-interop-types": "0.2.6", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-DhuYFr0eMhp+s/PNEk6SiMsYkc00+XVOUeKbrl8MOzQNZI3SKQpqoDR1d+keNDAutwmHRWTUAdXBoVPD+xxVUw=="],
170
-
171
- "@firebase/functions-compat": ["@firebase/functions-compat@0.5.0", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/functions": "0.14.0", "@firebase/functions-types": "0.6.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-T3BDIToESZUHt7438wyKYMkRjKg3m1xAIux5fVpNvTRQlDOFLukWniQWBDhJ2znpU9kxMTR6+e31TVo8P15PZw=="],
172
-
173
- "@firebase/functions-types": ["@firebase/functions-types@0.6.5", "", {}, "sha512-Zc0pURjthHXzSj54ZivCkzKDSV1r/wIpnmHdhq82q2yFFPVoncG/ZJjnVMiANvQfeww/QnElhzODpfwUucVwdA=="],
174
-
175
- "@firebase/installations": ["@firebase/installations@0.6.24", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/util": "1.15.3", "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-Ui52ey8wHoWqkBbXRKJEKYWylI0JZogZmoLS+o8Anh1bxWtK27ZYjLla1UJAAdC5DCKLJ2qAp0VpJOjg8VOX1g=="],
176
-
177
- "@firebase/installations-compat": ["@firebase/installations-compat@0.2.24", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/installations": "0.6.24", "@firebase/installations-types": "0.5.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-8M5nlcWwYt881x83COP2odq5vgf+NgwJh+RMd4LRSv8JI1pxwDfgrDJOERrjTgfC9J5Z0vnQX4b1pdN914e0Zw=="],
178
-
179
- "@firebase/installations-types": ["@firebase/installations-types@0.5.5", "", { "peerDependencies": { "@firebase/app-types": "0.x" } }, "sha512-e9UYcju3puDl1vdrcKIi5dExzHLameOT/Tc61Q48PYwxtsM1NzZh/ikGbdBQTsbRgg0EMZqdPr0/m5ODUBobrg=="],
180
-
181
- "@firebase/logger": ["@firebase/logger@0.5.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-J2VO4NFTc0xQFrxV1B/lm5balicm9cwuX2acR9Yn41fN8KgUeQFo+VJV222IqW2FPSXKDu9uo5WdQFWf9TPbYg=="],
182
-
183
- "@firebase/messaging": ["@firebase/messaging@0.13.2", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/installations": "0.6.24", "@firebase/messaging-interop-types": "0.2.6", "@firebase/util": "1.15.3", "idb": "7.1.1", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-KcZoqUu2ih4sLH91dW9tmyHjCR0IQyNzSLZunX5uq7cLeImXb4uO2I0wq5FACduO2I+FoTeWa2BtUv7Jpowqdw=="],
184
-
185
- "@firebase/messaging-compat": ["@firebase/messaging-compat@0.2.29", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/messaging": "0.13.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-8Twe4CeYvAx8AzjBxyyQFKzinaMGGt13hPQBqaARQ2QZjagrEaqSVBe+Zy6F4L/vT8DV168yRgRu7W/RK7p+4w=="],
186
-
187
- "@firebase/messaging-interop-types": ["@firebase/messaging-interop-types@0.2.6", "", {}, "sha512-MVzvkKe2V4H2dHu5oOxRfeKQcfTwWmCgnzsC4V1q3ixun5iiL8riCZ2qI35rDhCL4glPGiu0jHxDbpVNuTKfow=="],
188
-
189
- "@firebase/performance": ["@firebase/performance@0.7.14", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/installations": "0.6.24", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0", "web-vitals": "^4.2.4" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-9PH1XEZVHErxGdbXluvGz4Uyjw4W955H8v7Mv9rHIybRrg5F2Ac2tvf5+mSf5EtnxWNmxrD2WLnvMFaZP4sMrQ=="],
190
-
191
- "@firebase/performance-compat": ["@firebase/performance-compat@0.2.27", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/performance": "0.7.14", "@firebase/performance-types": "0.2.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-O/ozTf/EbChN94Pk7bd1eUC0PAC36726AwsaiJyC0bZzSBWfLGSWASGPH5pebUWXQPJtGxIJYRkkbX5l0Qa+Hw=="],
192
-
193
- "@firebase/performance-types": ["@firebase/performance-types@0.2.5", "", {}, "sha512-PRzOgB+/M+6AKlEkY8a9xy5Ff5SfZPIh4iShXhn2WAcL/euSbK7bdLqWysHduunNJhGFsXa22Ag3ixqL6rQtYg=="],
194
-
195
- "@firebase/remote-config": ["@firebase/remote-config@0.9.2", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/installations": "0.6.24", "@firebase/logger": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-Rii93DkXjM+RE/ytHdYa7EIiQLJbdBr+W8EEiqd/vbLCOC+1FdkDuRiumJBp6t3yewHGbdqbpjB3fk3z0cZ+8g=="],
196
-
197
- "@firebase/remote-config-compat": ["@firebase/remote-config-compat@0.2.29", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/logger": "0.5.2", "@firebase/remote-config": "0.9.2", "@firebase/remote-config-types": "0.5.2", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-mY7JtTISK6F4g4T0x3kVepr5cp0NXL7f5yjIUXXGaTrg4qUlFBWRbENaHaqZ78dwmNcLm24h/yPsOVRlDXEXhA=="],
198
-
199
- "@firebase/remote-config-types": ["@firebase/remote-config-types@0.5.2", "", {}, "sha512-i8k1omVfoAnaT1ZPv2FFjxMZrATYsO/GnFgHEj5+7fAJLzi8wLnGGv1WK06oEAD6ltrWXSO1HzeNyajn/NjMWw=="],
200
-
201
- "@firebase/storage": ["@firebase/storage@0.14.5", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x" } }, "sha512-r2tozN/BlEewLi70tJNUQPzWwbex9GM7NgXZsamHKQrjKEfcR0i4jGgHBKAKA4hbwiPE9eNMb3hcxWnDpeJZwg=="],
202
-
203
- "@firebase/storage-compat": ["@firebase/storage-compat@0.4.5", "", { "dependencies": { "@firebase/component": "0.7.5", "@firebase/storage": "0.14.5", "@firebase/storage-types": "0.8.5", "@firebase/util": "1.15.3", "tslib": "^2.1.0" }, "peerDependencies": { "@firebase/app": "0.x", "@firebase/app-compat": "0.x" } }, "sha512-vO0tFPxXbKDKdlTu8tYT08S9t9ezUTJYEdLCULpWxWq2aq6zojwN1QmAB+1a50AI/g47GlWUJn1XPncm/PDZsw=="],
204
-
205
- "@firebase/storage-types": ["@firebase/storage-types@0.8.5", "", { "peerDependencies": { "@firebase/app-types": "0.x", "@firebase/util": "1.x" } }, "sha512-GEDs5P+rNUfNcS+wxIdOLAHficife2YXtvTJnyi3ssrX11AAtBg+nDUdbYh8vuBzWehVQZPmztGUUnud4L+4yg=="],
206
-
207
- "@firebase/util": ["@firebase/util@1.15.3", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-c/z/gaIlaaLZEuGbE6sLUuJ61tskg1JghvhcNQzW948ASBinbVBBRnZTC4b4yt4LaEtJQkYlyzqLHcutFwEIvA=="],
208
-
209
- "@firebase/webchannel-wrapper": ["@firebase/webchannel-wrapper@1.0.7", "", {}, "sha512-phBFwieDLvkZGYN9CE9ZFNEIoBVksprzsnCzQejCmCHtgwCXReeuRpoEGN9C4EbhONztv8NRV1tau6Rb9pONwQ=="],
210
-
211
- "@google/genai": ["@google/genai@2.21.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-+PDtco2/Z0ONdzCGekCoCT+O1VJS9xJQNN4XzQpXG/t3El/SWWMkCWlFRO1KmivOHPa4Q0VjUYu1HBKCZ/v33Q=="],
212
-
213
- "@grpc/grpc-js": ["@grpc/grpc-js@1.9.16", "", { "dependencies": { "@grpc/proto-loader": "^0.7.8", "@types/node": ">=12.12.47" } }, "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ=="],
214
-
215
- "@grpc/proto-loader": ["@grpc/proto-loader@0.7.15", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ=="],
216
-
217
- "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
218
-
219
- "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
220
-
221
- "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
222
-
223
- "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="],
224
-
225
- "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
226
-
227
- "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="],
228
-
229
- "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
230
-
231
- "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
232
-
233
- "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
234
-
235
- "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
236
-
237
- "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
238
-
239
- "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
240
-
241
- "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
242
-
243
- "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
244
-
245
- "@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
246
-
247
- "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="],
248
-
249
- "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.63.1", "", { "os": "android", "cpu": "arm" }, "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ=="],
250
-
251
- "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.63.1", "", { "os": "android", "cpu": "arm64" }, "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ=="],
252
-
253
- "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.63.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q=="],
254
-
255
- "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.63.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg=="],
256
-
257
- "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.63.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw=="],
258
-
259
- "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.63.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q=="],
260
-
261
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.63.1", "", { "os": "linux", "cpu": "arm" }, "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw=="],
262
-
263
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.63.1", "", { "os": "linux", "cpu": "arm" }, "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw=="],
264
-
265
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.63.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg=="],
266
-
267
- "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.63.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw=="],
268
-
269
- "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ=="],
270
-
271
- "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA=="],
272
-
273
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.63.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA=="],
274
-
275
- "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.63.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA=="],
276
-
277
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w=="],
278
-
279
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.63.1", "", { "os": "linux", "cpu": "none" }, "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ=="],
280
-
281
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.63.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A=="],
282
-
283
- "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.63.1", "", { "os": "linux", "cpu": "x64" }, "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w=="],
284
-
285
- "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.63.1", "", { "os": "linux", "cpu": "x64" }, "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow=="],
286
-
287
- "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.63.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA=="],
288
-
289
- "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.63.1", "", { "os": "none", "cpu": "arm64" }, "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw=="],
290
-
291
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.63.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg=="],
292
-
293
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.63.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg=="],
294
-
295
- "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.63.1", "", { "os": "win32", "cpu": "x64" }, "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg=="],
296
-
297
- "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.63.1", "", { "os": "win32", "cpu": "x64" }, "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w=="],
298
-
299
- "@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
300
-
301
- "@tailwindcss/oxide": ["@tailwindcss/oxide@4.3.3", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="],
302
-
303
- "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.3.3", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="],
304
-
305
- "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="],
306
-
307
- "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="],
308
-
309
- "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.3.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="],
310
-
311
- "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="],
312
-
313
- "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="],
314
-
315
- "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="],
316
-
317
- "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="],
318
-
319
- "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="],
320
-
321
- "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.3.3", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="],
322
-
323
- "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.3.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="],
324
-
325
- "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="],
326
-
327
- "@tailwindcss/vite": ["@tailwindcss/vite@4.3.3", "", { "dependencies": { "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw=="],
328
-
329
- "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
330
-
331
- "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
332
-
333
- "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
334
-
335
- "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
336
-
337
- "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
338
-
339
- "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
340
-
341
- "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
342
-
343
- "@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="],
344
-
345
- "@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.9", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg=="],
346
-
347
- "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
348
-
349
- "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
350
-
351
- "@types/node": ["@types/node@22.20.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q=="],
352
-
353
- "@types/qs": ["@types/qs@6.15.1", "", {}, "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw=="],
354
-
355
- "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
356
-
357
- "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
358
-
359
- "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
360
-
361
- "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
362
-
363
- "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="],
364
-
365
- "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
366
-
367
- "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
368
-
369
- "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
370
-
371
- "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
372
-
373
- "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
374
-
375
- "autoprefixer": ["autoprefixer@10.5.5", "", { "dependencies": { "browserslist": "^4.28.9", "caniuse-lite": "^1.0.30001810", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-uiRYvQYe/nNSzBJ7OUnd2/TZVsAdob3blml44teEpee9Cc1f4rGZFewO+JT3Wo8mgFOSzNqes4FHZn/Qz8WOuw=="],
376
-
377
- "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
378
-
379
- "baseline-browser-mapping": ["baseline-browser-mapping@2.11.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ=="],
380
-
381
- "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
382
-
383
- "body-parser": ["body-parser@1.20.6", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g=="],
384
-
385
- "browserslist": ["browserslist@4.28.9", "", { "dependencies": { "baseline-browser-mapping": "^2.11.20", "caniuse-lite": "^1.0.30001810", "electron-to-chromium": "^1.5.420", "node-releases": "^2.0.54", "update-browserslist-db": "^1.3.2" }, "bin": { "browserslist": "cli.js" } }, "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg=="],
386
-
387
- "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
388
-
389
- "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
390
-
391
- "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
392
-
393
- "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
394
-
395
- "caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="],
396
-
397
- "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
398
-
399
- "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
400
-
401
- "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
402
-
403
- "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
404
-
405
- "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
406
-
407
- "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
408
-
409
- "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
410
-
411
- "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
412
-
413
- "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
414
-
415
- "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
416
-
417
- "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
418
-
419
- "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
420
-
421
- "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
422
-
423
- "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
424
-
425
- "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
426
-
427
- "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
428
-
429
- "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
430
-
431
- "electron-to-chromium": ["electron-to-chromium@1.5.422", "", {}, "sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA=="],
432
-
433
- "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
434
-
435
- "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
436
-
437
- "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="],
438
-
439
- "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
440
-
441
- "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
442
-
443
- "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
444
-
445
- "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
446
-
447
- "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
448
-
449
- "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
450
-
451
- "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
452
-
453
- "express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
454
-
455
- "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
456
-
457
- "faye-websocket": ["faye-websocket@0.11.4", "", { "dependencies": { "websocket-driver": ">=0.5.1" } }, "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g=="],
458
-
459
- "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
460
-
461
- "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
462
-
463
- "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
464
-
465
- "firebase": ["firebase@12.18.0", "", { "dependencies": { "@firebase/ai": "2.15.0", "@firebase/analytics": "0.10.24", "@firebase/analytics-compat": "0.2.30", "@firebase/app": "0.16.1", "@firebase/app-check": "0.13.1", "@firebase/app-check-compat": "0.4.7", "@firebase/app-compat": "0.5.17", "@firebase/app-types": "0.9.6", "@firebase/auth": "1.13.5", "@firebase/auth-compat": "0.6.10", "@firebase/data-connect": "0.7.4", "@firebase/database": "1.1.5", "@firebase/database-compat": "2.1.7", "@firebase/firestore": "4.17.1", "@firebase/firestore-compat": "0.4.13", "@firebase/functions": "0.14.0", "@firebase/functions-compat": "0.5.0", "@firebase/installations": "0.6.24", "@firebase/installations-compat": "0.2.24", "@firebase/messaging": "0.13.2", "@firebase/messaging-compat": "0.2.29", "@firebase/performance": "0.7.14", "@firebase/performance-compat": "0.2.27", "@firebase/remote-config": "0.9.2", "@firebase/remote-config-compat": "0.2.29", "@firebase/storage": "0.14.5", "@firebase/storage-compat": "0.4.5", "@firebase/util": "1.15.3" } }, "sha512-XaL6tlE5Xd20ZDhckqOMIw+JJTET+wTdeZPxQ7ihc42oxRb7kWUyn/j1LO5V9dH1xq8Rv5R71Pv1fBCdIkt9Rw=="],
466
-
467
- "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
468
-
469
- "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
470
-
471
- "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="],
472
-
473
- "framer-motion": ["framer-motion@12.43.0", "", { "dependencies": { "motion-dom": "^12.43.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g=="],
474
-
475
- "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
476
-
477
- "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
478
-
479
- "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
480
-
481
- "gaxios": ["gaxios@7.3.1", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-kB3rzJV7d9juLZh8/56QTXCwQfxyhdOMdyYk1HdQKFtF8TJTDTZQJtixWIwXdE9Jji91mC41DUNpjleo4L4eAQ=="],
482
-
483
- "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
484
-
485
- "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
486
-
487
- "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
488
-
489
- "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
490
-
491
- "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
492
-
493
- "google-auth-library": ["google-auth-library@10.9.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw=="],
494
-
495
- "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
496
-
497
- "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
498
-
499
- "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
500
-
501
- "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
502
-
503
- "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
504
-
505
- "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
506
-
507
- "http-parser-js": ["http-parser-js@0.5.10", "", {}, "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA=="],
508
-
509
- "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
510
-
511
- "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
512
-
513
- "idb": ["idb@7.1.1", "", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="],
514
-
515
- "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
516
-
517
- "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
518
-
519
- "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
520
-
521
- "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
522
-
523
- "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
524
-
525
- "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
526
-
527
- "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
528
-
529
- "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
530
-
531
- "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
532
-
533
- "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
534
-
535
- "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
536
-
537
- "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
538
-
539
- "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
540
-
541
- "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
542
-
543
- "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
544
-
545
- "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
546
-
547
- "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
548
-
549
- "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
550
-
551
- "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
552
-
553
- "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
554
-
555
- "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
556
-
557
- "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
558
-
559
- "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
560
-
561
- "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
562
-
563
- "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
564
-
565
- "lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="],
566
-
567
- "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
568
-
569
- "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
570
-
571
- "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
572
-
573
- "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
574
-
575
- "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
576
-
577
- "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
578
-
579
- "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
580
-
581
- "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
582
-
583
- "motion": ["motion@12.43.0", "", { "dependencies": { "framer-motion": "^12.43.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ=="],
584
-
585
- "motion-dom": ["motion-dom@12.43.0", "", { "dependencies": { "motion-utils": "^12.39.0" } }, "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag=="],
586
-
587
- "motion-utils": ["motion-utils@12.39.0", "", {}, "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ=="],
588
-
589
- "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
590
-
591
- "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
592
-
593
- "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
594
-
595
- "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
596
-
597
- "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
598
-
599
- "node-releases": ["node-releases@2.0.54", "", {}, "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ=="],
600
-
601
- "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
602
-
603
- "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
604
-
605
- "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
606
-
607
- "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
608
-
609
- "path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
610
-
611
- "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
612
-
613
- "picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="],
614
-
615
- "postcss": ["postcss@8.5.28", "", { "dependencies": { "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A=="],
616
-
617
- "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
618
-
619
- "protobufjs": ["protobufjs@7.6.6", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg=="],
620
-
621
- "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
622
-
623
- "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="],
624
-
625
- "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
626
-
627
- "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
628
-
629
- "re2js": ["re2js@2.8.6", "", {}, "sha512-xLgQil4kIUCrAzVk9fRSkxkFNwmygLFjVxXrLc65aE1F0+Zsb8rxumFBy4XKyvgMCTL6kilDq3EZ0piE2dP/Dg=="],
630
-
631
- "react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
632
-
633
- "react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
634
-
635
- "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
636
-
637
- "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
638
-
639
- "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
640
-
641
- "rollup": ["rollup@4.63.1", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.63.1", "@rollup/rollup-android-arm64": "4.63.1", "@rollup/rollup-darwin-arm64": "4.63.1", "@rollup/rollup-darwin-x64": "4.63.1", "@rollup/rollup-freebsd-arm64": "4.63.1", "@rollup/rollup-freebsd-x64": "4.63.1", "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", "@rollup/rollup-linux-arm-musleabihf": "4.63.1", "@rollup/rollup-linux-arm64-gnu": "4.63.1", "@rollup/rollup-linux-arm64-musl": "4.63.1", "@rollup/rollup-linux-loong64-gnu": "4.63.1", "@rollup/rollup-linux-loong64-musl": "4.63.1", "@rollup/rollup-linux-ppc64-gnu": "4.63.1", "@rollup/rollup-linux-ppc64-musl": "4.63.1", "@rollup/rollup-linux-riscv64-gnu": "4.63.1", "@rollup/rollup-linux-riscv64-musl": "4.63.1", "@rollup/rollup-linux-s390x-gnu": "4.63.1", "@rollup/rollup-linux-x64-gnu": "4.63.1", "@rollup/rollup-linux-x64-musl": "4.63.1", "@rollup/rollup-openbsd-x64": "4.63.1", "@rollup/rollup-openharmony-arm64": "4.63.1", "@rollup/rollup-win32-arm64-msvc": "4.63.1", "@rollup/rollup-win32-ia32-msvc": "4.63.1", "@rollup/rollup-win32-x64-gnu": "4.63.1", "@rollup/rollup-win32-x64-msvc": "4.63.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg=="],
642
-
643
- "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
644
-
645
- "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
646
-
647
- "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
648
-
649
- "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
650
-
651
- "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
652
-
653
- "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
654
-
655
- "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
656
-
657
- "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
658
-
659
- "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
660
-
661
- "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
662
-
663
- "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
664
-
665
- "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
666
-
667
- "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
668
-
669
- "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
670
-
671
- "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
672
-
673
- "tailwindcss": ["tailwindcss@4.3.3", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
674
-
675
- "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
676
-
677
- "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
678
-
679
- "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
680
-
681
- "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
682
-
683
- "tsx": ["tsx@4.23.13", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw=="],
684
-
685
- "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
686
-
687
- "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
688
-
689
- "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
690
-
691
- "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
692
-
693
- "update-browserslist-db": ["update-browserslist-db@1.3.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw=="],
694
-
695
- "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
696
-
697
- "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
698
-
699
- "vite": ["vite@6.4.3", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A=="],
700
-
701
- "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
702
-
703
- "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="],
704
-
705
- "websocket-driver": ["websocket-driver@0.7.5", "", { "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" } }, "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA=="],
706
-
707
- "websocket-extensions": ["websocket-extensions@0.1.4", "", {}, "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg=="],
708
-
709
- "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
710
-
711
- "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
712
-
713
- "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
714
-
715
- "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
716
-
717
- "yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
718
-
719
- "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
720
-
721
- "@babel/core/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
722
-
723
- "@babel/traverse/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
724
-
725
- "@grpc/grpc-js/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
726
-
727
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="],
728
-
729
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
730
-
731
- "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="],
732
-
733
- "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="],
734
-
735
- "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
736
-
737
- "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
738
-
739
- "@types/body-parser/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
740
-
741
- "@types/connect/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
742
-
743
- "@types/express-serve-static-core/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
744
-
745
- "@types/send/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
746
-
747
- "@types/serve-static/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
748
-
749
- "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
750
-
751
- "https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
752
-
753
- "protobufjs/@types/node": ["@types/node@26.4.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA=="],
754
-
755
- "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
756
-
757
- "tsx/esbuild": ["esbuild@0.28.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="],
758
-
759
- "@babel/core/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
760
-
761
- "@babel/traverse/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
762
-
763
- "@grpc/grpc-js/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
764
-
765
- "@types/body-parser/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
766
-
767
- "@types/connect/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
768
-
769
- "@types/express-serve-static-core/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
770
-
771
- "@types/send/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
772
-
773
- "@types/serve-static/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
774
-
775
- "https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
776
-
777
- "protobufjs/@types/node/undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
778
-
779
- "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="],
780
-
781
- "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.2", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="],
782
-
783
- "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.2", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="],
784
-
785
- "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.2", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="],
786
-
787
- "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="],
788
-
789
- "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="],
790
-
791
- "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="],
792
-
793
- "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="],
794
-
795
- "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.2", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="],
796
-
797
- "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="],
798
-
799
- "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="],
800
-
801
- "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="],
802
-
803
- "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="],
804
-
805
- "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="],
806
-
807
- "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.2", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="],
808
-
809
- "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="],
810
-
811
- "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.2", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="],
812
-
813
- "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="],
814
-
815
- "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.2", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="],
816
-
817
- "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="],
818
-
819
- "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="],
820
-
821
- "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.2", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="],
822
-
823
- "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="],
824
-
825
- "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="],
826
-
827
- "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="],
828
-
829
- "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="],
830
- }
831
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
firebase-applet-config.json DELETED
@@ -1,11 +0,0 @@
1
- {
2
- "projectId": "gen-lang-client-0212219650",
3
- "appId": "1:580282245939:web:f6211064861f0e63241446",
4
- "apiKey": "AIzaSyC2RYChsvJQ36tNTut-cPzxgDvb-FYFwiI",
5
- "authDomain": "gen-lang-client-0212219650.firebaseapp.com",
6
- "storageBucket": "gen-lang-client-0212219650.firebasestorage.app",
7
- "messagingSenderId": "580282245939",
8
- "measurementId": "",
9
- "oAuthClientId": "580282245939-1mo82qkdoo06vs5u5ogsc9j9lvqqlsfn.apps.googleusercontent.com",
10
- "recaptchaSiteKey": ""
11
- }
 
 
 
 
 
 
 
 
 
 
 
 
index.html CHANGED
@@ -9,7 +9,7 @@
9
  <meta property="og:description" content="Terminal quantitativo híbrido multi-timeframe com confluência de 4 escolas de trading, IA institucional, backtesting diário e alertas críticos." />
10
  <meta property="og:type" content="website" />
11
  <meta name="twitter:card" content="summary_large_image" />
12
- <script type="module" crossorigin src="./assets/index-DKO8x-qK.js"></script>
13
  <link rel="stylesheet" crossorigin href="./assets/index-CNuvSQCK.css">
14
  </head>
15
  <body>
 
9
  <meta property="og:description" content="Terminal quantitativo híbrido multi-timeframe com confluência de 4 escolas de trading, IA institucional, backtesting diário e alertas críticos." />
10
  <meta property="og:type" content="website" />
11
  <meta name="twitter:card" content="summary_large_image" />
12
+ <script type="module" crossorigin src="./assets/index-Bh0tM6mi.js"></script>
13
  <link rel="stylesheet" crossorigin href="./assets/index-CNuvSQCK.css">
14
  </head>
15
  <body>
metadata.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "name": "The God Protocol v2026",
3
- "description": "Terminal quantitativo híbrido multi-timeframe com confluência de 4 escolas de trading, IA institucional, backtesting diário e alertas críticos.",
4
- "requestFramePermissions": [],
5
- "majorCapabilities": ["MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"]
6
- }
7
-
 
 
 
 
 
 
 
 
package.json DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "name": "react-example",
3
- "private": true,
4
- "version": "0.0.0",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "tsx server.ts",
8
- "build": "vite build && esbuild server.ts --bundle --platform=node --format=cjs --packages=external --sourcemap --outfile=dist/server.cjs",
9
- "start": "node dist/server.cjs",
10
- "clean": "rm -rf dist server.js",
11
- "lint": "tsc --noEmit"
12
- },
13
- "dependencies": {
14
- "@google/genai": "^2.4.0",
15
- "@tailwindcss/vite": "^4.1.14",
16
- "@vitejs/plugin-react": "^5.0.4",
17
- "dotenv": "^17.2.3",
18
- "express": "^4.21.2",
19
- "firebase": "^12.18.0",
20
- "lucide-react": "^0.546.0",
21
- "motion": "^12.23.24",
22
- "react": "^19.0.1",
23
- "react-dom": "^19.0.1",
24
- "vite": "^6.2.3"
25
- },
26
- "devDependencies": {
27
- "@types/node": "^22.14.0",
28
- "autoprefixer": "^10.4.21",
29
- "esbuild": "^0.25.0",
30
- "tailwindcss": "^4.1.14",
31
- "tsx": "^4.21.0",
32
- "typescript": "~5.8.2",
33
- "vite": "^6.2.3",
34
- "@types/express": "^4.17.21"
35
- }
36
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server.cjs DELETED
The diff for this file is too large to render. See raw diff
 
server.cjs.map DELETED
The diff for this file is too large to render. See raw diff
 
server.ts DELETED
@@ -1,40 +0,0 @@
1
- import express from 'express';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { createServer as createViteServer } from 'vite';
5
- import { GoogleGenAI } from '@google/genai';
6
- import dotenv from 'dotenv';
7
- import { TradeSignal, SystemNotification } from './src/types.js';
8
- import { fetchTop100Cryptos, syncRealTimePrices, getBrasiliaTimeStr } from './server/realMarketData.js';
9
- import { getMarketSnapshot } from './server/market/marketSnapshotService.js';
10
- import { analyzeMarket } from './server/confluence/marketAnalysisService.js';
11
- import { fetchHistoricalBinanceCandles } from './server/backtest/historicalDataService.js';
12
- import { fetchHistoricalBinanceFunding } from './server/derivatives/historicalFundingService.js';
13
- import { runHistoricalBacktest } from './server/backtest/historicalBacktest.js';
14
- import { generateTradeSignal } from './server/signal/signalEngine.js';
15
- import { fetchRealCandles } from './server/market/candleService.js';
16
- import { buildQuantAnalystPrompt, parseQuantAnalystResponse } from './server/ai/quantAnalyst.js';
17
- import { paperTradingLoop } from './server/paper/paperTradingLoop.js';
18
- import { buildQuantitativeLab } from './server/quant/quantitativeLab.js';
19
- import { validateSelectedParameters } from './server/quant/parameterValidation.js';
20
-
21
- dotenv.config();
22
- const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); const PORT = 3000; app.use(express.json());
23
- let genAI: GoogleGenAI | null = null;
24
- if (process.env.GEMINI_API_KEY) { try { genAI = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY, httpOptions: { headers: { 'User-Agent': 'aistudio-build' } } }); } catch (err) { console.error('Error initializing GoogleGenAI:', err); } }
25
- let cachedSignals: TradeSignal[] = []; let cachedNotifications: SystemNotification[] = []; let isRefreshing = false; let lastRefreshTime = 0; let lastSourceInfo = 'Iniciando varredura das 100 maiores criptos por Market Cap...';
26
- async function refreshMarketData(): Promise<void> { if (isRefreshing) return; isRefreshing = true; try { const signals = await fetchTop100Cryptos(); if (signals?.length) { cachedSignals = signals; lastRefreshTime = Date.now(); lastSourceInfo = 'Top 100 Criptomoedas por Market Cap (Tempo Real • CoinGecko + Binance)'; } const validSignals = cachedSignals.filter(s => s.passedFilter); validSignals.forEach(signal => { if (!cachedNotifications.some(n => n.symbol === signal.symbol && Date.now() - n.timestamp < 900000)) { const timeBrasilia = getBrasiliaTimeStr(Date.now(), true); cachedNotifications.unshift({ id: `notif-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, timestamp: Date.now(), timeStr: timeBrasilia, type: 'CRITICAL_SIGNAL', severity: 'high', symbol: signal.symbol, title: `Setup Quantitativo Confirmado: ${signal.symbol} (#${signal.marketCapRank || 0})`, message: `Confluência institucional atingiu ${signal.confidence}%. Entrada em $${signal.entryPrice} e SL dinâmico em $${signal.stopLoss} (R/R 1:${signal.riskReward}) • ${timeBrasilia} (Horário de Brasília).`, read: false, actionable: true }); } }); cachedSignals.forEach(signal => { if (Math.abs(signal.change24h) >= 7 && !cachedNotifications.some(n => n.symbol === signal.symbol && n.type === 'FUNDING_ALERT' && Date.now() - n.timestamp < 1200000)) { const timeBrasilia = getBrasiliaTimeStr(Date.now(), true); cachedNotifications.unshift({ id: `notif-vol-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`, timestamp: Date.now(), timeStr: timeBrasilia, type: 'FUNDING_ALERT', severity: 'medium', symbol: signal.symbol, title: `Alerta de Volatilidade: ${signal.symbol} (#${signal.marketCapRank || 0})`, message: `Variação de 24h atingiu ${signal.change24h > 0 ? '+' : ''}${signal.change24h}% no ativo #${signal.marketCapRank || 0} • ${timeBrasilia} (Brasília).`, read: false }); } }); if (cachedNotifications.length > 50) cachedNotifications = cachedNotifications.slice(0, 50); } catch (err: any) { console.error('Failed to refresh real-time market data:', err.message); } finally { isRefreshing = false; } }
27
- refreshMarketData(); setInterval(refreshMarketData, 35000); setInterval(async () => { if (cachedSignals.length > 0) { await syncRealTimePrices(); lastRefreshTime = Date.now(); } }, 6000);
28
-
29
- app.get('/api/market/candles/:symbol', async (req, res) => { try { const symbol = decodeURIComponent(req.params.symbol).toUpperCase(); if (!/^[A-Z0-9]+\/[A-Z0-9]+$/.test(symbol)) return res.status(400).json({ error: 'Símbolo inválido. Use o formato BTC/USDT.' }); const snapshot = await getMarketSnapshot(symbol, { forceRefresh: req.query.refresh === 'true' }); return res.json({ success: true, source: 'Binance/OKX real OHLCV', snapshot }); } catch (error: any) { console.error('Real market snapshot error:', error); return res.status(502).json({ success: false, error: error?.message || 'Falha ao carregar dados reais de mercado.' }); } });
30
- app.get('/api/market/analysis/:symbol', async (req, res) => { try { const symbol = decodeURIComponent(req.params.symbol).toUpperCase(); if (!/^[A-Z0-9]+\/[A-Z0-9]+$/.test(symbol)) return res.status(400).json({ error: 'Símbolo inválido. Use o formato BTC/USDT.' }); const analysis = await analyzeMarket(symbol, req.query.refresh === 'true'); return res.json({ success: true, source: 'Binance/OKX real OHLCV + indicadores + estrutura + SMC + divergências + Gann + Wyckoff', analysis }); } catch (error: any) { console.error('Quantitative market analysis error:', error); return res.status(502).json({ success: false, error: error?.message || 'Falha ao executar análise quantitativa.' }); } });
31
- app.get('/api/signal/:symbol', async (req, res) => { try { const symbol = decodeURIComponent(req.params.symbol || 'BTC/USDT').toUpperCase().replace('-', '/'); if (!/^[A-Z0-9]+\/[A-Z0-9]+$/.test(symbol)) return res.status(400).json({ success: false, error: 'Símbolo inválido. Use BTC/USDT.' }); const riskPercent = Number(req.query.riskPercent ?? 1); const analysis = await analyzeMarket(symbol, req.query.refresh === 'true'); const series = await fetchRealCandles(symbol, '15m', 500); const signal = generateTradeSignal(analysis, series.candles, Number.isFinite(riskPercent) ? riskPercent : 1); return res.json({ success: true, source: 'Binance/OKX real OHLCV + quantitative signal engine', signal, generatedAt: Date.now() }); } catch (error: any) { console.error('Signal engine error:', error); return res.status(502).json({ success: false, error: error?.message || 'Falha ao gerar sinal.' }); } });
32
- app.get('/api/paper/status', (_req, res) => res.json({ success: true, ...paperTradingLoop.getStatus(), state: paperTradingLoop.getEngine().getState() })); app.post('/api/paper/start', (_req, res) => { paperTradingLoop.start(); res.json({ success: true, ...paperTradingLoop.getStatus() }); }); app.post('/api/paper/stop', (_req, res) => { paperTradingLoop.stop(); res.json({ success: true, ...paperTradingLoop.getStatus() }); }); app.post('/api/paper/tick', async (_req, res) => { await paperTradingLoop.tick(); const status = paperTradingLoop.getStatus(); const success = status.lastError === null; return res.status(success ? 200 : 502).json({ success, ...status, state: paperTradingLoop.getEngine().getState() }); }); app.post('/api/paper/reset', (_req, res) => { paperTradingLoop.stop(); const state = paperTradingLoop.getEngine().reset(); return res.json({ success: true, ...paperTradingLoop.getStatus(), state }); }); app.post('/api/paper/close/:positionId', (req, res) => { const price = Number(req.body?.price); const closed = paperTradingLoop.getEngine().close(req.params.positionId, price, 'MANUAL'); if (!closed) return res.status(404).json({ success: false, error: 'Posição não encontrada ou preço inválido.' }); return res.json({ success: true, position: closed, state: paperTradingLoop.getEngine().getState() }); });
33
- app.get('/api/market/signals', async (_req, res) => { if (!cachedSignals.length) await refreshMarketData(); res.json({ timestamp: lastRefreshTime || Date.now(), version: 'The God Protocol v2026 (v4.0)', dataSource: lastSourceInfo, isRealTime: true, signals: cachedSignals, activeCount: cachedSignals.filter(s => s.passedFilter).length, monitoredCount: cachedSignals.length }); }); app.post('/api/market/refresh-prices', async (_req, res) => { const updatedSignals = await syncRealTimePrices(); lastRefreshTime = Date.now(); res.json({ success: true, timestamp: lastRefreshTime, signals: updatedSignals, activeCount: updatedSignals.filter(s => s.passedFilter).length }); }); app.post('/api/market/scan', async (_req, res) => { await refreshMarketData(); res.json({ success: true, message: 'Varredura quantitativa em tempo real via Binance.US + OKX concluída com sucesso.', dataSource: lastSourceInfo, signals: cachedSignals }); });
34
- app.post('/api/ai-analysis', async (req, res) => { try { const requestedSymbol = typeof req.body?.symbol === 'string' ? req.body.symbol.toUpperCase().replace('-', '/') : 'BTC/USDT'; if (!/^[A-Z0-9]+\/[A-Z0-9]+$/.test(requestedSymbol)) return res.status(400).json({ error: 'Símbolo inválido. Use BTC/USDT.' }); const riskPercent = Number(req.body?.riskPercent ?? 1); const analysis = await analyzeMarket(requestedSymbol, true); const series = await fetchRealCandles(requestedSymbol, '15m', 500); const signal = generateTradeSignal(analysis, series.candles, Number.isFinite(riskPercent) ? riskPercent : 1); const model = 'gemini-3.8-flash'; if (!genAI) return res.json({ success: true, aiAvailable: false, analysis: { decision: 'WEAKEN', rationale: 'GEMINI_API_KEY não configurada; análise determinística disponível.', riskFlags: ['gemini_api_unavailable'], model: 'local-fallback' }, signal }); const prompt = buildQuantAnalystPrompt({ analysis, signal }); const response = await genAI.models.generateContent({ model, contents: prompt }); const quantAnalyst = parseQuantAnalystResponse(response.text || '', model); return res.json({ success: true, aiAvailable: true, analysis: quantAnalyst, signal, generatedAt: Date.now() }); } catch (error: any) { console.error('Gemini Quant Analyst error:', error); return res.status(502).json({ success: false, error: error?.message || 'Falha no Quant Analyst.' }); } });
35
- app.get('/api/backtest', async (req, res) => { try { const symbol = String(req.query.symbol || 'BTC/USDT').toUpperCase(); if (!/^[A-Z0-9]+\/[A-Z0-9]+$/.test(symbol)) return res.status(400).json({ success: false, error: 'Símbolo inválido. Use o formato BTC/USDT.' }); const requestedDays = Number.parseInt(String(req.query.days || '60'), 10); const days = Number.isFinite(requestedDays) ? Math.max(7, Math.min(requestedDays, 90)) : 60; const endTime = Date.now(); const startTime = endTime - days * 86_400_000; const [candles, historicalFunding] = await Promise.all([fetchHistoricalBinanceCandles(symbol, '15m', startTime, endTime), fetchHistoricalBinanceFunding(symbol, startTime, endTime)]); if (candles.length < 300) return res.status(422).json({ success: false, error: `Histórico insuficiente: ${candles.length} candles.` }); const result = runHistoricalBacktest({ symbol, candles, initialCapital: 10_000, riskPerTradePercent: 1, minScore: 35, minConfidence: 50, atrStopMultiple: 1.5, rewardRisk: 2, maxHoldingBars: 32, warmupBars: 220, historicalFunding }); return res.json({ success: true, source: 'Binance real OHLCV + Binance Futures historical funding', parameters: { symbol, timeframe: '15m', days, candles: candles.length, fundingEvents: historicalFunding.length, riskPerTradePercent: 1, rewardRisk: 2, maxHoldingBars: 32 }, ...result }); } catch (error: any) { console.error('Historical backtest error:', error); return res.status(502).json({ success: false, error: error?.message || 'Falha ao executar backtest histórico real.' }); } });
36
- app.get('/api/quant/lab', async (req, res) => { try { const symbol = String(req.query.symbol || 'BTC/USDT').toUpperCase(); const requestedDays = Number.parseInt(String(req.query.days || '30'), 10); const days = Number.isFinite(requestedDays) ? Math.max(7, Math.min(requestedDays, 90)) : 30; if (!/^[A-Z0-9]+\/[A-Z0-9]+$/.test(symbol)) return res.status(400).json({ success: false, error: 'Símbolo inválido. Use o formato BTC/USDT.' }); const endTime = Date.now(); const startTime = endTime - days * 86_400_000; const [candles, historicalFunding] = await Promise.all([fetchHistoricalBinanceCandles(symbol, '15m', startTime, endTime), fetchHistoricalBinanceFunding(symbol, startTime, endTime)]); if (candles.length < 300) return res.status(422).json({ success: false, error: `Histórico insuficiente: ${candles.length} candles.` }); const options = { symbol, candles, initialCapital: 10_000, riskPerTradePercent: 1, minScore: 35, minConfidence: 50, atrStopMultiple: 1.5, rewardRisk: 2, maxHoldingBars: 32, warmupBars: 220, historicalFunding } as const; const backtest = runHistoricalBacktest(options); const paperState = paperTradingLoop.getEngine().getState(); const lab = buildQuantitativeLab(backtest, paperState, candles); return res.json({ success: true, source: 'Binance real OHLCV + Binance Futures historical funding • Quant Lab completo', parameters: { symbol, timeframe: '15m', days, candles: candles.length, fundingEvents: historicalFunding.length, riskPerTradePercent: 1, rewardRisk: 2, maxHoldingBars: 32 }, ...lab }); } catch (error: any) { console.error('Quantitative lab error:', error); return res.status(502).json({ success: false, error: error?.message || 'Falha ao executar Quant Lab.' }); } });
37
- app.get('/api/quant/validate', async (req, res) => { try { const symbol = String(req.query.symbol || 'BTC/USDT').toUpperCase(); const requestedDays = Number.parseInt(String(req.query.days || '180'), 10); const days = Number.isFinite(requestedDays) ? Math.max(90, Math.min(requestedDays, 180)) : 180; const trainPercent = Number(req.query.trainPercent ?? 70); if (!/^[A-Z0-9]+\/[A-Z0-9]+$/.test(symbol)) return res.status(400).json({ success: false, error: 'Símbolo inválido. Use o formato BTC/USDT.' }); const endTime = Date.now(); const startTime = endTime - days * 86_400_000; const [candles, historicalFunding] = await Promise.all([fetchHistoricalBinanceCandles(symbol, '15m', startTime, endTime), fetchHistoricalBinanceFunding(symbol, startTime, endTime)]); if (candles.length < 5000) return res.status(422).json({ success: false, error: `Histórico insuficiente para OOS multi-regime: ${candles.length} candles. Recomenda-se pelo menos 5.000.` }); const baseOptions = { initialCapital: 10_000, riskPerTradePercent: 1, minScore: 35, minConfidence: 50, atrStopMultiple: 1.5, rewardRisk: 2, maxHoldingBars: 32, warmupBars: 220, historicalFunding }; const validation = validateSelectedParameters(symbol, candles, baseOptions, Number.isFinite(trainPercent) ? trainPercent : 70); return res.json({ success: true, source: 'Binance real OHLCV + Binance Futures historical funding • extended multi-regime OOS validation', parameters: { symbol, timeframe: '15m', days, candles: candles.length, fundingEvents: historicalFunding.length, trainPercent: validation.split.trainPercent, regimeWindowDays: days }, ...validation }); } catch (error: any) { console.error('Parameter OOS validation error:', error); return res.status(502).json({ success: false, error: error?.message || 'Falha na validação OOS multi-regime.' }); } });
38
- app.get('/api/notifications', (_req, res) => res.json({ notifications: cachedNotifications, unreadCount: cachedNotifications.filter(n => !n.read).length })); app.post('/api/notifications/mark-read', (_req, res) => { cachedNotifications.forEach(n => { n.read = true; }); res.json({ success: true, unreadCount: 0 }); }); app.post('/api/notifications/clear', (_req, res) => { cachedNotifications = []; res.json({ success: true, count: 0 }); });
39
- async function startServer() { if (process.env.NODE_ENV !== 'production') { const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'spa' }); app.use(vite.middlewares); } else { const distPath = path.join(process.cwd(), 'dist'); app.use(express.static(distPath)); app.get('*', (_req, res) => res.sendFile(path.join(distPath, 'index.html'))); } app.listen(PORT, '0.0.0.0', () => console.log(`[The God Protocol v2026] Server running on http://0.0.0.0:${PORT}`)); }
40
- startServer();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/ai/quantAnalyst.ts DELETED
@@ -1,42 +0,0 @@
1
- import type { MarketAnalysis } from '../confluence/marketAnalysisService.js';
2
- import type { TradeSignal } from '../signal/signalEngine.js';
3
-
4
- export type QuantAnalystDecision = 'CONFIRM' | 'WEAKEN' | 'REJECT';
5
-
6
- export interface QuantAnalystInput { analysis: MarketAnalysis; signal: TradeSignal; }
7
- export interface QuantAnalystResult { decision: QuantAnalystDecision; rationale: string; riskFlags: string[]; model: string; }
8
-
9
- function compactAnalysis(analysis: MarketAnalysis, signal: TradeSignal): string {
10
- const timeframes = analysis.timeframes.map(tf => ({
11
- timeframe: tf.timeframe,
12
- trend: tf.structure.trend,
13
- rsi: tf.indicators.rsi14,
14
- atr: tf.indicators.atr14,
15
- structure: tf.structure.events.slice(-4),
16
- smcBias: tf.smc.bias,
17
- latestFvg: tf.smc.fairValueGaps.at(-1) ?? null,
18
- latestOrderBlock: tf.smc.orderBlocks.at(-1) ?? null,
19
- divergence: tf.divergences.latest,
20
- gann: tf.gann,
21
- wyckoff: tf.wyckoff,
22
- }));
23
- return JSON.stringify({ symbol: analysis.symbol, fetchedAt: analysis.fetchedAt, confluence: analysis.confluence, derivatives: analysis.derivatives, signal, timeframes });
24
- }
25
-
26
- export function buildQuantAnalystPrompt(input: QuantAnalystInput): string {
27
- return `Você é o Quant Analyst de um sistema profissional de trading de criptomoedas.\n\nSua função é auditar um sinal quantitativo já calculado. NÃO invente preços, indicadores, níveis, eventos ou dados ausentes. NÃO substitua os cálculos determinísticos.\n\nRegras:\n1. Use somente os dados JSON fornecidos.\n2. Avalie alinhamento entre 15m, 1H, 4H e 1D.\n3. Dê peso especial à estrutura, SMC, Wyckoff, divergências e derivativos quando disponíveis.\n4. Procure conflitos, baixa qualidade, divergências contra a direção, ausência de confirmação e risco de contexto.\n5. A decisão deve ser exatamente uma de: CONFIRM, WEAKEN ou REJECT.\n6. CONFIRM apoia o sinal. WEAKEN indica conflitos relevantes. REJECT indica conflito estrutural ou dados insuficientes.\n7. Seja objetivo e não forneça promessa de lucro.\n\nResponda SOMENTE em JSON válido: {"decision":"CONFIRM|WEAKEN|REJECT","rationale":"...","riskFlags":["..."]}\n\nDADOS:\n${compactAnalysis(input.analysis, input.signal)}`;
28
- }
29
-
30
- export function parseQuantAnalystResponse(text: string, model: string): QuantAnalystResult {
31
- const fallback: QuantAnalystResult = { decision: 'WEAKEN', rationale: 'A resposta do modelo não pôde ser validada como JSON estruturado.', riskFlags: ['ai_response_invalid'], model };
32
- try {
33
- const parsed = JSON.parse(text) as Partial<QuantAnalystResult>;
34
- if (!['CONFIRM', 'WEAKEN', 'REJECT'].includes(String(parsed.decision))) return fallback;
35
- return {
36
- decision: parsed.decision as QuantAnalystDecision,
37
- rationale: typeof parsed.rationale === 'string' ? parsed.rationale : 'Sem justificativa estruturada.',
38
- riskFlags: Array.isArray(parsed.riskFlags) ? parsed.riskFlags.filter((flag): flag is string => typeof flag === 'string').slice(0, 10) : [],
39
- model,
40
- };
41
- } catch { return fallback; }
42
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/backtest/historicalBacktest.ts DELETED
@@ -1,104 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import type { HistoricalFundingRate } from '../derivatives/historicalFundingService.js';
3
- import { calculateIndicators } from '../indicators/technicalIndicators.js';
4
- import { analyzeMarketStructure } from '../structure/marketStructure.js';
5
- import { analyzeSMC } from '../structure/smc.js';
6
- import { detectDivergences } from '../divergence/divergenceEngine.js';
7
- import { analyzeGann } from '../gann/gannEngine.js';
8
- import { analyzeWyckoff } from '../wyckoff/wyckoffEngine.js';
9
- import { calculateConfluence } from '../confluence/confluenceEngine.js';
10
-
11
- export interface HistoricalBacktestOptions {
12
- symbol: string;
13
- candles: Candle[];
14
- initialCapital?: number;
15
- riskPerTradePercent?: number;
16
- minScore?: number;
17
- minConfidence?: number;
18
- atrStopMultiple?: number;
19
- rewardRisk?: number;
20
- maxHoldingBars?: number;
21
- warmupBars?: number;
22
- feeBpsPerSide?: number;
23
- slippageBpsPerSide?: number;
24
- latencySlippageBpsPerSide?: number;
25
- fundingRatePer8h?: number;
26
- historicalFunding?: HistoricalFundingRate[];
27
- }
28
-
29
- export interface HistoricalBacktestTrade {
30
- id: string; timestamp: number; date: string; symbol: string; direction: 'LONG' | 'SHORT'; entryPrice: number; exitPrice: number; stopLoss: number; takeProfit: number; rrRatio: number; confidence: number; score: number; grossPnlPercent: number; feePercent: number; slippagePercent: number; pnlPercent: number; pnlR: number; grossPnlR: number; feesR: number; fundingPercent: number; fundingR: number; status: 'TP ATINGIDO' | 'SL ATINGIDO' | 'TIMEOUT'; holdingBars: number;
31
- }
32
-
33
- export interface HistoricalBacktestResult {
34
- symbol: string; timeframe: string; startDate: string; endDate: string; periodDays: number; initialCapital: number; finalCapital: number; totalTrades: number; winningTrades: number; losingTrades: number; winRate: number; profitFactor: number; netProfitPercent: number; grossProfitPercent: number; totalFeesPercent: number; totalSlippagePercent: number; totalFundingPercent: number; maxDrawdownPercent: number; sharpeRatio: number; sortinoRatio: number; averageRR: number; expectancyR: number; grossExpectancyR: number; trades: HistoricalBacktestTrade[]; equityCurve: { date: string; equity: number; tradePnl: number; drawdown: number }[]; costModel: { feeBpsPerSide: number; slippageBpsPerSide: number; latencySlippageBpsPerSide: number; fundingRatePer8h: number; fundingIncluded: boolean; fundingSource: 'historical-binance' | 'proxy' | 'none' };
35
- }
36
-
37
- function mean(values: number[]): number { return values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0; }
38
- function std(values: number[]): number { if (values.length < 2) return 0; const m = mean(values); return Math.sqrt(mean(values.map(v => (v - m) ** 2))); }
39
- function calculateRatio(values: number[], downsideOnly = false): number { if (values.length < 2) return 0; const filtered = downsideOnly ? values.filter(v => v < 0) : values; const denominator = std(filtered); return denominator === 0 ? 0 : mean(values) / denominator * Math.sqrt(values.length); }
40
- function clampIndex(index: number, length: number): number { return Math.max(0, Math.min(index, length - 1)); }
41
-
42
- function historicalFundingSum(funding: HistoricalFundingRate[], entryTimestamp: number, exitTimestamp: number): number {
43
- if (!funding.length) return 0;
44
- let total = 0;
45
- for (const row of funding) {
46
- if (row.timestamp >= entryTimestamp && row.timestamp <= exitTimestamp) total += row.fundingRate;
47
- }
48
- return total;
49
- }
50
-
51
- /** Historical simulation uses only candles available at the signal timestamp. */
52
- export function runHistoricalBacktest(options: HistoricalBacktestOptions): HistoricalBacktestResult {
53
- const candles = [...options.candles].sort((a, b) => a.timestamp - b.timestamp);
54
- const initialCapital = options.initialCapital ?? 10_000;
55
- const riskPerTradePercent = options.riskPerTradePercent ?? 1;
56
- const minScore = options.minScore ?? 35;
57
- const minConfidence = options.minConfidence ?? 50;
58
- const atrStopMultiple = options.atrStopMultiple ?? 1.5;
59
- const rewardRisk = options.rewardRisk ?? 2;
60
- const maxHoldingBars = options.maxHoldingBars ?? 32;
61
- const warmupBars = Math.max(options.warmupBars ?? 220, 220);
62
- const feeBpsPerSide = Math.max(0, options.feeBpsPerSide ?? 5);
63
- const baseSlippageBpsPerSide = Math.max(0, options.slippageBpsPerSide ?? 2);
64
- const latencySlippageBpsPerSide = Math.max(0, options.latencySlippageBpsPerSide ?? 1);
65
- const effectiveSlippageBpsPerSide = baseSlippageBpsPerSide + latencySlippageBpsPerSide;
66
- const proxyFundingRatePer8h = options.fundingRatePer8h ?? 0.0001;
67
- const feeRate = feeBpsPerSide / 10_000;
68
- const slippageRate = effectiveSlippageBpsPerSide / 10_000;
69
- const fundingRate = Number.isFinite(proxyFundingRatePer8h) ? Math.max(-0.01, Math.min(0.01, proxyFundingRatePer8h)) : 0;
70
- const historicalFunding = [...(options.historicalFunding ?? [])].filter(r => Number.isFinite(r.timestamp) && Number.isFinite(r.fundingRate)).sort((a, b) => a.timestamp - b.timestamp);
71
- const hasHistoricalFunding = historicalFunding.length > 0;
72
-
73
- const trades: HistoricalBacktestTrade[] = [];
74
- const equityCurve: HistoricalBacktestResult['equityCurve'] = [];
75
- let equity = initialCapital; let peak = equity; let maxDrawdownPercent = 0; let totalFees = 0; let totalSlippage = 0; let totalFunding = 0; let grossProfit = 0; let nextFreeIndex = warmupBars;
76
-
77
- for (let i = warmupBars; i < candles.length - 2; i += 1) {
78
- if (i < nextFreeIndex) continue;
79
- const history = candles.slice(0, i + 1);
80
- const indicators = calculateIndicators(history); const structure = analyzeMarketStructure(history); const smc = analyzeSMC(history, structure, indicators); const divergences = detectDivergences(history, indicators); const gann = analyzeGann(history, structure); const wyckoff = analyzeWyckoff(history, structure, indicators); const confluence = calculateConfluence([{ timeframe: '15m', indicators, structure, smc, divergences, gann, wyckoff }], null);
81
- if (Math.abs(confluence.score) < minScore || confluence.confidence < minConfidence || confluence.entryQuality === 'avoid') continue;
82
- const direction: 'LONG' | 'SHORT' = confluence.bias === 'bullish' ? 'LONG' : 'SHORT'; if (confluence.bias === 'neutral') continue;
83
- const entryIndex = i + 1; const entry = candles[entryIndex]; const atr = indicators.atr14; if (!Number.isFinite(atr) || atr <= 0 || !Number.isFinite(entry.open)) continue;
84
- const stopDistance = atr * atrStopMultiple; const rawEntryPrice = entry.open; const entryPrice = direction === 'LONG' ? rawEntryPrice * (1 + slippageRate) : rawEntryPrice * (1 - slippageRate); const stopLoss = direction === 'LONG' ? entryPrice - stopDistance : entryPrice + stopDistance; const takeProfit = direction === 'LONG' ? entryPrice + stopDistance * rewardRisk : entryPrice - stopDistance * rewardRisk;
85
- const lastIndex = clampIndex(entryIndex + maxHoldingBars, candles.length - 1); let rawExitPrice = candles[lastIndex].close; let status: HistoricalBacktestTrade['status'] = 'TIMEOUT'; let exitIndex = lastIndex;
86
- for (let j = entryIndex; j <= lastIndex; j += 1) { const candle = candles[j]; const hitStop = direction === 'LONG' ? candle.low <= stopLoss : candle.high >= stopLoss; const hitTarget = direction === 'LONG' ? candle.high >= takeProfit : candle.low <= takeProfit; if (hitStop) { rawExitPrice = stopLoss; status = 'SL ATINGIDO'; exitIndex = j; break; } if (hitTarget) { rawExitPrice = takeProfit; status = 'TP ATINGIDO'; exitIndex = j; break; } }
87
- const exitPrice = direction === 'LONG' ? rawExitPrice * (1 - slippageRate) : rawExitPrice * (1 + slippageRate);
88
- const units = (equity * (riskPerTradePercent / 100)) / stopDistance; const notionalEntry = units * entryPrice; const notionalExit = units * exitPrice; const grossPnl = direction === 'LONG' ? units * (exitPrice - entryPrice) : units * (entryPrice - exitPrice); const fees = (notionalEntry + notionalExit) * feeRate;
89
- const slippageCost = units * Math.abs(entryPrice - rawEntryPrice) + units * Math.abs(exitPrice - rawExitPrice);
90
- const holdingBars = exitIndex - entryIndex + 1;
91
- const fundingRateApplied = hasHistoricalFunding ? historicalFundingSum(historicalFunding, entry.timestamp, candles[exitIndex].timestamp) : fundingRate * (holdingBars / 32);
92
- const fundingSignedCost = (direction === 'LONG' ? 1 : -1) * notionalEntry * fundingRateApplied;
93
- const netPnl = grossPnl - fees - fundingSignedCost;
94
- const grossPnlPercent = notionalEntry > 0 ? grossPnl / notionalEntry * 100 : 0; const feePercent = notionalEntry > 0 ? fees / notionalEntry * 100 : 0; const slippagePercent = notionalEntry > 0 ? slippageCost / notionalEntry * 100 : 0; const fundingPercent = notionalEntry > 0 ? fundingSignedCost / notionalEntry * 100 : 0; const pnlPercent = notionalEntry > 0 ? netPnl / notionalEntry * 100 : 0;
95
- const riskCapital = equity * (riskPerTradePercent / 100); const grossPnlR = riskCapital > 0 ? grossPnl / riskCapital : 0; const feesR = riskCapital > 0 ? fees / riskCapital : 0; const fundingR = riskCapital > 0 ? fundingSignedCost / riskCapital : 0; const pnlR = riskCapital > 0 ? netPnl / riskCapital : 0;
96
- let worstIntratradeEquity = equity; const entryFee = notionalEntry * feeRate;
97
- for (let j = entryIndex; j <= exitIndex; j += 1) { const candle = candles[j]; const adversePrice = direction === 'LONG' ? candle.low : candle.high; const unrealizedPnl = direction === 'LONG' ? units * (adversePrice - entryPrice) : units * (entryPrice - adversePrice); worstIntratradeEquity = Math.min(worstIntratradeEquity, equity + unrealizedPnl - entryFee); }
98
- if (peak > 0) maxDrawdownPercent = Math.max(maxDrawdownPercent, Math.max(0, (peak - worstIntratradeEquity) / peak * 100));
99
- equity += netPnl; totalFees += fees; totalSlippage += slippageCost; totalFunding += fundingSignedCost; if (grossPnl > 0) grossProfit += grossPnl; peak = Math.max(peak, equity); const drawdown = peak > 0 ? (peak - equity) / peak * 100 : 0; maxDrawdownPercent = Math.max(maxDrawdownPercent, drawdown);
100
- const date = new Date(entry.timestamp).toISOString().slice(0, 10); trades.push({ id: `${options.symbol.replace('/', '')}-${entry.timestamp}`, timestamp: entry.timestamp, date, symbol: options.symbol, direction, entryPrice, exitPrice, stopLoss, takeProfit, rrRatio: rewardRisk, confidence: confluence.confidence, score: confluence.score, grossPnlPercent, feePercent, slippagePercent, pnlPercent, pnlR, grossPnlR, feesR, fundingPercent, fundingR, status, holdingBars }); equityCurve.push({ date, equity, tradePnl: netPnl, drawdown }); nextFreeIndex = exitIndex + 1;
101
- }
102
- const rValues = trades.map(t => t.pnlR); const grossRValues = trades.map(t => t.grossPnlR); const wins = trades.filter(t => t.pnlR > 0); const losses = trades.filter(t => t.pnlR < 0); const netRProfit = wins.reduce((sum, t) => sum + t.pnlR, 0); const netRLoss = Math.abs(losses.reduce((sum, t) => sum + t.pnlR, 0)); const start = candles[0]?.timestamp ?? Date.now(); const end = candles[candles.length - 1]?.timestamp ?? start; const periodDays = Math.max(0, (end - start) / 86_400_000);
103
- return { symbol: options.symbol, timeframe: '15m', startDate: new Date(start).toISOString(), endDate: new Date(end).toISOString(), periodDays, initialCapital, finalCapital: equity, totalTrades: trades.length, winningTrades: wins.length, losingTrades: losses.length, winRate: trades.length ? wins.length / trades.length * 100 : 0, profitFactor: netRLoss > 0 ? netRProfit / netRLoss : netRProfit > 0 ? Infinity : 0, netProfitPercent: (equity - initialCapital) / initialCapital * 100, grossProfitPercent: initialCapital > 0 ? grossProfit / initialCapital * 100 : 0, totalFeesPercent: initialCapital > 0 ? totalFees / initialCapital * 100 : 0, totalSlippagePercent: initialCapital > 0 ? totalSlippage / initialCapital * 100 : 0, totalFundingPercent: initialCapital > 0 ? totalFunding / initialCapital * 100 : 0, maxDrawdownPercent, sharpeRatio: calculateRatio(rValues), sortinoRatio: calculateRatio(rValues, true), averageRR: mean(rValues), expectancyR: mean(rValues), grossExpectancyR: mean(grossRValues), trades, equityCurve, costModel: { feeBpsPerSide, slippageBpsPerSide: baseSlippageBpsPerSide, latencySlippageBpsPerSide, fundingRatePer8h: hasHistoricalFunding ? 0 : fundingRate, fundingIncluded: hasHistoricalFunding || fundingRate !== 0, fundingSource: hasHistoricalFunding ? 'historical-binance' : fundingRate !== 0 ? 'proxy' : 'none' } };
104
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/backtest/historicalDataService.ts DELETED
@@ -1,192 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
-
3
- const INTERVAL_MS: Record<string, number> = {
4
- '15m': 15 * 60_000,
5
- '1h': 60 * 60_000,
6
- '4h': 4 * 60 * 60_000,
7
- '1d': 24 * 60 * 60_000,
8
- };
9
-
10
- const OKX_BAR: Record<string, string> = {
11
- '15m': '15m',
12
- '1h': '1H',
13
- '4h': '4H',
14
- '1d': '1Dutc',
15
- };
16
-
17
- const TIMEOUT_MS = 10_000;
18
-
19
- function normalizeSymbol(symbol: string): string {
20
- return symbol.replace('/', '').toUpperCase();
21
- }
22
-
23
- function normalizeOkxSpotInstrument(symbol: string): string {
24
- return symbol.replace('/', '-').toUpperCase();
25
- }
26
-
27
- function parseCandle(row: unknown): Candle | null {
28
- if (!Array.isArray(row) || row.length < 6) return null;
29
- const timestamp = Number(row[0]);
30
- const open = Number(row[1]);
31
- const high = Number(row[2]);
32
- const low = Number(row[3]);
33
- const close = Number(row[4]);
34
- const volume = Number(row[5]);
35
-
36
- if (
37
- !Number.isFinite(timestamp) ||
38
- !Number.isFinite(open) ||
39
- !Number.isFinite(high) ||
40
- !Number.isFinite(low) ||
41
- !Number.isFinite(close) ||
42
- !Number.isFinite(volume) ||
43
- high < Math.max(open, close, low) ||
44
- low > Math.min(open, close, high) ||
45
- volume < 0
46
- ) return null;
47
-
48
- return {
49
- timestamp,
50
- timeStr: new Date(timestamp).toISOString(),
51
- open,
52
- high,
53
- low,
54
- close,
55
- volume,
56
- };
57
- }
58
-
59
- async function fetchBinanceHistoricalCandles(
60
- symbol: string,
61
- interval: string,
62
- startTime: number,
63
- endTime: number,
64
- ): Promise<Candle[]> {
65
- const step = INTERVAL_MS[interval];
66
- if (!step) throw new Error(`Unsupported historical interval: ${interval}`);
67
-
68
- const result: Candle[] = [];
69
- let cursor = startTime;
70
- const maxPages = Math.ceil((endTime - startTime) / step / 1000) + 2;
71
-
72
- for (let page = 0; page < maxPages && cursor < endTime; page += 1) {
73
- const url = new URL('https://api.binance.com/api/v3/klines');
74
- url.searchParams.set('symbol', normalizeSymbol(symbol));
75
- url.searchParams.set('interval', interval);
76
- url.searchParams.set('limit', '1000');
77
- url.searchParams.set('startTime', String(cursor));
78
- url.searchParams.set('endTime', String(endTime));
79
-
80
- const response = await fetch(url, {
81
- headers: { Accept: 'application/json' },
82
- signal: AbortSignal.timeout(TIMEOUT_MS),
83
- });
84
- if (!response.ok) throw new Error(`Binance historical HTTP ${response.status}`);
85
- const data = await response.json() as unknown;
86
- if (!Array.isArray(data)) throw new Error('Binance historical payload is invalid');
87
- if (data.length === 0) break;
88
-
89
- for (const row of data) {
90
- const candle = parseCandle(row);
91
- if (candle && candle.timestamp >= startTime && candle.timestamp < endTime) result.push(candle);
92
- }
93
-
94
- const lastTimestamp = Number((data[data.length - 1] as unknown[])[0]);
95
- if (!Number.isFinite(lastTimestamp) || lastTimestamp < cursor) break;
96
- cursor = lastTimestamp + step;
97
- if (data.length < 1000) break;
98
- }
99
-
100
- return result;
101
- }
102
-
103
- async function fetchOkxHistoricalCandles(
104
- symbol: string,
105
- interval: string,
106
- startTime: number,
107
- endTime: number,
108
- ): Promise<Candle[]> {
109
- const step = INTERVAL_MS[interval];
110
- const bar = OKX_BAR[interval];
111
- if (!step || !bar) throw new Error(`Unsupported OKX historical interval: ${interval}`);
112
-
113
- const instId = normalizeOkxSpotInstrument(symbol);
114
- const result: Candle[] = [];
115
- let after: number | null = null;
116
- const maxPages = Math.ceil((endTime - startTime) / step / 300) + 4;
117
-
118
- for (let page = 0; page < maxPages; page += 1) {
119
- const url = new URL('https://www.okx.com/api/v5/market/history-candles');
120
- url.searchParams.set('instId', instId);
121
- url.searchParams.set('bar', bar);
122
- url.searchParams.set('limit', '300');
123
- if (after !== null) url.searchParams.set('after', String(after));
124
-
125
- const response = await fetch(url, {
126
- headers: { Accept: 'application/json' },
127
- signal: AbortSignal.timeout(TIMEOUT_MS),
128
- });
129
- if (!response.ok) throw new Error(`OKX historical HTTP ${response.status}`);
130
-
131
- const payload = await response.json() as unknown;
132
- if (!payload || typeof payload !== 'object') throw new Error('OKX historical payload is invalid');
133
- const body = payload as { code?: string; msg?: string; data?: unknown[] };
134
- if (body.code !== '0' || !Array.isArray(body.data)) {
135
- throw new Error(`OKX historical returned code ${body.code ?? 'unknown'}: ${body.msg ?? 'invalid payload'}`);
136
- }
137
- if (body.data.length === 0) break;
138
-
139
- let oldestTimestamp = Number.POSITIVE_INFINITY;
140
- let added = 0;
141
-
142
- for (const row of body.data) {
143
- const candle = parseCandle(row);
144
- if (!candle) continue;
145
- oldestTimestamp = Math.min(oldestTimestamp, candle.timestamp);
146
- if (candle.timestamp >= startTime && candle.timestamp < endTime) {
147
- result.push(candle);
148
- added += 1;
149
- }
150
- }
151
-
152
- if (!Number.isFinite(oldestTimestamp) || oldestTimestamp <= startTime) break;
153
- if (added === 0 && oldestTimestamp < startTime) break;
154
-
155
- const nextAfter = oldestTimestamp;
156
- if (after !== null && nextAfter >= after) break;
157
- after = nextAfter;
158
- }
159
-
160
- return result;
161
- }
162
-
163
- function dedupeAndSort(candles: Candle[]): Candle[] {
164
- const unique = new Map<number, Candle>();
165
- for (const candle of candles) unique.set(candle.timestamp, candle);
166
- return [...unique.values()].sort((a, b) => a.timestamp - b.timestamp);
167
- }
168
-
169
- export async function fetchHistoricalBinanceCandles(
170
- symbol: string,
171
- interval = '15m',
172
- startTime: number,
173
- endTime: number,
174
- ): Promise<Candle[]> {
175
- const step = INTERVAL_MS[interval];
176
- if (!step) throw new Error(`Unsupported historical interval: ${interval}`);
177
- if (endTime <= startTime) throw new Error('Historical endTime must be greater than startTime');
178
-
179
- try {
180
- const binanceCandles = await fetchBinanceHistoricalCandles(symbol, interval, startTime, endTime);
181
- if (binanceCandles.length >= 300) return dedupeAndSort(binanceCandles);
182
- } catch (error) {
183
- console.warn(`Binance historical unavailable for ${symbol}:`, error);
184
- }
185
-
186
- const okxCandles = await fetchOkxHistoricalCandles(symbol, interval, startTime, endTime);
187
- const candles = dedupeAndSort(okxCandles);
188
- if (candles.length === 0) {
189
- throw new Error(`All historical providers failed for ${symbol}. Binance and OKX returned no usable candles.`);
190
- }
191
- return candles;
192
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/confluence/confluenceEngine.ts DELETED
@@ -1,123 +0,0 @@
1
- import type { IndicatorSnapshot } from '../indicators/technicalIndicators.js';
2
- import type { MarketStructureSnapshot } from '../structure/marketStructure.js';
3
- import type { SMCAnalysis } from '../structure/smc.js';
4
- import type { DerivativesAnalysis } from '../derivatives/derivativesAnalysis.js';
5
- import type { DivergenceAnalysis } from '../divergence/divergenceEngine.js';
6
- import type { GannAnalysis } from '../gann/gannEngine.js';
7
- import type { WyckoffAnalysis } from '../wyckoff/wyckoffEngine.js';
8
-
9
- export type ConfluenceBias = 'bullish' | 'bearish' | 'neutral';
10
-
11
- export interface TimeframeConfluence {
12
- timeframe: '15m' | '1h' | '4h' | '1d';
13
- bias: ConfluenceBias;
14
- score: number;
15
- reasons: string[];
16
- }
17
-
18
- export interface ConfluenceAnalysis {
19
- bias: ConfluenceBias;
20
- score: number;
21
- confidence: number;
22
- timeframes: TimeframeConfluence[];
23
- confirmations: string[];
24
- conflicts: string[];
25
- entryQuality: 'A+' | 'A' | 'B' | 'C' | 'avoid';
26
- derivatives: DerivativesAnalysis | null;
27
- divergenceScore: number;
28
- gannScore: number;
29
- wyckoffScore: number;
30
- }
31
-
32
- function signBias(bias: ConfluenceBias): number {
33
- return bias === 'bullish' ? 1 : bias === 'bearish' ? -1 : 0;
34
- }
35
-
36
- function indicatorBias(ind: IndicatorSnapshot): { bias: ConfluenceBias; reasons: string[] } {
37
- const reasons: string[] = [];
38
- let score = 0;
39
- if (ind.ema9 > ind.ema21) { score += 1; reasons.push('EMA9 acima da EMA21'); }
40
- else if (ind.ema9 < ind.ema21) { score -= 1; reasons.push('EMA9 abaixo da EMA21'); }
41
- if (ind.ema21 > ind.ema50) { score += 1; reasons.push('EMA21 acima da EMA50'); }
42
- else if (ind.ema21 < ind.ema50) { score -= 1; reasons.push('EMA21 abaixo da EMA50'); }
43
- if (ind.ema50 > ind.ema200) { score += 1; reasons.push('EMA50 acima da EMA200'); }
44
- else if (ind.ema50 < ind.ema200) { score -= 1; reasons.push('EMA50 abaixo da EMA200'); }
45
- if (ind.rsi14 >= 55 && ind.rsi14 <= 70) { score += 1; reasons.push('RSI em regime comprador'); }
46
- else if (ind.rsi14 <= 45 && ind.rsi14 >= 30) { score -= 1; reasons.push('RSI em regime vendedor'); }
47
- if (ind.relativeVolume20 >= 1.2) reasons.push('Volume relativo acima da média');
48
- return { bias: score > 0 ? 'bullish' : score < 0 ? 'bearish' : 'neutral', reasons };
49
- }
50
-
51
- export function calculateConfluence(
52
- inputs: Array<{ timeframe: '15m' | '1h' | '4h' | '1d'; indicators: IndicatorSnapshot; structure: MarketStructureSnapshot; smc: SMCAnalysis; divergences?: DivergenceAnalysis; gann?: GannAnalysis; wyckoff?: WyckoffAnalysis }>,
53
- derivatives: DerivativesAnalysis | null = null,
54
- ): ConfluenceAnalysis {
55
- const weights: Record<'15m' | '1h' | '4h' | '1d', number> = { '15m': 1, '1h': 2, '4h': 3, '1d': 4 };
56
- const timeframes: TimeframeConfluence[] = [];
57
- let weightedScore = 0;
58
- let totalWeight = 0;
59
- const confirmations: string[] = [];
60
- const conflicts: string[] = [];
61
- let divergenceScore = 0;
62
- let gannScore = 0;
63
- let wyckoffScore = 0;
64
-
65
- for (const input of inputs) {
66
- const ib = indicatorBias(input.indicators);
67
- let score = signBias(input.structure.trend) * 2 + signBias(input.smc.bias) * 2 + signBias(ib.bias);
68
- const reasons = [...ib.reasons];
69
- if (input.structure.latestEvent?.type === 'BOS') { score += signBias(input.structure.latestEvent.direction); reasons.push(`BOS ${input.structure.latestEvent.direction}`); }
70
- if (input.structure.latestEvent?.type === 'CHoCH') { score += signBias(input.structure.latestEvent.direction); reasons.push(`CHoCH ${input.structure.latestEvent.direction}`); }
71
- if (input.structure.latestSweep) { const sweepBias = input.structure.latestSweep.type === 'low' ? 1 : -1; score += sweepBias; reasons.push(input.structure.latestSweep.type === 'low' ? 'Sweep de sell-side liquidity' : 'Sweep de buy-side liquidity'); }
72
- if (input.smc.premiumDiscount?.zone === 'discount') score += 1;
73
- if (input.smc.premiumDiscount?.zone === 'premium') score -= 1;
74
-
75
- const latestDivergence = input.divergences?.latest;
76
- if (latestDivergence && latestDivergence.strength >= 25) {
77
- const dScore = latestDivergence.type.startsWith('bullish') ? 2 : -2;
78
- score += dScore;
79
- divergenceScore += dScore * weights[input.timeframe];
80
- reasons.push(`Divergência ${latestDivergence.type} (${latestDivergence.strength}/100)`);
81
- }
82
- if (input.gann) {
83
- const gScore = Math.max(-1, Math.min(1, input.gann.score));
84
- score += gScore;
85
- gannScore += gScore * weights[input.timeframe];
86
- if (gScore !== 0) reasons.push(`Gann ${input.gann.bias}`);
87
- }
88
- if (input.wyckoff) {
89
- const wScore = Math.max(-2, Math.min(2, input.wyckoff.score));
90
- score += wScore;
91
- wyckoffScore += wScore * weights[input.timeframe];
92
- if (input.wyckoff.latestEvent) reasons.push(`Wyckoff ${input.wyckoff.latestEvent}`);
93
- reasons.push(`Fase Wyckoff ${input.wyckoff.phase}`);
94
- }
95
-
96
- const bias: ConfluenceBias = score >= 2 ? 'bullish' : score <= -2 ? 'bearish' : 'neutral';
97
- const weight = weights[input.timeframe];
98
- weightedScore += score * weight;
99
- totalWeight += weight * 13;
100
- timeframes.push({ timeframe: input.timeframe, bias, score, reasons });
101
- if (score >= 5) confirmations.push(`${input.timeframe}: confluência bullish forte`);
102
- if (score <= -5) confirmations.push(`${input.timeframe}: confluência bearish forte`);
103
- }
104
-
105
- if (derivatives) {
106
- weightedScore += signBias(derivatives.bias) * 2;
107
- totalWeight += 2 * 13;
108
- if (derivatives.score > 0) confirmations.push('Derivativos favorecem cenário bullish');
109
- if (derivatives.score < 0) confirmations.push('Derivativos mostram excesso de posicionamento comprador');
110
- if (derivatives.score === 0) conflicts.push('Derivativos sem confirmação direcional');
111
- }
112
-
113
- const normalized = totalWeight ? weightedScore / totalWeight : 0;
114
- const score = Math.round(Math.max(-100, Math.min(100, normalized * 100)));
115
- const bias: ConfluenceBias = score >= 20 ? 'bullish' : score <= -20 ? 'bearish' : 'neutral';
116
- const confidence = Math.round(Math.min(99, Math.abs(score) + confirmations.length * 3));
117
- const bullishCount = timeframes.filter(t => t.bias === 'bullish').length;
118
- const bearishCount = timeframes.filter(t => t.bias === 'bearish').length;
119
- if (bullishCount > 0 && bearishCount > 0) conflicts.push('Timeframes apresentam conflito de direção');
120
- if (timeframes.some(t => t.timeframe === '1d' && t.bias !== bias)) conflicts.push('Daily não confirma o viés dominante');
121
- const entryQuality = confidence >= 80 && conflicts.length === 0 ? 'A+' : confidence >= 70 ? 'A' : confidence >= 55 ? 'B' : confidence >= 35 ? 'C' : 'avoid';
122
- return { bias, score, confidence, timeframes, confirmations, conflicts, entryQuality, derivatives, divergenceScore, gannScore, wyckoffScore };
123
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/confluence/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { calculateConfluence } from './confluenceEngine.js';
2
- export type { ConfluenceBias, TimeframeConfluence, ConfluenceAnalysis } from './confluenceEngine.js';
 
 
 
server/confluence/marketAnalysisService.ts DELETED
@@ -1,60 +0,0 @@
1
- import { calculateIndicators } from '../indicators/technicalIndicators.js';
2
- import { getMarketSnapshot } from '../market/marketSnapshotService.js';
3
- import { analyzeMarketStructure } from '../structure/marketStructure.js';
4
- import { analyzeSMC } from '../structure/smc.js';
5
- import { fetchDerivativesSnapshot } from '../derivatives/derivativesClient.js';
6
- import { analyzeDerivatives } from '../derivatives/derivativesAnalysis.js';
7
- import { detectDivergences, type DivergenceAnalysis } from '../divergence/divergenceEngine.js';
8
- import { analyzeGann, type GannAnalysis } from '../gann/gannEngine.js';
9
- import { analyzeWyckoff, type WyckoffAnalysis } from '../wyckoff/wyckoffEngine.js';
10
- import { calculateConfluence, type ConfluenceAnalysis } from './confluenceEngine.js';
11
- import type { MarketTimeframe } from '../market/candleService.js';
12
-
13
- export interface TimeframeMarketAnalysis {
14
- timeframe: MarketTimeframe;
15
- exchange: string;
16
- latestTimestamp: number;
17
- indicators: ReturnType<typeof calculateIndicators>;
18
- structure: ReturnType<typeof analyzeMarketStructure>;
19
- smc: ReturnType<typeof analyzeSMC>;
20
- divergences: DivergenceAnalysis;
21
- gann: GannAnalysis;
22
- wyckoff: WyckoffAnalysis;
23
- }
24
-
25
- export interface MarketAnalysis {
26
- symbol: string;
27
- fetchedAt: number;
28
- timeframes: TimeframeMarketAnalysis[];
29
- derivatives: ReturnType<typeof analyzeDerivatives> | null;
30
- confluence: ConfluenceAnalysis;
31
- }
32
-
33
- export async function analyzeMarket(symbol: string, forceRefresh = false): Promise<MarketAnalysis> {
34
- const snapshot = await getMarketSnapshot(symbol, { forceRefresh });
35
- const timeframes = (['15m', '1h', '4h', '1d'] as MarketTimeframe[]).map((timeframe) => {
36
- const series = snapshot.series[timeframe];
37
- const indicators = calculateIndicators(series.candles);
38
- const structure = analyzeMarketStructure(series.candles);
39
- const smc = analyzeSMC(series.candles, structure, indicators);
40
- const divergences = detectDivergences(series.candles, indicators);
41
- const gann = analyzeGann(series.candles, structure);
42
- const wyckoff = analyzeWyckoff(series.candles, structure, indicators);
43
- return { timeframe, exchange: series.exchange, latestTimestamp: series.latestTimestamp, indicators, structure, smc, divergences, gann, wyckoff };
44
- });
45
-
46
- let derivatives: ReturnType<typeof analyzeDerivatives> | null = null;
47
- try {
48
- derivatives = analyzeDerivatives(await fetchDerivativesSnapshot(symbol));
49
- } catch (error) {
50
- console.warn(`Derivatives unavailable for ${symbol}:`, error);
51
- }
52
-
53
- return {
54
- symbol: snapshot.symbol,
55
- fetchedAt: snapshot.fetchedAt,
56
- timeframes,
57
- derivatives,
58
- confluence: calculateConfluence(timeframes, derivatives),
59
- };
60
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/derivatives/derivativesAnalysis.ts DELETED
@@ -1,46 +0,0 @@
1
- import type { DerivativesSnapshot } from './derivativesClient.js';
2
-
3
- export type DerivativesBias = 'bullish' | 'bearish' | 'neutral';
4
-
5
- export interface DerivativesAnalysis {
6
- bias: DerivativesBias;
7
- score: number;
8
- fundingState: 'positive' | 'negative' | 'neutral' | 'unavailable';
9
- positioningState: 'long-heavy' | 'short-heavy' | 'balanced' | 'unavailable';
10
- reasons: string[];
11
- }
12
-
13
- export function analyzeDerivatives(data: DerivativesSnapshot): DerivativesAnalysis {
14
- let score = 0;
15
- const reasons: string[] = [];
16
-
17
- if (data.fundingRate === null) {
18
- reasons.push('Funding indisponível');
19
- } else if (data.fundingRate > 0.0003) {
20
- score -= 1;
21
- reasons.push('Funding positivo elevado, favorece risco de longs congestionados');
22
- } else if (data.fundingRate < -0.0003) {
23
- score += 1;
24
- reasons.push('Funding negativo elevado, favorece risco de shorts congestionados');
25
- } else {
26
- reasons.push('Funding próximo do neutro');
27
- }
28
-
29
- if (data.longShortRatio === null) {
30
- reasons.push('Long/Short indisponível');
31
- } else if (data.longShortRatio > 1.2) {
32
- score -= 1;
33
- reasons.push('Contas posicionadas majoritariamente em long');
34
- } else if (data.longShortRatio < 0.83) {
35
- score += 1;
36
- reasons.push('Contas posicionadas majoritariamente em short');
37
- } else {
38
- reasons.push('Posicionamento Long/Short equilibrado');
39
- }
40
-
41
- const bias: DerivativesBias = score > 0 ? 'bullish' : score < 0 ? 'bearish' : 'neutral';
42
- const fundingState = data.fundingRate === null ? 'unavailable' : data.fundingRate > 0.0001 ? 'positive' : data.fundingRate < -0.0001 ? 'negative' : 'neutral';
43
- const positioningState = data.longShortRatio === null ? 'unavailable' : data.longShortRatio > 1.2 ? 'long-heavy' : data.longShortRatio < 0.83 ? 'short-heavy' : 'balanced';
44
-
45
- return { bias, score, fundingState, positioningState, reasons };
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/derivatives/derivativesClient.ts DELETED
@@ -1,116 +0,0 @@
1
- export interface DerivativesSnapshot {
2
- exchange: 'binance-futures' | 'okx-swap';
3
- symbol: string;
4
- timestamp: number;
5
- fundingRate: number | null;
6
- fundingTime: number | null;
7
- openInterest: number | null;
8
- openInterestValue: number | null;
9
- longShortRatio: number | null;
10
- longAccountRatio: number | null;
11
- shortAccountRatio: number | null;
12
- }
13
-
14
- const TIMEOUT_MS = 10_000;
15
-
16
- function normalizeSymbol(symbol: string): string {
17
- return symbol.replace('/', '').toUpperCase();
18
- }
19
-
20
- function normalizeOkxSwapInstrument(symbol: string): string {
21
- const normalized = symbol.replace('/', '-').toUpperCase();
22
- return normalized.endsWith('-SWAP') ? normalized : `${normalized}-SWAP`;
23
- }
24
-
25
- async function getJson(url: string): Promise<any> {
26
- const response = await fetch(url, {
27
- headers: { Accept: 'application/json' },
28
- signal: AbortSignal.timeout(TIMEOUT_MS),
29
- });
30
- if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
31
- return response.json();
32
- }
33
-
34
- async function fetchBinanceDerivativesSnapshot(symbol: string): Promise<DerivativesSnapshot> {
35
- const normalized = normalizeSymbol(symbol);
36
- const base = 'https://fapi.binance.com';
37
- const [funding, oi, ratio] = await Promise.all([
38
- getJson(`${base}/fapi/v1/premiumIndex?symbol=${normalized}`),
39
- getJson(`${base}/fapi/v1/openInterest?symbol=${normalized}`),
40
- getJson(`${base}/futures/data/globalLongShortAccountRatio?symbol=${normalized}&period=5m&limit=1`),
41
- ]);
42
-
43
- const ratioRow = Array.isArray(ratio) ? ratio[0] : null;
44
- const longShortRatio = ratioRow && Number.isFinite(Number(ratioRow.longShortRatio)) ? Number(ratioRow.longShortRatio) : null;
45
- const longAccountRatio = ratioRow && Number.isFinite(Number(ratioRow.longAccount)) ? Number(ratioRow.longAccount) : null;
46
- const shortAccountRatio = ratioRow && Number.isFinite(Number(ratioRow.shortAccount)) ? Number(ratioRow.shortAccount) : null;
47
- const fundingRate = Number.isFinite(Number(funding?.lastFundingRate)) ? Number(funding.lastFundingRate) : null;
48
- const openInterest = Number.isFinite(Number(oi?.openInterest)) ? Number(oi.openInterest) : null;
49
- const markPrice = Number.isFinite(Number(funding?.markPrice)) ? Number(funding.markPrice) : null;
50
-
51
- return {
52
- exchange: 'binance-futures',
53
- symbol: normalized,
54
- timestamp: Date.now(),
55
- fundingRate,
56
- fundingTime: Number.isFinite(Number(funding?.nextFundingTime)) ? Number(funding.nextFundingTime) : null,
57
- openInterest,
58
- openInterestValue: openInterest !== null && markPrice !== null ? openInterest * markPrice : null,
59
- longShortRatio,
60
- longAccountRatio,
61
- shortAccountRatio,
62
- };
63
- }
64
-
65
- async function fetchOkxDerivativesSnapshot(symbol: string): Promise<DerivativesSnapshot> {
66
- const instId = normalizeOkxSwapInstrument(symbol);
67
- const base = 'https://www.okx.com';
68
- const [funding, openInterestResponse] = await Promise.all([
69
- getJson(`${base}/api/v5/public/funding-rate?instId=${encodeURIComponent(instId)}`),
70
- getJson(`${base}/api/v5/public/open-interest?instType=SWAP&instId=${encodeURIComponent(instId)}`),
71
- ]);
72
-
73
- if (funding?.code !== '0' || !Array.isArray(funding?.data) || !funding.data[0]) {
74
- throw new Error(`OKX funding-rate returned an invalid payload for ${instId}`);
75
- }
76
- if (openInterestResponse?.code !== '0' || !Array.isArray(openInterestResponse?.data) || !openInterestResponse.data[0]) {
77
- throw new Error(`OKX open-interest returned an invalid payload for ${instId}`);
78
- }
79
-
80
- const fundingRow = funding.data[0];
81
- const oiRow = openInterestResponse.data[0];
82
- const fundingRate = Number.isFinite(Number(fundingRow.fundingRate)) ? Number(fundingRow.fundingRate) : null;
83
- const openInterest = Number.isFinite(Number(oiRow.oi)) ? Number(oiRow.oi) : null;
84
- const openInterestValue = Number.isFinite(Number(oiRow.oiUsd)) ? Number(oiRow.oiUsd) : null;
85
-
86
- return {
87
- exchange: 'okx-swap',
88
- symbol: normalizeSymbol(symbol),
89
- timestamp: Date.now(),
90
- fundingRate,
91
- fundingTime: Number.isFinite(Number(fundingRow.fundingTime)) ? Number(fundingRow.fundingTime) : null,
92
- openInterest,
93
- openInterestValue,
94
- longShortRatio: null,
95
- longAccountRatio: null,
96
- shortAccountRatio: null,
97
- };
98
- }
99
-
100
- export async function fetchDerivativesSnapshot(symbol: string): Promise<DerivativesSnapshot> {
101
- const errors: string[] = [];
102
-
103
- try {
104
- return await fetchBinanceDerivativesSnapshot(symbol);
105
- } catch (error) {
106
- errors.push(`Binance Futures: ${String(error)}`);
107
- }
108
-
109
- try {
110
- return await fetchOkxDerivativesSnapshot(symbol);
111
- } catch (error) {
112
- errors.push(`OKX SWAP: ${String(error)}`);
113
- }
114
-
115
- throw new Error(`All derivatives providers failed. ${errors.join(' | ')}`);
116
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/derivatives/historicalFundingService.ts DELETED
@@ -1,151 +0,0 @@
1
- export interface HistoricalFundingRate {
2
- timestamp: number;
3
- fundingRate: number;
4
- symbol: string;
5
- source: 'binance-futures' | 'okx-swap';
6
- }
7
-
8
- const TIMEOUT_MS = 10_000;
9
- const PAGE_LIMIT_BINANCE = 1000;
10
- const PAGE_LIMIT_OKX = 400;
11
- const OKX_MAX_HISTORY_MS = 90 * 86_400_000;
12
- const MAX_RATE = 0.01;
13
-
14
- function normalizeSymbol(symbol: string): string {
15
- return symbol.replace('/', '').toUpperCase();
16
- }
17
-
18
- function normalizeOkxSwap(symbol: string): string {
19
- const [base, quote] = symbol.toUpperCase().split('/');
20
- return `${base}-${quote}-SWAP`;
21
- }
22
-
23
- async function getJson(url: string): Promise<any> {
24
- const response = await fetch(url, {
25
- headers: { Accept: 'application/json' },
26
- signal: AbortSignal.timeout(TIMEOUT_MS),
27
- });
28
- if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
29
- return response.json();
30
- }
31
-
32
- function uniqueSorted(rows: HistoricalFundingRate[]): HistoricalFundingRate[] {
33
- const unique = new Map<string, HistoricalFundingRate>();
34
- for (const row of rows) unique.set(`${row.source}:${row.timestamp}`, row);
35
- return [...unique.values()].sort((a, b) => a.timestamp - b.timestamp);
36
- }
37
-
38
- async function fetchBinanceHistoricalFunding(
39
- symbol: string,
40
- startTime: number,
41
- endTime: number,
42
- ): Promise<HistoricalFundingRate[]> {
43
- const normalized = normalizeSymbol(symbol);
44
- if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime <= startTime) return [];
45
-
46
- const result: HistoricalFundingRate[] = [];
47
- let cursor = Math.max(0, Math.floor(startTime));
48
- const finalTime = Math.floor(endTime);
49
-
50
- while (cursor <= finalTime) {
51
- const url = `https://fapi.binance.com/fapi/v1/fundingRate?symbol=${normalized}&startTime=${cursor}&endTime=${finalTime}&limit=${PAGE_LIMIT_BINANCE}`;
52
- const rows = await getJson(url);
53
- if (!Array.isArray(rows) || rows.length === 0) break;
54
-
55
- let newest = cursor;
56
- for (const row of rows) {
57
- const timestamp = Number(row?.fundingTime);
58
- const fundingRate = Number(row?.fundingRate);
59
- if (!Number.isFinite(timestamp) || !Number.isFinite(fundingRate)) continue;
60
- if (timestamp < startTime || timestamp > finalTime) continue;
61
- result.push({
62
- timestamp,
63
- fundingRate: Math.max(-MAX_RATE, Math.min(MAX_RATE, fundingRate)),
64
- symbol: normalized,
65
- source: 'binance-futures',
66
- });
67
- newest = Math.max(newest, timestamp);
68
- }
69
-
70
- if (rows.length < PAGE_LIMIT_BINANCE || newest <= cursor) break;
71
- cursor = newest + 1;
72
- }
73
-
74
- return uniqueSorted(result);
75
- }
76
-
77
- /**
78
- * OKX fallback for environments where Binance historical endpoints are geo-blocked.
79
- * The public OKX API currently exposes roughly three months through this endpoint.
80
- */
81
- export async function fetchHistoricalOkxFunding(
82
- symbol: string,
83
- startTime: number,
84
- endTime: number,
85
- ): Promise<HistoricalFundingRate[]> {
86
- if (!Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime <= startTime) return [];
87
-
88
- const instId = normalizeOkxSwap(symbol);
89
- const effectiveStart = Math.max(Math.floor(startTime), Math.floor(endTime) - OKX_MAX_HISTORY_MS);
90
- const finalTime = Math.floor(endTime);
91
- const result: HistoricalFundingRate[] = [];
92
- let after: number | null = null;
93
-
94
- for (let page = 0; page < 100; page += 1) {
95
- const params = new URLSearchParams({ instId, limit: String(PAGE_LIMIT_OKX) });
96
- if (after !== null) params.set('after', String(after));
97
- const payload = await getJson(`https://www.okx.com/api/v5/public/funding-rate-history?${params.toString()}`);
98
- if (String(payload?.code ?? '0') !== '0') throw new Error(`OKX funding history error: ${payload?.msg || payload?.code || 'unknown error'}`);
99
- const rows = Array.isArray(payload?.data) ? payload.data : [];
100
- if (!rows.length) break;
101
-
102
- let oldest = Number.POSITIVE_INFINITY;
103
- for (const row of rows) {
104
- const timestamp = Number(row?.fundingTime);
105
- const fundingRate = Number(row?.realizedRate ?? row?.fundingRate);
106
- if (!Number.isFinite(timestamp) || !Number.isFinite(fundingRate)) continue;
107
- oldest = Math.min(oldest, timestamp);
108
- if (timestamp < effectiveStart || timestamp > finalTime) continue;
109
- result.push({
110
- timestamp,
111
- fundingRate: Math.max(-MAX_RATE, Math.min(MAX_RATE, fundingRate)),
112
- symbol: instId,
113
- source: 'okx-swap',
114
- });
115
- }
116
-
117
- if (oldest === Number.POSITIVE_INFINITY || oldest <= effectiveStart || rows.length < PAGE_LIMIT_OKX) break;
118
- after = oldest;
119
- }
120
-
121
- return uniqueSorted(result);
122
- }
123
-
124
- /**
125
- * Backward-compatible entry point used by the server routes.
126
- * Binance remains canonical. If Binance returns an access error or no data,
127
- * use real OKX historical funding rather than failing the whole backtest.
128
- */
129
- export async function fetchHistoricalBinanceFunding(
130
- symbol: string,
131
- startTime: number,
132
- endTime: number,
133
- ): Promise<HistoricalFundingRate[]> {
134
- try {
135
- const binance = await fetchBinanceHistoricalFunding(symbol, startTime, endTime);
136
- if (binance.length) return binance;
137
- } catch (error) {
138
- console.warn(`Binance historical funding unavailable for ${symbol}:`, error);
139
- }
140
-
141
- try {
142
- const okx = await fetchHistoricalOkxFunding(symbol, startTime, endTime);
143
- if (okx.length) return okx;
144
- } catch (error) {
145
- console.warn(`OKX historical funding unavailable for ${symbol}:`, error);
146
- }
147
-
148
- return [];
149
- }
150
-
151
- export const fetchHistoricalFunding = fetchHistoricalBinanceFunding;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/derivatives/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { fetchDerivativesSnapshot } from './derivativesClient.js';
2
- export type { DerivativesSnapshot } from './derivativesClient.js';
 
 
 
server/divergence/divergenceEngine.ts DELETED
@@ -1,99 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import type { IndicatorSnapshot } from '../indicators/technicalIndicators.js';
3
-
4
- export type DivergenceType = 'bullish-regular' | 'bearish-regular' | 'bullish-hidden' | 'bearish-hidden';
5
-
6
- export interface DivergenceSignal {
7
- type: DivergenceType;
8
- firstIndex: number;
9
- secondIndex: number;
10
- firstTimestamp: number;
11
- secondTimestamp: number;
12
- priceFirst: number;
13
- priceSecond: number;
14
- rsiFirst: number;
15
- rsiSecond: number;
16
- strength: number;
17
- invalidation: number;
18
- }
19
-
20
- export interface DivergenceAnalysis {
21
- bullish: DivergenceSignal[];
22
- bearish: DivergenceSignal[];
23
- latest: DivergenceSignal | null;
24
- }
25
-
26
- interface Pivot { index: number; price: number; oscillator: number; timestamp: number; }
27
-
28
- function localLow(values: number[], i: number, radius = 3): boolean {
29
- if (i < radius || i >= values.length - radius) return false;
30
- for (let j = 1; j <= radius; j++) if (values[i] >= values[i - j] || values[i] > values[i + j]) return false;
31
- return true;
32
- }
33
-
34
- function localHigh(values: number[], i: number, radius = 3): boolean {
35
- if (i < radius || i >= values.length - radius) return false;
36
- for (let j = 1; j <= radius; j++) if (values[i] <= values[i - j] || values[i] < values[i + j]) return false;
37
- return true;
38
- }
39
-
40
- function oscillatorSeries(candles: Candle[], fallback: number): number[] {
41
- const closes = candles.map(c => c.close);
42
- const result = closes.map(() => fallback);
43
- let gains = 0;
44
- let losses = 0;
45
- for (let i = 1; i < closes.length; i++) {
46
- const change = closes[i] - closes[i - 1];
47
- gains = (gains * 13 + Math.max(change, 0)) / 14;
48
- losses = (losses * 13 + Math.max(-change, 0)) / 14;
49
- result[i] = losses === 0 ? 100 : 100 - (100 / (1 + gains / losses));
50
- }
51
- return result;
52
- }
53
-
54
- function buildPivots(candles: Candle[], rsi: number[], kind: 'low' | 'high'): Pivot[] {
55
- const values = candles.map(c => kind === 'low' ? c.low : c.high);
56
- const pivots: Pivot[] = [];
57
- for (let i = 3; i < candles.length - 3; i++) {
58
- const pivot = kind === 'low' ? localLow(values, i) : localHigh(values, i);
59
- if (pivot) pivots.push({ index: i, price: values[i], oscillator: rsi[i], timestamp: candles[i].timestamp });
60
- }
61
- return pivots;
62
- }
63
-
64
- export function detectDivergences(candles: Candle[], indicators?: IndicatorSnapshot): DivergenceAnalysis {
65
- if (candles.length < 30) return { bullish: [], bearish: [], latest: null };
66
- const fallbackRsi = indicators?.rsi14 ?? 50;
67
- const rsi = oscillatorSeries(candles, fallbackRsi);
68
- const lows = buildPivots(candles, rsi, 'low');
69
- const highs = buildPivots(candles, rsi, 'high');
70
- const bullish: DivergenceSignal[] = [];
71
- const bearish: DivergenceSignal[] = [];
72
-
73
- for (let i = 1; i < lows.length; i++) {
74
- const a = lows[i - 1], b = lows[i];
75
- const priceDelta = (b.price - a.price) / a.price;
76
- const rsiDelta = b.oscillator - a.oscillator;
77
- const regular = priceDelta < -0.001 && rsiDelta > 2;
78
- const hidden = priceDelta > 0.001 && rsiDelta < -2;
79
- if (regular || hidden) {
80
- const strength = Math.min(100, Math.round(Math.abs(priceDelta) * 2500 + Math.abs(rsiDelta) * 4));
81
- bullish.push({ type: regular ? 'bullish-regular' : 'bullish-hidden', firstIndex: a.index, secondIndex: b.index, firstTimestamp: a.timestamp, secondTimestamp: b.timestamp, priceFirst: a.price, priceSecond: b.price, rsiFirst: a.oscillator, rsiSecond: b.oscillator, strength, invalidation: b.price });
82
- }
83
- }
84
-
85
- for (let i = 1; i < highs.length; i++) {
86
- const a = highs[i - 1], b = highs[i];
87
- const priceDelta = (b.price - a.price) / a.price;
88
- const rsiDelta = b.oscillator - a.oscillator;
89
- const regular = priceDelta > 0.001 && rsiDelta < -2;
90
- const hidden = priceDelta < -0.001 && rsiDelta > 2;
91
- if (regular || hidden) {
92
- const strength = Math.min(100, Math.round(Math.abs(priceDelta) * 2500 + Math.abs(rsiDelta) * 4));
93
- bearish.push({ type: regular ? 'bearish-regular' : 'bearish-hidden', firstIndex: a.index, secondIndex: b.index, firstTimestamp: a.timestamp, secondTimestamp: b.timestamp, priceFirst: a.price, priceSecond: b.price, rsiFirst: a.oscillator, rsiSecond: b.oscillator, strength, invalidation: b.price });
94
- }
95
- }
96
-
97
- const all = [...bullish, ...bearish].sort((a, b) => b.secondTimestamp - a.secondTimestamp);
98
- return { bullish, bearish, latest: all[0] ?? null };
99
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/divergence/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { detectDivergences } from './divergenceEngine.js';
2
- export type { DivergenceType, DivergenceSignal, DivergenceAnalysis } from './divergenceEngine.js';
 
 
 
server/gann/gannEngine.ts DELETED
@@ -1,61 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import type { MarketStructureSnapshot } from '../structure/marketStructure.js';
3
-
4
- export interface GannLevel {
5
- ratio: number;
6
- price: number;
7
- relation: 'support' | 'resistance';
8
- }
9
-
10
- export interface GannTimingWindow {
11
- bars: number;
12
- targetTimestamp: number;
13
- }
14
-
15
- export interface GannAnalysis {
16
- anchorLow: number | null;
17
- anchorHigh: number | null;
18
- range: number | null;
19
- levels: GannLevel[];
20
- timingWindows: GannTimingWindow[];
21
- bias: 'bullish' | 'bearish' | 'neutral';
22
- score: number;
23
- reasons: string[];
24
- }
25
-
26
- const RATIOS = [0.25, 0.382, 0.5, 0.618, 0.75, 1, 1.272, 1.618];
27
- const TIMING_BARS = [9, 18, 27, 36, 45];
28
-
29
- export function analyzeGann(candles: Candle[], structure: MarketStructureSnapshot): GannAnalysis {
30
- const swings = structure.swings;
31
- if (candles.length < 20 || swings.length < 2) {
32
- return { anchorLow: null, anchorHigh: null, range: null, levels: [], timingWindows: [], bias: 'neutral', score: 0, reasons: ['Dados insuficientes para análise Gann'] };
33
- }
34
-
35
- const highs = swings.filter(s => s.type === 'high');
36
- const lows = swings.filter(s => s.type === 'low');
37
- const anchorHigh = highs.length ? highs[highs.length - 1].price : Math.max(...candles.map(c => c.high));
38
- const anchorLow = lows.length ? lows[lows.length - 1].price : Math.min(...candles.map(c => c.low));
39
- const low = Math.min(anchorLow, anchorHigh);
40
- const high = Math.max(anchorLow, anchorHigh);
41
- const range = high - low;
42
- const price = candles[candles.length - 1].close;
43
-
44
- const levels = RATIOS.map(ratio => {
45
- const level = low + range * ratio;
46
- return { ratio, price: level, relation: level <= price ? 'support' : 'resistance' as 'support' | 'resistance' };
47
- });
48
-
49
- const interval = candles.length > 1 ? candles[candles.length - 1].timestamp - candles[candles.length - 2].timestamp : 0;
50
- const timingWindows = interval > 0 ? TIMING_BARS.map(bars => ({ bars, targetTimestamp: candles[candles.length - 1].timestamp + interval * bars })) : [];
51
-
52
- const midpoint = low + range * 0.5;
53
- let score = 0;
54
- const reasons: string[] = [];
55
- if (price > midpoint) { score += 1; reasons.push('Preço acima de 50% do range âncora'); }
56
- else if (price < midpoint) { score -= 1; reasons.push('Preço abaixo de 50% do range âncora'); }
57
- if (structure.trend === 'bullish') { score += 1; reasons.push('Estrutura confirma viés bullish'); }
58
- if (structure.trend === 'bearish') { score -= 1; reasons.push('Estrutura confirma viés bearish'); }
59
-
60
- return { anchorLow: low, anchorHigh: high, range, levels, timingWindows, bias: score > 0 ? 'bullish' : score < 0 ? 'bearish' : 'neutral', score, reasons };
61
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/gann/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { analyzeGann } from './gannEngine.js';
2
- export type { GannLevel, GannTimingWindow, GannAnalysis } from './gannEngine.js';
 
 
 
server/indicators/index.ts DELETED
@@ -1,2 +0,0 @@
1
- export { atr, bollinger, calculateIndicators, ema, rsi, volumeSma } from './technicalIndicators.js';
2
- export type { IndicatorSnapshot } from './technicalIndicators.js';
 
 
 
server/indicators/technicalIndicators.ts DELETED
@@ -1,103 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
-
3
- export interface IndicatorSnapshot {
4
- ema9: number;
5
- ema21: number;
6
- ema50: number;
7
- ema200: number;
8
- rsi14: number;
9
- atr14: number;
10
- bollinger: {
11
- middle: number;
12
- upper: number;
13
- lower: number;
14
- widthPercent: number;
15
- };
16
- volumeSma20: number;
17
- relativeVolume20: number;
18
- momentumPercent: number;
19
- }
20
-
21
- function closes(candles: Candle[]): number[] {
22
- return candles.map(c => c.close).filter(Number.isFinite);
23
- }
24
-
25
- export function ema(values: number[], period: number): number {
26
- if (values.length === 0) return 0;
27
- const seed = values.slice(0, Math.min(period, values.length)).reduce((a, b) => a + b, 0) / Math.min(period, values.length);
28
- if (values.length <= period) return seed;
29
- const multiplier = 2 / (period + 1);
30
- let result = seed;
31
- for (let i = period; i < values.length; i++) result = (values[i] - result) * multiplier + result;
32
- return result;
33
- }
34
-
35
- export function rsi(values: number[], period = 14): number {
36
- if (values.length <= period) return 50;
37
- let gains = 0;
38
- let losses = 0;
39
- for (let i = 1; i <= period; i++) {
40
- const change = values[i] - values[i - 1];
41
- if (change >= 0) gains += change; else losses -= change;
42
- }
43
- let avgGain = gains / period;
44
- let avgLoss = losses / period;
45
- for (let i = period + 1; i < values.length; i++) {
46
- const change = values[i] - values[i - 1];
47
- avgGain = (avgGain * (period - 1) + Math.max(change, 0)) / period;
48
- avgLoss = (avgLoss * (period - 1) + Math.max(-change, 0)) / period;
49
- }
50
- if (avgLoss === 0) return 100;
51
- if (avgGain === 0) return 0;
52
- const rs = avgGain / avgLoss;
53
- return 100 - 100 / (1 + rs);
54
- }
55
-
56
- export function atr(candles: Candle[], period = 14): number {
57
- if (candles.length < 2) return 0;
58
- const ranges: number[] = [];
59
- for (let i = 1; i < candles.length; i++) {
60
- const c = candles[i];
61
- const prev = candles[i - 1].close;
62
- ranges.push(Math.max(c.high - c.low, Math.abs(c.high - prev), Math.abs(c.low - prev)));
63
- }
64
- const window = ranges.slice(-period);
65
- return window.length ? window.reduce((a, b) => a + b, 0) / window.length : 0;
66
- }
67
-
68
- export function bollinger(values: number[], period = 20, deviations = 2) {
69
- const window = values.slice(-period);
70
- if (!window.length) return { middle: 0, upper: 0, lower: 0, widthPercent: 0 };
71
- const middle = window.reduce((a, b) => a + b, 0) / window.length;
72
- const variance = window.reduce((sum, value) => sum + (value - middle) ** 2, 0) / window.length;
73
- const std = Math.sqrt(variance);
74
- const upper = middle + deviations * std;
75
- const lower = middle - deviations * std;
76
- return { middle, upper, lower, widthPercent: middle ? ((upper - lower) / middle) * 100 : 0 };
77
- }
78
-
79
- export function volumeSma(candles: Candle[], period = 20): number {
80
- const values = candles.slice(-period).map(c => c.volume).filter(Number.isFinite);
81
- return values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0;
82
- }
83
-
84
- export function calculateIndicators(candles: Candle[]): IndicatorSnapshot {
85
- const values = closes(candles);
86
- const latest = values.at(-1) ?? 0;
87
- const previous = values.at(-2) ?? latest;
88
- const volumeAverage = volumeSma(candles, 20);
89
- const latestVolume = candles.at(-1)?.volume ?? 0;
90
-
91
- return {
92
- ema9: ema(values, 9),
93
- ema21: ema(values, 21),
94
- ema50: ema(values, 50),
95
- ema200: ema(values, 200),
96
- rsi14: rsi(values, 14),
97
- atr14: atr(candles, 14),
98
- bollinger: bollinger(values, 20, 2),
99
- volumeSma20: volumeAverage,
100
- relativeVolume20: volumeAverage > 0 ? latestVolume / volumeAverage : 0,
101
- momentumPercent: previous !== 0 ? ((latest - previous) / previous) * 100 : 0,
102
- };
103
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/market/OKX_FIX_PENDING.md DELETED
@@ -1 +0,0 @@
1
- Runtime validation identified an OKX instrument normalization issue for BTC/USDT. The fallback currently requests BTC-USDT-SWAP and the runtime reported an invalid candle payload. This marker is temporary and must be removed after the exchange client is corrected and CI/runtime validation passes.
 
 
server/market/candleService.ts DELETED
@@ -1,78 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import {
3
- fetchCandlesWithFallback,
4
- type ExchangeId,
5
- type RawCandle,
6
- } from './exchangeClient.js';
7
-
8
- export type MarketTimeframe = '15m' | '1h' | '4h' | '1d';
9
-
10
- const INTERVAL_MAP: Record<MarketTimeframe, string> = {
11
- '15m': '15m',
12
- '1h': '1h',
13
- '4h': '4h',
14
- '1d': '1d',
15
- };
16
-
17
- function toCandle(raw: RawCandle): Candle {
18
- return {
19
- timestamp: raw.timestamp,
20
- timeStr: new Date(raw.timestamp).toISOString(),
21
- open: raw.open,
22
- high: raw.high,
23
- low: raw.low,
24
- close: raw.close,
25
- volume: raw.volume,
26
- };
27
- }
28
-
29
- function validateCandle(candle: Candle): boolean {
30
- return Number.isFinite(candle.timestamp)
31
- && Number.isFinite(candle.open)
32
- && Number.isFinite(candle.high)
33
- && Number.isFinite(candle.low)
34
- && Number.isFinite(candle.close)
35
- && Number.isFinite(candle.volume)
36
- && candle.high >= Math.max(candle.open, candle.close, candle.low)
37
- && candle.low <= Math.min(candle.open, candle.close, candle.high)
38
- && candle.volume >= 0;
39
- }
40
-
41
- export async function fetchRealCandles(
42
- symbol: string,
43
- timeframe: MarketTimeframe,
44
- limit = 500,
45
- preferredExchange: ExchangeId = 'binance',
46
- ): Promise<{ exchange: ExchangeId; candles: Candle[] }> {
47
- const result = await fetchCandlesWithFallback({
48
- symbol,
49
- interval: INTERVAL_MAP[timeframe],
50
- limit,
51
- }, preferredExchange);
52
-
53
- const candles = result.candles
54
- .map(toCandle)
55
- .filter(validateCandle)
56
- .sort((a, b) => a.timestamp - b.timestamp);
57
-
58
- const deduplicated = candles.filter((candle, index) => (
59
- index === 0 || candle.timestamp !== candles[index - 1].timestamp
60
- ));
61
-
62
- return { exchange: result.exchange, candles: deduplicated };
63
- }
64
-
65
- export async function fetchMultiTimeframeCandles(
66
- symbol: string,
67
- limits: Partial<Record<MarketTimeframe, number>> = {},
68
- ): Promise<Record<MarketTimeframe, { exchange: ExchangeId; candles: Candle[] }>> {
69
- const timeframes: MarketTimeframe[] = ['15m', '1h', '4h', '1d'];
70
- const results = await Promise.all(
71
- timeframes.map(async (timeframe) => [
72
- timeframe,
73
- await fetchRealCandles(symbol, timeframe, limits[timeframe] ?? 500),
74
- ] as const),
75
- );
76
-
77
- return Object.fromEntries(results) as Record<MarketTimeframe, { exchange: ExchangeId; candles: Candle[] }>;
78
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/market/exchangeClient.okx-fix-notes.md DELETED
@@ -1 +0,0 @@
1
- Temporary note
 
 
server/market/exchangeClient.ts DELETED
@@ -1,123 +0,0 @@
1
- export type ExchangeId = 'binance' | 'okx';
2
-
3
- export interface RawCandle {
4
- timestamp: number;
5
- open: number;
6
- high: number;
7
- low: number;
8
- close: number;
9
- volume: number;
10
- }
11
-
12
- export interface FetchCandlesOptions {
13
- symbol: string;
14
- interval: string;
15
- limit?: number;
16
- signal?: AbortSignal;
17
- }
18
-
19
- const DEFAULT_TIMEOUT_MS = 10_000;
20
-
21
- function withTimeout(signal?: AbortSignal, timeoutMs = DEFAULT_TIMEOUT_MS): AbortSignal {
22
- if (signal) return signal;
23
- return AbortSignal.timeout(timeoutMs);
24
- }
25
-
26
- function normalizeBinanceSymbol(symbol: string): string {
27
- return symbol.replace('/', '').toUpperCase();
28
- }
29
-
30
- function normalizeOkxInstrument(symbol: string): string {
31
- return symbol.replace('/', '-').toUpperCase();
32
- }
33
-
34
- function normalizeOkxBar(interval: string): string {
35
- const bars: Record<string, string> = {
36
- '15m': '15m',
37
- '1h': '1H',
38
- '4h': '4H',
39
- '1d': '1D',
40
- };
41
- return bars[interval] ?? interval;
42
- }
43
-
44
- async function fetchJson(url: string, signal?: AbortSignal): Promise<unknown> {
45
- const response = await fetch(url, {
46
- headers: { Accept: 'application/json' },
47
- signal: withTimeout(signal),
48
- });
49
-
50
- if (!response.ok) {
51
- throw new Error(`Exchange HTTP ${response.status}: ${response.statusText}`);
52
- }
53
-
54
- return response.json();
55
- }
56
-
57
- export async function fetchBinanceCandles(options: FetchCandlesOptions): Promise<RawCandle[]> {
58
- const symbol = normalizeBinanceSymbol(options.symbol);
59
- const limit = Math.min(Math.max(options.limit ?? 500, 1), 1000);
60
- const url = new URL('https://api.binance.com/api/v3/klines');
61
- url.searchParams.set('symbol', symbol);
62
- url.searchParams.set('interval', options.interval);
63
- url.searchParams.set('limit', String(limit));
64
-
65
- const data = await fetchJson(url.toString(), options.signal);
66
- if (!Array.isArray(data)) throw new Error('Binance returned an invalid kline payload');
67
-
68
- return data.map((row: unknown[]) => ({
69
- timestamp: Number(row[0]),
70
- open: Number(row[1]),
71
- high: Number(row[2]),
72
- low: Number(row[3]),
73
- close: Number(row[4]),
74
- volume: Number(row[5]),
75
- }));
76
- }
77
-
78
- export async function fetchOkxCandles(options: FetchCandlesOptions): Promise<RawCandle[]> {
79
- const instId = normalizeOkxInstrument(options.symbol);
80
- const limit = Math.min(Math.max(options.limit ?? 500, 1), 1000);
81
- const url = new URL('https://www.okx.com/api/v5/market/candles');
82
- url.searchParams.set('instId', instId);
83
- url.searchParams.set('bar', normalizeOkxBar(options.interval));
84
- url.searchParams.set('limit', String(limit));
85
-
86
- const payload = await fetchJson(url.toString(), options.signal) as { code?: string; data?: string[][]; msg?: string };
87
- if (payload.code !== '0' || !Array.isArray(payload.data)) {
88
- throw new Error(`OKX returned an invalid candle payload for ${instId}: ${payload.msg ?? 'unknown error'}`);
89
- }
90
-
91
- return payload.data
92
- .map((row) => ({
93
- timestamp: Number(row[0]),
94
- open: Number(row[1]),
95
- high: Number(row[2]),
96
- low: Number(row[3]),
97
- close: Number(row[4]),
98
- volume: Number(row[5]),
99
- }))
100
- .sort((a, b) => a.timestamp - b.timestamp);
101
- }
102
-
103
- export async function fetchCandlesWithFallback(
104
- options: FetchCandlesOptions,
105
- preferred: ExchangeId = 'binance',
106
- ): Promise<{ exchange: ExchangeId; candles: RawCandle[] }> {
107
- const order: ExchangeId[] = preferred === 'binance' ? ['binance', 'okx'] : ['okx', 'binance'];
108
- const errors: string[] = [];
109
-
110
- for (const exchange of order) {
111
- try {
112
- const candles = exchange === 'binance'
113
- ? await fetchBinanceCandles(options)
114
- : await fetchOkxCandles(options);
115
- if (candles.length === 0) throw new Error(`${exchange} returned no candles`);
116
- return { exchange, candles };
117
- } catch (error) {
118
- errors.push(`${exchange}: ${String(error)}`);
119
- }
120
- }
121
-
122
- throw new Error(`All candle providers failed. ${errors.join(' | ')}`);
123
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/market/marketCache.ts DELETED
@@ -1,61 +0,0 @@
1
- import type { MultiTimeframeSnapshot } from './timeframeService.js';
2
-
3
- interface CacheEntry {
4
- snapshot: MultiTimeframeSnapshot;
5
- expiresAt: number;
6
- }
7
-
8
- const cache = new Map<string, CacheEntry>();
9
- const DEFAULT_TTL_MS = 15_000;
10
- const MAX_ENTRIES = 100;
11
-
12
- function normalizeSymbol(symbol: string): string {
13
- return symbol.trim().toUpperCase();
14
- }
15
-
16
- export function getCachedMarketSnapshot(symbol: string): MultiTimeframeSnapshot | null {
17
- const key = normalizeSymbol(symbol);
18
- const entry = cache.get(key);
19
- if (!entry) return null;
20
-
21
- if (Date.now() >= entry.expiresAt) {
22
- cache.delete(key);
23
- return null;
24
- }
25
-
26
- return entry.snapshot;
27
- }
28
-
29
- export function setCachedMarketSnapshot(
30
- symbol: string,
31
- snapshot: MultiTimeframeSnapshot,
32
- ttlMs = DEFAULT_TTL_MS,
33
- ): void {
34
- const key = normalizeSymbol(symbol);
35
- cache.delete(key);
36
- cache.set(key, {
37
- snapshot,
38
- expiresAt: Date.now() + Math.max(ttlMs, 1_000),
39
- });
40
-
41
- while (cache.size > MAX_ENTRIES) {
42
- const oldestKey = cache.keys().next().value as string | undefined;
43
- if (!oldestKey) break;
44
- cache.delete(oldestKey);
45
- }
46
- }
47
-
48
- export function invalidateMarketSnapshot(symbol?: string): void {
49
- if (symbol) {
50
- cache.delete(normalizeSymbol(symbol));
51
- return;
52
- }
53
- cache.clear();
54
- }
55
-
56
- export function getMarketCacheStats(): { entries: number; symbols: string[] } {
57
- return {
58
- entries: cache.size,
59
- symbols: [...cache.keys()],
60
- };
61
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/market/marketSnapshotService.ts DELETED
@@ -1,19 +0,0 @@
1
- import { getCachedMarketSnapshot, setCachedMarketSnapshot } from './marketCache.js';
2
- import { loadMarketSnapshot, type MultiTimeframeSnapshot } from './timeframeService.js';
3
-
4
- export async function getMarketSnapshot(
5
- symbol: string,
6
- options: {
7
- forceRefresh?: boolean;
8
- limits?: Parameters<typeof loadMarketSnapshot>[1];
9
- } = {},
10
- ): Promise<MultiTimeframeSnapshot> {
11
- if (!options.forceRefresh) {
12
- const cached = getCachedMarketSnapshot(symbol);
13
- if (cached) return cached;
14
- }
15
-
16
- const snapshot = await loadMarketSnapshot(symbol, options.limits);
17
- setCachedMarketSnapshot(symbol, snapshot);
18
- return snapshot;
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/market/timeframeService.ts DELETED
@@ -1,40 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import { fetchMultiTimeframeCandles, type MarketTimeframe } from './candleService.js';
3
- import type { ExchangeId } from './exchangeClient.js';
4
-
5
- export interface TimeframeSeries {
6
- timeframe: MarketTimeframe;
7
- exchange: ExchangeId;
8
- candles: Candle[];
9
- latestTimestamp: number;
10
- }
11
-
12
- export interface MultiTimeframeSnapshot {
13
- symbol: string;
14
- fetchedAt: number;
15
- series: Record<MarketTimeframe, TimeframeSeries>;
16
- }
17
-
18
- export async function loadMarketSnapshot(
19
- symbol: string,
20
- limits: Partial<Record<MarketTimeframe, number>> = {},
21
- ): Promise<MultiTimeframeSnapshot> {
22
- const raw = await fetchMultiTimeframeCandles(symbol, limits);
23
- const series = {} as Record<MarketTimeframe, TimeframeSeries>;
24
-
25
- for (const timeframe of ['15m', '1h', '4h', '1d'] as MarketTimeframe[]) {
26
- const candles = raw[timeframe].candles;
27
- series[timeframe] = {
28
- timeframe,
29
- exchange: raw[timeframe].exchange,
30
- candles,
31
- latestTimestamp: candles.at(-1)?.timestamp ?? 0,
32
- };
33
- }
34
-
35
- return {
36
- symbol,
37
- fetchedAt: Date.now(),
38
- series,
39
- };
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/paper/paperTradingEngine.ts DELETED
@@ -1,32 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import type { TradeSignal } from '../signal/signalEngine.js';
3
-
4
- export type PaperPositionStatus = 'OPEN' | 'CLOSED';
5
- export type PaperCloseReason = 'TP1' | 'TP2' | 'TP3' | 'STOP' | 'INVALIDATION' | 'MANUAL' | 'TIMEOUT';
6
-
7
- export interface PaperAccount { initialCapital:number; equity:number; realizedPnl:number; unrealizedPnl:number; feesPaid:number; slippagePaid:number; peakEquity:number; maxDrawdownPercent:number; halted:boolean; lastMarkPrice:number|null; }
8
- export interface PaperPosition { id:string; symbol:string; direction:'LONG'|'SHORT'; status:PaperPositionStatus; openedAt:number; closedAt:number|null; entryPrice:number; quantity:number; notional:number; stopLoss:number; initialStopLoss:number; takeProfits:{tp1:number;tp2:number;tp3:number}; remainingQuantity:number; realizedPnl:number; unrealizedPnl:number; feesPaid:number; slippagePaid:number; closeReason:PaperCloseReason|null; tp1Hit:boolean; tp2Hit:boolean; breakevenActivated:boolean; lastMarkPrice:number|null; }
9
- export interface PaperTradingConfig { initialCapital?:number; feeBpsPerSide?:number; slippageBpsPerSide?:number; maxRiskPercent?:number; maxOpenPositions?:number; maxDrawdownPercent?:number; partialTpPercent?:number; }
10
- export interface PaperTradingStats { totalTrades:number; winningTrades:number; losingTrades:number; breakevenTrades:number; winRatePercent:number; profitFactor:number; netPnl:number; netPnlPercent:number; averageTradePnl:number; averageR:number; bestTradePnl:number; worstTradePnl:number; tp1HitRatePercent:number; tp2HitRatePercent:number; halted:boolean; }
11
- export interface PaperTradingState { account:PaperAccount; positions:PaperPosition[]; history:PaperPosition[]; stats:PaperTradingStats; }
12
- const clamp=(v:number,a:number,b:number)=>Math.max(a,Math.min(b,v));
13
- const positive=(v:number)=>Number.isFinite(v)&&v>0;
14
- const round=(v:number,d=8)=>Math.round(v*10**d)/10**d;
15
- export class PaperTradingEngine {
16
- private readonly config:Required<PaperTradingConfig>; private state:PaperTradingState;
17
- constructor(config:PaperTradingConfig={}){this.config={initialCapital:positive(config.initialCapital??10000)?config.initialCapital??10000:10000,feeBpsPerSide:clamp(config.feeBpsPerSide??5,0,100),slippageBpsPerSide:clamp(config.slippageBpsPerSide??2,0,100),maxRiskPercent:clamp(config.maxRiskPercent??1,.1,2),maxOpenPositions:Math.max(1,Math.floor(config.maxOpenPositions??1)),maxDrawdownPercent:clamp(config.maxDrawdownPercent??10,1,100),partialTpPercent:clamp(config.partialTpPercent??33.333333,10,90)};this.state=this.emptyState();}
18
- private emptyStats():PaperTradingStats{return{totalTrades:0,winningTrades:0,losingTrades:0,breakevenTrades:0,winRatePercent:0,profitFactor:0,netPnl:0,netPnlPercent:0,averageTradePnl:0,averageR:0,bestTradePnl:0,worstTradePnl:0,tp1HitRatePercent:0,tp2HitRatePercent:0,halted:false};}
19
- private emptyState():PaperTradingState{const capital=this.config.initialCapital;return{account:{initialCapital:capital,equity:capital,realizedPnl:0,unrealizedPnl:0,feesPaid:0,slippagePaid:0,peakEquity:capital,maxDrawdownPercent:0,halted:false,lastMarkPrice:null},positions:[],history:[],stats:this.emptyStats()};}
20
- getState(){return structuredClone(this.state);}
21
- reset(){this.state=this.emptyState();return this.getState();}
22
- openFromSignal(signal:TradeSignal,timestamp=Date.now()){if(signal.direction==='NO TRADE'||!signal.entryZone||!signal.stopLoss||!signal.takeProfits||this.state.account.halted||this.state.positions.length>=this.config.maxOpenPositions)return null;const riskPct=Math.min(signal.riskPercent,this.config.maxRiskPercent),riskCash=this.state.account.equity*riskPct/100,entry=signal.entryZone.reference,distance=Math.abs(entry-signal.stopLoss);if(!positive(entry)||!positive(distance)||riskCash<=0)return null;const quantity=riskCash/distance,notional=quantity*entry,entrySlip=entry*this.config.slippageBpsPerSide/10000,executedEntry=signal.direction==='LONG'?entry+entrySlip:entry-entrySlip,fee=notional*this.config.feeBpsPerSide/10000,slippageCost=quantity*Math.abs(executedEntry-entry);const p:PaperPosition={id:`paper-${signal.symbol.replace('/','-')}-${timestamp}-${Math.random().toString(36).slice(2,8)}`,symbol:signal.symbol,direction:signal.direction,status:'OPEN',openedAt:timestamp,closedAt:null,entryPrice:round(executedEntry),quantity:round(quantity),notional:round(notional,4),stopLoss:signal.stopLoss,initialStopLoss:signal.stopLoss,takeProfits:signal.takeProfits,remainingQuantity:round(quantity),realizedPnl:round(-fee-slippageCost),unrealizedPnl:0,feesPaid:round(fee),slippagePaid:round(slippageCost),closeReason:null,tp1Hit:false,tp2Hit:false,breakevenActivated:false,lastMarkPrice:round(executedEntry)};this.state.positions.push(p);this.state.account.realizedPnl-=fee+slippageCost;this.state.account.feesPaid+=fee;this.state.account.slippagePaid+=slippageCost;this.markToMarket(executedEntry);return structuredClone(p);}
23
- processCandle(candle:Candle,timestamp=candle.timestamp){for(const p of [...this.state.positions]){const hit=this.firstHit(p,candle);if(!hit){if(timestamp-p.openedAt>=32*15*60_000)this.executeClose(p,candle.close,'TIMEOUT',timestamp);continue;}if(hit.reason==='STOP')this.executeClose(p,hit.price,'STOP',timestamp);else if(hit.reason==='TP1'&&!p.tp1Hit){this.executePartialClose(p,hit.price,'TP1',timestamp);p.tp1Hit=true;p.breakevenActivated=true;p.stopLoss=p.entryPrice;}else if(hit.reason==='TP2'&&p.tp1Hit&&!p.tp2Hit){this.executePartialClose(p,hit.price,'TP2',timestamp);p.tp2Hit=true;p.stopLoss=p.entryPrice;}else if(hit.reason==='TP3'&&p.tp2Hit)this.executeClose(p,hit.price,'TP3',timestamp);}this.markToMarket(candle.close,timestamp);}
24
- markToMarket(price:number,_timestamp=Date.now()){if(!positive(price))return;let unrealized=0;for(const p of this.state.positions){p.lastMarkPrice=round(price);p.unrealizedPnl=round(p.direction==='LONG'?(price-p.entryPrice)*p.remainingQuantity:(p.entryPrice-price)*p.remainingQuantity);unrealized+=p.unrealizedPnl;}this.state.account.unrealizedPnl=round(unrealized);this.state.account.equity=round(this.state.account.initialCapital+this.state.account.realizedPnl+unrealized,4);this.state.account.lastMarkPrice=round(price);this.state.account.peakEquity=Math.max(this.state.account.peakEquity,this.state.account.equity);const dd=this.state.account.peakEquity>0?(this.state.account.peakEquity-this.state.account.equity)/this.state.account.peakEquity*100:0;this.state.account.maxDrawdownPercent=Math.max(this.state.account.maxDrawdownPercent,dd);if(this.state.account.maxDrawdownPercent>=this.config.maxDrawdownPercent)this.state.account.halted=true;this.recalculateStats();}
25
- close(positionId:string,price:number,reason:PaperCloseReason='MANUAL',timestamp=Date.now()){const p=this.state.positions.find(x=>x.id===positionId);if(!p||!positive(price))return null;this.executeClose(p,price,reason,timestamp);this.markToMarket(price,timestamp);return structuredClone(p);}
26
- private firstHit(p:PaperPosition,c:Candle){if(p.direction==='LONG'){if(c.low<=p.stopLoss)return{price:p.stopLoss,reason:'STOP' as PaperCloseReason};if(!p.tp1Hit&&c.high>=p.takeProfits.tp1)return{price:p.takeProfits.tp1,reason:'TP1' as PaperCloseReason};if(p.tp1Hit&&!p.tp2Hit&&c.high>=p.takeProfits.tp2)return{price:p.takeProfits.tp2,reason:'TP2' as PaperCloseReason};if(p.tp2Hit&&c.high>=p.takeProfits.tp3)return{price:p.takeProfits.tp3,reason:'TP3' as PaperCloseReason};}else{if(c.high>=p.stopLoss)return{price:p.stopLoss,reason:'STOP' as PaperCloseReason};if(!p.tp1Hit&&c.low<=p.takeProfits.tp1)return{price:p.takeProfits.tp1,reason:'TP1' as PaperCloseReason};if(p.tp1Hit&&!p.tp2Hit&&c.low<=p.takeProfits.tp2)return{price:p.takeProfits.tp2,reason:'TP2' as PaperCloseReason};if(p.tp2Hit&&c.low<=p.takeProfits.tp3)return{price:p.takeProfits.tp3,reason:'TP3' as PaperCloseReason};}return null;}
27
- private executePartialClose(p:PaperPosition,price:number,_reason:PaperCloseReason,_timestamp:number){if(p.status==='CLOSED')return;const quantity=Math.min(p.remainingQuantity,p.quantity*this.config.partialTpPercent/100);this.executeQuantityClose(p,quantity,price);}
28
- private executeQuantityClose(p:PaperPosition,quantity:number,price:number){if(p.status==='CLOSED'||quantity<=0)return;const slip=price*this.config.slippageBpsPerSide/10000,executed=p.direction==='LONG'?price-slip:price+slip,gross=p.direction==='LONG'?(executed-p.entryPrice)*quantity:(p.entryPrice-executed)*quantity,fee=Math.abs(executed*quantity)*this.config.feeBpsPerSide/10000,slippageCost=quantity*Math.abs(executed-price),net=gross-fee-slippageCost;p.realizedPnl=round(p.realizedPnl+net);p.feesPaid=round(p.feesPaid+fee);p.slippagePaid=round(p.slippagePaid+slippageCost);p.remainingQuantity=round(Math.max(0,p.remainingQuantity-quantity));p.unrealizedPnl=0;this.state.account.realizedPnl+=net;this.state.account.feesPaid+=fee;this.state.account.slippagePaid+=slippageCost;}
29
- private executeClose(p:PaperPosition,price:number,reason:PaperCloseReason,timestamp:number){if(p.status==='CLOSED')return;if(p.remainingQuantity>0)this.executeQuantityClose(p,p.remainingQuantity,price);p.remainingQuantity=0;p.unrealizedPnl=0;p.status='CLOSED';p.closedAt=timestamp;p.closeReason=reason;this.state.positions=this.state.positions.filter(x=>x.id!==p.id);this.state.history.push(structuredClone(p));this.recalculateStats();}
30
- private recalculateStats(){const h=this.state.history,total=h.length,w=h.filter(p=>p.realizedPnl>0).length,l=h.filter(p=>p.realizedPnl<0).length,be=total-w-l,gp=h.reduce((s,p)=>s+Math.max(0,p.realizedPnl),0),gl=Math.abs(h.reduce((s,p)=>s+Math.min(0,p.realizedPnl),0)),net=this.state.account.realizedPnl;const avgR=total?h.reduce((s,p)=>{const risk=Math.abs(p.entryPrice-p.initialStopLoss)*p.quantity;return s+(risk>0?p.realizedPnl/risk:0)},0)/total:0;this.state.stats={totalTrades:total,winningTrades:w,losingTrades:l,breakevenTrades:be,winRatePercent:total?w/total*100:0,profitFactor:gl>0?gp/gl:gp>0?Infinity:0,netPnl:round(net,4),netPnlPercent:this.state.account.initialCapital>0?round(net/this.state.account.initialCapital*100,4):0,averageTradePnl:total?round(net/total,4):0,averageR:round(avgR,4),bestTradePnl:total?round(Math.max(...h.map(p=>p.realizedPnl)),4):0,worstTradePnl:total?round(Math.min(...h.map(p=>p.realizedPnl)),4):0,tp1HitRatePercent:total?h.filter(p=>p.tp1Hit).length/total*100:0,tp2HitRatePercent:total?h.filter(p=>p.tp2Hit).length/total*100:0,halted:this.state.account.halted};}
31
- }
32
- export const createPaperTradingEngine=(config:PaperTradingConfig={})=>new PaperTradingEngine(config);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/paper/paperTradingLoop.ts DELETED
@@ -1,25 +0,0 @@
1
- import { fetchRealCandles } from '../market/candleService.js';
2
- import { analyzeMarket } from '../confluence/marketAnalysisService.js';
3
- import { generateTradeSignal } from '../signal/signalEngine.js';
4
- import { PaperTradingEngine } from './paperTradingEngine.js';
5
-
6
- export interface PaperLoopStatus { running:boolean; symbol:string; intervalMs:number; lastTickAt:number|null; lastCandleTimestamp:number|null; lastError:string|null; }
7
-
8
- export class PaperTradingLoop {
9
- private readonly engine: PaperTradingEngine;
10
- private readonly symbol: string;
11
- private readonly intervalMs: number;
12
- private timer: ReturnType<typeof setInterval>|null = null;
13
- private ticking = false;
14
- private lastCandleTimestamp:number|null = null;
15
- private lastTickAt:number|null = null;
16
- private lastError:string|null = null;
17
-
18
- constructor(engine=new PaperTradingEngine(),symbol='BTC/USDT',intervalMs=15_000){this.engine=engine;this.symbol=symbol;this.intervalMs=intervalMs;}
19
- getEngine(){return this.engine;}
20
- getStatus():PaperLoopStatus{return{running:this.timer!==null,symbol:this.symbol,intervalMs:this.intervalMs,lastTickAt:this.lastTickAt,lastCandleTimestamp:this.lastCandleTimestamp,lastError:this.lastError};}
21
- async tick(){if(this.ticking)return;this.ticking=true;try{const series=await fetchRealCandles(this.symbol,'15m',500);const candles=series.candles;if(candles.length<50)throw new Error('insufficient_closed_candles');const closed=candles.slice(0,-1);const candle=closed.at(-1);if(!candle)throw new Error('no_closed_candle');this.lastTickAt=Date.now();this.lastError=null;if(this.lastCandleTimestamp===candle.timestamp)return;this.engine.processCandle(candle);this.lastCandleTimestamp=candle.timestamp;if(this.engine.getState().positions.length===0&&!this.engine.getState().account.halted){const analysis=await analyzeMarket(this.symbol);const signal=generateTradeSignal(analysis,candles.slice(0,-1));if(signal.direction!=='NO TRADE')this.engine.openFromSignal(signal,Date.now());}}catch(error){this.lastError=error instanceof Error?error.message:'paper_loop_error';}finally{this.ticking=false;}}
22
- start(){if(this.timer)return;void this.tick();this.timer=setInterval(()=>void this.tick(),this.intervalMs);}
23
- stop(){if(this.timer){clearInterval(this.timer);this.timer=null;}}
24
- }
25
- export const paperTradingLoop=new PaperTradingLoop();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/monteCarloAnalysis.ts DELETED
@@ -1,116 +0,0 @@
1
- import type { HistoricalBacktestTrade } from '../backtest/historicalBacktest.js';
2
-
3
- export interface MonteCarloAnalysisResult {
4
- simulations: number;
5
- sampleSize: number;
6
- expectancyR: number;
7
- medianFinalR: number;
8
- p05FinalR: number;
9
- p95FinalR: number;
10
- medianMaxDrawdownR: number;
11
- p95MaxDrawdownR: number;
12
- probabilityOfLossPercent: number;
13
- probabilityOfDrawdownOver10RPercent: number;
14
- grade: 'ROBUST' | 'MODERATE' | 'FRAGILE' | 'INSUFFICIENT_DATA';
15
- warnings: string[];
16
- }
17
-
18
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
19
-
20
- function rng(seed: number): () => number {
21
- let s = seed >>> 0;
22
- return () => { s = (1664525 * s + 1013904223) >>> 0; return s / 4294967296; };
23
- }
24
-
25
- function percentile(values: number[], p: number): number {
26
- if (!values.length) return 0;
27
- const index = (values.length - 1) * p;
28
- const lower = Math.floor(index);
29
- const upper = Math.ceil(index);
30
- if (lower === upper) return values[lower];
31
- return values[lower] + (values[upper] - values[lower]) * (index - lower);
32
- }
33
-
34
- /**
35
- * Bootstrap Monte Carlo using account-equity compounding.
36
- * pnlR remains the trade result in units of risk, while the simulated
37
- * account applies the configured risk percentage to each trade.
38
- */
39
- export function analyzeMonteCarlo(
40
- trades: HistoricalBacktestTrade[],
41
- simulations = 3000,
42
- riskPerTradePercent = 1,
43
- ): MonteCarloAnalysisResult {
44
- const values = trades.map(t => t.pnlR).filter(Number.isFinite);
45
- const safeRiskPercent = Number.isFinite(riskPerTradePercent) && riskPerTradePercent > 0 ? riskPerTradePercent : 1;
46
- const riskFraction = safeRiskPercent / 100;
47
-
48
- if (values.length < 30) return {
49
- simulations,
50
- sampleSize: values.length,
51
- expectancyR: round(values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0),
52
- medianFinalR: 0,
53
- p05FinalR: 0,
54
- p95FinalR: 0,
55
- medianMaxDrawdownR: 0,
56
- p95MaxDrawdownR: 0,
57
- probabilityOfLossPercent: 0,
58
- probabilityOfDrawdownOver10RPercent: 0,
59
- grade: 'INSUFFICIENT_DATA',
60
- warnings: ['São necessários pelo menos 30 trades para Monte Carlo.'],
61
- };
62
-
63
- const random = rng(20260908 + values.length + Math.round(safeRiskPercent * 100));
64
- const finals: number[] = [];
65
- const drawdowns: number[] = [];
66
- let losses = 0;
67
- let largeDrawdowns = 0;
68
-
69
- for (let i = 0; i < simulations; i++) {
70
- let equity = 1;
71
- let peak = 1;
72
- let maxDdFraction = 0;
73
-
74
- for (let j = 0; j < values.length; j++) {
75
- const tradeR = values[Math.floor(random() * values.length)];
76
- equity *= Math.max(0, 1 + tradeR * riskFraction);
77
- peak = Math.max(peak, equity);
78
- maxDdFraction = Math.max(maxDdFraction, peak > 0 ? (peak - equity) / peak : 0);
79
- }
80
-
81
- finals.push((equity - 1) / riskFraction);
82
- drawdowns.push(maxDdFraction / riskFraction);
83
- if (equity < 1) losses++;
84
- if (maxDdFraction / riskFraction > 10) largeDrawdowns++;
85
- }
86
-
87
- finals.sort((a, b) => a - b);
88
- drawdowns.sort((a, b) => a - b);
89
- const probabilityLoss = losses / simulations * 100;
90
- const probabilityLargeDd = largeDrawdowns / simulations * 100;
91
- const warnings: string[] = [];
92
-
93
- if (probabilityLoss > 25) warnings.push(`Monte Carlo: ${round(probabilityLoss, 2)}% das simulações terminaram abaixo do capital inicial.`);
94
- if (probabilityLargeDd > 25) warnings.push(`Monte Carlo: ${round(probabilityLargeDd, 2)}% das simulações excederam 10R de drawdown.`);
95
-
96
- const grade = probabilityLoss < 5 && probabilityLargeDd < 10
97
- ? 'ROBUST'
98
- : probabilityLoss < 15 && probabilityLargeDd < 25
99
- ? 'MODERATE'
100
- : 'FRAGILE';
101
-
102
- return {
103
- simulations,
104
- sampleSize: values.length,
105
- expectancyR: round(values.reduce((a, b) => a + b, 0) / values.length),
106
- medianFinalR: round(percentile(finals, 0.5)),
107
- p05FinalR: round(percentile(finals, 0.05)),
108
- p95FinalR: round(percentile(finals, 0.95)),
109
- medianMaxDrawdownR: round(percentile(drawdowns, 0.5)),
110
- p95MaxDrawdownR: round(percentile(drawdowns, 0.95)),
111
- probabilityOfLossPercent: round(probabilityLoss, 2),
112
- probabilityOfDrawdownOver10RPercent: round(probabilityLargeDd, 2),
113
- grade,
114
- warnings,
115
- };
116
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/multiRegimeAnalytics.ts DELETED
@@ -1,223 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import type { HistoricalBacktestTrade } from '../backtest/historicalBacktest.js';
3
- import type { HistoricalFundingRate } from '../derivatives/historicalFundingService.js';
4
-
5
- export type TrendRegime = 'BULL' | 'BEAR' | 'RANGE';
6
- export type VolatilityRegime = 'LOW' | 'NORMAL' | 'HIGH';
7
- export type FundingRegime = 'FAVORABLE' | 'NEUTRAL' | 'ADVERSE' | 'UNAVAILABLE';
8
-
9
- export interface MultiRegimeCell {
10
- key: string;
11
- trend: TrendRegime;
12
- volatility: VolatilityRegime;
13
- funding: FundingRegime;
14
- trades: number;
15
- wins: number;
16
- winRatePercent: number;
17
- netR: number;
18
- expectancyR: number;
19
- profitFactor: number;
20
- maxDrawdownR: number;
21
- }
22
-
23
- export interface MultiRegimeValidationResult {
24
- totalCells: number;
25
- coveredCells: number;
26
- evaluatedCells: number;
27
- positiveCells: number;
28
- coveragePercent: number;
29
- consistencyPercent: number;
30
- worstExpectancyR: number;
31
- status: 'ROBUST' | 'MIXED' | 'FRAGILE' | 'INSUFFICIENT_DATA';
32
- minimumTradesPerCell: number;
33
- fundingEvents: number;
34
- fundingCoveragePercent: number;
35
- fundingSources: { binance: number; okx: number };
36
- firstFundingTimestamp: number | null;
37
- lastFundingTimestamp: number | null;
38
- warnings: string[];
39
- cells: MultiRegimeCell[];
40
- }
41
-
42
- const round = (value: number, digits = 4) => Number((Number.isFinite(value) ? value : 0).toFixed(digits));
43
- const mean = (values: number[]) => values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0;
44
-
45
- function profitFactor(values: number[]): number {
46
- const wins = values.filter(v => v > 0).reduce((a, b) => a + b, 0);
47
- const losses = Math.abs(values.filter(v => v < 0).reduce((a, b) => a + b, 0));
48
- return losses > 0 ? wins / losses : wins > 0 ? Infinity : 0;
49
- }
50
-
51
- function drawdownR(values: number[]): number {
52
- let equity = 0;
53
- let peak = 0;
54
- let max = 0;
55
- for (const value of values) {
56
- equity += value;
57
- peak = Math.max(peak, equity);
58
- max = Math.max(max, peak - equity);
59
- }
60
- return max;
61
- }
62
-
63
- function percentile(values: number[], p: number): number {
64
- if (!values.length) return 0;
65
- const sorted = [...values].sort((a, b) => a - b);
66
- return sorted[Math.min(sorted.length - 1, Math.max(0, Math.floor((sorted.length - 1) * p)))];
67
- }
68
-
69
- function latestFundingAtOrBefore(funding: HistoricalFundingRate[], timestamp: number): HistoricalFundingRate | null {
70
- let latest: HistoricalFundingRate | null = null;
71
- for (const row of funding) {
72
- if (row.timestamp > timestamp) break;
73
- latest = row;
74
- }
75
- return latest;
76
- }
77
-
78
- function classifyTrend(candles: Candle[], index: number): TrendRegime {
79
- const start = Math.max(0, index - 63);
80
- const window = candles.slice(start, index + 1);
81
- if (window.length < 32) return 'RANGE';
82
- const closes = window.map(c => c.close);
83
- const move = closes.at(-1)! / closes[0] - 1;
84
- const volatility = Math.sqrt(mean(closes.slice(1).map((v, i) => {
85
- const r = v / closes[i] - 1;
86
- return r * r;
87
- })));
88
- const threshold = Math.max(volatility * 2.5, 0.003);
89
- if (move >= threshold) return 'BULL';
90
- if (move <= -threshold) return 'BEAR';
91
- return 'RANGE';
92
- }
93
-
94
- function classifyVolatility(candles: Candle[], index: number): VolatilityRegime {
95
- const start = Math.max(0, index - 95);
96
- const window = candles.slice(start, index + 1);
97
- if (window.length < 32) return 'NORMAL';
98
- const returns = window.slice(1).map((c, i) => c.close / window[i].close - 1);
99
- const currentWindow = returns.slice(-31);
100
- const current = Math.sqrt(mean(currentWindow.map(r => r * r)));
101
- const history: number[] = [];
102
- for (let i = 31; i < returns.length; i += 1) {
103
- const sample = returns.slice(i - 31, i + 1);
104
- history.push(Math.sqrt(mean(sample.map(r => r * r))));
105
- }
106
- const low = percentile(history, 0.33);
107
- const high = percentile(history, 0.67);
108
- if (current <= low) return 'LOW';
109
- if (current >= high) return 'HIGH';
110
- return 'NORMAL';
111
- }
112
-
113
- function classifyFunding(
114
- funding: HistoricalFundingRate[],
115
- timestamp: number,
116
- direction: HistoricalBacktestTrade['direction'],
117
- ): FundingRegime {
118
- const latest = latestFundingAtOrBefore(funding, timestamp);
119
- if (!latest) return 'UNAVAILABLE';
120
- const abs = Math.abs(latest.fundingRate);
121
- const neutralThreshold = 0.00005;
122
- if (abs <= neutralThreshold) return 'NEUTRAL';
123
- const favorable = direction === 'LONG' ? latest.fundingRate < 0 : latest.fundingRate > 0;
124
- return favorable ? 'FAVORABLE' : 'ADVERSE';
125
- }
126
-
127
- export function validateMultiRegime(
128
- candles: Candle[],
129
- trades: HistoricalBacktestTrade[],
130
- historicalFunding: HistoricalFundingRate[] = [],
131
- minimumTradesPerCell = 10,
132
- ): MultiRegimeValidationResult {
133
- const safeCandles = [...candles].sort((a, b) => a.timestamp - b.timestamp);
134
- const safeFunding = [...historicalFunding].filter(x => Number.isFinite(x.timestamp) && Number.isFinite(x.fundingRate)).sort((a, b) => a.timestamp - b.timestamp);
135
- const trendValues: TrendRegime[] = ['BULL', 'BEAR', 'RANGE'];
136
- const volatilityValues: VolatilityRegime[] = ['LOW', 'NORMAL', 'HIGH'];
137
- const fundingValues: FundingRegime[] = safeFunding.length ? ['FAVORABLE', 'NEUTRAL', 'ADVERSE'] : ['FAVORABLE', 'NEUTRAL', 'ADVERSE', 'UNAVAILABLE'];
138
- const buckets = new Map<string, number[]>();
139
-
140
- for (const trade of trades) {
141
- let entryIndex = safeCandles.findIndex(c => c.timestamp === trade.timestamp);
142
- if (entryIndex < 0) entryIndex = safeCandles.findIndex(c => c.timestamp >= trade.timestamp);
143
- if (entryIndex <= 0) continue;
144
- const signalIndex = entryIndex - 1;
145
- const trend = classifyTrend(safeCandles, signalIndex);
146
- const volatility = classifyVolatility(safeCandles, signalIndex);
147
- const funding = classifyFunding(safeFunding, safeCandles[signalIndex].timestamp, trade.direction);
148
- const key = `${trend}|${volatility}|${funding}`;
149
- buckets.set(key, [...(buckets.get(key) ?? []), trade.pnlR]);
150
- }
151
-
152
- const cells: MultiRegimeCell[] = [];
153
- for (const trend of trendValues) {
154
- for (const volatility of volatilityValues) {
155
- for (const funding of fundingValues) {
156
- const key = `${trend}|${volatility}|${funding}`;
157
- const values = buckets.get(key) ?? [];
158
- const wins = values.filter(v => v > 0).length;
159
- cells.push({
160
- key,
161
- trend,
162
- volatility,
163
- funding,
164
- trades: values.length,
165
- wins,
166
- winRatePercent: round(values.length ? wins / values.length * 100 : 0, 2),
167
- netR: round(values.reduce((a, b) => a + b, 0)),
168
- expectancyR: round(mean(values)),
169
- profitFactor: round(profitFactor(values)),
170
- maxDrawdownR: round(drawdownR(values)),
171
- });
172
- }
173
- }
174
- }
175
-
176
- const covered = cells.filter(c => c.trades > 0);
177
- const evaluated = cells.filter(c => c.trades >= minimumTradesPerCell);
178
- const positive = evaluated.filter(c => c.expectancyR > 0);
179
- const coveragePercent = cells.length ? covered.length / cells.length * 100 : 0;
180
- const consistencyPercent = evaluated.length ? positive.length / evaluated.length * 100 : 0;
181
- const worstExpectancyR = evaluated.length ? Math.min(...evaluated.map(c => c.expectancyR)) : 0;
182
- const sourceCounts = {
183
- binance: safeFunding.filter(x => x.source === 'binance-futures').length,
184
- okx: safeFunding.filter(x => x.source === 'okx-swap').length,
185
- };
186
- const entryTimestamps = trades.map(t => t.timestamp).filter(Number.isFinite);
187
- const firstEntry = entryTimestamps.length ? Math.min(...entryTimestamps) : null;
188
- const lastEntry = entryTimestamps.length ? Math.max(...entryTimestamps) : null;
189
- const fundingAtEntries = trades.filter(t => latestFundingAtOrBefore(safeFunding, t.timestamp)).length;
190
- const fundingCoveragePercent = trades.length ? fundingAtEntries / trades.length * 100 : 0;
191
- const warnings: string[] = [];
192
- if (!safeFunding.length) warnings.push('Nenhum evento histórico de funding disponível para classificar o eixo de funding.');
193
- if (fundingCoveragePercent < 95 && trades.length) warnings.push(`Cobertura de funding na entrada em ${round(fundingCoveragePercent, 1)}% dos trades.`);
194
- if (covered.length < Math.min(9, cells.length)) warnings.push('A matriz cobre poucas combinações de regimes; aumentar a janela pode melhorar a representatividade.');
195
- if (evaluated.some(c => c.expectancyR <= 0)) warnings.push('Existe pelo menos uma combinação de regime relevante com expectancy não positiva.');
196
- if (evaluated.length < 5) warnings.push(`Menos de cinco células atingiram o mínimo de ${minimumTradesPerCell} trades.`);
197
- const status = evaluated.length < 5
198
- ? 'INSUFFICIENT_DATA'
199
- : consistencyPercent >= 75 && worstExpectancyR > 0
200
- ? 'ROBUST'
201
- : consistencyPercent >= 50
202
- ? 'MIXED'
203
- : 'FRAGILE';
204
-
205
- return {
206
- totalCells: cells.length,
207
- coveredCells: covered.length,
208
- evaluatedCells: evaluated.length,
209
- positiveCells: positive.length,
210
- coveragePercent: round(coveragePercent, 1),
211
- consistencyPercent: round(consistencyPercent, 1),
212
- worstExpectancyR: round(worstExpectancyR),
213
- status,
214
- minimumTradesPerCell,
215
- fundingEvents: safeFunding.length,
216
- fundingCoveragePercent: round(fundingCoveragePercent, 1),
217
- fundingSources: sourceCounts,
218
- firstFundingTimestamp: safeFunding[0]?.timestamp ?? null,
219
- lastFundingTimestamp: safeFunding.at(-1)?.timestamp ?? null,
220
- warnings,
221
- cells,
222
- };
223
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/parameterValidation.ts DELETED
@@ -1,75 +0,0 @@
1
- import type { HistoricalBacktestOptions, HistoricalBacktestResult } from '../backtest/historicalBacktest.js';
2
- import type { Candle } from '../../src/types.js';
3
- import { runHistoricalBacktest } from '../backtest/historicalBacktest.js';
4
- import { selectRobustParameters, type RobustParameterSelectionResult } from './robustParameterSelection.js';
5
- import { analyzeMonteCarlo, type MonteCarloAnalysisResult } from './monteCarloAnalysis.js';
6
- import { runStressTest, type StressTestResult } from './stressTest.js';
7
- import { analyzeRegimes, type RegimeAnalyticsResult } from './regimeAnalytics.js';
8
- import { validateMultiRegime, type MultiRegimeValidationResult } from './multiRegimeAnalytics.js';
9
- import type { HistoricalFundingRate } from '../derivatives/historicalFundingService.js';
10
-
11
- export interface OverfittingGuardResult { level: 'LOW' | 'MODERATE' | 'HIGH' | 'INSUFFICIENT_DATA'; score: number; trainExpectancyR: number; holdoutExpectancyR: number; expectancyRetentionPercent: number; trainProfitFactor: number; holdoutProfitFactor: number; profitFactorRetentionPercent: number; tradeCount: number; warnings: string[]; }
12
- export interface OosSignificanceResult { sampleSize: number; meanR: number; bootstrapCi95R: { low: number; high: number } | null; probabilityPositiveExpectancyPercent: number; probabilityPositiveDeltaPercent: number; baselineDeltaCi95R: { low: number; high: number } | null; status: 'SIGNIFICANT' | 'WEAK' | 'INSUFFICIENT_DATA'; warnings: string[]; }
13
- export interface RegimeValidationResult { coveredRegimes: number; evaluatedRegimes: number; positiveRegimes: number; coveragePercent: number; consistencyPercent: number; worstExpectancyR: number; status: 'ROBUST' | 'MIXED' | 'FRAGILE' | 'INSUFFICIENT_DATA'; warnings: string[]; buckets: RegimeAnalyticsResult['buckets']; }
14
-
15
- export interface ParameterValidationResult {
16
- split: { trainCandles: number; holdoutCandles: number; trainPercent: number };
17
- optimization: RobustParameterSelectionResult;
18
- baselineHoldout: HistoricalBacktestResult;
19
- selectedHoldout: HistoricalBacktestResult | null;
20
- regimeWindow: HistoricalBacktestResult | null;
21
- monteCarlo: MonteCarloAnalysisResult | null;
22
- stressTest: StressTestResult | null;
23
- comparison: { expectancyDeltaR: number; netProfitDeltaPercent: number; drawdownDeltaPercent: number; selectedBeatsBaseline: boolean };
24
- overfittingGuard: OverfittingGuardResult;
25
- significance: OosSignificanceResult;
26
- regimeValidation: RegimeValidationResult;
27
- multiRegimeValidation: MultiRegimeValidationResult;
28
- verdict: 'PASS' | 'CAUTION' | 'REJECT' | 'INSUFFICIENT_DATA';
29
- warnings: string[];
30
- }
31
-
32
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
33
- const displayPf = (v: number) => Number.isFinite(v) ? v : v > 0 ? 99 : 0;
34
-
35
- function assessOverfitting(trainResult: HistoricalBacktestResult, holdoutResult: HistoricalBacktestResult | null, selected: RobustParameterSelectionResult['selected']): OverfittingGuardResult {
36
- if (!holdoutResult || !selected || trainResult.totalTrades < 30 || holdoutResult.totalTrades < 30) return { level: 'INSUFFICIENT_DATA', score: 0, trainExpectancyR: round(trainResult.expectancyR), holdoutExpectancyR: round(holdoutResult?.expectancyR ?? 0), expectancyRetentionPercent: 0, trainProfitFactor: round(displayPf(trainResult.profitFactor)), holdoutProfitFactor: round(displayPf(holdoutResult?.profitFactor ?? 0)), profitFactorRetentionPercent: 0, tradeCount: holdoutResult?.totalTrades ?? 0, warnings: ['Amostra insuficiente para medir overfitting com confiança.'] };
37
- const trainExpectancy = trainResult.expectancyR; const holdoutExpectancy = holdoutResult.expectancyR; const trainPf = displayPf(trainResult.profitFactor); const holdoutPf = displayPf(holdoutResult.profitFactor);
38
- const expectancyRetention = trainExpectancy > 0 ? holdoutExpectancy / trainExpectancy * 100 : holdoutExpectancy > 0 ? 100 : 0; const pfRetention = trainPf > 0 ? holdoutPf / trainPf * 100 : 0;
39
- const stableBonus = selected.stabilityScore >= selected.rankScore * 0.85 ? 10 : selected.stabilityScore >= selected.rankScore * 0.7 ? 5 : 0; const samplePenalty = holdoutResult.totalTrades < 50 ? 10 : 0; const expectancyPenalty = expectancyRetention < 30 ? 45 : expectancyRetention < 50 ? 30 : expectancyRetention < 70 ? 15 : 0; const pfPenalty = pfRetention < 40 ? 25 : pfRetention < 60 ? 15 : pfRetention < 80 ? 5 : 0;
40
- const score = Math.max(0, Math.min(100, 100 + stableBonus - samplePenalty - expectancyPenalty - pfPenalty)); const level = score >= 75 ? 'LOW' : score >= 50 ? 'MODERATE' : 'HIGH'; const warnings: string[] = [];
41
- if (expectancyRetention < 70) warnings.push(`Retenção de expectancy treino→OOS em ${round(expectancyRetention, 1)}%.`); if (pfRetention < 80) warnings.push(`Retenção de Profit Factor treino→OOS em ${round(pfRetention, 1)}%.`); if (holdoutResult.totalTrades < 50) warnings.push('Holdout tem menos de 50 trades; risco estatístico maior.'); if (level === 'HIGH') warnings.push('Risco alto de overfitting: não liberar para uso operacional.');
42
- return { level, score: round(score, 1), trainExpectancyR: round(trainExpectancy), holdoutExpectancyR: round(holdoutExpectancy), expectancyRetentionPercent: round(expectancyRetention, 1), trainProfitFactor: round(trainPf), holdoutProfitFactor: round(holdoutPf), profitFactorRetentionPercent: round(pfRetention, 1), tradeCount: holdoutResult.totalTrades, warnings };
43
- }
44
-
45
- function bootstrapSignificance(values: number[], baselineValues: number[] | null, simulations = 3000): OosSignificanceResult {
46
- if (values.length < 30) return { sampleSize: values.length, meanR: round(values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0), bootstrapCi95R: null, probabilityPositiveExpectancyPercent: 0, probabilityPositiveDeltaPercent: 0, baselineDeltaCi95R: null, status: 'INSUFFICIENT_DATA', warnings: ['São necessários pelo menos 30 trades OOS.'] };
47
- let seed = 20260908 + values.length; const random = () => { seed = (1664525 * seed + 1013904223) >>> 0; return seed / 4294967296; };
48
- const means: number[] = []; const deltas: number[] = []; let positive = 0; let positiveDelta = 0; const observed = values.reduce((a, b) => a + b, 0) / values.length;
49
- for (let s = 0; s < simulations; s++) { let sum = 0; let deltaSum = 0; for (let i = 0; i < values.length; i++) { const idx = Math.floor(random() * values.length); const v = values[idx]; sum += v; if (baselineValues?.length) deltaSum += v - baselineValues[Math.floor(random() * baselineValues.length)]; } const m = sum / values.length; means.push(m); if (m > 0) positive++; if (baselineValues?.length) { const d = deltaSum / values.length; deltas.push(d); if (d > 0) positiveDelta++; } }
50
- means.sort((a, b) => a - b); deltas.sort((a, b) => a - b); const percentile = (arr: number[], p: number) => arr.length ? arr[Math.min(arr.length - 1, Math.max(0, Math.floor((arr.length - 1) * p)))] : 0;
51
- const ci = { low: percentile(means, 0.025), high: percentile(means, 0.975) }; const deltaCi = deltas.length ? { low: percentile(deltas, 0.025), high: percentile(deltas, 0.975) } : null; const pPositive = positive / simulations * 100; const pDelta = baselineValues?.length ? positiveDelta / simulations * 100 : 0; const significant = ci.low > 0 && (!deltaCi || deltaCi.low > 0) && pPositive >= 97.5;
52
- const warnings: string[] = []; if (ci.low <= 0) warnings.push('IC bootstrap 95% da expectancy inclui zero.'); if (deltaCi && deltaCi.low <= 0) warnings.push('IC bootstrap da vantagem contra baseline inclui zero.');
53
- return { sampleSize: values.length, meanR: round(observed), bootstrapCi95R: { low: round(ci.low), high: round(ci.high) }, probabilityPositiveExpectancyPercent: round(pPositive, 2), probabilityPositiveDeltaPercent: round(pDelta, 2), baselineDeltaCi95R: deltaCi ? { low: round(deltaCi.low), high: round(deltaCi.high) } : null, status: significant ? 'SIGNIFICANT' : 'WEAK', warnings };
54
- }
55
-
56
- function validateRegimes(candles: Candle[], trades: HistoricalBacktestResult['trades']): RegimeValidationResult {
57
- const analysis = analyzeRegimes(candles, trades); const evaluated = analysis.buckets.filter(b => b.trades >= 10); const positive = evaluated.filter(b => b.expectancyR > 0); const covered = analysis.buckets.filter(b => b.trades > 0).length; const coverage = covered / analysis.buckets.length * 100; const consistency = evaluated.length ? positive.length / evaluated.length * 100 : 0; const worst = evaluated.length ? Math.min(...evaluated.map(b => b.expectancyR)) : 0; const warnings = [...analysis.warnings]; if (covered < 3) warnings.push('Menos de três regimes tiveram operações na janela ampliada.'); if (evaluated.some(b => b.expectancyR <= 0)) warnings.push('Existe pelo menos um regime relevante com expectancy não positiva.'); const status = evaluated.length < 3 ? 'INSUFFICIENT_DATA' : consistency >= 75 && worst > 0 ? 'ROBUST' : consistency >= 50 ? 'MIXED' : 'FRAGILE'; return { coveredRegimes: covered, evaluatedRegimes: evaluated.length, positiveRegimes: positive.length, coveragePercent: round(coverage, 1), consistencyPercent: round(consistency, 1), worstExpectancyR: round(worst), status, warnings, buckets: analysis.buckets };
58
- }
59
-
60
- export function validateSelectedParameters(symbol: string, candles: Candle[], baseOptions: Omit<HistoricalBacktestOptions, 'symbol' | 'candles'> = {}, trainPercent = 70): ParameterValidationResult {
61
- const safePercent = Math.max(60, Math.min(80, trainPercent)); const splitIndex = Math.floor(candles.length * safePercent / 100); const train = candles.slice(0, splitIndex); const holdout = candles.slice(splitIndex); const optimization = selectRobustParameters(symbol, train, baseOptions);
62
- const baselineHoldout = runHistoricalBacktest({ ...baseOptions, symbol, candles: holdout });
63
- const selectedHoldout = optimization.selected ? runHistoricalBacktest({ ...baseOptions, symbol, candles: holdout, minScore: optimization.selected.minScore, minConfidence: optimization.selected.minConfidence, atrStopMultiple: optimization.selected.atrStopMultiple, rewardRisk: optimization.selected.rewardRisk, maxHoldingBars: optimization.selected.maxHoldingBars }) : null;
64
- const trainSelected = optimization.selected ? runHistoricalBacktest({ ...baseOptions, symbol, candles: train, minScore: optimization.selected.minScore, minConfidence: optimization.selected.minConfidence, atrStopMultiple: optimization.selected.atrStopMultiple, rewardRisk: optimization.selected.rewardRisk, maxHoldingBars: optimization.selected.maxHoldingBars }) : null;
65
- const regimeWindow = optimization.selected ? runHistoricalBacktest({ ...baseOptions, symbol, candles, minScore: optimization.selected.minScore, minConfidence: optimization.selected.minConfidence, atrStopMultiple: optimization.selected.atrStopMultiple, rewardRisk: optimization.selected.rewardRisk, maxHoldingBars: optimization.selected.maxHoldingBars }) : null;
66
- const monteCarlo = selectedHoldout && selectedHoldout.totalTrades >= 30 ? analyzeMonteCarlo(selectedHoldout.trades, 3000, baseOptions.riskPerTradePercent ?? 1) : null;
67
- const stressTest = selectedHoldout && selectedHoldout.totalTrades >= 30 ? runStressTest(symbol, holdout, { ...baseOptions, minScore: optimization.selected?.minScore, minConfidence: optimization.selected?.minConfidence, atrStopMultiple: optimization.selected?.atrStopMultiple, rewardRisk: optimization.selected?.rewardRisk, maxHoldingBars: optimization.selected?.maxHoldingBars }) : null;
68
- const expectancyDeltaR = round((selectedHoldout?.expectancyR ?? 0) - baselineHoldout.expectancyR); const netProfitDeltaPercent = round((selectedHoldout?.netProfitPercent ?? 0) - baselineHoldout.netProfitPercent, 2); const drawdownDeltaPercent = round((selectedHoldout?.maxDrawdownPercent ?? 0) - baselineHoldout.maxDrawdownPercent, 2); const selectedBeatsBaseline = !!selectedHoldout && selectedHoldout.totalTrades >= 30 && selectedHoldout.expectancyR > 0 && selectedHoldout.profitFactor > 1 && selectedHoldout.expectancyR >= baselineHoldout.expectancyR;
69
- const overfittingGuard = assessOverfitting(trainSelected ?? runHistoricalBacktest({ ...baseOptions, symbol, candles: train }), selectedHoldout, optimization.selected); const significance = bootstrapSignificance(selectedHoldout?.trades.map(t => t.pnlR).filter(Number.isFinite) ?? [], baselineHoldout.trades.map(t => t.pnlR).filter(Number.isFinite)); const regimeValidation = validateRegimes(candles, regimeWindow?.trades ?? []);
70
- const historicalFunding = (baseOptions.historicalFunding ?? []) as HistoricalFundingRate[];
71
- const multiRegimeValidation = validateMultiRegime(candles, regimeWindow?.trades ?? [], historicalFunding, 10);
72
- const warnings: string[] = [...overfittingGuard.warnings, ...significance.warnings, ...regimeValidation.warnings, ...multiRegimeValidation.warnings]; if (train.length < 3000 || holdout.length < 1500) warnings.push('Janela inferior à recomendada para validação multi-regime estendida.'); if (!optimization.selected) warnings.push('Nenhum parâmetro foi selecionado no treino.'); if (selectedHoldout && selectedHoldout.totalTrades < 30) warnings.push('O holdout selecionado tem menos de 30 trades.'); if (monteCarlo?.grade === 'FRAGILE') warnings.push('Monte Carlo classificou a distribuição como FRAGILE.'); if (stressTest?.grade === 'FRAGILE') warnings.push('Stress Test classificou a configuração como FRAGILE.');
73
- const enough = train.length >= 3000 && holdout.length >= 1500 && !!selectedHoldout && selectedHoldout.totalTrades >= 30; const riskChecksPass = !!monteCarlo && monteCarlo.grade !== 'FRAGILE' && !!stressTest && stressTest.grade !== 'FRAGILE'; const verdict = !enough ? 'INSUFFICIENT_DATA' : !selectedBeatsBaseline || overfittingGuard.level === 'HIGH' || significance.status !== 'SIGNIFICANT' || regimeValidation.status === 'FRAGILE' || regimeValidation.status === 'INSUFFICIENT_DATA' || multiRegimeValidation.status === 'FRAGILE' || multiRegimeValidation.status === 'INSUFFICIENT_DATA' ? 'REJECT' : selectedBeatsBaseline && riskChecksPass && overfittingGuard.level === 'LOW' && regimeValidation.status === 'ROBUST' && multiRegimeValidation.status === 'ROBUST' ? 'PASS' : 'CAUTION';
74
- return { split: { trainCandles: train.length, holdoutCandles: holdout.length, trainPercent: safePercent }, optimization, baselineHoldout, selectedHoldout, regimeWindow, monteCarlo, stressTest, comparison: { expectancyDeltaR, netProfitDeltaPercent, drawdownDeltaPercent, selectedBeatsBaseline }, overfittingGuard, significance, regimeValidation, multiRegimeValidation, verdict, warnings };
75
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/quantitativeLab.ts DELETED
@@ -1,31 +0,0 @@
1
- import type { HistoricalBacktestResult } from '../backtest/historicalBacktest.js';
2
- import type { PaperTradingState } from '../paper/paperTradingEngine.js';
3
- import type { Candle } from '../../src/types.js';
4
- import { analyzeRegimes, type RegimeAnalyticsResult } from './regimeAnalytics.js';
5
- import { analyzeStatistics, type StatisticalAnalysisResult } from './statisticalAnalysis.js';
6
- import { analyzeRobustness, type RobustnessAnalysisResult } from './robustnessAnalysis.js';
7
- import { analyzeTimeSeries, type TimeSeriesAnalysisResult } from './timeSeriesAnalysis.js';
8
- import { runWalkForwardAnalysis, type WalkForwardAnalysisResult } from './walkForwardAnalysis.js';
9
- import { runStressTest, type StressTestResult } from './stressTest.js';
10
- import { analyzeMonteCarlo, type MonteCarloAnalysisResult } from './monteCarloAnalysis.js';
11
- import type { RobustParameterSelectionResult } from './robustParameterSelection.js';
12
-
13
- export interface QuantMetricSet { trades: number; winRatePercent: number; profitFactor: number; netPnl: number; netPnlPercent: number; expectancyR: number; averageR: number; maxDrawdownPercent: number; bestTradePnl: number; worstTradePnl: number; tp1HitRatePercent: number; tp2HitRatePercent: number; fees: number; slippage: number; }
14
- export interface QuantitativeLabResult {
15
- generatedAt: number; symbol: string;
16
- backtest: QuantMetricSet & { initialCapital: number; finalCapital: number; periodDays: number; sharpeRatio: number; sortinoRatio: number; grossExpectancyR: number; tradeDistribution: { wins: number; losses: number; breakevens: number }; equityCurve: HistoricalBacktestResult['equityCurve'] };
17
- paper: QuantMetricSet & { initialCapital: number; equity: number; openPositions: number; halted: boolean; history: PaperTradingState['history'] };
18
- comparison: { netPnlDeltaPercent: number; winRateDeltaPercent: number; expectancyDeltaR: number; drawdownDeltaPercent: number; profitFactorDelta: number; status: 'ALIGNED' | 'DIVERGENT' | 'INSUFFICIENT_DATA'; reasons: string[] };
19
- quality: { score: number; grade: 'A' | 'B' | 'C' | 'D' | 'INSUFFICIENT_DATA'; checks: string[]; warnings: string[] };
20
- regimes: RegimeAnalyticsResult; statistics: StatisticalAnalysisResult; robustness: RobustnessAnalysisResult; timeSeries: TimeSeriesAnalysisResult; walkForward: WalkForwardAnalysisResult; stressTest: StressTestResult; monteCarlo: MonteCarloAnalysisResult; parameterSelection: RobustParameterSelectionResult;
21
- }
22
- const finite = (v: number, fallback = 0) => Number.isFinite(v) ? v : fallback;
23
- const round = (v: number, d = 4) => Number(finite(v).toFixed(d));
24
- const delta = (a: number, b: number) => round(a - b);
25
- function backtestMetrics(result: HistoricalBacktestResult): QuantitativeLabResult['backtest'] { const wins = result.trades.filter(t => t.pnlR > 0).length; const losses = result.trades.filter(t => t.pnlR < 0).length; return { trades: result.totalTrades, winRatePercent: round(result.winRate), profitFactor: finite(result.profitFactor), netPnl: round(result.finalCapital - result.initialCapital, 2), netPnlPercent: round(result.netProfitPercent), expectancyR: round(result.expectancyR), averageR: round(result.averageRR), maxDrawdownPercent: round(result.maxDrawdownPercent), bestTradePnl: result.trades.length ? round(Math.max(...result.trades.map(t => t.pnlPercent)), 4) : 0, worstTradePnl: result.trades.length ? round(Math.min(...result.trades.map(t => t.pnlPercent)), 4) : 0, tp1HitRatePercent: round(result.trades.filter(t => t.status === 'TP ATINGIDO').length / Math.max(1, result.totalTrades) * 100), tp2HitRatePercent: 0, fees: round(result.totalFeesPercent, 4), slippage: round(result.totalSlippagePercent, 4), initialCapital: result.initialCapital, finalCapital: result.finalCapital, periodDays: round(result.periodDays, 2), sharpeRatio: round(result.sharpeRatio), sortinoRatio: round(result.sortinoRatio), grossExpectancyR: round(result.grossExpectancyR), tradeDistribution: { wins, losses, breakevens: result.totalTrades - wins - losses }, equityCurve: result.equityCurve }; }
26
- function paperMetrics(state: PaperTradingState): QuantitativeLabResult['paper'] { return { trades: state.stats.totalTrades, winRatePercent: round(state.stats.winRatePercent), profitFactor: finite(state.stats.profitFactor), netPnl: round(state.account.realizedPnl, 2), netPnlPercent: round(state.stats.netPnlPercent), expectancyR: round(state.stats.averageR), averageR: round(state.stats.averageR), maxDrawdownPercent: round(state.account.maxDrawdownPercent), bestTradePnl: round(state.stats.bestTradePnl, 4), worstTradePnl: round(state.stats.worstTradePnl, 4), tp1HitRatePercent: round(state.stats.tp1HitRatePercent), tp2HitRatePercent: round(state.stats.tp2HitRatePercent), fees: round(state.account.feesPaid, 4), slippage: round(state.account.slippagePaid, 4), initialCapital: state.account.initialCapital, equity: state.account.equity, openPositions: state.positions.length, halted: state.account.halted, history: state.history }; }
27
- function buildComparison(backtest: QuantitativeLabResult['backtest'], paper: QuantitativeLabResult['paper']): QuantitativeLabResult['comparison'] { const common = { netPnlDeltaPercent: delta(paper.netPnlPercent, backtest.netPnlPercent), winRateDeltaPercent: delta(paper.winRatePercent, backtest.winRatePercent), expectancyDeltaR: delta(paper.expectancyR, backtest.expectancyR), drawdownDeltaPercent: delta(paper.maxDrawdownPercent, backtest.maxDrawdownPercent), profitFactorDelta: delta(paper.profitFactor, backtest.profitFactor) }; if (backtest.trades < 30 || paper.trades < 10) return { ...common, status: 'INSUFFICIENT_DATA', reasons: ['São necessários pelo menos 30 trades de backtest e 10 de Paper Trading para comparação robusta.'] }; const reasons: string[] = []; if (Math.abs(common.winRateDeltaPercent) > 15) reasons.push(`Win rate diverge ${round(Math.abs(common.winRateDeltaPercent), 2)} pontos percentuais.`); if (Math.abs(common.expectancyDeltaR) > 0.25) reasons.push(`Expectancy diverge ${round(Math.abs(common.expectancyDeltaR), 3)}R.`); if (Math.abs(common.drawdownDeltaPercent) > 5) reasons.push(`Drawdown máximo diverge ${round(Math.abs(common.drawdownDeltaPercent), 2)} pontos percentuais.`); return { ...common, status: reasons.length ? 'DIVERGENT' : 'ALIGNED', reasons: reasons.length ? reasons : ['Métricas principais estão dentro das bandas de alinhamento definidas.'] }; }
28
- function assessQuality(result: HistoricalBacktestResult): QuantitativeLabResult['quality'] { if (result.totalTrades < 30) return { score: 0, grade: 'INSUFFICIENT_DATA', checks: [`Amostra: ${result.totalTrades} trades.`], warnings: ['Menos de 30 trades.'] }; let score = 0; const checks: string[] = []; const warnings: string[] = []; if (result.expectancyR > 0) { score += 25; checks.push('Expectancy positiva.'); } else warnings.push('Expectancy não é positiva.'); if (result.profitFactor > 1.2) { score += 20; checks.push('Profit Factor acima de 1.20.'); } else warnings.push('Profit Factor baixo.'); if (result.maxDrawdownPercent < 10) { score += 20; checks.push('Drawdown abaixo de 10%.'); } else warnings.push('Drawdown elevado.'); if (result.winRate >= 45) { score += 15; checks.push('Win rate >= 45%.'); } else warnings.push('Win rate abaixo de 45%.'); if (result.sharpeRatio > 1) { score += 10; checks.push('Sharpe acima de 1.'); } else warnings.push('Sharpe não supera 1.'); if (result.totalTrades >= 100) { score += 10; checks.push('Amostra >= 100 trades.'); } else warnings.push('Amostra ainda abaixo de 100 trades.'); const grade = score >= 85 ? 'A' : score >= 70 ? 'B' : score >= 50 ? 'C' : 'D'; return { score, grade, checks, warnings }; }
29
- const baseOptions = { initialCapital: 10_000, riskPerTradePercent: 1, minScore: 35, minConfidence: 50, atrStopMultiple: 1.5, rewardRisk: 2, maxHoldingBars: 32, warmupBars: 220 } as const;
30
- const emptySelection = (): RobustParameterSelectionResult => ({ candidates: [], selected: null, baseline: null, stableRegion: { minScore: [], minConfidence: [], atrStopMultiple: [], rewardRisk: [], maxHoldingBars: [] }, grade: 'INSUFFICIENT_DATA', warnings: ['Otimização de parâmetros é executada apenas pela validação OOS.'] });
31
- export function buildQuantitativeLab(result: HistoricalBacktestResult, paperState: PaperTradingState, candles: Candle[]): QuantitativeLabResult { const backtest = backtestMetrics(result); const paper = paperMetrics(paperState); return { generatedAt: Date.now(), symbol: result.symbol, backtest, paper, comparison: buildComparison(backtest, paper), quality: assessQuality(result), regimes: analyzeRegimes(candles, result.trades), statistics: analyzeStatistics(result.trades), robustness: analyzeRobustness(result.trades), timeSeries: analyzeTimeSeries(result.trades), walkForward: runWalkForwardAnalysis(result.symbol, candles, baseOptions), stressTest: runStressTest(result.symbol, candles, baseOptions), monteCarlo: analyzeMonteCarlo(result.trades, 3000, baseOptions.riskPerTradePercent), parameterSelection: emptySelection() }; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/regimeAnalytics.ts DELETED
@@ -1,27 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import type { HistoricalBacktestTrade } from '../backtest/historicalBacktest.js';
3
-
4
- export type MarketRegime = 'TREND_BULL' | 'TREND_BEAR' | 'RANGE' | 'HIGH_VOLATILITY' | 'LOW_VOLATILITY' | 'NEUTRAL';
5
- export interface RegimeBucket { regime: MarketRegime; trades: number; wins: number; losses: number; winRatePercent: number; netR: number; expectancyR: number; profitFactor: number; maxDrawdownR: number; }
6
- export interface RegimeAnalyticsResult { buckets: RegimeBucket[]; dominantRegime: MarketRegime; strongestRegime: MarketRegime | null; weakestRegime: MarketRegime | null; warnings: string[]; }
7
- const mean = (values: number[]) => values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0;
8
- const round = (value: number, digits = 4) => Number((Number.isFinite(value) ? value : 0).toFixed(digits));
9
- function classifyRegime(candles: Candle[], index: number): MarketRegime { const start = Math.max(0, index - 49); const window = candles.slice(start, index + 1); if (window.length < 20) return 'NEUTRAL'; const closes = window.map(c => c.close); const returns = closes.slice(1).map((v, i) => v / closes[i] - 1); const meanReturn = mean(returns); const volatility = Math.sqrt(mean(returns.map(r => (r - meanReturn) ** 2))); const move = closes.at(-1)! / closes[0] - 1; const threshold = Math.max(volatility * 2, 0.0015); if (volatility > 0.008) return 'HIGH_VOLATILITY'; if (volatility < 0.0025) return 'LOW_VOLATILITY'; if (move > threshold * 2) return 'TREND_BULL'; if (move < -threshold * 2) return 'TREND_BEAR'; return 'RANGE'; }
10
- function drawdownR(values: number[]): number { let equity = 0; let peak = 0; let max = 0; for (const value of values) { equity += value; peak = Math.max(peak, equity); max = Math.max(max, peak - equity); } return max; }
11
- export function analyzeRegimes(candles: Candle[], trades: HistoricalBacktestTrade[]): RegimeAnalyticsResult {
12
- const buckets = new Map<MarketRegime, number[]>();
13
- for (const trade of trades) {
14
- let entryIndex = candles.findIndex(c => c.timestamp === trade.timestamp);
15
- if (entryIndex < 0) entryIndex = candles.findIndex(c => c.timestamp >= trade.timestamp);
16
- if (entryIndex <= 0) continue;
17
- // Trade timestamps represent the next candle's entry. Classify the regime
18
- // using the signal candle immediately before entry to avoid look-ahead.
19
- const regime = classifyRegime(candles, entryIndex - 1);
20
- buckets.set(regime, [...(buckets.get(regime) ?? []), trade.pnlR]);
21
- }
22
- const allRegimes: MarketRegime[] = ['TREND_BULL', 'TREND_BEAR', 'RANGE', 'HIGH_VOLATILITY', 'LOW_VOLATILITY', 'NEUTRAL'];
23
- const result = allRegimes.map(regime => { const values = buckets.get(regime) ?? []; const wins = values.filter(v => v > 0).length; const losses = values.filter(v => v < 0).length; const grossWin = values.filter(v => v > 0).reduce((a, b) => a + b, 0); const grossLoss = Math.abs(values.filter(v => v < 0).reduce((a, b) => a + b, 0)); return { regime, trades: values.length, wins, losses, winRatePercent: round(values.length ? wins / values.length * 100 : 0, 2), netR: round(values.reduce((a, b) => a + b, 0)), expectancyR: round(mean(values)), profitFactor: round(grossLoss > 0 ? grossWin / grossLoss : grossWin > 0 ? Infinity : 0), maxDrawdownR: round(drawdownR(values)) }; });
24
- const active = result.filter(x => x.trades > 0); const strongest = active.filter(x => x.trades >= 5).sort((a, b) => b.expectancyR - a.expectancyR)[0]?.regime ?? null; const weakest = active.filter(x => x.trades >= 5).sort((a, b) => a.expectancyR - b.expectancyR)[0]?.regime ?? null; const dominant = active.sort((a, b) => b.trades - a.trades)[0]?.regime ?? 'NEUTRAL'; const warnings: string[] = [];
25
- for (const bucket of active) { if (bucket.trades < 5) warnings.push(`${bucket.regime}: amostra pequena (${bucket.trades} trades).`); if (bucket.expectancyR < 0) warnings.push(`${bucket.regime}: expectancy negativa.`); }
26
- return { buckets: result, dominantRegime: dominant, strongestRegime: strongest, weakestRegime: weakest, warnings };
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/robustParameterSelection.ts DELETED
@@ -1,47 +0,0 @@
1
- import type { HistoricalBacktestOptions, HistoricalBacktestResult } from '../backtest/historicalBacktest.js';
2
- import type { Candle } from '../../src/types.js';
3
- import { runHistoricalBacktest } from '../backtest/historicalBacktest.js';
4
-
5
- export interface ParameterCandidate { id: string; minScore: number; minConfidence: number; atrStopMultiple: number; rewardRisk: number; maxHoldingBars: number; trades: number; expectancyR: number; profitFactor: number; netProfitPercent: number; maxDrawdownPercent: number; stabilityScore: number; rankScore: number; }
6
- export interface RobustParameterSelectionResult { candidates: ParameterCandidate[]; selected: ParameterCandidate | null; baseline: ParameterCandidate | null; stableRegion: { minScore: number[]; minConfidence: number[]; atrStopMultiple: number[]; rewardRisk: number[]; maxHoldingBars: number[] }; grade: 'ROBUST' | 'PROMISING' | 'FRAGILE' | 'INSUFFICIENT_DATA'; warnings: string[]; }
7
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
8
- const pf = (r: HistoricalBacktestResult) => Number.isFinite(r.profitFactor) ? r.profitFactor : r.profitFactor > 0 ? 99 : 0;
9
- function score(r: HistoricalBacktestResult): number { if (r.totalTrades < 20) return 0; const expectancy = Math.max(-1, Math.min(2, r.expectancyR)); const profitFactor = Math.max(0, Math.min(3, pf(r))); const ddPenalty = Math.max(0, Math.min(1, r.maxDrawdownPercent / 20)); const sampleBonus = Math.min(1, r.totalTrades / 100); return round(Math.max(0, expectancy / 2) * 35 + Math.min(1, profitFactor / 2) * 30 + (1 - ddPenalty) * 20 + sampleBonus * 15, 2); }
10
- function candidate(id: string, options: Omit<HistoricalBacktestOptions, 'symbol' | 'candles'>, r: HistoricalBacktestResult): ParameterCandidate { const rankScore = score(r); return { id, minScore: options.minScore ?? 35, minConfidence: options.minConfidence ?? 50, atrStopMultiple: options.atrStopMultiple ?? 1.5, rewardRisk: options.rewardRisk ?? 2, maxHoldingBars: options.maxHoldingBars ?? 32, trades: r.totalTrades, expectancyR: round(r.expectancyR), profitFactor: round(pf(r)), netProfitPercent: round(r.netProfitPercent, 2), maxDrawdownPercent: round(r.maxDrawdownPercent, 2), stabilityScore: rankScore, rankScore }; }
11
- function configKey(config: [number, number, number, number, number]) { return config.join('-'); }
12
-
13
- /** Bounded local search: 15 strategically spaced configurations instead of the previous 31 full backtests. */
14
- export function selectRobustParameters(symbol: string, candles: Candle[], baseOptions: Omit<HistoricalBacktestOptions, 'symbol' | 'candles'> = {}): RobustParameterSelectionResult {
15
- const base: [number, number, number, number, number] = [baseOptions.minScore ?? 35, baseOptions.minConfidence ?? 50, baseOptions.atrStopMultiple ?? 1.5, baseOptions.rewardRisk ?? 2, baseOptions.maxHoldingBars ?? 32];
16
- const configs: Array<[number, number, number, number, number]> = [base];
17
- const add = (config: [number, number, number, number, number]) => { if (!configs.some(existing => configKey(existing) === configKey(config))) configs.push(config); };
18
- add([30, base[1], base[2], base[3], base[4]]); add([40, base[1], base[2], base[3], base[4]]);
19
- add([base[0], 45, base[2], base[3], base[4]]); add([base[0], 55, base[2], base[3], base[4]]);
20
- add([base[0], base[1], 1.25, base[3], base[4]]); add([base[0], base[1], 1.75, base[3], base[4]]);
21
- add([base[0], base[1], base[2], 1.75, base[4]]); add([base[0], base[1], base[2], 2.25, base[4]]);
22
- add([base[0], base[1], base[2], base[3], 24]); add([base[0], base[1], base[2], base[3], 40]);
23
- add([30, 45, 1.25, 1.75, 24]); add([40, 55, 1.75, 2.25, 40]); add([30, 55, 1.75, 2.25, 32]); add([40, 45, 1.25, 2, 40]);
24
-
25
- const candidates: ParameterCandidate[] = [];
26
- for (const [s, c, a, r, h] of configs) {
27
- const options = { ...baseOptions, minScore: s, minConfidence: c, atrStopMultiple: a, rewardRisk: r, maxHoldingBars: h };
28
- candidates.push(candidate(`${s}-${c}-${a}-${r}-${h}`, options, runHistoricalBacktest({ ...options, symbol, candles })));
29
- }
30
- const baseline = candidates.find(c => c.minScore === base[0] && c.minConfidence === base[1] && c.atrStopMultiple === base[2] && c.rewardRisk === base[3] && c.maxHoldingBars === base[4]) ?? null;
31
- for (const c of candidates) {
32
- const nearby = candidates.filter(n => { const distance = Math.abs(n.minScore - c.minScore) / 10 + Math.abs(n.minConfidence - c.minConfidence) / 10 + Math.abs(n.atrStopMultiple - c.atrStopMultiple) / 0.25 + Math.abs(n.rewardRisk - c.rewardRisk) / 0.25 + Math.abs(n.maxHoldingBars - c.maxHoldingBars) / 8; return distance <= 2.01 && n.trades >= 30; });
33
- const viable = nearby.filter(n => n.expectancyR > 0 && n.profitFactor > 1).length;
34
- c.stabilityScore = round(c.rankScore * 0.7 + (nearby.length ? viable / nearby.length * 100 : 0) * 0.3, 2);
35
- }
36
- const eligible = candidates.filter(c => c.trades >= 30 && c.expectancyR > 0 && c.profitFactor > 1);
37
- eligible.sort((a, b) => b.stabilityScore - a.stabilityScore || b.rankScore - a.rankScore || b.trades - a.trades);
38
- const selected = eligible[0] ?? null;
39
- const stable = candidates.filter(c => selected && Math.abs(c.stabilityScore - selected.stabilityScore) <= 8 && c.trades >= 30 && c.expectancyR > 0 && c.profitFactor > 1);
40
- const warnings: string[] = [];
41
- if (selected && baseline && selected.id !== baseline.id) warnings.push('A configuração selecionada difere do baseline. Validar em OOS antes de qualquer uso operacional.');
42
- if (candles.length < 1500) warnings.push('Histórico curto para otimização robusta; aumentar a janela antes de concluir sobre parâmetros.');
43
- if (!selected) warnings.push('Nenhuma configuração atingiu os critérios mínimos de robustez.');
44
- warnings.push('Busca compacta de parâmetros: validação OOS e estabilidade regional continuam obrigatórias.');
45
- const grade = selected === null ? 'INSUFFICIENT_DATA' : stable.length >= 7 ? 'ROBUST' : stable.length >= 3 ? 'PROMISING' : 'FRAGILE';
46
- return { candidates: candidates.sort((a, b) => b.stabilityScore - a.stabilityScore).slice(0, 30), selected, baseline, stableRegion: { minScore: [...new Set(stable.map(c => c.minScore))].sort((a, b) => a - b), minConfidence: [...new Set(stable.map(c => c.minConfidence))].sort((a, b) => a - b), atrStopMultiple: [...new Set(stable.map(c => c.atrStopMultiple))].sort((a, b) => a - b), rewardRisk: [...new Set(stable.map(c => c.rewardRisk))].sort((a, b) => a - b), maxHoldingBars: [...new Set(stable.map(c => c.maxHoldingBars))].sort((a, b) => a - b) }, grade, warnings };
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/robustnessAnalysis.ts DELETED
@@ -1,82 +0,0 @@
1
- import type { HistoricalBacktestTrade } from '../backtest/historicalBacktest.js';
2
-
3
- export interface RobustnessAnalysisResult {
4
- sampleSize: number;
5
- positiveExpectancy: boolean;
6
- profitFactorAboveOne: boolean;
7
- bootstrapExpectancy95: { low: number; high: number } | null;
8
- stabilityScore: number;
9
- grade: 'ROBUST' | 'MODERATE' | 'FRAGILE' | 'INSUFFICIENT_DATA';
10
- checks: string[];
11
- warnings: string[];
12
- }
13
-
14
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
15
-
16
- function mean(values: number[]): number {
17
- return values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0;
18
- }
19
-
20
- function seededRandom(seed: number): () => number {
21
- let state = seed >>> 0;
22
- return () => {
23
- state = (1664525 * state + 1013904223) >>> 0;
24
- return state / 4294967296;
25
- };
26
- }
27
-
28
- function bootstrap(values: number[], iterations = 2000, seed = 20260908): { low: number; high: number } | null {
29
- if (values.length < 30) return null;
30
- const random = seededRandom(seed + values.length);
31
- const samples = new Array<number>(iterations);
32
- for (let i = 0; i < iterations; i++) {
33
- let total = 0;
34
- for (let j = 0; j < values.length; j++) total += values[Math.floor(random() * values.length)];
35
- samples[i] = total / values.length;
36
- }
37
- samples.sort((a, b) => a - b);
38
- return { low: samples[Math.floor(iterations * 0.025)], high: samples[Math.floor(iterations * 0.975)] };
39
- }
40
-
41
- export function analyzeRobustness(trades: HistoricalBacktestTrade[]): RobustnessAnalysisResult {
42
- const values = trades.map(t => t.pnlR).filter(Number.isFinite);
43
- if (values.length < 30) {
44
- return {
45
- sampleSize: values.length,
46
- positiveExpectancy: mean(values) > 0,
47
- profitFactorAboveOne: false,
48
- bootstrapExpectancy95: null,
49
- stabilityScore: 0,
50
- grade: 'INSUFFICIENT_DATA',
51
- checks: [],
52
- warnings: ['São necessários pelo menos 30 trades para a análise de robustez.'],
53
- };
54
- }
55
- const wins = values.filter(v => v > 0).reduce((a, b) => a + b, 0);
56
- const losses = Math.abs(values.filter(v => v < 0).reduce((a, b) => a + b, 0));
57
- const pf = losses > 0 ? wins / losses : Infinity;
58
- const expectancy = mean(values);
59
- const bootstrapCi = bootstrap(values);
60
- const checks: string[] = [];
61
- const warnings: string[] = [];
62
- let score = 0;
63
- if (expectancy > 0) { score += 25; checks.push('Expectancy histórica positiva.'); } else warnings.push('Expectancy histórica negativa.');
64
- if (pf > 1.2) { score += 25; checks.push('Profit Factor acima de 1.20.'); } else warnings.push('Profit Factor não supera 1.20.');
65
- if (bootstrapCi && bootstrapCi.low > 0) { score += 30; checks.push('Bootstrap 95% da expectancy permanece acima de zero.'); } else warnings.push('Bootstrap 95% ainda inclui expectancy não positiva.');
66
- const median = [...values].sort((a, b) => a - b)[Math.floor(values.length / 2)];
67
- if (Math.sign(expectancy) === Math.sign(median) && median > 0) { score += 10; checks.push('Mediana e média possuem sinal positivo.'); } else warnings.push('Distribuição não confirma claramente a vantagem pela mediana.');
68
- const firstHalf = mean(values.slice(0, Math.floor(values.length / 2)));
69
- const secondHalf = mean(values.slice(Math.floor(values.length / 2)));
70
- if (firstHalf > 0 && secondHalf > 0) { score += 10; checks.push('Expectancy positiva nas duas metades da amostra.'); } else warnings.push('Uma das metades da amostra apresenta expectancy não positiva.');
71
- const grade = score >= 80 ? 'ROBUST' : score >= 55 ? 'MODERATE' : 'FRAGILE';
72
- return {
73
- sampleSize: values.length,
74
- positiveExpectancy: expectancy > 0,
75
- profitFactorAboveOne: pf > 1,
76
- bootstrapExpectancy95: bootstrapCi ? { low: round(bootstrapCi.low), high: round(bootstrapCi.high) } : null,
77
- stabilityScore: score,
78
- grade,
79
- checks,
80
- warnings,
81
- };
82
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/statisticalAnalysis.ts DELETED
@@ -1,137 +0,0 @@
1
- import type { HistoricalBacktestTrade } from '../backtest/historicalBacktest.js';
2
-
3
- export interface DirectionBucket {
4
- direction: 'LONG' | 'SHORT' | 'UNKNOWN';
5
- trades: number;
6
- wins: number;
7
- losses: number;
8
- winRatePercent: number;
9
- netR: number;
10
- expectancyR: number;
11
- medianR: number;
12
- stdDevR: number;
13
- profitFactor: number;
14
- payoffRatio: number;
15
- maxConsecutiveLosses: number;
16
- maxConsecutiveWins: number;
17
- }
18
-
19
- export interface StatisticalAnalysisResult {
20
- sampleSize: number;
21
- meanR: number;
22
- medianR: number;
23
- stdDevR: number;
24
- standardErrorR: number;
25
- expectancyCi95R: { low: number; high: number } | null;
26
- positiveTradeRatePercent: number;
27
- payoffRatio: number;
28
- maxConsecutiveLosses: number;
29
- maxConsecutiveWins: number;
30
- directions: DirectionBucket[];
31
- warnings: string[];
32
- }
33
-
34
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
35
-
36
- function median(values: number[]): number {
37
- if (!values.length) return 0;
38
- const sorted = [...values].sort((a, b) => a - b);
39
- const mid = Math.floor(sorted.length / 2);
40
- return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
41
- }
42
-
43
- function stats(values: number[]) {
44
- const mean = values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0;
45
- const variance = values.length > 1
46
- ? values.reduce((sum, value) => sum + (value - mean) ** 2, 0) / (values.length - 1)
47
- : 0;
48
- const wins = values.filter(v => v > 0);
49
- const losses = values.filter(v => v < 0);
50
- const grossWin = wins.reduce((a, b) => a + b, 0);
51
- const grossLoss = Math.abs(losses.reduce((a, b) => a + b, 0));
52
- return {
53
- mean,
54
- median: median(values),
55
- stdDev: Math.sqrt(variance),
56
- wins: wins.length,
57
- losses: losses.length,
58
- profitFactor: grossLoss > 0 ? grossWin / grossLoss : grossWin > 0 ? Infinity : 0,
59
- payoffRatio: wins.length && losses.length ? (grossWin / wins.length) / (grossLoss / losses.length) : 0,
60
- };
61
- }
62
-
63
- function streak(values: number[]) {
64
- let wins = 0, losses = 0, maxWins = 0, maxLosses = 0;
65
- for (const value of values) {
66
- if (value > 0) { wins++; losses = 0; maxWins = Math.max(maxWins, wins); }
67
- else if (value < 0) { losses++; wins = 0; maxLosses = Math.max(maxLosses, losses); }
68
- else { wins = 0; losses = 0; }
69
- }
70
- return { maxWins, maxLosses };
71
- }
72
-
73
- function directionOf(trade: HistoricalBacktestTrade): DirectionBucket['direction'] {
74
- const candidate = trade as HistoricalBacktestTrade & { direction?: string; side?: string; signal?: { direction?: string } };
75
- const value = String(candidate.direction ?? candidate.side ?? candidate.signal?.direction ?? '').toUpperCase();
76
- if (value.includes('LONG') || value === 'BUY') return 'LONG';
77
- if (value.includes('SHORT') || value === 'SELL') return 'SHORT';
78
- return 'UNKNOWN';
79
- }
80
-
81
- function directionBucket(direction: DirectionBucket['direction'], trades: HistoricalBacktestTrade[]): DirectionBucket {
82
- const values = trades.map(t => t.pnlR);
83
- const s = stats(values);
84
- const streaks = streak(values);
85
- return {
86
- direction,
87
- trades: values.length,
88
- wins: s.wins,
89
- losses: s.losses,
90
- winRatePercent: round(values.length ? s.wins / values.length * 100 : 0, 2),
91
- netR: round(values.reduce((a, b) => a + b, 0)),
92
- expectancyR: round(s.mean),
93
- medianR: round(s.median),
94
- stdDevR: round(s.stdDev),
95
- profitFactor: round(s.profitFactor),
96
- payoffRatio: round(s.payoffRatio),
97
- maxConsecutiveLosses: streaks.maxLosses,
98
- maxConsecutiveWins: streaks.maxWins,
99
- };
100
- }
101
-
102
- export function analyzeStatistics(trades: HistoricalBacktestTrade[]): StatisticalAnalysisResult {
103
- const values = trades.map(t => t.pnlR).filter(Number.isFinite);
104
- const s = stats(values);
105
- const streaks = streak(values);
106
- const standardError = values.length > 1 ? s.stdDev / Math.sqrt(values.length) : 0;
107
- const ci = values.length >= 30 ? { low: s.mean - 1.96 * standardError, high: s.mean + 1.96 * standardError } : null;
108
- const groups = new Map<DirectionBucket['direction'], HistoricalBacktestTrade[]>();
109
- for (const trade of trades) {
110
- const direction = directionOf(trade);
111
- groups.set(direction, [...(groups.get(direction) ?? []), trade]);
112
- }
113
- const directions = (['LONG', 'SHORT', 'UNKNOWN'] as const).map(direction => directionBucket(direction, groups.get(direction) ?? []));
114
- const warnings: string[] = [];
115
- if (values.length < 30) warnings.push(`Amostra estatística pequena: ${values.length} trades.`);
116
- if (values.length >= 30 && s.mean <= 0) warnings.push('Intervalo de confiança da expectancy não parte de uma média positiva.');
117
- if (s.stdDev > Math.abs(s.mean) * 3 && values.length >= 30) warnings.push('Alta dispersão dos resultados em relação à expectancy.');
118
- if (streaks.maxLosses >= 6) warnings.push(`Sequência máxima de ${streaks.maxLosses} perdas consecutivas.`);
119
- for (const bucket of directions.filter(d => d.trades > 0)) {
120
- if (bucket.trades < 20) warnings.push(`${bucket.direction}: amostra abaixo de 20 trades.`);
121
- if (bucket.expectancyR < 0) warnings.push(`${bucket.direction}: expectancy negativa.`);
122
- }
123
- return {
124
- sampleSize: values.length,
125
- meanR: round(s.mean),
126
- medianR: round(s.median),
127
- stdDevR: round(s.stdDev),
128
- standardErrorR: round(standardError),
129
- expectancyCi95R: ci ? { low: round(ci.low), high: round(ci.high) } : null,
130
- positiveTradeRatePercent: round(values.length ? s.wins / values.length * 100 : 0, 2),
131
- payoffRatio: round(s.payoffRatio),
132
- maxConsecutiveLosses: streaks.maxLosses,
133
- maxConsecutiveWins: streaks.maxWins,
134
- directions,
135
- warnings,
136
- };
137
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/stressTest.ts DELETED
@@ -1,60 +0,0 @@
1
- import type { HistoricalBacktestOptions, HistoricalBacktestResult } from '../backtest/historicalBacktest.js';
2
- import { runHistoricalBacktest } from '../backtest/historicalBacktest.js';
3
- import type { Candle } from '../../src/types.js';
4
-
5
- export interface StressScenario {
6
- name: string;
7
- feeBpsPerSide: number;
8
- slippageBpsPerSide: number;
9
- latencySlippageBpsPerSide: number;
10
- fundingRatePer8h: number;
11
- result: { trades: number; netProfitPercent: number; expectancyR: number; profitFactor: number; maxDrawdownPercent: number; winRatePercent: number };
12
- deltaFromBase: { netProfitPercent: number; expectancyR: number; maxDrawdownPercent: number };
13
- status: 'PASS' | 'DEGRADED' | 'FAIL' | 'INSUFFICIENT_DATA';
14
- }
15
-
16
- export interface StressTestResult {
17
- base: StressScenario;
18
- scenarios: StressScenario[];
19
- passedScenarios: number;
20
- grade: 'RESILIENT' | 'MODERATE' | 'FRAGILE' | 'INSUFFICIENT_DATA';
21
- warnings: string[];
22
- }
23
-
24
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
25
- const displayProfitFactor = (v: number) => Number.isFinite(v) ? round(v) : v > 0 ? 99 : 0;
26
-
27
- function summarize(result: HistoricalBacktestResult, fee: number, slippage: number, latency: number, funding: number, base?: HistoricalBacktestResult): StressScenario {
28
- const netDelta = base ? result.netProfitPercent - base.netProfitPercent : 0;
29
- const expDelta = base ? result.expectancyR - base.expectancyR : 0;
30
- const ddDelta = base ? result.maxDrawdownPercent - base.maxDrawdownPercent : 0;
31
- const enough = result.totalTrades >= 30;
32
- const positive = result.expectancyR > 0 && result.profitFactor > 1;
33
- const baseExpectancy = base?.expectancyR ?? result.expectancyR;
34
- const expectancyThreshold = baseExpectancy > 0 ? baseExpectancy * 0.7 : 0;
35
- const status = !enough ? 'INSUFFICIENT_DATA' : !positive ? 'FAIL' : result.expectancyR >= expectancyThreshold ? 'PASS' : 'DEGRADED';
36
- return { name: `${fee}bps fee / ${slippage}bps slip / ${latency}bps latency / ${(funding * 100).toFixed(3)}% funding/8h`, feeBpsPerSide: fee, slippageBpsPerSide: slippage, latencySlippageBpsPerSide: latency, fundingRatePer8h: funding, result: { trades: result.totalTrades, netProfitPercent: round(result.netProfitPercent, 2), expectancyR: round(result.expectancyR), profitFactor: displayProfitFactor(result.profitFactor), maxDrawdownPercent: round(result.maxDrawdownPercent, 2), winRatePercent: round(result.winRate, 2) }, deltaFromBase: { netProfitPercent: round(netDelta, 2), expectancyR: round(expDelta), maxDrawdownPercent: round(ddDelta, 2) }, status };
37
- }
38
-
39
- export function runStressTest(symbol: string, candles: Candle[], baseOptions: Omit<HistoricalBacktestOptions, 'symbol' | 'candles' | 'feeBpsPerSide' | 'slippageBpsPerSide' | 'latencySlippageBpsPerSide' | 'fundingRatePer8h'> = {}): StressTestResult {
40
- const baseResult = runHistoricalBacktest({ ...baseOptions, symbol, candles, feeBpsPerSide: 5, slippageBpsPerSide: 2, latencySlippageBpsPerSide: 1, fundingRatePer8h: 0.0001 });
41
- const scenarios = [
42
- [7, 3, 1, 0.0001],
43
- [10, 5, 2, 0.0002],
44
- [15, 8, 3, 0.0003],
45
- [20, 10, 5, 0.0005],
46
- ].map(([fee, slippage, latency, funding]) => summarize(
47
- runHistoricalBacktest({ ...baseOptions, symbol, candles, feeBpsPerSide: fee, slippageBpsPerSide: slippage, latencySlippageBpsPerSide: latency, fundingRatePer8h: funding }),
48
- fee, slippage, latency, funding, baseResult,
49
- ));
50
- const base = summarize(baseResult, 5, 2, 1, 0.0001);
51
- const passed = scenarios.filter(s => s.status === 'PASS').length;
52
- const evaluated = scenarios.filter(s => s.status !== 'INSUFFICIENT_DATA').length;
53
- const warnings: string[] = [];
54
- if (base.result.trades < 30) warnings.push('Amostra base inferior a 30 trades.');
55
- if (evaluated < scenarios.length) warnings.push('Alguns cenários não possuem amostra suficiente.');
56
- if (scenarios.some(s => s.status === 'FAIL')) warnings.push('A estratégia perde expectancy positiva em pelo menos um cenário de custos.');
57
- if (scenarios.some(s => s.result.maxDrawdownPercent > 15)) warnings.push('Drawdown acima de 15% em cenário de stress.');
58
- const grade = base.result.trades < 30 ? 'INSUFFICIENT_DATA' : passed === scenarios.length ? 'RESILIENT' : passed >= 2 ? 'MODERATE' : 'FRAGILE';
59
- return { base, scenarios, passedScenarios: passed, grade, warnings };
60
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/timeSeriesAnalysis.ts DELETED
@@ -1,64 +0,0 @@
1
- import type { HistoricalBacktestTrade } from '../backtest/historicalBacktest.js';
2
-
3
- export interface TimeBucket {
4
- period: string;
5
- trades: number;
6
- netR: number;
7
- expectancyR: number;
8
- winRatePercent: number;
9
- profitFactor: number;
10
- }
11
-
12
- export interface TimeSeriesAnalysisResult {
13
- periods: TimeBucket[];
14
- positivePeriods: number;
15
- negativePeriods: number;
16
- consistencyPercent: number;
17
- bestPeriod: string | null;
18
- worstPeriod: string | null;
19
- warnings: string[];
20
- }
21
-
22
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
23
-
24
- function periodKey(timestamp: number): string {
25
- const date = new Date(timestamp);
26
- return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}`;
27
- }
28
-
29
- export function analyzeTimeSeries(trades: HistoricalBacktestTrade[]): TimeSeriesAnalysisResult {
30
- const groups = new Map<string, HistoricalBacktestTrade[]>();
31
- for (const trade of trades) {
32
- const key = periodKey(trade.timestamp);
33
- groups.set(key, [...(groups.get(key) ?? []), trade]);
34
- }
35
- const periods = [...groups.entries()].sort((a, b) => a[0].localeCompare(b[0])).map(([period, items]) => {
36
- const values = items.map(t => t.pnlR).filter(Number.isFinite);
37
- const wins = values.filter(v => v > 0).reduce((a, b) => a + b, 0);
38
- const losses = Math.abs(values.filter(v => v < 0).reduce((a, b) => a + b, 0));
39
- return {
40
- period,
41
- trades: values.length,
42
- netR: round(values.reduce((a, b) => a + b, 0)),
43
- expectancyR: round(values.length ? values.reduce((a, b) => a + b, 0) / values.length : 0),
44
- winRatePercent: round(values.length ? values.filter(v => v > 0).length / values.length * 100 : 0, 2),
45
- profitFactor: round(losses > 0 ? wins / losses : wins > 0 ? Infinity : 0),
46
- };
47
- });
48
- const active = periods.filter(p => p.trades > 0);
49
- const positive = active.filter(p => p.netR > 0).length;
50
- const negative = active.filter(p => p.netR < 0).length;
51
- const warnings: string[] = [];
52
- if (active.length < 3) warnings.push('Menos de três períodos disponíveis para avaliar estabilidade temporal.');
53
- for (const period of active) if (period.trades < 10) warnings.push(`${period.period}: amostra pequena (${period.trades} trades).`);
54
- if (active.length && positive / active.length < 0.5) warnings.push('Menos da metade dos períodos apresentou resultado líquido positivo.');
55
- return {
56
- periods,
57
- positivePeriods: positive,
58
- negativePeriods: negative,
59
- consistencyPercent: round(active.length ? positive / active.length * 100 : 0, 2),
60
- bestPeriod: active.length ? [...active].sort((a, b) => b.expectancyR - a.expectancyR)[0].period : null,
61
- worstPeriod: active.length ? [...active].sort((a, b) => a.expectancyR - b.expectancyR)[0].period : null,
62
- warnings,
63
- };
64
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/quant/walkForwardAnalysis.ts DELETED
@@ -1,113 +0,0 @@
1
- import type { HistoricalBacktestOptions, HistoricalBacktestResult } from '../backtest/historicalBacktest.js';
2
- import { runHistoricalBacktest } from '../backtest/historicalBacktest.js';
3
- import { selectRobustParameters } from './robustParameterSelection.js';
4
- import type { Candle } from '../../src/types.js';
5
-
6
- export interface WalkForwardWindow {
7
- index: number;
8
- trainStart: string;
9
- trainEnd: string;
10
- testStart: string;
11
- testEnd: string;
12
- trainTrades: number;
13
- testTrades: number;
14
- trainExpectancyR: number;
15
- testExpectancyR: number;
16
- trainNetProfitPercent: number;
17
- testNetProfitPercent: number;
18
- testProfitFactor: number;
19
- testMaxDrawdownPercent: number;
20
- selectedParameters: { minScore: number; minConfidence: number; atrStopMultiple: number; rewardRisk: number; maxHoldingBars: number } | null;
21
- status: 'PASS' | 'FAIL' | 'INSUFFICIENT_DATA';
22
- }
23
-
24
- export interface WalkForwardAnalysisResult {
25
- windows: WalkForwardWindow[];
26
- trainBars: number;
27
- testBars: number;
28
- stepBars: number;
29
- adaptive: boolean;
30
- outOfSampleTrades: number;
31
- outOfSampleExpectancyR: number;
32
- outOfSampleNetProfitPercent: number;
33
- outOfSampleWinRatePercent: number;
34
- outOfSampleProfitFactor: number;
35
- outOfSampleMaxDrawdownPercent: number;
36
- passedWindows: number;
37
- consistencyPercent: number;
38
- grade: 'ROBUST' | 'PROMISING' | 'FRAGILE' | 'INSUFFICIENT_DATA';
39
- warnings: string[];
40
- }
41
-
42
- const round = (v: number, d = 4) => Number((Number.isFinite(v) ? v : 0).toFixed(d));
43
- type BacktestParams = Omit<HistoricalBacktestOptions, 'symbol' | 'candles'>;
44
-
45
- function runWindow(symbol: string, candles: Candle[], base: BacktestParams): HistoricalBacktestResult {
46
- return runHistoricalBacktest({ ...base, symbol, candles });
47
- }
48
-
49
- function calculateOosMetrics(trades: HistoricalBacktestResult['trades'], riskPerTradePercent: number) {
50
- let equity = 100;
51
- let peak = equity;
52
- let maxDrawdownPercent = 0;
53
- const riskFraction = Math.max(0.0001, riskPerTradePercent) / 100;
54
- for (const trade of trades) {
55
- equity *= Math.max(0, 1 + trade.pnlR * riskFraction);
56
- peak = Math.max(peak, equity);
57
- if (peak > 0) maxDrawdownPercent = Math.max(maxDrawdownPercent, (peak - equity) / peak * 100);
58
- }
59
- const wins = trades.filter(t => t.pnlR > 0).reduce((a, t) => a + t.pnlR, 0);
60
- const losses = Math.abs(trades.filter(t => t.pnlR < 0).reduce((a, t) => a + t.pnlR, 0));
61
- return { netProfitPercent: equity - 100, maxDrawdownPercent, profitFactor: losses > 0 ? wins / losses : wins > 0 ? Infinity : 0 };
62
- }
63
-
64
- /** Adaptive rolling walk-forward: each train window selects robust parameters and only then applies them to the following test window. */
65
- export function runWalkForwardAnalysis(symbol: string, candles: Candle[], baseOptions: BacktestParams = {}, trainBars = 2_000, testBars = 500, stepBars = 500): WalkForwardAnalysisResult {
66
- const sorted = [...candles].sort((a, b) => a.timestamp - b.timestamp);
67
- const windows: WalkForwardWindow[] = [];
68
- const allTestTrades: HistoricalBacktestResult['trades'] = [];
69
- let windowIndex = 0;
70
-
71
- for (let trainStart = 0; trainStart + trainBars + testBars <= sorted.length; trainStart += stepBars) {
72
- const train = sorted.slice(trainStart, trainStart + trainBars);
73
- const test = sorted.slice(trainStart + trainBars, trainStart + trainBars + testBars);
74
- const selection = selectRobustParameters(symbol, train, baseOptions);
75
- const selected = selection.selected;
76
- const trainResult = selected
77
- ? runWindow(symbol, train, { ...baseOptions, minScore: selected.minScore, minConfidence: selected.minConfidence, atrStopMultiple: selected.atrStopMultiple, rewardRisk: selected.rewardRisk, maxHoldingBars: selected.maxHoldingBars })
78
- : runWindow(symbol, train, baseOptions);
79
- const testResult = selected
80
- ? runWindow(symbol, test, { ...baseOptions, minScore: selected.minScore, minConfidence: selected.minConfidence, atrStopMultiple: selected.atrStopMultiple, rewardRisk: selected.rewardRisk, maxHoldingBars: selected.maxHoldingBars })
81
- : runWindow(symbol, test, baseOptions);
82
- const enough = !!selected && trainResult.totalTrades >= 20 && testResult.totalTrades >= 10;
83
- const pass = enough && testResult.expectancyR > 0 && testResult.profitFactor > 1;
84
- windows.push({
85
- index: windowIndex++, trainStart: trainResult.startDate, trainEnd: trainResult.endDate, testStart: testResult.startDate, testEnd: testResult.endDate,
86
- trainTrades: trainResult.totalTrades, testTrades: testResult.totalTrades, trainExpectancyR: round(trainResult.expectancyR), testExpectancyR: round(testResult.expectancyR),
87
- trainNetProfitPercent: round(trainResult.netProfitPercent, 2), testNetProfitPercent: round(testResult.netProfitPercent, 2), testProfitFactor: round(testResult.profitFactor),
88
- testMaxDrawdownPercent: round(testResult.maxDrawdownPercent, 2),
89
- selectedParameters: selected ? { minScore: selected.minScore, minConfidence: selected.minConfidence, atrStopMultiple: selected.atrStopMultiple, rewardRisk: selected.rewardRisk, maxHoldingBars: selected.maxHoldingBars } : null,
90
- status: !enough ? 'INSUFFICIENT_DATA' : pass ? 'PASS' : 'FAIL',
91
- });
92
- allTestTrades.push(...testResult.trades);
93
- }
94
-
95
- const wins = allTestTrades.filter(t => t.pnlR > 0);
96
- const losses = allTestTrades.filter(t => t.pnlR < 0);
97
- const grossWin = wins.reduce((a, t) => a + t.pnlR, 0);
98
- const grossLoss = Math.abs(losses.reduce((a, t) => a + t.pnlR, 0));
99
- const oosExpectancy = allTestTrades.length ? allTestTrades.reduce((a, t) => a + t.pnlR, 0) / allTestTrades.length : 0;
100
- const passed = windows.filter(w => w.status === 'PASS').length;
101
- const evaluated = windows.filter(w => w.status !== 'INSUFFICIENT_DATA').length;
102
- const oosMetrics = calculateOosMetrics(allTestTrades, baseOptions.riskPerTradePercent ?? 1);
103
- const consistency = evaluated ? passed / evaluated * 100 : 0;
104
- const warnings: string[] = [];
105
- if (windows.length < 3) warnings.push('Menos de três janelas walk-forward disponíveis.');
106
- if (allTestTrades.length < 30) warnings.push(`Amostra OOS pequena: ${allTestTrades.length} trades.`);
107
- if (evaluated && consistency < 60) warnings.push('Menos de 60% das janelas adaptativas foram positivas.');
108
- if (oosExpectancy <= 0) warnings.push('Expectancy agregada OOS não é positiva.');
109
- if (windows.some(w => w.selectedParameters === null)) warnings.push('Uma ou mais janelas não encontraram parâmetros robustos no treino.');
110
- const grade = allTestTrades.length < 30 || evaluated < 3 ? 'INSUFFICIENT_DATA' : consistency >= 75 && oosExpectancy > 0 ? 'ROBUST' : consistency >= 60 && oosExpectancy > 0 ? 'PROMISING' : 'FRAGILE';
111
-
112
- return { windows, trainBars, testBars, stepBars, adaptive: true, outOfSampleTrades: allTestTrades.length, outOfSampleExpectancyR: round(oosExpectancy), outOfSampleNetProfitPercent: round(oosMetrics.netProfitPercent, 2), outOfSampleWinRatePercent: round(allTestTrades.length ? wins.length / allTestTrades.length * 100 : 0, 2), outOfSampleProfitFactor: round(grossLoss > 0 ? grossWin / grossLoss : grossWin > 0 ? Infinity : 0), outOfSampleMaxDrawdownPercent: round(oosMetrics.maxDrawdownPercent, 2), passedWindows: passed, consistencyPercent: round(consistency, 2), grade, warnings };
113
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/realMarketData.ts DELETED
@@ -1,1018 +0,0 @@
1
- import {
2
- TradeSignal,
3
- Candle,
4
- SignalDecision,
5
- WyckoffPhase,
6
- DailyBacktestMetrics,
7
- DailyBacktestTrade,
8
- FibonacciTarget
9
- } from '../src/types.js';
10
-
11
- export interface MonitoredCoinConfig {
12
- symbol: string;
13
- binanceSymbol: string;
14
- okxInstId: string;
15
- coinbaseProduct?: string;
16
- name: string;
17
- decimals: number;
18
- }
19
-
20
- export const MONITORED_PAIRS: MonitoredCoinConfig[] = [
21
- { symbol: 'BTC/USDT', binanceSymbol: 'BTCUSDT', okxInstId: 'BTC-USDT-SWAP', coinbaseProduct: 'BTC-USD', name: 'Bitcoin', decimals: 1 },
22
- { symbol: 'ETH/USDT', binanceSymbol: 'ETHUSDT', okxInstId: 'ETH-USDT-SWAP', coinbaseProduct: 'ETH-USD', name: 'Ethereum', decimals: 2 },
23
- { symbol: 'SOL/USDT', binanceSymbol: 'SOLUSDT', okxInstId: 'SOL-USDT-SWAP', coinbaseProduct: 'SOL-USD', name: 'Solana', decimals: 2 },
24
- { symbol: 'BNB/USDT', binanceSymbol: 'BNBUSDT', okxInstId: 'BNB-USDT-SWAP', name: 'BNB Chain', decimals: 2 },
25
- { symbol: 'XRP/USDT', binanceSymbol: 'XRPUSDT', okxInstId: 'XRP-USDT-SWAP', coinbaseProduct: 'XRP-USD', name: 'Ripple', decimals: 4 },
26
- { symbol: 'AVAX/USDT', binanceSymbol: 'AVAXUSDT', okxInstId: 'AVAX-USDT-SWAP', coinbaseProduct: 'AVAX-USD', name: 'Avalanche', decimals: 3 },
27
- { symbol: 'LINK/USDT', binanceSymbol: 'LINKUSDT', okxInstId: 'LINK-USDT-SWAP', coinbaseProduct: 'LINK-USD', name: 'Chainlink', decimals: 3 },
28
- { symbol: 'NEAR/USDT', binanceSymbol: 'NEARUSDT', okxInstId: 'NEAR-USDT-SWAP', coinbaseProduct: 'NEAR-USD', name: 'NEAR Protocol', decimals: 3 },
29
- { symbol: 'DOGE/USDT', binanceSymbol: 'DOGEUSDT', okxInstId: 'DOGE-USDT-SWAP', coinbaseProduct: 'DOGE-USD', name: 'Dogecoin', decimals: 4 },
30
- { symbol: 'ADA/USDT', binanceSymbol: 'ADAUSDT', okxInstId: 'ADA-USDT-SWAP', coinbaseProduct: 'ADA-USD', name: 'Cardano', decimals: 4 },
31
- ];
32
-
33
- /**
34
- * Returns formatted time string in Horário de Brasília (BRT / UTC-3).
35
- */
36
- export function getBrasiliaTimeStr(timestamp: number = Date.now(), includeSeconds = true): string {
37
- try {
38
- return new Date(timestamp).toLocaleTimeString('pt-BR', {
39
- timeZone: 'America/Sao_Paulo',
40
- hour: '2-digit',
41
- minute: '2-digit',
42
- ...(includeSeconds ? { second: '2-digit' } : {})
43
- });
44
- } catch {
45
- const d = new Date(timestamp - 3 * 3600 * 1000);
46
- const h = String(d.getUTCHours()).padStart(2, '0');
47
- const m = String(d.getUTCMinutes()).padStart(2, '0');
48
- const s = String(d.getUTCSeconds()).padStart(2, '0');
49
- return includeSeconds ? `${h}:${m}:${s}` : `${h}:${m}`;
50
- }
51
- }
52
-
53
- /**
54
- * Returns formatted date-time string in Horário de Brasília.
55
- */
56
- export function getBrasiliaDateStr(timestamp: number = Date.now()): string {
57
- try {
58
- return new Date(timestamp).toLocaleDateString('pt-BR', {
59
- timeZone: 'America/Sao_Paulo',
60
- day: '2-digit',
61
- month: '2-digit',
62
- year: 'numeric'
63
- });
64
- } catch {
65
- const d = new Date(timestamp - 3 * 3600 * 1000);
66
- const day = String(d.getUTCDate()).padStart(2, '0');
67
- const month = String(d.getUTCMonth() + 1).padStart(2, '0');
68
- const year = d.getUTCFullYear();
69
- return `${day}/${month}/${year}`;
70
- }
71
- }
72
-
73
- /**
74
- * Returns formatted date with hour and minute in Horário de Brasília (DD/MM/YYYY HH:mm).
75
- */
76
- export function getBrasiliaDateTimeStr(timestamp: number = Date.now()): string {
77
- try {
78
- const datePart = new Date(timestamp).toLocaleDateString('pt-BR', {
79
- timeZone: 'America/Sao_Paulo',
80
- day: '2-digit',
81
- month: '2-digit',
82
- year: 'numeric'
83
- });
84
- const timePart = new Date(timestamp).toLocaleTimeString('pt-BR', {
85
- timeZone: 'America/Sao_Paulo',
86
- hour: '2-digit',
87
- minute: '2-digit'
88
- });
89
- return `${datePart} ${timePart}`;
90
- } catch {
91
- const d = new Date(timestamp - 3 * 3600 * 1000);
92
- const day = String(d.getUTCDate()).padStart(2, '0');
93
- const month = String(d.getUTCMonth() + 1).padStart(2, '0');
94
- const year = d.getUTCFullYear();
95
- const h = String(d.getUTCHours()).padStart(2, '0');
96
- const m = String(d.getUTCMinutes()).padStart(2, '0');
97
- return `${day}/${month}/${year} ${h}:${m}`;
98
- }
99
- }
100
-
101
- export const FIBONACCI_TARGET_RATIOS = [
102
- { level: 1, ratio: 1.618, label: 'TP1 (1.618 Fibo Golden)' },
103
- { level: 2, ratio: 2.000, label: 'TP2 (2.000 Expansão)' },
104
- { level: 3, ratio: 2.618, label: 'TP3 (2.618 Extensão Maior)' },
105
- { level: 4, ratio: 3.618, label: 'TP4 (3.618 Projeção Institucional)' },
106
- { level: 5, ratio: 4.236, label: 'TP5 (4.236 Clímax Máximo)' },
107
- ];
108
-
109
- /**
110
- * Calculates 5 institutional Fibonacci take profit targets based on risk / impulse distance.
111
- */
112
- export function calculateFibonacciTargets(
113
- entryPrice: number,
114
- stopLoss: number,
115
- direction: 'COMPRA' | 'VENDA' | 'LONG' | 'SHORT' | SignalDecision,
116
- currentPrice: number = entryPrice,
117
- decimals: number = 2
118
- ): FibonacciTarget[] {
119
- const isLong = direction === 'COMPRA' || direction === 'LONG';
120
- const risk = Math.abs(entryPrice - stopLoss) || (entryPrice * 0.018);
121
-
122
- return FIBONACCI_TARGET_RATIOS.map((item) => {
123
- const targetPriceRaw = isLong
124
- ? entryPrice + (risk * item.ratio)
125
- : entryPrice - (risk * item.ratio);
126
-
127
- const targetPrice = Number(targetPriceRaw.toFixed(decimals));
128
- const pnlPercentRaw = isLong
129
- ? ((targetPrice - entryPrice) / (entryPrice || 1)) * 100
130
- : ((entryPrice - targetPrice) / (entryPrice || 1)) * 100;
131
- const pnlPercent = Number(pnlPercentRaw.toFixed(2));
132
-
133
- const isHit = isLong
134
- ? currentPrice >= targetPrice
135
- : currentPrice <= targetPrice;
136
-
137
- return {
138
- level: item.level,
139
- ratioLabel: item.label,
140
- ratio: item.ratio,
141
- price: targetPrice,
142
- pnlPercent,
143
- isHit,
144
- };
145
- });
146
- }
147
-
148
- // Technical analysis indicators
149
- export function calculateEMA(prices: number[], period: number): number {
150
- if (prices.length === 0) return 0;
151
- if (prices.length < period) {
152
- const sum = prices.reduce((acc, p) => acc + p, 0);
153
- return Number((sum / prices.length).toFixed(4));
154
- }
155
- const k = 2 / (period + 1);
156
- let ema = prices.slice(0, period).reduce((acc, p) => acc + p, 0) / period;
157
- for (let i = period; i < prices.length; i++) {
158
- ema = prices[i] * k + ema * (1 - k);
159
- }
160
- return Number(ema.toFixed(4));
161
- }
162
-
163
- export function calculateRSI(closes: number[], period = 14): number {
164
- if (closes.length < period + 1) return 50;
165
- let gains = 0;
166
- let losses = 0;
167
- for (let i = 1; i <= period; i++) {
168
- const change = closes[i] - closes[i - 1];
169
- if (change >= 0) gains += change;
170
- else losses -= change;
171
- }
172
- let avgGain = gains / period;
173
- let avgLoss = losses / period;
174
- for (let i = period + 1; i < closes.length; i++) {
175
- const change = closes[i] - closes[i - 1];
176
- if (change >= 0) {
177
- avgGain = (avgGain * (period - 1) + change) / period;
178
- avgLoss = (avgLoss * (period - 1)) / period;
179
- } else {
180
- avgGain = (avgGain * (period - 1)) / period;
181
- avgLoss = (avgLoss * (period - 1) - change) / period;
182
- }
183
- }
184
- if (avgLoss === 0) return 100;
185
- const rs = avgGain / avgLoss;
186
- return Math.round(100 - (100 / (1 + rs)));
187
- }
188
-
189
- export function calculateATR(candles: Candle[], period = 14): number {
190
- if (candles.length < 2) return 0;
191
- const trs: number[] = [];
192
- for (let i = 1; i < candles.length; i++) {
193
- const current = candles[i];
194
- const prev = candles[i - 1];
195
- const tr = Math.max(
196
- current.high - current.low,
197
- Math.abs(current.high - prev.close),
198
- Math.abs(current.low - prev.close)
199
- );
200
- trs.push(tr);
201
- }
202
- if (trs.length === 0) return 0;
203
- const atr = trs.slice(-period).reduce((acc, v) => acc + v, 0) / Math.min(trs.length, period);
204
- return Number(atr.toFixed(4));
205
- }
206
-
207
- // Convert sparkline hourly prices into structured candles
208
- export function generateCandlesFromSparkline(prices: number[], baseTime = Date.now()): {
209
- candles15m: Candle[];
210
- candles1h: Candle[];
211
- candles1d: Candle[];
212
- } {
213
- const candles1h: Candle[] = [];
214
- const count1h = Math.min(prices.length, 30);
215
- const startIdx = prices.length - count1h;
216
-
217
- for (let i = startIdx; i < prices.length; i++) {
218
- const p = prices[i];
219
- const prevP = i > 0 ? prices[i - 1] : p;
220
- const time = baseTime - (prices.length - 1 - i) * 3600 * 1000;
221
- const spread = Math.abs(p - prevP) * 0.4 || p * 0.003;
222
-
223
- candles1h.push({
224
- timestamp: time,
225
- timeStr: getBrasiliaTimeStr(time, false),
226
- open: prevP,
227
- high: Math.max(prevP, p) + spread * 0.6,
228
- low: Math.min(prevP, p) - spread * 0.6,
229
- close: p,
230
- volume: Math.round(p * 120),
231
- });
232
- }
233
-
234
- // 15m candles derived from recent 1h movement
235
- const candles15m: Candle[] = [];
236
- const latestPrice = prices[prices.length - 1] || 1;
237
- for (let i = 29; i >= 0; i--) {
238
- const time = baseTime - i * 15 * 60 * 1000;
239
- const offset = (Math.sin(i * 0.4) * 0.003) * latestPrice;
240
- const open = latestPrice + offset;
241
- const close = open + ((i % 2 === 0 ? 1 : -1) * 0.0015 * latestPrice);
242
- const high = Math.max(open, close) + 0.001 * latestPrice;
243
- const low = Math.min(open, close) - 0.001 * latestPrice;
244
-
245
- candles15m.push({
246
- timestamp: time,
247
- timeStr: getBrasiliaTimeStr(time, false),
248
- open: Number(open.toFixed(4)),
249
- high: Number(high.toFixed(4)),
250
- low: Number(low.toFixed(4)),
251
- close: Number(close.toFixed(4)),
252
- volume: Math.round(latestPrice * 45),
253
- });
254
- }
255
-
256
- // 1d candles grouped every 24 hourly points
257
- const candles1d: Candle[] = [];
258
- const daysCount = Math.floor(prices.length / 24);
259
- for (let d = daysCount - 1; d >= 0; d--) {
260
- const slice = prices.slice(d * 24, (d + 1) * 24);
261
- if (slice.length === 0) continue;
262
- const time = baseTime - (daysCount - 1 - d) * 86400 * 1000;
263
- const open = slice[0];
264
- const close = slice[slice.length - 1];
265
- const high = Math.max(...slice);
266
- const low = Math.min(...slice);
267
-
268
- candles1d.push({
269
- timestamp: time,
270
- timeStr: getBrasiliaDateStr(time).slice(0, 5),
271
- open,
272
- high,
273
- low,
274
- close,
275
- volume: Math.round(close * 2500),
276
- });
277
- }
278
-
279
- return { candles15m, candles1h, candles1d };
280
- }
281
-
282
- // In-memory cache of Top 100 signals
283
- let cachedTop100Signals: TradeSignal[] = [];
284
- let lastTop100FetchTime = 0;
285
-
286
- /**
287
- * Fetches the Top 100 cryptocurrencies by market capitalization in real-time.
288
- * Stablecoins (USDT, USDC, etc.) are excluded so exactly 100 tradeable assets are processed.
289
- */
290
- export async function fetchTop100Cryptos(): Promise<TradeSignal[]> {
291
- const now = Date.now();
292
- // Return cache if fetched less than 30s ago
293
- if (cachedTop100Signals.length >= 80 && now - lastTop100FetchTime < 30000) {
294
- return cachedTop100Signals;
295
- }
296
-
297
- try {
298
- const url = 'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=120&page=1&sparkline=true';
299
- const res = await fetch(url, {
300
- headers: { 'User-Agent': 'GodProtocol-Quantitative/4.0' },
301
- signal: AbortSignal.timeout(10000),
302
- });
303
-
304
- if (!res.ok) {
305
- throw new Error(`CoinGecko status: ${res.status}`);
306
- }
307
-
308
- const rawCoins: any[] = await res.json();
309
- const stableSymbols = new Set([
310
- 'USDT', 'USDC', 'USDS', 'DAI', 'FDUSD', 'USDE', 'PYUSD',
311
- 'TUSD', 'USDD', 'FRAX', 'USD0', 'BUSD', 'EURC', 'GUSD', 'USDG'
312
- ]);
313
-
314
- // Keep non-stablecoins and take exactly 100 top assets by market capitalization
315
- const filteredTop100 = rawCoins
316
- .filter((c: any) => !stableSymbols.has(c.symbol.toUpperCase()))
317
- .slice(0, 100);
318
-
319
- const generatedSignals: TradeSignal[] = [];
320
-
321
- // Process all 100 coins
322
- for (let index = 0; index < filteredTop100.length; index++) {
323
- const coin = filteredTop100[index];
324
- const symbol = `${coin.symbol.toUpperCase()}/USDT`;
325
- const name = coin.name;
326
- const currentPrice = Number(coin.current_price) || 0.0001;
327
- const change24h = Number(Number(coin.price_change_percentage_24h || 0).toFixed(2));
328
- const volume24h = Math.round(coin.total_volume || 0);
329
- const marketCap = coin.market_cap || 0;
330
- const marketCapRank = coin.market_cap_rank || (index + 1);
331
-
332
- // Decimals formatting rule
333
- let decimals = 2;
334
- if (currentPrice < 0.001) decimals = 6;
335
- else if (currentPrice < 1) decimals = 4;
336
- else if (currentPrice < 10) decimals = 3;
337
-
338
- // Extract sparkline prices
339
- const sparkPrices: number[] = coin.sparkline_in_7d?.price || [];
340
- const change7d = sparkPrices.length > 0 && sparkPrices[0] > 0
341
- ? Number((((currentPrice - sparkPrices[0]) / sparkPrices[0]) * 100).toFixed(2))
342
- : Number(Number(coin.price_change_percentage_7d_in_currency || (change24h * 2.8)).toFixed(2));
343
- const { candles15m, candles1h, candles1d } = generateCandlesFromSparkline(
344
- sparkPrices.length >= 10 ? sparkPrices : [currentPrice * 0.98, currentPrice * 0.99, currentPrice],
345
- now
346
- );
347
-
348
- // Technical Indicators
349
- const closes = sparkPrices.length >= 14 ? sparkPrices : [currentPrice];
350
- const ema9 = Number(calculateEMA(closes, 9).toFixed(decimals));
351
- const ema21 = Number(calculateEMA(closes, 21).toFixed(decimals));
352
- const ema50 = Number(calculateEMA(closes, 50).toFixed(decimals));
353
- const ema200 = Number(calculateEMA(closes, 200).toFixed(decimals));
354
- const isBullishCross = ema9 > ema21;
355
- const rsiValue = calculateRSI(closes, 14);
356
- const rawAtr = calculateATR(candles15m, 14);
357
- const atrValue = rawAtr > 0 ? Number(rawAtr.toFixed(decimals)) : Number((currentPrice * 0.018).toFixed(decimals));
358
-
359
- // Trend & Pattern
360
- const htfTrend: 'ALTA (BULLISH)' | 'BAIXA (BEARISH)' | 'LATERAL' =
361
- change24h > 1.0 && currentPrice >= ema50 ? 'ALTA (BULLISH)' :
362
- change24h < -1.0 && currentPrice <= ema50 ? 'BAIXA (BEARISH)' :
363
- 'LATERAL';
364
-
365
- const patterns = [
366
- 'Ombro-Cabeça-Ombro Invertido (Bullish)',
367
- 'Fundo Duplo em Suporte Institucional',
368
- 'Canal de Reacumulação Rompendo para Cima',
369
- 'Ombro-Cabeça-Ombro Tradicional (Bearish)',
370
- 'Topo Duplo com Exaustão de Compradores',
371
- 'Triângulo Ascendente em Compressão de Volatilidade',
372
- 'Bandeira de Alta pós-Impulso Institucional',
373
- ];
374
- const mtfPattern = isBullishCross ? patterns[0] : patterns[3];
375
-
376
- // Wyckoff Phase
377
- let currentPhase: WyckoffPhase = 'Consolidação Neutra';
378
- if (isBullishCross && htfTrend === 'ALTA (BULLISH)') currentPhase = 'Reexpansão (Markup)';
379
- else if (rsiValue < 38) currentPhase = 'Acumulação (Spring/Test)';
380
- else if (!isBullishCross && htfTrend === 'BAIXA (BEARISH)') currentPhase = 'Markdown (Queda Livre)';
381
- else if (rsiValue > 68) currentPhase = 'Distribuição (UTAD)';
382
-
383
- // 4 Pillars Scoring
384
- let taScore = 60;
385
- if (isBullishCross) taScore += 16;
386
- if (currentPrice > ema50) taScore += 12;
387
- if (rsiValue >= 40 && rsiValue <= 60) taScore += 10;
388
- taScore = Math.min(95, Math.max(45, taScore));
389
-
390
- let smcScore = 65;
391
- const sweepDetected = rsiValue > 65 || rsiValue < 35;
392
- if (sweepDetected) smcScore += 15;
393
- smcScore = Math.min(95, Math.max(45, smcScore));
394
-
395
- let wyckoffScore = 62;
396
- if (currentPhase.includes('Acumulação') || currentPhase.includes('Reexpansão')) wyckoffScore += 18;
397
- wyckoffScore = Math.min(95, Math.max(45, wyckoffScore));
398
-
399
- // Sentiment
400
- let fundingRate = 0.008; // neutral
401
- if (change24h > 4) fundingRate = 0.022; // euphoria
402
- else if (change24h < -4) fundingRate = -0.006; // panic / negative
403
- let sentimentScore = 65;
404
- if (fundingRate >= -0.005 && fundingRate <= 0.015) sentimentScore += 15;
405
- else if (fundingRate < -0.005) sentimentScore += 20;
406
- else sentimentScore -= 10;
407
- sentimentScore = Math.min(95, Math.max(45, sentimentScore));
408
-
409
- let rawConfluence = Math.round(
410
- (taScore * 0.25) + (smcScore * 0.3) + (wyckoffScore * 0.25) + (sentimentScore * 0.2)
411
- );
412
-
413
- // Boost top institutional setups where Triple Screen and indicators align perfectly
414
- if (rawConfluence >= 82 && (isBullishCross || currentPrice > ema50)) {
415
- if (index <= 5 || marketCapRank <= 10 || Math.abs(change24h) >= 3.0) {
416
- rawConfluence = Math.min(96, Math.max(90, rawConfluence + 8)); // Reaches 90% - 96%
417
- }
418
- }
419
-
420
- const confluenceAverage = Math.min(98, Math.max(45, rawConfluence));
421
-
422
- // Decision and Risk Management
423
- const isLongSetup = (htfTrend === 'ALTA (BULLISH)' || isBullishCross) && currentPrice >= ema50 * 0.99;
424
- const isShortSetup = (htfTrend === 'BAIXA (BEARISH)' || !isBullishCross) && currentPrice <= ema50 * 1.01;
425
-
426
- let decision: SignalDecision = 'AGUARDAR';
427
- let stopLoss = 0;
428
- let riskReward = 0;
429
- let breakevenTrigger = 0;
430
-
431
- const passesConfidence = confluenceAverage >= 75;
432
-
433
- if (passesConfidence && isLongSetup) {
434
- decision = 'COMPRA';
435
- stopLoss = Number((currentPrice - (atrValue * 1.5)).toFixed(decimals));
436
- const risk = Math.max(currentPrice - stopLoss, currentPrice * 0.005);
437
-
438
- // Expanded Risk/Reward spectrum (2:1, 3:1, 4:1, 5:1, 6:1 up to 10:1)
439
- let calculatedRR = 2.0;
440
- if (confluenceAverage >= 90) {
441
- const highRROptions = [5.0, 6.0, 7.5, 8.0, 10.0];
442
- calculatedRR = highRROptions[(marketCapRank || 1) % highRROptions.length];
443
- } else if (confluenceAverage >= 82) {
444
- const medRROptions = [3.0, 3.5, 4.0, 4.5, 5.0];
445
- calculatedRR = medRROptions[(marketCapRank || 1) % medRROptions.length];
446
- } else {
447
- calculatedRR = Number((2.0 + (((marketCapRank || 1) * 3) % 11) * 0.1).toFixed(1));
448
- }
449
- riskReward = calculatedRR;
450
- breakevenTrigger = Number((currentPrice + (risk * 1.0)).toFixed(decimals));
451
- } else if (passesConfidence && isShortSetup) {
452
- decision = 'VENDA';
453
- stopLoss = Number((currentPrice + (atrValue * 1.5)).toFixed(decimals));
454
- const risk = Math.max(stopLoss - currentPrice, currentPrice * 0.005);
455
-
456
- let calculatedRR = 2.0;
457
- if (confluenceAverage >= 90) {
458
- const highRROptions = [5.0, 6.0, 7.5, 8.0, 10.0];
459
- calculatedRR = highRROptions[(marketCapRank || 1) % highRROptions.length];
460
- } else if (confluenceAverage >= 82) {
461
- const medRROptions = [3.0, 3.5, 4.0, 4.5, 5.0];
462
- calculatedRR = medRROptions[(marketCapRank || 1) % medRROptions.length];
463
- } else {
464
- calculatedRR = Number((2.0 + (((marketCapRank || 1) * 5) % 11) * 0.1).toFixed(1));
465
- }
466
- riskReward = calculatedRR;
467
- breakevenTrigger = Number((currentPrice - (risk * 1.0)).toFixed(decimals));
468
- } else {
469
- decision = 'AGUARDAR';
470
- const waitRR = Number((1.2 + (((marketCapRank || 1) * 3) % 9) * 0.1).toFixed(2));
471
- riskReward = waitRR;
472
- stopLoss = Number((currentPrice * 0.98).toFixed(decimals));
473
- breakevenTrigger = Number((currentPrice * 1.015).toFixed(decimals));
474
- }
475
-
476
- // Calculate 5 Fibonacci targets (1.618, 2.000, 2.618, 3.618, 4.236)
477
- const fiboTargets = calculateFibonacciTargets(currentPrice, stopLoss, decision, currentPrice, decimals);
478
- const takeProfit1 = fiboTargets[0].price;
479
- const takeProfit2 = fiboTargets[1].price;
480
- const takeProfit3 = fiboTargets[2].price;
481
- const takeProfit4 = fiboTargets[3].price;
482
- const takeProfit5 = fiboTargets[4].price;
483
-
484
- const passedFilter = decision !== 'AGUARDAR' && confluenceAverage >= 75 && riskReward >= 2.0;
485
-
486
- generatedSignals.push({
487
- id: `sig-${coin.symbol.toUpperCase()}-${now}`,
488
- symbol,
489
- name,
490
- marketCapRank,
491
- marketCap,
492
- currentPrice,
493
- change24h,
494
- change7d,
495
- sparkline7d: sparkPrices.length >= 7 ? sparkPrices : candles1d.map(c => c.close),
496
- volume24h,
497
- timestamp: now,
498
- timeStr: getBrasiliaTimeStr(now, true),
499
- decision,
500
- confidence: confluenceAverage,
501
- riskReward,
502
- entryPrice: currentPrice,
503
- stopLoss,
504
- takeProfit1,
505
- takeProfit2,
506
- takeProfit3,
507
- takeProfit4,
508
- takeProfit5,
509
- fibonacciTargets: fiboTargets,
510
- breakevenTrigger,
511
- atrValue,
512
- passedFilter,
513
- tripleScreen: {
514
- htf: {
515
- timeframe: '1D (Diário)',
516
- trend: htfTrend,
517
- ema50,
518
- ema200,
519
- description: `Tendência ${htfTrend}. Rank #${marketCapRank} por Market Cap com variação 24h de ${change24h > 0 ? '+' : ''}${change24h}%.`,
520
- candles: candles1d,
521
- },
522
- mtf: {
523
- timeframe: '1H (1 Hora)',
524
- pattern: mtfPattern,
525
- ema50,
526
- dynamicSupportResistance: currentPrice > ema50 ? 'Suporte na EMA 50' : 'Resistência na EMA 50',
527
- candles: candles1h,
528
- },
529
- ltf: {
530
- timeframe: '15m (15 Minutos)',
531
- ema9,
532
- ema21,
533
- emaCross: isBullishCross ? 'Cruzamento de Alta (9 > 21)' : 'Cruzamento de Baixa (9 < 21)',
534
- rsi: rsiValue,
535
- rsiStatus: rsiValue > 70 ? 'Sobrecomprado (>70)' : rsiValue < 30 ? 'Sobrevendido (<30)' : 'Momentum Neutro/Saudável',
536
- atr: atrValue,
537
- candles: candles15m,
538
- },
539
- },
540
- fourPillars: {
541
- classicTA: {
542
- score: taScore,
543
- status: taScore >= 75 ? 'Favorável' : taScore >= 60 ? 'Neutro' : 'Desfavorável',
544
- emaAlignment: isBullishCross ? 'Alta (9>21>50>200)' : 'Baixa (9<21<50<200)',
545
- rsiValue,
546
- rsiInterpretation: rsiValue > 60 ? 'Pressão compradora sem exaustão' : 'Zona neutra de consolidação',
547
- patternDetected: mtfPattern,
548
- details: `EMA 9 ($${ema9}) e EMA 21 ($${ema21}) calculadas sobre histórico de preços horário.`,
549
- },
550
- smc: {
551
- score: smcScore,
552
- status: smcScore >= 75 ? 'Favorável' : smcScore >= 60 ? 'Neutro' : 'Desfavorável',
553
- liquiditySweep: {
554
- detected: sweepDetected,
555
- type: sweepDetected ? 'Sell Side Liquidity (SSL) Capturada' : 'Nenhum Sweep Recente',
556
- priceLevel: Number((currentPrice * 0.985).toFixed(decimals)),
557
- },
558
- imbalanceFVG: {
559
- present: true,
560
- zone: `${(currentPrice * 0.992).toFixed(decimals)} - ${(currentPrice * 0.996).toFixed(decimals)}`,
561
- },
562
- orderBlock: {
563
- type: isLongSetup ? 'Bullish OB' : 'Bearish OB',
564
- zone: `${(currentPrice * 0.988).toFixed(decimals)} (1H Institucional)`,
565
- },
566
- details: 'Detecção institucional de Fair Value Gap e varredura de liquidez em níveis chave.',
567
- },
568
- wyckoff: {
569
- score: wyckoffScore,
570
- status: wyckoffScore >= 75 ? 'Favorável' : wyckoffScore >= 60 ? 'Neutro' : 'Desfavorável',
571
- currentPhase,
572
- effortVsResult: 'Volume Alto com Absorção (Institucional Atuando)',
573
- volumeRatio: 1.45,
574
- details: `Fase de ${currentPhase} confirmada por fluxo e VSA institucional.`,
575
- },
576
- sentiment: {
577
- score: sentimentScore,
578
- status: sentimentScore >= 75 ? 'Favorável' : sentimentScore >= 60 ? 'Neutro' : 'Desfavorável',
579
- openInterest: Math.round(currentPrice * 180000),
580
- oi24hChange: Number((change24h * 1.2).toFixed(2)),
581
- oiInterpretation: change24h > 0 ? 'Dinheiro Novo Entrando (Confirma Tendência)' : 'Fechamento de Posições (Exaustão)',
582
- fundingRate,
583
- fundingSentiment: fundingRate > 0.02 ? 'Euforia Excessiva (Perigo de Queda)' : fundingRate < -0.005 ? 'Pânico / Negativo (Oportunidade de Compra)' : 'Taxa Neutra e Saudável',
584
- longShortRatio: 1.25,
585
- details: `Funding estimado em ${(fundingRate * 100).toFixed(3)}%. Sentimento do mercado de derivativos.`,
586
- },
587
- confluenceAverage,
588
- },
589
- aiThesis: {
590
- summary: passedFilter
591
- ? `Setup de ${decision} para ${symbol} (#${marketCapRank}) validado com ${confluenceAverage}% de confluência institucional e R/R 1:${riskReward}.`
592
- : `Critérios do God Protocol v2026 pendentes (${confluenceAverage}% < 75% ou sem alinhamento R/R). Recomendado AGUARDAR.`,
593
- institutionalContext: `HTF Diário em ${htfTrend}. Moeda do Top 100 Market Cap (#${marketCapRank}). Stop ATR em $${stopLoss}.`,
594
- primaryCatalyst: `Estrutura de médias e RSI(${rsiValue}) em 15m alinhados à sustentação de EMA 50 em MTF (1H).`,
595
- riskWarning: `Controle rígido: limitar exposição a 1% do capital total. Alerta em Horário de Brasília (BRT).`,
596
- verdict: passedFilter ? 'EXECUTAR' : 'AGUARDAR',
597
- source: 'Agente Quantitativo Local',
598
- },
599
- squeezeBreakout: (() => {
600
- const period = Math.min(sparkPrices.length, 20);
601
- const recentSpark = sparkPrices.length >= period ? sparkPrices.slice(-period) : [currentPrice];
602
- const sma20 = recentSpark.reduce((a, b) => a + b, 0) / (recentSpark.length || 1);
603
- const variance = recentSpark.reduce((a, b) => a + Math.pow(b - sma20, 2), 0) / (recentSpark.length || 1);
604
- const stdDev = Math.sqrt(variance) || (currentPrice * 0.015);
605
- const upperBB = sma20 + 2.0 * stdDev;
606
- const lowerBB = Math.max(0.0001, sma20 - 2.0 * stdDev);
607
- const bbWidth = Number((((upperBB - lowerBB) / sma20) * 100).toFixed(2));
608
- const upperKC = sma20 + 1.5 * atrValue;
609
- const lowerKC = Math.max(0.0001, sma20 - 1.5 * atrValue);
610
- const kcWidth = Number((((upperKC - lowerKC) / sma20) * 100).toFixed(2));
611
- const isSqueezeOn = upperBB < upperKC && lowerBB > lowerKC;
612
- const isSqueezeFired = (change24h >= 4.5 && bbWidth < 7.0) || (change24h >= 6.5);
613
-
614
- let squeezeState: 'SQUEEZE_ATIVO' | 'IGNICAO_DISPARADA' | 'EXPANSAO_ALTA' | 'NORMAL' = 'NORMAL';
615
- let stateLabel = 'VOLATILIDADE REGULAR';
616
- let urgency: 'CRITICA' | 'ALTA' | 'MODERADA' | 'BAIXA' = 'BAIXA';
617
- let explosionScore = 32;
618
-
619
- const catalysts: string[] = [];
620
-
621
- if (isSqueezeFired || change24h >= 7.0) {
622
- squeezeState = 'IGNICAO_DISPARADA';
623
- stateLabel = 'DISPARO DE EXPLOSÃO (BREAKOUT 8%+)';
624
- urgency = 'CRITICA';
625
- explosionScore = Math.min(98, 88 + (marketCapRank % 11));
626
- catalysts.push('Expansão violenta das Bandas de Bollinger com gatilho de breakout');
627
- catalysts.push(`Rompimento altista com variação 24h de +${change24h}%`);
628
- } else if (isSqueezeOn) {
629
- squeezeState = 'SQUEEZE_ATIVO';
630
- stateLabel = 'COMPRESSÃO MÁXIMA (SQUEEZE ATIVO)';
631
- urgency = bbWidth < 3.5 ? 'ALTA' : 'MODERADA';
632
- explosionScore = Math.min(87, 72 + Math.round((10 - bbWidth) * 2));
633
- catalysts.push(`Bandas de Bollinger estranguladas dentro do Canal Keltner (BandWidth: ${bbWidth}%)`);
634
- catalysts.push('Acúmulo intenso de volatilidade: energia prestes a ser liberada');
635
- } else if (change24h > 2.5) {
636
- squeezeState = 'EXPANSAO_ALTA';
637
- stateLabel = 'EXPANSÃO DE MOMENTUM';
638
- urgency = 'MODERADA';
639
- explosionScore = Math.min(74, 58 + Math.round(change24h * 1.5));
640
- catalysts.push('Fluxo comprador dominante em andamento');
641
- }
642
-
643
- let shortSqueezeRisk: 'EXTREMO' | 'ALTO' | 'MODERADO' | 'BAIXO' = 'MODERADO';
644
- if (fundingRate <= 0.001) {
645
- shortSqueezeRisk = 'EXTREMO';
646
- catalysts.push(`Taxa de funding negativa/zerada (${(fundingRate * 100).toFixed(3)}%): Vendedores expostos a Short Squeeze`);
647
- } else if (fundingRate <= 0.006) {
648
- shortSqueezeRisk = 'ALTO';
649
- }
650
-
651
- return {
652
- isSqueezeOn,
653
- isSqueezeFired,
654
- squeezeBarsCount: isSqueezeOn ? Math.max(3, Math.min(18, Math.round(14 - bbWidth))) : 1,
655
- state: squeezeState,
656
- stateLabel,
657
- explosionScore,
658
- urgency,
659
- bollingerBandWidth: bbWidth,
660
- keltnerWidth: kcWidth,
661
- compressionPercent: Math.min(100, Math.max(10, Math.round((1 - (bbWidth / Math.max(kcWidth, 0.1))) * 100 + 50))),
662
- momentumDirection: currentPrice >= sma20 ? 'ALTA' : 'BAIXA',
663
- shortSqueezeRisk,
664
- estimatedTarget8Pct: Number((currentPrice * 1.082).toFixed(decimals)),
665
- estimatedTarget15Pct: Number((currentPrice * 1.154).toFixed(decimals)),
666
- recommendedStopLoss: Number((Math.min(lowerBB, currentPrice * 0.978)).toFixed(decimals)),
667
- catalysts,
668
- };
669
- })(),
670
- });
671
- }
672
-
673
- if (generatedSignals.length > 0) {
674
- cachedTop100Signals = generatedSignals;
675
- lastTop100FetchTime = now;
676
- // Immediately overlay live Binance.US pricing if available
677
- await syncRealTimePrices().catch(() => {});
678
- }
679
- return cachedTop100Signals;
680
- } catch (err: any) {
681
- console.warn('CoinGecko Top 100 fetch failed, returning cached signals:', err.message);
682
- // Even if CoinGecko failed, try updating cached signals with live Binance.US prices
683
- await syncRealTimePrices().catch(() => {});
684
- return cachedTop100Signals;
685
- }
686
- }
687
-
688
- /**
689
- * High-frequency real-time price synchronizer using Binance.US.
690
- * Updates currentPrice, timestamps, and recalculates Fibonacci target hits in milliseconds.
691
- */
692
- export async function syncRealTimePrices(): Promise<TradeSignal[]> {
693
- if (!cachedTop100Signals || cachedTop100Signals.length === 0) {
694
- return cachedTop100Signals;
695
- }
696
-
697
- try {
698
- const res = await fetch('https://api.binance.us/api/v3/ticker/price', {
699
- headers: { 'User-Agent': 'GodProtocol-Ticker/4.0' },
700
- signal: AbortSignal.timeout(4000),
701
- });
702
-
703
- if (!res.ok) return cachedTop100Signals;
704
-
705
- const list: Array<{ symbol: string; price: string }> = await res.json();
706
- const priceMap = new Map<string, number>();
707
- for (const item of list) {
708
- const p = parseFloat(item.price);
709
- if (!isNaN(p) && p > 0) {
710
- priceMap.set(item.symbol, p);
711
- }
712
- }
713
-
714
- const now = Date.now();
715
- const brasiliaTime = getBrasiliaTimeStr(now, true);
716
-
717
- for (const signal of cachedTop100Signals) {
718
- const cleanSym = signal.symbol.replace('/', '').toUpperCase();
719
- let livePrice = priceMap.get(cleanSym);
720
-
721
- // Also handle alternate mappings if necessary (e.g. BTCUSD vs BTCUSDT)
722
- if (!livePrice && cleanSym.endsWith('USDT')) {
723
- livePrice = priceMap.get(cleanSym.replace('USDT', 'USD'));
724
- }
725
-
726
- if (livePrice && livePrice > 0) {
727
- let decimals = 2;
728
- if (livePrice < 0.001) decimals = 6;
729
- else if (livePrice < 1) decimals = 4;
730
- else if (livePrice < 10) decimals = 3;
731
-
732
- signal.currentPrice = Number(livePrice.toFixed(decimals));
733
- signal.timestamp = now;
734
- signal.timeStr = brasiliaTime;
735
-
736
- // Recalculate Fibonacci targets with the updated live currentPrice to verify hits
737
- const entry = signal.entryPrice || signal.currentPrice;
738
- const stop = signal.stopLoss || (entry * 0.98);
739
- signal.fibonacciTargets = calculateFibonacciTargets(
740
- entry,
741
- stop,
742
- signal.decision,
743
- signal.currentPrice,
744
- decimals
745
- );
746
- signal.takeProfit1 = signal.fibonacciTargets[0].price;
747
- signal.takeProfit2 = signal.fibonacciTargets[1].price;
748
- signal.takeProfit3 = signal.fibonacciTargets[2].price;
749
- signal.takeProfit4 = signal.fibonacciTargets[3].price;
750
- signal.takeProfit5 = signal.fibonacciTargets[4].price;
751
- }
752
- }
753
-
754
- return cachedTop100Signals;
755
- } catch (err: any) {
756
- // Non-blocking catch
757
- return cachedTop100Signals;
758
- }
759
- }
760
-
761
- // Compatibility helper
762
- export async function fetchLiveTickers(): Promise<Map<string, any>> {
763
- const result = new Map<string, any>();
764
- for (const s of cachedTop100Signals) {
765
- result.set(s.symbol, {
766
- symbol: s.symbol,
767
- price: s.currentPrice,
768
- change24h: s.change24h,
769
- volume24h: s.volume24h,
770
- });
771
- }
772
- return result;
773
- }
774
-
775
- // Generate deterministic, mathematically sound backtest from top crypto assets
776
- export function generateRealDailyBacktest(currentSignals: TradeSignal[], days: number = 60): DailyBacktestMetrics {
777
- const validDays = Math.max(7, Math.min(days, 730));
778
- const trades: DailyBacktestTrade[] = [];
779
- const now = Date.now();
780
- let equity = 10000;
781
- let peakEquity = equity;
782
- let maxDrawdown = 0;
783
- let winningTrades = 0;
784
- let losingTrades = 0;
785
- let grossProfit = 0;
786
- let grossLoss = 0;
787
-
788
- const pillarWins = {
789
- classicTA: 0,
790
- smc: 0,
791
- wyckoff: 0,
792
- sentiment: 0,
793
- };
794
- const pillarTotals = {
795
- classicTA: 0,
796
- smc: 0,
797
- wyckoff: 0,
798
- sentiment: 0,
799
- };
800
-
801
- const pool = (currentSignals && currentSignals.length >= 10) ? currentSignals : [
802
- { symbol: 'BTC/USDT', currentPrice: 87500 },
803
- { symbol: 'ETH/USDT', currentPrice: 2650 },
804
- { symbol: 'SOL/USDT', currentPrice: 165.2 },
805
- { symbol: 'BNB/USDT', currentPrice: 620.5 },
806
- { symbol: 'XRP/USDT', currentPrice: 2.35 },
807
- { symbol: 'AVAX/USDT', currentPrice: 32.4 },
808
- { symbol: 'LINK/USDT', currentPrice: 17.8 },
809
- { symbol: 'NEAR/USDT', currentPrice: 5.6 },
810
- { symbol: 'DOGE/USDT', currentPrice: 0.22 },
811
- { symbol: 'ADA/USDT', currentPrice: 0.78 },
812
- ] as any[];
813
-
814
- // Deterministic Pseudo-Random Number Generator (PRNG) to ensure consistent, stable backtest results
815
- let seed = 20260315 + validDays * 31;
816
- const prng = () => {
817
- seed = (seed * 9301 + 49297) % 233280;
818
- return seed / 233280;
819
- };
820
-
821
- const equityCurve: { date: string; equity: number; tradePnl: number; drawdown: number }[] = [];
822
- const returnsList: number[] = [];
823
-
824
- // Start with baseline day 0 equity curve point
825
- const startTimestamp = now - validDays * 86400 * 1000;
826
- const startDateStr = getBrasiliaDateStr(startTimestamp);
827
- const endDateStr = getBrasiliaDateStr(now);
828
-
829
- equityCurve.push({
830
- date: startDateStr,
831
- equity: 10000,
832
- tradePnl: 0,
833
- drawdown: 0,
834
- });
835
-
836
- interface ExtendedTrade extends DailyBacktestTrade {
837
- timestamp: number;
838
- }
839
- const allGeneratedTrades: ExtendedTrade[] = [];
840
-
841
- for (let i = validDays; i >= 0; i--) {
842
- const dayBaseTimestamp = now - i * 86400 * 1000;
843
- const dayDateOnly = getBrasiliaDateStr(dayBaseTimestamp);
844
-
845
- // Pick 1 to 2 setups per day based on institutional criteria
846
- const tradesToday = prng() > 0.45 ? 1 : 2;
847
- let dayReturn = 0;
848
-
849
- for (let t = 0; t < tradesToday; t++) {
850
- const coinIndex = Math.floor(prng() * pool.length);
851
- const signal = pool[coinIndex] || pool[0];
852
- const direction: 'LONG' | 'SHORT' = prng() > 0.46 ? 'LONG' : 'SHORT';
853
- const confidence = Math.floor(75 + prng() * 21); // 75 to 95%
854
- const rrRatio = Number((2.0 + prng() * 1.5).toFixed(2)); // 2.0 to 3.5
855
-
856
- const priceVariation = (prng() - 0.5) * 0.04;
857
- const entryPrice = signal.currentPrice * (1 + priceVariation);
858
- const stopDistance = entryPrice * 0.018;
859
- const stopLoss = direction === 'LONG' ? entryPrice - stopDistance : entryPrice + stopDistance;
860
-
861
- const decimals = entryPrice < 0.01 ? 6 : entryPrice < 1 ? 4 : entryPrice < 10 ? 3 : 2;
862
-
863
- // Calculate 5 Fibonacci Targets for this trade setup
864
- const fiboTargets = calculateFibonacciTargets(entryPrice, stopLoss, direction, entryPrice, decimals);
865
-
866
- // Distribute realistic trade hours (morning / afternoon sessions in Horário de Brasília)
867
- const tradeHour = t === 0 ? Math.floor(8 + prng() * 5) : Math.floor(14 + prng() * 7);
868
- const tradeMinute = Math.floor(prng() * 60);
869
- const tradeExactTimestamp = dayBaseTimestamp + (tradeHour * 3600 + tradeMinute * 60) * 1000;
870
- const tradeDateTimeStr = getBrasiliaDateTimeStr(tradeExactTimestamp);
871
-
872
- // Calibrated win rate around 74%
873
- const isWin = prng() < 0.74;
874
- const pillars: ('Clássica' | 'SMC' | 'Wyckoff' | 'Sentimento')[] = ['Clássica', 'SMC', 'Wyckoff', 'Sentimento'];
875
- const topPillar = pillars[Math.floor(prng() * pillars.length)];
876
- if (topPillar === 'Clássica') pillarTotals.classicTA++;
877
- else if (topPillar === 'SMC') pillarTotals.smc++;
878
- else if (topPillar === 'Wyckoff') pillarTotals.wyckoff++;
879
- else pillarTotals.sentiment++;
880
-
881
- let pnlPercent = 0;
882
- let exitPrice = 0;
883
- let status: 'TP ATINGIDO' | 'SL ATINGIDO' | 'BREAKEVEN' = 'TP ATINGIDO';
884
- let hitTargetLevel: number | undefined = undefined;
885
- let hitTargetLabel: string | undefined = undefined;
886
-
887
- if (isWin) {
888
- winningTrades++;
889
- status = 'TP ATINGIDO';
890
-
891
- // Choose which Fibonacci target was conquered
892
- const roll = prng();
893
- if (roll < 0.48) hitTargetLevel = 1; // TP1 (1.618 Fibo)
894
- else if (roll < 0.76) hitTargetLevel = 2; // TP2 (2.000 Fibo)
895
- else if (roll < 0.90) hitTargetLevel = 3; // TP3 (2.618 Fibo)
896
- else if (roll < 0.97) hitTargetLevel = 4; // TP4 (3.618 Fibo)
897
- else hitTargetLevel = 5; // TP5 (4.236 Fibo)
898
-
899
- const hitTarget = fiboTargets[hitTargetLevel - 1] || fiboTargets[0];
900
- exitPrice = hitTarget.price;
901
- hitTargetLabel = hitTarget.ratioLabel;
902
-
903
- // Exact asset percentage movement from Entry to Exit
904
- pnlPercent = direction === 'LONG'
905
- ? Number((((exitPrice - entryPrice) / (entryPrice || 1)) * 100).toFixed(2))
906
- : Number((((entryPrice - exitPrice) / (entryPrice || 1)) * 100).toFixed(2));
907
-
908
- // Portfolio impact: 1% account risk scaled with R/R multiplier
909
- const rrMultiplier = Math.max(1.8, Math.abs(pnlPercent) / 1.8);
910
- const dollarGain = equity * 0.01 * rrMultiplier;
911
- equity += dollarGain;
912
- grossProfit += dollarGain;
913
- dayReturn += pnlPercent;
914
- returnsList.push(pnlPercent);
915
-
916
- if (topPillar === 'Clássica') pillarWins.classicTA++;
917
- else if (topPillar === 'SMC') pillarWins.smc++;
918
- else if (topPillar === 'Wyckoff') pillarWins.wyckoff++;
919
- else pillarWins.sentiment++;
920
- } else {
921
- losingTrades++;
922
- status = 'SL ATINGIDO';
923
- exitPrice = stopLoss;
924
- hitTargetLevel = 0;
925
- hitTargetLabel = 'Stop Loss (Proteção)';
926
-
927
- pnlPercent = direction === 'LONG'
928
- ? Number((((stopLoss - entryPrice) / (entryPrice || 1)) * 100).toFixed(2))
929
- : Number((((entryPrice - stopLoss) / (entryPrice || 1)) * 100).toFixed(2));
930
-
931
- const dollarLoss = equity * 0.01;
932
- equity -= dollarLoss;
933
- grossLoss += dollarLoss;
934
- dayReturn += pnlPercent;
935
- returnsList.push(pnlPercent);
936
- }
937
-
938
- if (equity > peakEquity) peakEquity = equity;
939
- const currentDd = ((peakEquity - equity) / (peakEquity || 1)) * 100;
940
- if (currentDd > maxDrawdown) maxDrawdown = currentDd;
941
-
942
- allGeneratedTrades.push({
943
- id: `bt-${i}-${t}-${signal.symbol.replace(/[^a-zA-Z0-9]/g, '')}`,
944
- date: tradeDateTimeStr,
945
- symbol: signal.symbol,
946
- direction,
947
- entryPrice: Number(entryPrice.toFixed(decimals)),
948
- exitPrice: Number(exitPrice.toFixed(decimals)),
949
- stopLoss: Number(stopLoss.toFixed(decimals)),
950
- takeProfit: Number(fiboTargets[0].price.toFixed(decimals)),
951
- rrRatio,
952
- confidence,
953
- pnlPercent,
954
- status,
955
- holdingBars: Math.floor(4 + prng() * 18),
956
- topPillar,
957
- hitTargetLevel,
958
- hitTargetLabel,
959
- fibonacciTargets: fiboTargets,
960
- timestamp: tradeExactTimestamp,
961
- });
962
- }
963
-
964
- const currentDd = ((peakEquity - equity) / (peakEquity || 1)) * 100;
965
- equityCurve.push({
966
- date: dayDateOnly,
967
- equity: Number(equity.toFixed(2)),
968
- tradePnl: Number(dayReturn.toFixed(2)),
969
- drawdown: Number(currentDd.toFixed(2)),
970
- });
971
- }
972
-
973
- // Exact metrics verification ensuring 0% divergence
974
- const totalTrades = allGeneratedTrades.length;
975
- const verifiedWinningTrades = allGeneratedTrades.filter(t => t.status === 'TP ATINGIDO').length;
976
- const verifiedLosingTrades = allGeneratedTrades.filter(t => t.status === 'SL ATINGIDO').length;
977
- const winRate = totalTrades > 0 ? Number(((verifiedWinningTrades / totalTrades) * 100).toFixed(1)) : 0;
978
- const profitFactor = grossLoss > 0 ? Number((grossProfit / grossLoss).toFixed(2)) : 3.85;
979
- const netProfitPercent = Number((((equity - 10000) / 10000) * 100).toFixed(1));
980
-
981
- // Compute mathematical Sharpe ratio from returns
982
- let sharpeRatio = 2.45;
983
- if (returnsList.length > 2) {
984
- const mean = returnsList.reduce((a, b) => a + b, 0) / returnsList.length;
985
- const variance = returnsList.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / returnsList.length;
986
- const stdDev = Math.sqrt(variance) || 1;
987
- sharpeRatio = Number(((mean / stdDev) * Math.sqrt(252)).toFixed(2));
988
- if (sharpeRatio <= 0 || isNaN(sharpeRatio)) sharpeRatio = 2.45;
989
- }
990
-
991
- // Sort trades with the most recent first for tabular and timeline inspection
992
- const sortedTrades = allGeneratedTrades.sort((a, b) => b.timestamp - a.timestamp);
993
-
994
- return {
995
- totalTrades,
996
- winningTrades: verifiedWinningTrades,
997
- losingTrades: verifiedLosingTrades,
998
- winRate,
999
- profitFactor,
1000
- netProfitPercent,
1001
- maxDrawdownPercent: Number(maxDrawdown.toFixed(1)),
1002
- sharpeRatio,
1003
- averageRR: 2.42,
1004
- initialCapital: 10000,
1005
- finalCapital: Number(equity.toFixed(2)),
1006
- pillarWinRates: {
1007
- classicTA: pillarTotals.classicTA > 0 ? Math.round((pillarWins.classicTA / (pillarTotals.classicTA || 1)) * 100) : 74,
1008
- smc: pillarTotals.smc > 0 ? Math.round((pillarWins.smc / (pillarTotals.smc || 1)) * 100) : 78,
1009
- wyckoff: pillarTotals.wyckoff > 0 ? Math.round((pillarWins.wyckoff / (pillarTotals.wyckoff || 1)) * 100) : 76,
1010
- sentiment: pillarTotals.sentiment > 0 ? Math.round((pillarWins.sentiment / (pillarTotals.sentiment || 1)) * 100) : 72,
1011
- },
1012
- equityCurve,
1013
- trades: sortedTrades,
1014
- periodDays: validDays,
1015
- startDate: startDateStr,
1016
- endDate: endDateStr,
1017
- };
1018
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/signal/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './signalEngine.js';
 
 
server/signal/signalEngine.ts DELETED
@@ -1,188 +0,0 @@
1
- import type { Candle } from '../../src/types.js';
2
- import type { MarketAnalysis, TimeframeMarketAnalysis } from '../confluence/marketAnalysisService.js';
3
-
4
- export type SignalDirection = 'LONG' | 'SHORT' | 'NO TRADE';
5
- export type SignalStrength = 'A+' | 'A' | 'B' | 'C' | 'NONE';
6
-
7
- export interface TradeSignal {
8
- symbol: string;
9
- timestamp: number;
10
- direction: SignalDirection;
11
- strength: SignalStrength;
12
- score: number;
13
- confidence: number;
14
- entryZone: { low: number; high: number; reference: number } | null;
15
- stopLoss: number | null;
16
- invalidation: number | null;
17
- takeProfits: { tp1: number; tp2: number; tp3: number } | null;
18
- riskReward: { tp1: number; tp2: number; tp3: number } | null;
19
- riskPercent: number;
20
- positionRiskDistance: number | null;
21
- reasons: string[];
22
- warnings: string[];
23
- }
24
-
25
- function round(value: number, decimals = 2): number {
26
- const factor = 10 ** decimals;
27
- return Math.round(value * factor) / factor;
28
- }
29
-
30
- function finite(value: number | null | undefined): value is number {
31
- return value !== null && value !== undefined && Number.isFinite(value);
32
- }
33
-
34
- function noTrade(symbol: string, timestamp: number, reasons: string[], confidence = 0, score = 0, warnings: string[] = []): TradeSignal {
35
- return {
36
- symbol,
37
- timestamp,
38
- direction: 'NO TRADE',
39
- strength: 'NONE',
40
- score: round(score),
41
- confidence,
42
- entryZone: null,
43
- stopLoss: null,
44
- invalidation: null,
45
- takeProfits: null,
46
- riskReward: null,
47
- riskPercent: 0,
48
- positionRiskDistance: null,
49
- reasons,
50
- warnings,
51
- };
52
- }
53
-
54
- /**
55
- * Deterministic signal layer. It converts the structured market analysis into
56
- * a trade plan without placing orders. The 15m chart is the execution context;
57
- * higher timeframes provide directional confirmation.
58
- */
59
- export function generateTradeSignal(analysis: MarketAnalysis, candles: Candle[], riskPercent = 1): TradeSignal {
60
- const latest = candles.at(-1);
61
- const primary = analysis.timeframes.find(tf => tf.timeframe === '15m') ?? analysis.timeframes.at(-1);
62
-
63
- if (!latest || !primary) {
64
- return noTrade(analysis.symbol, Date.now(), ['Dados insuficientes para gerar o plano.']);
65
- }
66
-
67
- const rawScore = analysis.confluence.score;
68
- const confidence = analysis.confluence.confidence;
69
- const absScore = Math.abs(rawScore);
70
- const direction: SignalDirection = rawScore >= 20 ? 'LONG' : rawScore <= -20 ? 'SHORT' : 'NO TRADE';
71
- const higher = analysis.timeframes.filter(tf => tf.timeframe !== primary.timeframe);
72
- const alignedHigher = countAligned(higher, direction);
73
- const conflicts = countConflicts(higher, direction);
74
-
75
- const warnings: string[] = [];
76
- if (conflicts > 0) warnings.push(`${conflicts} timeframe(s) superior(es) em conflito.`);
77
- if (analysis.confluence.conflicts.length > 0) warnings.push(...analysis.confluence.conflicts.slice(0, 3));
78
- if (confidence < 60) warnings.push('Confiança abaixo do nível operacional preferencial.');
79
- if (analysis.confluence.entryQuality === 'avoid') warnings.push('Confluência classificou a entrada como evitável.');
80
-
81
- if (direction === 'NO TRADE' || absScore < 20 || confidence < 50 || analysis.confluence.entryQuality === 'avoid') {
82
- return noTrade(
83
- analysis.symbol,
84
- latest.timestamp,
85
- ['Confluência insuficiente para um setup operacional de qualidade.'],
86
- confidence,
87
- rawScore,
88
- warnings,
89
- );
90
- }
91
-
92
- const atr = primary.indicators.atr14;
93
- if (!finite(atr) || atr <= 0 || latest.close <= 0) {
94
- return noTrade(analysis.symbol, latest.timestamp, ['ATR ou preço inválido.'], confidence, rawScore, warnings);
95
- }
96
-
97
- const reference = latest.close;
98
- const swings = primary.structure.swings;
99
- const swingLow = latestSwingPrice(swings, 'low');
100
- const swingHigh = latestSwingPrice(swings, 'high');
101
-
102
- const volatilityStop = direction === 'LONG' ? reference - atr * 1.5 : reference + atr * 1.5;
103
- const structureStop = direction === 'LONG'
104
- ? finite(swingLow) && swingLow < reference ? swingLow - atr * 0.15 : volatilityStop
105
- : finite(swingHigh) && swingHigh > reference ? swingHigh + atr * 0.15 : volatilityStop;
106
- const stopLoss = direction === 'LONG'
107
- ? Math.min(volatilityStop, structureStop)
108
- : Math.max(volatilityStop, structureStop);
109
-
110
- const distance = Math.abs(reference - stopLoss);
111
- if (!finite(distance) || distance <= 0 || distance > reference * 0.08) {
112
- return noTrade(analysis.symbol, latest.timestamp, ['Distância de stop fora do limite operacional.'], confidence, rawScore, warnings);
113
- }
114
-
115
- const entryBuffer = Math.min(atr * 0.25, reference * 0.0025);
116
- const entryZone = { low: reference - entryBuffer, high: reference + entryBuffer, reference };
117
- const tp1 = direction === 'LONG' ? reference + distance * 1.5 : reference - distance * 1.5;
118
- const tp2 = direction === 'LONG' ? reference + distance * 2.5 : reference - distance * 2.5;
119
- const tp3 = direction === 'LONG' ? reference + distance * 4 : reference - distance * 4;
120
-
121
- const strength: SignalStrength = absScore >= 70 && confidence >= 80 && alignedHigher >= 2 && conflicts === 0
122
- ? 'A+'
123
- : absScore >= 55 && confidence >= 70
124
- ? 'A'
125
- : absScore >= 40 && confidence >= 60
126
- ? 'B'
127
- : 'C';
128
-
129
- return {
130
- symbol: analysis.symbol,
131
- timestamp: latest.timestamp,
132
- direction,
133
- strength,
134
- score: round(rawScore),
135
- confidence,
136
- entryZone: { low: round(entryZone.low), high: round(entryZone.high), reference: round(reference) },
137
- stopLoss: round(stopLoss),
138
- invalidation: round(stopLoss),
139
- takeProfits: { tp1: round(tp1), tp2: round(tp2), tp3: round(tp3) },
140
- riskReward: { tp1: 1.5, tp2: 2.5, tp3: 4 },
141
- riskPercent: Math.max(0.1, Math.min(2, riskPercent)),
142
- positionRiskDistance: round(distance),
143
- reasons: buildReasons(direction, analysis, primary, alignedHigher),
144
- warnings,
145
- };
146
- }
147
-
148
- function latestSwingPrice(swings: TimeframeMarketAnalysis['structure']['swings'], type: 'high' | 'low'): number | null {
149
- for (let i = swings.length - 1; i >= 0; i -= 1) {
150
- if (swings[i].type === type && finite(swings[i].price)) return swings[i].price;
151
- }
152
- return null;
153
- }
154
-
155
- function countAligned(timeframes: TimeframeMarketAnalysis[], direction: SignalDirection): number {
156
- if (direction === 'LONG') return timeframes.filter(tf => tf.structure.trend === 'bullish').length;
157
- if (direction === 'SHORT') return timeframes.filter(tf => tf.structure.trend === 'bearish').length;
158
- return 0;
159
- }
160
-
161
- function countConflicts(timeframes: TimeframeMarketAnalysis[], direction: SignalDirection): number {
162
- if (direction === 'LONG') return timeframes.filter(tf => tf.structure.trend === 'bearish').length;
163
- if (direction === 'SHORT') return timeframes.filter(tf => tf.structure.trend === 'bullish').length;
164
- return 0;
165
- }
166
-
167
- function buildReasons(
168
- direction: SignalDirection,
169
- analysis: MarketAnalysis,
170
- primary: TimeframeMarketAnalysis,
171
- alignedHigher: number,
172
- ): string[] {
173
- const reasons: string[] = [
174
- `Confluência ${direction} com score ${round(Math.abs(analysis.confluence.score), 1)}.`,
175
- `Confiança estrutural em ${analysis.confluence.confidence}%.`,
176
- ];
177
- if (primary.structure.trend === (direction === 'LONG' ? 'bullish' : 'bearish')) {
178
- reasons.push(`Estrutura ${direction === 'LONG' ? 'bullish' : 'bearish'} no 15m.`);
179
- }
180
- const latestFvg = primary.smc.fairValueGaps.at(-1);
181
- const latestOrderBlock = primary.smc.orderBlocks.at(-1);
182
- if (latestFvg) reasons.push(`FVG ${latestFvg.type} no contexto.`);
183
- if (latestOrderBlock) reasons.push(`Order Block ${latestOrderBlock.type} identificado.`);
184
- if (primary.wyckoff.latestEvent) reasons.push(`Evento Wyckoff ${primary.wyckoff.latestEvent} detectado.`);
185
- if (primary.divergences.latest) reasons.push(`Divergência ${primary.divergences.latest.type} detectada.`);
186
- if (alignedHigher > 0) reasons.push(`${alignedHigher} timeframe(s) superior(es) alinhado(s).`);
187
- return reasons;
188
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
server/signal/signalRoute.ts DELETED
@@ -1,28 +0,0 @@
1
- import { Router } from 'express';
2
- import { analyzeMarket } from '../confluence/marketAnalysisService.js';
3
- import { fetchRealCandles } from '../market/candleService.js';
4
- import { generateTradeSignal } from './signalEngine.js';
5
-
6
- export const signalRouter = Router();
7
-
8
- signalRouter.get('/api/signal/:symbol', async (req, res) => {
9
- try {
10
- const symbol = decodeURIComponent(String(req.params.symbol || 'BTC/USDT')).toUpperCase().replace('-', '/');
11
- const riskPercent = Number(req.query.riskPercent ?? 1);
12
- const analysis = await analyzeMarket(symbol, req.query.refresh === 'true');
13
- const series = await fetchRealCandles(symbol, '15m', 500);
14
- const signal = generateTradeSignal(analysis, series.candles, Number.isFinite(riskPercent) ? riskPercent : 1);
15
-
16
- return res.json({
17
- success: true,
18
- source: 'Binance/OKX real OHLCV + quantitative signal engine',
19
- signal,
20
- generatedAt: Date.now(),
21
- });
22
- } catch (error) {
23
- return res.status(502).json({
24
- success: false,
25
- error: error instanceof Error ? error.message : 'Falha ao gerar sinal',
26
- });
27
- }
28
- });