@catalyst-team/poly-sdk 0.4.0 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +8 -1
  2. package/README.zh-CN.md +2 -0
  3. package/dist/src/__tests__/integration/data-api.integration.test.js +21 -18
  4. package/dist/src/__tests__/integration/data-api.integration.test.js.map +1 -1
  5. package/dist/src/catalyst/catalyst-query-service.d.ts +109 -0
  6. package/dist/src/catalyst/catalyst-query-service.d.ts.map +1 -0
  7. package/dist/src/catalyst/catalyst-query-service.js +141 -0
  8. package/dist/src/catalyst/catalyst-query-service.js.map +1 -0
  9. package/dist/src/catalyst/catalyst-realtime-service.d.ts +40 -0
  10. package/dist/src/catalyst/catalyst-realtime-service.d.ts.map +1 -0
  11. package/dist/src/catalyst/catalyst-realtime-service.js +125 -0
  12. package/dist/src/catalyst/catalyst-realtime-service.js.map +1 -0
  13. package/dist/src/catalyst/index.d.ts +4 -0
  14. package/dist/src/catalyst/index.d.ts.map +1 -0
  15. package/dist/src/catalyst/index.js +4 -0
  16. package/dist/src/catalyst/index.js.map +1 -0
  17. package/dist/src/catalyst/types.d.ts +178 -0
  18. package/dist/src/catalyst/types.d.ts.map +1 -0
  19. package/dist/src/catalyst/types.js +2 -0
  20. package/dist/src/catalyst/types.js.map +1 -0
  21. package/dist/src/clients/data-api.d.ts +75 -14
  22. package/dist/src/clients/data-api.d.ts.map +1 -1
  23. package/dist/src/clients/data-api.js +40 -18
  24. package/dist/src/clients/data-api.js.map +1 -1
  25. package/dist/src/clients/gamma-api.d.ts +54 -0
  26. package/dist/src/clients/gamma-api.d.ts.map +1 -1
  27. package/dist/src/clients/gamma-api.js.map +1 -1
  28. package/dist/src/core/order-status.d.ts +159 -0
  29. package/dist/src/core/order-status.d.ts.map +1 -0
  30. package/dist/src/core/order-status.js +254 -0
  31. package/dist/src/core/order-status.js.map +1 -0
  32. package/dist/src/core/types.d.ts +124 -0
  33. package/dist/src/core/types.d.ts.map +1 -1
  34. package/dist/src/core/types.js +120 -0
  35. package/dist/src/core/types.js.map +1 -1
  36. package/dist/src/index.d.ts +20 -4
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/index.js +28 -2
  39. package/dist/src/index.js.map +1 -1
  40. package/dist/src/insider-scan/index.d.ts +3 -0
  41. package/dist/src/insider-scan/index.d.ts.map +1 -0
  42. package/dist/src/insider-scan/index.js +3 -0
  43. package/dist/src/insider-scan/index.js.map +1 -0
  44. package/dist/src/insider-scan/insider-scan-service.d.ts +63 -0
  45. package/dist/src/insider-scan/insider-scan-service.d.ts.map +1 -0
  46. package/dist/src/insider-scan/insider-scan-service.js +153 -0
  47. package/dist/src/insider-scan/insider-scan-service.js.map +1 -0
  48. package/dist/src/insider-scan/types.d.ts +205 -0
  49. package/dist/src/insider-scan/types.d.ts.map +1 -0
  50. package/dist/src/insider-scan/types.js +7 -0
  51. package/dist/src/insider-scan/types.js.map +1 -0
  52. package/dist/src/services/ctf-detector.d.ts +215 -0
  53. package/dist/src/services/ctf-detector.d.ts.map +1 -0
  54. package/dist/src/services/ctf-detector.js +420 -0
  55. package/dist/src/services/ctf-detector.js.map +1 -0
  56. package/dist/src/services/ctf-manager.d.ts +202 -0
  57. package/dist/src/services/ctf-manager.d.ts.map +1 -0
  58. package/dist/src/services/ctf-manager.js +542 -0
  59. package/dist/src/services/ctf-manager.js.map +1 -0
  60. package/dist/src/services/market-service.d.ts +61 -0
  61. package/dist/src/services/market-service.d.ts.map +1 -1
  62. package/dist/src/services/market-service.js +58 -0
  63. package/dist/src/services/market-service.js.map +1 -1
  64. package/dist/src/services/onchain-service.d.ts +10 -2
  65. package/dist/src/services/onchain-service.d.ts.map +1 -1
  66. package/dist/src/services/onchain-service.js +8 -0
  67. package/dist/src/services/onchain-service.js.map +1 -1
  68. package/dist/src/services/order-manager.d.ts +281 -0
  69. package/dist/src/services/order-manager.d.ts.map +1 -0
  70. package/dist/src/services/order-manager.js +641 -0
  71. package/dist/src/services/order-manager.js.map +1 -0
  72. package/dist/src/services/order-manager.test.d.ts +8 -0
  73. package/dist/src/services/order-manager.test.d.ts.map +1 -0
  74. package/dist/src/services/order-manager.test.js +477 -0
  75. package/dist/src/services/order-manager.test.js.map +1 -0
  76. package/dist/src/services/smart-money-service.d.ts +420 -3
  77. package/dist/src/services/smart-money-service.d.ts.map +1 -1
  78. package/dist/src/services/smart-money-service.js +869 -3
  79. package/dist/src/services/smart-money-service.js.map +1 -1
  80. package/dist/src/services/trading-service.d.ts +94 -1
  81. package/dist/src/services/trading-service.d.ts.map +1 -1
  82. package/dist/src/services/trading-service.js +232 -1
  83. package/dist/src/services/trading-service.js.map +1 -1
  84. package/dist/src/services/wallet-service.d.ts +38 -2
  85. package/dist/src/services/wallet-service.d.ts.map +1 -1
  86. package/dist/src/services/wallet-service.js +72 -5
  87. package/dist/src/services/wallet-service.js.map +1 -1
  88. package/dist/src/signal/index.d.ts +8 -0
  89. package/dist/src/signal/index.d.ts.map +1 -0
  90. package/dist/src/signal/index.js +7 -0
  91. package/dist/src/signal/index.js.map +1 -0
  92. package/dist/src/signal/signal-service.d.ts +89 -0
  93. package/dist/src/signal/signal-service.d.ts.map +1 -0
  94. package/dist/src/signal/signal-service.js +226 -0
  95. package/dist/src/signal/signal-service.js.map +1 -0
  96. package/dist/src/signal/types.d.ts +280 -0
  97. package/dist/src/signal/types.d.ts.map +1 -0
  98. package/dist/src/signal/types.js +7 -0
  99. package/dist/src/signal/types.js.map +1 -0
  100. package/dist/src/wallet-report/index.d.ts +3 -0
  101. package/dist/src/wallet-report/index.d.ts.map +1 -0
  102. package/dist/src/wallet-report/index.js +3 -0
  103. package/dist/src/wallet-report/index.js.map +1 -0
  104. package/dist/src/wallet-report/types.d.ts +187 -0
  105. package/dist/src/wallet-report/types.d.ts.map +1 -0
  106. package/dist/src/wallet-report/types.js +7 -0
  107. package/dist/src/wallet-report/types.js.map +1 -0
  108. package/dist/src/wallet-report/wallet-report-service.d.ts +91 -0
  109. package/dist/src/wallet-report/wallet-report-service.d.ts.map +1 -0
  110. package/dist/src/wallet-report/wallet-report-service.js +208 -0
  111. package/dist/src/wallet-report/wallet-report-service.js.map +1 -0
  112. package/dist/src/wallets/hot-wallet-service.d.ts +162 -0
  113. package/dist/src/wallets/hot-wallet-service.d.ts.map +1 -0
  114. package/dist/src/wallets/hot-wallet-service.js +251 -0
  115. package/dist/src/wallets/hot-wallet-service.js.map +1 -0
  116. package/dist/src/wallets/index.d.ts +15 -0
  117. package/dist/src/wallets/index.d.ts.map +1 -0
  118. package/dist/src/wallets/index.js +26 -0
  119. package/dist/src/wallets/index.js.map +1 -0
  120. package/package.json +5 -5
  121. package/dist/__tests__/clob-api.test.d.ts +0 -5
  122. package/dist/__tests__/clob-api.test.d.ts.map +0 -1
  123. package/dist/__tests__/clob-api.test.js +0 -240
  124. package/dist/__tests__/clob-api.test.js.map +0 -1
  125. package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts +0 -12
  126. package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts.map +0 -1
  127. package/dist/__tests__/integration/arbitrage-service.integration.test.js +0 -267
  128. package/dist/__tests__/integration/arbitrage-service.integration.test.js.map +0 -1
  129. package/dist/__tests__/integration/bridge-client.integration.test.d.ts +0 -11
  130. package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +0 -1
  131. package/dist/__tests__/integration/bridge-client.integration.test.js +0 -260
  132. package/dist/__tests__/integration/bridge-client.integration.test.js.map +0 -1
  133. package/dist/__tests__/integration/clob-api.integration.test.d.ts +0 -13
  134. package/dist/__tests__/integration/clob-api.integration.test.d.ts.map +0 -1
  135. package/dist/__tests__/integration/clob-api.integration.test.js +0 -170
  136. package/dist/__tests__/integration/clob-api.integration.test.js.map +0 -1
  137. package/dist/__tests__/integration/ctf-client.integration.test.d.ts +0 -17
  138. package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +0 -1
  139. package/dist/__tests__/integration/ctf-client.integration.test.js +0 -234
  140. package/dist/__tests__/integration/ctf-client.integration.test.js.map +0 -1
  141. package/dist/__tests__/integration/data-api.integration.test.d.ts +0 -9
  142. package/dist/__tests__/integration/data-api.integration.test.d.ts.map +0 -1
  143. package/dist/__tests__/integration/data-api.integration.test.js +0 -164
  144. package/dist/__tests__/integration/data-api.integration.test.js.map +0 -1
  145. package/dist/__tests__/integration/gamma-api.integration.test.d.ts +0 -9
  146. package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +0 -1
  147. package/dist/__tests__/integration/gamma-api.integration.test.js +0 -170
  148. package/dist/__tests__/integration/gamma-api.integration.test.js.map +0 -1
  149. package/dist/__tests__/integration/market-service.integration.test.d.ts +0 -10
  150. package/dist/__tests__/integration/market-service.integration.test.d.ts.map +0 -1
  151. package/dist/__tests__/integration/market-service.integration.test.js +0 -173
  152. package/dist/__tests__/integration/market-service.integration.test.js.map +0 -1
  153. package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts +0 -10
  154. package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +0 -1
  155. package/dist/__tests__/integration/realtime-service-v2.integration.test.js +0 -307
  156. package/dist/__tests__/integration/realtime-service-v2.integration.test.js.map +0 -1
  157. package/dist/__tests__/integration/trading-service.integration.test.d.ts +0 -10
  158. package/dist/__tests__/integration/trading-service.integration.test.d.ts.map +0 -1
  159. package/dist/__tests__/integration/trading-service.integration.test.js +0 -58
  160. package/dist/__tests__/integration/trading-service.integration.test.js.map +0 -1
  161. package/dist/__tests__/test-utils.d.ts +0 -92
  162. package/dist/__tests__/test-utils.d.ts.map +0 -1
  163. package/dist/__tests__/test-utils.js +0 -143
  164. package/dist/__tests__/test-utils.js.map +0 -1
  165. package/dist/clients/bridge-client.d.ts +0 -388
  166. package/dist/clients/bridge-client.d.ts.map +0 -1
  167. package/dist/clients/bridge-client.js +0 -587
  168. package/dist/clients/bridge-client.js.map +0 -1
  169. package/dist/clients/clob-api.d.ts +0 -391
  170. package/dist/clients/clob-api.d.ts.map +0 -1
  171. package/dist/clients/clob-api.js +0 -448
  172. package/dist/clients/clob-api.js.map +0 -1
  173. package/dist/clients/ctf-client.d.ts +0 -475
  174. package/dist/clients/ctf-client.d.ts.map +0 -1
  175. package/dist/clients/ctf-client.js +0 -915
  176. package/dist/clients/ctf-client.js.map +0 -1
  177. package/dist/clients/data-api.d.ts +0 -452
  178. package/dist/clients/data-api.d.ts.map +0 -1
  179. package/dist/clients/data-api.js +0 -637
  180. package/dist/clients/data-api.js.map +0 -1
  181. package/dist/clients/gamma-api.d.ts +0 -406
  182. package/dist/clients/gamma-api.d.ts.map +0 -1
  183. package/dist/clients/gamma-api.js +0 -354
  184. package/dist/clients/gamma-api.js.map +0 -1
  185. package/dist/clients/subgraph.d.ts +0 -196
  186. package/dist/clients/subgraph.d.ts.map +0 -1
  187. package/dist/clients/subgraph.js +0 -332
  188. package/dist/clients/subgraph.js.map +0 -1
  189. package/dist/clients/trading-client.d.ts +0 -252
  190. package/dist/clients/trading-client.d.ts.map +0 -1
  191. package/dist/clients/trading-client.js +0 -543
  192. package/dist/clients/trading-client.js.map +0 -1
  193. package/dist/clients/websocket-manager.d.ts +0 -103
  194. package/dist/clients/websocket-manager.d.ts.map +0 -1
  195. package/dist/clients/websocket-manager.js +0 -200
  196. package/dist/clients/websocket-manager.js.map +0 -1
  197. package/dist/core/cache-adapter-bridge.d.ts +0 -36
  198. package/dist/core/cache-adapter-bridge.d.ts.map +0 -1
  199. package/dist/core/cache-adapter-bridge.js +0 -81
  200. package/dist/core/cache-adapter-bridge.js.map +0 -1
  201. package/dist/core/cache.d.ts +0 -43
  202. package/dist/core/cache.d.ts.map +0 -1
  203. package/dist/core/cache.js +0 -76
  204. package/dist/core/cache.js.map +0 -1
  205. package/dist/core/errors.d.ts +0 -39
  206. package/dist/core/errors.d.ts.map +0 -1
  207. package/dist/core/errors.js +0 -86
  208. package/dist/core/errors.js.map +0 -1
  209. package/dist/core/rate-limiter.d.ts +0 -33
  210. package/dist/core/rate-limiter.d.ts.map +0 -1
  211. package/dist/core/rate-limiter.js +0 -82
  212. package/dist/core/rate-limiter.js.map +0 -1
  213. package/dist/core/types.d.ts +0 -506
  214. package/dist/core/types.d.ts.map +0 -1
  215. package/dist/core/types.js +0 -49
  216. package/dist/core/types.js.map +0 -1
  217. package/dist/core/types.test.d.ts +0 -7
  218. package/dist/core/types.test.d.ts.map +0 -1
  219. package/dist/core/types.test.js +0 -122
  220. package/dist/core/types.test.js.map +0 -1
  221. package/dist/core/unified-cache.d.ts +0 -63
  222. package/dist/core/unified-cache.d.ts.map +0 -1
  223. package/dist/core/unified-cache.js +0 -114
  224. package/dist/core/unified-cache.js.map +0 -1
  225. package/dist/index.d.ts +0 -159
  226. package/dist/index.d.ts.map +0 -1
  227. package/dist/index.js +0 -262
  228. package/dist/index.js.map +0 -1
  229. package/dist/services/arbitrage-service.d.ts +0 -409
  230. package/dist/services/arbitrage-service.d.ts.map +0 -1
  231. package/dist/services/arbitrage-service.js +0 -1450
  232. package/dist/services/arbitrage-service.js.map +0 -1
  233. package/dist/services/authorization-service.d.ts +0 -97
  234. package/dist/services/authorization-service.d.ts.map +0 -1
  235. package/dist/services/authorization-service.js +0 -279
  236. package/dist/services/authorization-service.js.map +0 -1
  237. package/dist/services/binance-service.d.ts +0 -154
  238. package/dist/services/binance-service.d.ts.map +0 -1
  239. package/dist/services/binance-service.js +0 -266
  240. package/dist/services/binance-service.js.map +0 -1
  241. package/dist/services/dip-arb-service.d.ts +0 -209
  242. package/dist/services/dip-arb-service.d.ts.map +0 -1
  243. package/dist/services/dip-arb-service.js +0 -1602
  244. package/dist/services/dip-arb-service.js.map +0 -1
  245. package/dist/services/dip-arb-types.d.ts +0 -553
  246. package/dist/services/dip-arb-types.d.ts.map +0 -1
  247. package/dist/services/dip-arb-types.js +0 -164
  248. package/dist/services/dip-arb-types.js.map +0 -1
  249. package/dist/services/market-service.d.ts +0 -367
  250. package/dist/services/market-service.d.ts.map +0 -1
  251. package/dist/services/market-service.js +0 -1187
  252. package/dist/services/market-service.js.map +0 -1
  253. package/dist/services/onchain-service.d.ts +0 -309
  254. package/dist/services/onchain-service.d.ts.map +0 -1
  255. package/dist/services/onchain-service.js +0 -417
  256. package/dist/services/onchain-service.js.map +0 -1
  257. package/dist/services/realtime-service-v2.d.ts +0 -362
  258. package/dist/services/realtime-service-v2.d.ts.map +0 -1
  259. package/dist/services/realtime-service-v2.js +0 -858
  260. package/dist/services/realtime-service-v2.js.map +0 -1
  261. package/dist/services/realtime-service.d.ts +0 -82
  262. package/dist/services/realtime-service.d.ts.map +0 -1
  263. package/dist/services/realtime-service.js +0 -182
  264. package/dist/services/realtime-service.js.map +0 -1
  265. package/dist/services/smart-money-service.d.ts +0 -352
  266. package/dist/services/smart-money-service.d.ts.map +0 -1
  267. package/dist/services/smart-money-service.js +0 -582
  268. package/dist/services/smart-money-service.js.map +0 -1
  269. package/dist/services/swap-service.d.ts +0 -217
  270. package/dist/services/swap-service.d.ts.map +0 -1
  271. package/dist/services/swap-service.js +0 -695
  272. package/dist/services/swap-service.js.map +0 -1
  273. package/dist/services/trading-service.d.ts +0 -177
  274. package/dist/services/trading-service.d.ts.map +0 -1
  275. package/dist/services/trading-service.js +0 -422
  276. package/dist/services/trading-service.js.map +0 -1
  277. package/dist/services/wallet-service.d.ts +0 -316
  278. package/dist/services/wallet-service.d.ts.map +0 -1
  279. package/dist/services/wallet-service.js +0 -681
  280. package/dist/services/wallet-service.js.map +0 -1
  281. package/dist/utils/price-utils.d.ts +0 -153
  282. package/dist/utils/price-utils.d.ts.map +0 -1
  283. package/dist/utils/price-utils.js +0 -236
  284. package/dist/utils/price-utils.js.map +0 -1
  285. package/dist/utils/price-utils.test.d.ts +0 -5
  286. package/dist/utils/price-utils.test.d.ts.map +0 -1
  287. package/dist/utils/price-utils.test.js +0 -192
  288. package/dist/utils/price-utils.test.js.map +0 -1
@@ -25,6 +25,78 @@
25
25
  * ⚠️ Activity WebSocket 不会广播用户自己的交易!
26
26
  * 验证跟单结果请使用 TradingService.getTrades()
27
27
  */
28
+ /**
29
+ * Keywords for market categorization by category
30
+ */
31
+ export const CATEGORY_KEYWORDS = {
32
+ crypto: /\b(btc|bitcoin|eth|ethereum|sol|solana|xrp|crypto|doge|ada|matic)\b/i,
33
+ politics: /\b(trump|biden|election|president|senate|congress|vote|political|maga|democrat|republican)\b/i,
34
+ sports: /\b(nfl|nba|mlb|nhl|super bowl|world cup|championship|game|match|ufc|soccer|football|basketball)\b/i,
35
+ economics: /\b(fed|interest rate|inflation|gdp|recession|economic|unemployment|cpi)\b/i,
36
+ entertainment: /\b(oscar|grammy|movie|twitter|celebrity|entertainment|netflix|spotify)\b/i,
37
+ science: /\b(spacex|nasa|ai|openai|google|apple|tesla|tech|technology|science)\b/i,
38
+ other: /.*/, // Matches everything as fallback
39
+ };
40
+ /**
41
+ * Categorize a market based on its title
42
+ *
43
+ * @param title - Market title to categorize
44
+ * @returns The market category
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * import { categorizeMarket } from '@catalyst-team/poly-sdk';
49
+ *
50
+ * categorizeMarket('Will BTC hit $100k?'); // 'crypto'
51
+ * categorizeMarket('Trump wins 2024?'); // 'politics'
52
+ * categorizeMarket('Lakers win NBA?'); // 'sports'
53
+ * categorizeMarket('Random event?'); // 'other'
54
+ * ```
55
+ */
56
+ export function categorizeMarket(title) {
57
+ const lowerTitle = title.toLowerCase();
58
+ // Check each category in priority order
59
+ if (CATEGORY_KEYWORDS.crypto.test(lowerTitle))
60
+ return 'crypto';
61
+ if (CATEGORY_KEYWORDS.politics.test(lowerTitle))
62
+ return 'politics';
63
+ if (CATEGORY_KEYWORDS.sports.test(lowerTitle))
64
+ return 'sports';
65
+ if (CATEGORY_KEYWORDS.economics.test(lowerTitle))
66
+ return 'economics';
67
+ if (CATEGORY_KEYWORDS.entertainment.test(lowerTitle))
68
+ return 'entertainment';
69
+ if (CATEGORY_KEYWORDS.science.test(lowerTitle))
70
+ return 'science';
71
+ return 'other';
72
+ }
73
+ // ============================================================================
74
+ // Report Types (02-smart-money)
75
+ // ============================================================================
76
+ /**
77
+ * Category color scheme for charts
78
+ */
79
+ export const CATEGORY_COLORS = {
80
+ crypto: '#f7931a', // Bitcoin orange
81
+ politics: '#3b82f6', // Blue
82
+ sports: '#22c55e', // Green
83
+ entertainment: '#a855f7', // Purple
84
+ economics: '#eab308', // Yellow
85
+ science: '#06b6d4', // Cyan
86
+ other: '#6b7280', // Gray
87
+ };
88
+ /**
89
+ * Category labels for display
90
+ */
91
+ export const CATEGORY_LABELS = {
92
+ crypto: 'Crypto',
93
+ politics: 'Politics',
94
+ sports: 'Sports',
95
+ entertainment: 'Entertainment',
96
+ economics: 'Economics',
97
+ science: 'Science',
98
+ other: 'Other',
99
+ };
28
100
  // ============================================================================
29
101
  // SmartMoneyService
30
102
  // ============================================================================
@@ -32,21 +104,30 @@ export class SmartMoneyService {
32
104
  walletService;
33
105
  realtimeService;
34
106
  tradingService;
107
+ dataApi;
35
108
  config;
36
109
  smartMoneyCache = new Map();
37
110
  smartMoneySet = new Set();
38
111
  cacheTimestamp = 0;
39
112
  activeSubscription = null;
40
113
  tradeHandlers = new Set();
41
- constructor(walletService, realtimeService, tradingService, config = {}) {
114
+ constructor(walletService, realtimeService, tradingService, config = {}, dataApi) {
42
115
  this.walletService = walletService;
43
116
  this.realtimeService = realtimeService;
44
117
  this.tradingService = tradingService;
118
+ this.dataApi = dataApi ?? null;
45
119
  this.config = {
46
120
  minPnl: config.minPnl ?? 1000,
47
121
  cacheTtl: config.cacheTtl ?? 300000,
48
122
  };
49
123
  }
124
+ /**
125
+ * Set DataApiClient for report generation
126
+ * This allows setting the client after construction
127
+ */
128
+ setDataApiClient(dataApi) {
129
+ this.dataApi = dataApi;
130
+ }
50
131
  // ============================================================================
51
132
  // Smart Money Info
52
133
  // ============================================================================
@@ -360,8 +441,8 @@ export class SmartMoneyService {
360
441
  const limit = Math.min(options.limit ?? 50, 500);
361
442
  const sortBy = options.sortBy ?? 'pnl';
362
443
  const offset = Math.min(options.offset ?? 0, 10000);
363
- const entries = await this.walletService.getLeaderboardByPeriod(period, limit, sortBy, 'OVERALL', offset);
364
- return entries.map(e => ({
444
+ const result = await this.walletService.fetchLeaderboardByPeriod(period, limit, sortBy, 'OVERALL', offset);
445
+ const entries = result.entries.map(e => ({
365
446
  address: e.address,
366
447
  rank: e.rank,
367
448
  pnl: e.pnl,
@@ -369,7 +450,25 @@ export class SmartMoneyService {
369
450
  tradeCount: e.tradeCount,
370
451
  userName: e.userName,
371
452
  profileImage: e.profileImage,
453
+ // 社交信息
454
+ xUsername: e.xUsername,
455
+ verifiedBadge: e.verifiedBadge,
456
+ // Extended fields
457
+ totalPnl: e.totalPnl,
458
+ realizedPnl: e.realizedPnl,
459
+ unrealizedPnl: e.unrealizedPnl,
460
+ buyCount: e.buyCount,
461
+ sellCount: e.sellCount,
462
+ buyVolume: e.buyVolume,
463
+ sellVolume: e.sellVolume,
464
+ makerVolume: e.makerVolume,
465
+ takerVolume: e.takerVolume,
372
466
  }));
467
+ return {
468
+ entries,
469
+ hasMore: result.hasMore,
470
+ request: result.request,
471
+ };
373
472
  }
374
473
  // ============================================================================
375
474
  // Wallet Report - 钱包报告
@@ -564,6 +663,773 @@ export class SmartMoneyService {
564
663
  };
565
664
  }
566
665
  // ============================================================================
666
+ // Report Generation (02-smart-money)
667
+ // ============================================================================
668
+ /**
669
+ * Get daily wallet report
670
+ *
671
+ * @param address - Wallet address
672
+ * @param date - Date for the report (default: today)
673
+ *
674
+ * @example
675
+ * ```typescript
676
+ * const report = await sdk.smartMoney.getDailyReport('0x...', new Date('2026-01-08'));
677
+ * console.log(report.summary.realizedPnL);
678
+ * ```
679
+ */
680
+ async getDailyReport(address, date) {
681
+ const targetDate = date || new Date();
682
+ const dateStr = this.formatDate(targetDate);
683
+ // Get start/end of day in Unix seconds
684
+ const startOfDay = new Date(targetDate);
685
+ startOfDay.setHours(0, 0, 0, 0);
686
+ const endOfDay = new Date(targetDate);
687
+ endOfDay.setHours(23, 59, 59, 999);
688
+ const startTimestamp = Math.floor(startOfDay.getTime() / 1000);
689
+ const endTimestamp = Math.floor(endOfDay.getTime() / 1000);
690
+ // Fetch activities for the day
691
+ const activitySummary = await this.walletService.getWalletActivity(address, {
692
+ start: startTimestamp,
693
+ end: endTimestamp,
694
+ limit: 500,
695
+ });
696
+ // Fetch closed positions for the day
697
+ const closedPositions = this.dataApi
698
+ ? await this.dataApi.getClosedPositions(address, {
699
+ sortBy: 'TIMESTAMP',
700
+ sortDirection: 'DESC',
701
+ limit: 50,
702
+ })
703
+ : [];
704
+ // Filter closed positions for today
705
+ const todaysClosed = closedPositions.filter(p => {
706
+ const posDate = new Date(p.timestamp);
707
+ return posDate >= startOfDay && posDate <= endOfDay;
708
+ });
709
+ // Calculate summary
710
+ const trades = activitySummary.activities.filter(a => a.type === 'TRADE');
711
+ const buys = trades.filter(t => t.side === 'BUY');
712
+ const sells = trades.filter(t => t.side === 'SELL');
713
+ const summary = {
714
+ totalTrades: trades.length,
715
+ buyCount: buys.length,
716
+ sellCount: sells.length,
717
+ buyVolume: buys.reduce((sum, t) => sum + (t.usdcSize || 0), 0),
718
+ sellVolume: sells.reduce((sum, t) => sum + (t.usdcSize || 0), 0),
719
+ realizedPnL: todaysClosed.reduce((sum, p) => sum + p.realizedPnl, 0),
720
+ positionsClosed: todaysClosed.length,
721
+ positionsOpened: buys.filter(b => {
722
+ // Count unique new positions (approximate)
723
+ return !sells.some(s => s.conditionId === b.conditionId);
724
+ }).length,
725
+ };
726
+ // Calculate category breakdown
727
+ const categoryBreakdown = this.calculateCategoryBreakdownFromActivities(trades);
728
+ // Get significant trades (top 10 by value)
729
+ const significantTrades = trades
730
+ .map(t => ({
731
+ market: t.title || '',
732
+ conditionId: t.conditionId,
733
+ outcome: t.outcome || '',
734
+ side: t.side,
735
+ price: t.price,
736
+ size: t.size,
737
+ usdcValue: t.usdcSize || t.size * t.price,
738
+ timestamp: new Date(t.timestamp),
739
+ }))
740
+ .sort((a, b) => b.usdcValue - a.usdcValue)
741
+ .slice(0, 10);
742
+ // New positions
743
+ const newPositions = buys
744
+ .filter(b => !sells.some(s => s.conditionId === b.conditionId))
745
+ .slice(0, 10)
746
+ .map(b => ({
747
+ market: b.title || '',
748
+ conditionId: b.conditionId,
749
+ outcome: b.outcome || '',
750
+ size: b.size,
751
+ avgPrice: b.price,
752
+ }));
753
+ // Closed markets
754
+ const closedMarkets = todaysClosed.map(p => ({
755
+ market: p.title,
756
+ conditionId: p.conditionId,
757
+ outcome: p.outcome,
758
+ realizedPnL: p.realizedPnl,
759
+ closePrice: p.curPrice,
760
+ }));
761
+ return {
762
+ address,
763
+ reportDate: dateStr,
764
+ generatedAt: new Date(),
765
+ summary,
766
+ categoryBreakdown,
767
+ significantTrades,
768
+ newPositions,
769
+ closedMarkets,
770
+ };
771
+ }
772
+ /**
773
+ * Get wallet lifecycle report
774
+ *
775
+ * @param address - Wallet address
776
+ * @param options - Report options with progress callback
777
+ *
778
+ * @example
779
+ * ```typescript
780
+ * const report = await sdk.smartMoney.getLifecycleReport('0x...', {
781
+ * onProgress: (p, msg) => console.log(`${p * 100}%: ${msg}`)
782
+ * });
783
+ * console.log(report.performance.winRate);
784
+ * ```
785
+ */
786
+ async getLifecycleReport(address, options) {
787
+ const { onProgress } = options || {};
788
+ // 1. Get basic info
789
+ onProgress?.(0.1, 'Fetching profile...');
790
+ const profile = await this.walletService.getWalletProfile(address);
791
+ // 2. Get all closed positions (paginated)
792
+ onProgress?.(0.2, 'Fetching closed positions...');
793
+ const closedPositions = await this.fetchAllClosedPositions(address);
794
+ // 3. Get current positions
795
+ onProgress?.(0.6, 'Fetching current positions...');
796
+ const currentPositions = await this.walletService.getWalletPositions(address);
797
+ // 4. Calculate metrics
798
+ onProgress?.(0.8, 'Calculating metrics...');
799
+ const performance = this.calculatePerformanceMetricsFromClosed(closedPositions, currentPositions);
800
+ const categoryDistribution = this.calculateCategoryDistributionFromClosed(closedPositions);
801
+ const topMarkets = this.getTopMarkets(closedPositions, 10);
802
+ const worstMarkets = this.getWorstMarkets(closedPositions, 10);
803
+ const patterns = this.analyzeTradingPatterns(closedPositions, currentPositions);
804
+ // 5. Determine data range
805
+ let firstActivityAt = new Date();
806
+ let lastActivityAt = new Date();
807
+ let totalDays = 0;
808
+ if (closedPositions.length > 0) {
809
+ const timestamps = closedPositions.map(p => p.timestamp);
810
+ firstActivityAt = new Date(Math.min(...timestamps));
811
+ lastActivityAt = new Date(Math.max(...timestamps));
812
+ totalDays = Math.ceil((lastActivityAt.getTime() - firstActivityAt.getTime()) / (1000 * 60 * 60 * 24));
813
+ }
814
+ // 6. Current positions summary
815
+ const currentPosCategories = this.calculateCategoryDistributionFromPositions(currentPositions);
816
+ const currentPositionsSummary = {
817
+ count: currentPositions.length,
818
+ totalValue: currentPositions.reduce((sum, p) => sum + (p.currentValue ?? p.size * (p.curPrice ?? 0)), 0),
819
+ unrealizedPnL: currentPositions.reduce((sum, p) => sum + (p.cashPnl ?? 0), 0),
820
+ categories: currentPosCategories,
821
+ };
822
+ onProgress?.(1.0, 'Report generated');
823
+ return {
824
+ address,
825
+ generatedAt: new Date(),
826
+ dataRange: {
827
+ firstActivityAt,
828
+ lastActivityAt,
829
+ totalDays,
830
+ },
831
+ performance,
832
+ categoryDistribution,
833
+ topMarkets,
834
+ worstMarkets,
835
+ patterns,
836
+ currentPositions: currentPositionsSummary,
837
+ };
838
+ }
839
+ /**
840
+ * Get wallet chart data
841
+ *
842
+ * @param address - Wallet address
843
+ *
844
+ * @example
845
+ * ```typescript
846
+ * const chartData = await sdk.smartMoney.getWalletChartData('0x...');
847
+ * // Use chartData.tradeDistribution with recharts
848
+ * ```
849
+ */
850
+ async getWalletChartData(address) {
851
+ // Fetch data
852
+ const closedPositions = await this.fetchAllClosedPositions(address);
853
+ const currentPositions = await this.walletService.getWalletPositions(address);
854
+ // Trade distribution (by count)
855
+ const tradeDistribution = this.buildPieChart('Trade Distribution', closedPositions.map(p => ({ title: p.title, value: 1 })), 'count');
856
+ // Position distribution (by value)
857
+ const positionDistribution = this.buildPieChart('Position Distribution', currentPositions.map(p => ({
858
+ title: p.title,
859
+ value: p.currentValue ?? p.size * (p.curPrice ?? 0),
860
+ })), 'value');
861
+ // Profit distribution (by PnL)
862
+ const profitPositions = closedPositions.filter(p => p.realizedPnl > 0);
863
+ const profitDistribution = this.buildPieChart('Profit Distribution', profitPositions.map(p => ({ title: p.title, value: p.realizedPnl })), 'pnl');
864
+ // Determine data range
865
+ let fromDate = new Date();
866
+ let toDate = new Date();
867
+ if (closedPositions.length > 0) {
868
+ const timestamps = closedPositions.map(p => p.timestamp);
869
+ fromDate = new Date(Math.min(...timestamps));
870
+ toDate = new Date(Math.max(...timestamps));
871
+ }
872
+ return {
873
+ tradeDistribution,
874
+ positionDistribution,
875
+ profitDistribution,
876
+ metadata: {
877
+ address,
878
+ generatedAt: new Date(),
879
+ dataRange: { from: fromDate, to: toDate },
880
+ },
881
+ };
882
+ }
883
+ /**
884
+ * Generate a text analysis report for a wallet
885
+ *
886
+ * @param address - Wallet address
887
+ * @param options - Report options with progress callback
888
+ *
889
+ * @example
890
+ * ```typescript
891
+ * const textReport = await sdk.smartMoney.generateTextReport('0x...');
892
+ * console.log(textReport.markdown);
893
+ * ```
894
+ */
895
+ async generateTextReport(address, options) {
896
+ const { onProgress } = options || {};
897
+ // Get lifecycle report data
898
+ onProgress?.(0.1, 'Fetching wallet data...');
899
+ const report = await this.getLifecycleReport(address, {
900
+ onProgress: (p, msg) => onProgress?.(0.1 + p * 0.7, msg),
901
+ });
902
+ // Get chart data for category distribution
903
+ onProgress?.(0.8, 'Analyzing patterns...');
904
+ const chartData = await this.getWalletChartData(address);
905
+ // Generate text report
906
+ onProgress?.(0.9, 'Generating report...');
907
+ const markdown = this.buildTextReport(address, report, chartData);
908
+ onProgress?.(1.0, 'Report complete');
909
+ return {
910
+ address,
911
+ generatedAt: new Date(),
912
+ markdown,
913
+ metrics: {
914
+ totalPnL: report.performance.realizedPnL + report.currentPositions.unrealizedPnL,
915
+ winRate: report.performance.winRate,
916
+ profitFactor: report.performance.profitFactor,
917
+ totalMarketsTraded: report.performance.totalMarketsTraded,
918
+ totalDays: report.dataRange.totalDays,
919
+ },
920
+ };
921
+ }
922
+ /**
923
+ * Build markdown text report from data
924
+ */
925
+ buildTextReport(address, report, chartData) {
926
+ const { performance, categoryDistribution, topMarkets, worstMarkets, patterns, currentPositions, dataRange } = report;
927
+ const totalPnL = performance.realizedPnL + currentPositions.unrealizedPnL;
928
+ const formatPnL = (v) => v >= 0 ? `+$${v.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` : `-$${Math.abs(v).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
929
+ const formatPercent = (v) => `${(v * 100).toFixed(1)}%`;
930
+ const formatDate = (d) => d.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
931
+ // Determine trading style
932
+ const tradingStyle = this.analyzeTradingStyle(patterns, categoryDistribution);
933
+ const riskAssessment = this.assessRisk(performance, patterns, categoryDistribution);
934
+ const recommendation = this.generateRecommendation(performance, riskAssessment, tradingStyle);
935
+ const sections = [];
936
+ // Header
937
+ sections.push(`# Wallet Analysis Report\n`);
938
+ sections.push(`**Address**: \`${address.slice(0, 10)}...${address.slice(-8)}\``);
939
+ sections.push(`**Report Date**: ${formatDate(new Date())}`);
940
+ sections.push(`**Data Range**: ${formatDate(dataRange.firstActivityAt)} - ${formatDate(dataRange.lastActivityAt)} (${dataRange.totalDays} days)\n`);
941
+ // Executive Summary
942
+ sections.push(`## Executive Summary\n`);
943
+ sections.push(`| Metric | Value |`);
944
+ sections.push(`|--------|-------|`);
945
+ sections.push(`| Total PnL | ${formatPnL(totalPnL)} |`);
946
+ sections.push(`| Realized PnL | ${formatPnL(performance.realizedPnL)} |`);
947
+ sections.push(`| Unrealized PnL | ${formatPnL(currentPositions.unrealizedPnL)} |`);
948
+ sections.push(`| Win Rate | ${formatPercent(performance.winRate)} |`);
949
+ sections.push(`| Profit Factor | ${performance.profitFactor.toFixed(2)} |`);
950
+ sections.push(`| Markets Traded | ${performance.totalMarketsTraded} |`);
951
+ sections.push(`| Current Positions | ${currentPositions.count} |`);
952
+ sections.push(``);
953
+ // Trading Style
954
+ sections.push(`## Trading Style Analysis\n`);
955
+ sections.push(`- **Position Preference**: ${tradingStyle.positionPreference}`);
956
+ sections.push(`- **Trading Frequency**: ${tradingStyle.tradingFrequency}`);
957
+ sections.push(`- **Position Management**: ${tradingStyle.positionManagement}`);
958
+ sections.push(`- **Primary Focus**: ${tradingStyle.primaryFocus}`);
959
+ sections.push(``);
960
+ // Category Distribution
961
+ sections.push(`## Market Category Distribution\n`);
962
+ sections.push(`| Category | Trades | PnL | Share |`);
963
+ sections.push(`|----------|--------|-----|-------|`);
964
+ for (const cat of categoryDistribution.slice(0, 6)) {
965
+ sections.push(`| ${CATEGORY_LABELS[cat.category]} | ${cat.tradeCount} | ${formatPnL(cat.pnl)} | ${cat.percentage.toFixed(1)}% |`);
966
+ }
967
+ sections.push(``);
968
+ // Top Markets
969
+ if (topMarkets.length > 0) {
970
+ sections.push(`## Best Performing Markets\n`);
971
+ for (let i = 0; i < Math.min(5, topMarkets.length); i++) {
972
+ const m = topMarkets[i];
973
+ sections.push(`${i + 1}. **${m.market}**: ${formatPnL(m.pnl)}`);
974
+ }
975
+ sections.push(``);
976
+ }
977
+ // Worst Markets
978
+ if (worstMarkets.length > 0) {
979
+ sections.push(`## Worst Performing Markets\n`);
980
+ for (let i = 0; i < Math.min(5, worstMarkets.length); i++) {
981
+ const m = worstMarkets[i];
982
+ sections.push(`${i + 1}. **${m.market}**: ${formatPnL(m.pnl)}`);
983
+ }
984
+ sections.push(``);
985
+ }
986
+ // Risk Assessment
987
+ sections.push(`## Risk Assessment\n`);
988
+ sections.push(`- **Concentration Risk**: ${riskAssessment.concentrationRisk}`);
989
+ sections.push(`- **Drawdown Risk**: ${riskAssessment.drawdownRisk}`);
990
+ sections.push(`- **Overall Risk Level**: ${riskAssessment.overallRisk}`);
991
+ sections.push(``);
992
+ // Copy Trading Recommendation
993
+ sections.push(`## Copy Trading Recommendation\n`);
994
+ sections.push(`**Verdict**: ${recommendation.verdict}\n`);
995
+ sections.push(`${recommendation.reasoning}\n`);
996
+ if (recommendation.suitableMarkets.length > 0) {
997
+ sections.push(`**Suitable Markets**: ${recommendation.suitableMarkets.join(', ')}`);
998
+ }
999
+ if (recommendation.avoidMarkets.length > 0) {
1000
+ sections.push(`**Markets to Avoid**: ${recommendation.avoidMarkets.join(', ')}`);
1001
+ }
1002
+ if (recommendation.warnings.length > 0) {
1003
+ sections.push(`\n**Warnings**:`);
1004
+ for (const w of recommendation.warnings) {
1005
+ sections.push(`- ${w}`);
1006
+ }
1007
+ }
1008
+ return sections.join('\n');
1009
+ }
1010
+ /**
1011
+ * Analyze trading style from patterns
1012
+ */
1013
+ analyzeTradingStyle(patterns, categoryDistribution) {
1014
+ // Position preference
1015
+ let positionPreference;
1016
+ if (patterns.preferredSide === 'YES') {
1017
+ positionPreference = 'YES-biased (tends to bet on positive outcomes)';
1018
+ }
1019
+ else if (patterns.preferredSide === 'NO') {
1020
+ positionPreference = 'NO-biased (tends to bet against outcomes)';
1021
+ }
1022
+ else {
1023
+ positionPreference = 'Balanced (no strong directional bias)';
1024
+ }
1025
+ // Trading frequency
1026
+ let tradingFrequency;
1027
+ if (patterns.avgTradesPerDay > 10) {
1028
+ tradingFrequency = 'High-frequency (>10 trades/day)';
1029
+ }
1030
+ else if (patterns.avgTradesPerDay > 3) {
1031
+ tradingFrequency = 'Active (3-10 trades/day)';
1032
+ }
1033
+ else if (patterns.avgTradesPerDay > 1) {
1034
+ tradingFrequency = 'Moderate (1-3 trades/day)';
1035
+ }
1036
+ else {
1037
+ tradingFrequency = 'Low-frequency (<1 trade/day)';
1038
+ }
1039
+ // Position management
1040
+ let positionManagement;
1041
+ if (patterns.positionConcentration > 0.5) {
1042
+ positionManagement = 'Concentrated (high single-position exposure)';
1043
+ }
1044
+ else if (patterns.positionConcentration > 0.25) {
1045
+ positionManagement = 'Moderate diversification';
1046
+ }
1047
+ else {
1048
+ positionManagement = 'Well-diversified';
1049
+ }
1050
+ // Primary focus
1051
+ const topCategory = categoryDistribution[0];
1052
+ const primaryFocus = topCategory
1053
+ ? `${CATEGORY_LABELS[topCategory.category]} (${topCategory.percentage.toFixed(0)}% of trades)`
1054
+ : 'Diversified';
1055
+ return {
1056
+ positionPreference,
1057
+ tradingFrequency,
1058
+ positionManagement,
1059
+ primaryFocus,
1060
+ };
1061
+ }
1062
+ /**
1063
+ * Assess risk profile
1064
+ */
1065
+ assessRisk(performance, patterns, categoryDistribution) {
1066
+ // Concentration risk
1067
+ const topCategoryShare = categoryDistribution[0]?.percentage || 0;
1068
+ let concentrationRisk;
1069
+ if (topCategoryShare > 70) {
1070
+ concentrationRisk = 'High (>70% in single category)';
1071
+ }
1072
+ else if (topCategoryShare > 50) {
1073
+ concentrationRisk = 'Medium (50-70% in top category)';
1074
+ }
1075
+ else {
1076
+ concentrationRisk = 'Low (well diversified)';
1077
+ }
1078
+ // Drawdown risk
1079
+ const maxLossRatio = performance.maxLoss / Math.max(Math.abs(performance.realizedPnL), 1);
1080
+ let drawdownRisk;
1081
+ if (maxLossRatio > 0.5) {
1082
+ drawdownRisk = 'High (max loss >50% of total PnL)';
1083
+ }
1084
+ else if (maxLossRatio > 0.25) {
1085
+ drawdownRisk = 'Medium';
1086
+ }
1087
+ else {
1088
+ drawdownRisk = 'Low';
1089
+ }
1090
+ // Overall risk
1091
+ let overallRisk;
1092
+ if (performance.winRate < 0.4 || performance.profitFactor < 1) {
1093
+ overallRisk = 'HIGH - Unprofitable strategy';
1094
+ }
1095
+ else if (performance.winRate < 0.5 || performance.profitFactor < 1.5) {
1096
+ overallRisk = 'MEDIUM - Moderate performance';
1097
+ }
1098
+ else if (patterns.positionConcentration > 0.5) {
1099
+ overallRisk = 'MEDIUM - High concentration';
1100
+ }
1101
+ else {
1102
+ overallRisk = 'LOW - Solid track record';
1103
+ }
1104
+ return { concentrationRisk, drawdownRisk, overallRisk };
1105
+ }
1106
+ /**
1107
+ * Generate copy trading recommendation
1108
+ */
1109
+ generateRecommendation(performance, risk, style) {
1110
+ const warnings = [];
1111
+ const suitableMarkets = [];
1112
+ const avoidMarkets = [];
1113
+ // Determine verdict
1114
+ let verdict;
1115
+ let reasoning;
1116
+ if (performance.winRate >= 0.6 && performance.profitFactor >= 1.5) {
1117
+ verdict = 'RECOMMENDED';
1118
+ reasoning = `This wallet shows consistent profitability with a ${(performance.winRate * 100).toFixed(0)}% win rate and ${performance.profitFactor.toFixed(1)}x profit factor. The trading pattern is suitable for copy trading.`;
1119
+ }
1120
+ else if (performance.winRate >= 0.5 && performance.profitFactor >= 1.2) {
1121
+ verdict = 'CAUTIOUSLY RECOMMENDED';
1122
+ reasoning = `This wallet is profitable but with moderate consistency. Consider following with smaller position sizes.`;
1123
+ }
1124
+ else if (performance.realizedPnL > 0) {
1125
+ verdict = 'NOT RECOMMENDED';
1126
+ reasoning = `While overall profitable, the low win rate (${(performance.winRate * 100).toFixed(0)}%) or profit factor (${performance.profitFactor.toFixed(1)}x) suggests inconsistent performance.`;
1127
+ }
1128
+ else {
1129
+ verdict = 'AVOID';
1130
+ reasoning = `This wallet has negative overall performance. Not suitable for copy trading.`;
1131
+ }
1132
+ // Add warnings
1133
+ if (risk.overallRisk.includes('HIGH')) {
1134
+ warnings.push('High risk profile detected');
1135
+ }
1136
+ if (performance.losingMarkets > performance.winningMarkets) {
1137
+ warnings.push('More losing markets than winning markets');
1138
+ }
1139
+ if (style.tradingFrequency.includes('High-frequency')) {
1140
+ warnings.push('High-frequency trading may incur significant fees when copying');
1141
+ }
1142
+ return {
1143
+ verdict,
1144
+ reasoning,
1145
+ suitableMarkets,
1146
+ avoidMarkets,
1147
+ warnings,
1148
+ };
1149
+ }
1150
+ // ============================================================================
1151
+ // Report Helper Methods
1152
+ // ============================================================================
1153
+ /**
1154
+ * Categorize market based on title keywords
1155
+ */
1156
+ categorizeMarket(title) {
1157
+ const lowerTitle = title.toLowerCase();
1158
+ // Crypto
1159
+ if (/\b(btc|bitcoin|eth|ethereum|sol|solana|xrp|crypto|doge|ada|matic)\b/.test(lowerTitle)) {
1160
+ return 'crypto';
1161
+ }
1162
+ // Politics
1163
+ if (/\b(trump|biden|election|president|senate|congress|vote|political|maga|democrat|republican)\b/.test(lowerTitle)) {
1164
+ return 'politics';
1165
+ }
1166
+ // Sports
1167
+ if (/\b(nfl|nba|mlb|nhl|super bowl|world cup|championship|game|match|ufc|soccer|football|basketball)\b/.test(lowerTitle)) {
1168
+ return 'sports';
1169
+ }
1170
+ // Economics
1171
+ if (/\b(fed|interest rate|inflation|gdp|recession|economic|unemployment|cpi)\b/.test(lowerTitle)) {
1172
+ return 'economics';
1173
+ }
1174
+ // Entertainment
1175
+ if (/\b(oscar|grammy|movie|twitter|celebrity|entertainment|netflix|spotify)\b/.test(lowerTitle)) {
1176
+ return 'entertainment';
1177
+ }
1178
+ // Science
1179
+ if (/\b(spacex|nasa|ai|openai|google|apple|tesla|tech|technology|science)\b/.test(lowerTitle)) {
1180
+ return 'science';
1181
+ }
1182
+ return 'other';
1183
+ }
1184
+ /**
1185
+ * Fetch all closed positions with pagination
1186
+ */
1187
+ async fetchAllClosedPositions(address) {
1188
+ if (!this.dataApi) {
1189
+ return [];
1190
+ }
1191
+ const allPositions = [];
1192
+ let offset = 0;
1193
+ const limit = 50;
1194
+ const maxIterations = 200; // Max 10000 positions
1195
+ for (let i = 0; i < maxIterations; i++) {
1196
+ const result = await this.dataApi.getClosedPositions(address, {
1197
+ limit,
1198
+ offset,
1199
+ sortBy: 'TIMESTAMP',
1200
+ sortDirection: 'DESC',
1201
+ });
1202
+ if (result.length === 0)
1203
+ break;
1204
+ allPositions.push(...result);
1205
+ offset += limit;
1206
+ // If less than limit returned, we've reached the end
1207
+ if (result.length < limit)
1208
+ break;
1209
+ }
1210
+ return allPositions;
1211
+ }
1212
+ /**
1213
+ * Calculate performance metrics from closed positions
1214
+ */
1215
+ calculatePerformanceMetricsFromClosed(closedPositions, currentPositions) {
1216
+ const wins = closedPositions.filter(p => p.realizedPnl > 0);
1217
+ const losses = closedPositions.filter(p => p.realizedPnl < 0);
1218
+ const totalWinAmount = wins.reduce((sum, p) => sum + p.realizedPnl, 0);
1219
+ const totalLossAmount = Math.abs(losses.reduce((sum, p) => sum + p.realizedPnl, 0));
1220
+ const realizedPnL = closedPositions.reduce((sum, p) => sum + p.realizedPnl, 0);
1221
+ const unrealizedPnL = currentPositions.reduce((sum, p) => sum + (p.cashPnl ?? 0), 0);
1222
+ return {
1223
+ totalPnL: realizedPnL + unrealizedPnL,
1224
+ realizedPnL,
1225
+ unrealizedPnL,
1226
+ totalVolume: closedPositions.reduce((sum, p) => sum + p.totalBought, 0),
1227
+ winRate: closedPositions.length > 0 ? wins.length / closedPositions.length : 0,
1228
+ profitFactor: totalLossAmount > 0 ? totalWinAmount / totalLossAmount : totalWinAmount,
1229
+ avgWin: wins.length > 0 ? totalWinAmount / wins.length : 0,
1230
+ avgLoss: losses.length > 0 ? totalLossAmount / losses.length : 0,
1231
+ maxWin: wins.length > 0 ? Math.max(...wins.map(p => p.realizedPnl)) : 0,
1232
+ maxLoss: losses.length > 0 ? Math.max(...losses.map(p => Math.abs(p.realizedPnl))) : 0,
1233
+ totalMarketsTraded: closedPositions.length,
1234
+ winningMarkets: wins.length,
1235
+ losingMarkets: losses.length,
1236
+ };
1237
+ }
1238
+ /**
1239
+ * Calculate category distribution from closed positions
1240
+ */
1241
+ calculateCategoryDistributionFromClosed(positions) {
1242
+ const categoryMap = new Map();
1243
+ for (const pos of positions) {
1244
+ const category = this.categorizeMarket(pos.title);
1245
+ const current = categoryMap.get(category) || { count: 0, volume: 0, pnl: 0 };
1246
+ categoryMap.set(category, {
1247
+ count: current.count + 1,
1248
+ volume: current.volume + pos.totalBought,
1249
+ pnl: current.pnl + pos.realizedPnl,
1250
+ });
1251
+ }
1252
+ const total = positions.length;
1253
+ return Array.from(categoryMap.entries())
1254
+ .map(([category, stats]) => ({
1255
+ category,
1256
+ tradeCount: stats.count,
1257
+ volume: stats.volume,
1258
+ pnl: stats.pnl,
1259
+ percentage: total > 0 ? (stats.count / total) * 100 : 0,
1260
+ }))
1261
+ .sort((a, b) => b.tradeCount - a.tradeCount);
1262
+ }
1263
+ /**
1264
+ * Calculate category distribution from current positions
1265
+ */
1266
+ calculateCategoryDistributionFromPositions(positions) {
1267
+ const categoryMap = new Map();
1268
+ for (const pos of positions) {
1269
+ const category = this.categorizeMarket(pos.title || '');
1270
+ const current = categoryMap.get(category) || { count: 0, volume: 0, pnl: 0 };
1271
+ categoryMap.set(category, {
1272
+ count: current.count + 1,
1273
+ volume: current.volume + (pos.currentValue ?? pos.size * (pos.curPrice ?? 0)),
1274
+ pnl: current.pnl + (pos.cashPnl ?? 0),
1275
+ });
1276
+ }
1277
+ const total = positions.length;
1278
+ return Array.from(categoryMap.entries())
1279
+ .map(([category, stats]) => ({
1280
+ category,
1281
+ tradeCount: stats.count,
1282
+ volume: stats.volume,
1283
+ pnl: stats.pnl,
1284
+ percentage: total > 0 ? (stats.count / total) * 100 : 0,
1285
+ }))
1286
+ .sort((a, b) => b.tradeCount - a.tradeCount);
1287
+ }
1288
+ /**
1289
+ * Calculate category breakdown from activity trades
1290
+ */
1291
+ calculateCategoryBreakdownFromActivities(trades) {
1292
+ const categoryMap = new Map();
1293
+ for (const trade of trades) {
1294
+ const category = this.categorizeMarket(trade.title || '');
1295
+ const current = categoryMap.get(category) || { count: 0, volume: 0 };
1296
+ categoryMap.set(category, {
1297
+ count: current.count + 1,
1298
+ volume: current.volume + (trade.usdcSize || trade.size * trade.price),
1299
+ });
1300
+ }
1301
+ const total = trades.length;
1302
+ return Array.from(categoryMap.entries())
1303
+ .map(([category, stats]) => ({
1304
+ category,
1305
+ tradeCount: stats.count,
1306
+ volume: stats.volume,
1307
+ pnl: 0, // Not available from trades
1308
+ percentage: total > 0 ? (stats.count / total) * 100 : 0,
1309
+ }))
1310
+ .sort((a, b) => b.tradeCount - a.tradeCount);
1311
+ }
1312
+ /**
1313
+ * Get top markets by PnL
1314
+ */
1315
+ getTopMarkets(positions, limit) {
1316
+ return positions
1317
+ .filter(p => p.realizedPnl > 0)
1318
+ .sort((a, b) => b.realizedPnl - a.realizedPnl)
1319
+ .slice(0, limit)
1320
+ .map(p => ({
1321
+ market: p.title,
1322
+ conditionId: p.conditionId,
1323
+ category: this.categorizeMarket(p.title),
1324
+ pnl: p.realizedPnl,
1325
+ volume: p.totalBought,
1326
+ tradeCount: 1, // Each closed position is one market
1327
+ outcome: 'win',
1328
+ avgPrice: p.avgPrice,
1329
+ closePrice: p.curPrice,
1330
+ }));
1331
+ }
1332
+ /**
1333
+ * Get worst markets by PnL
1334
+ */
1335
+ getWorstMarkets(positions, limit) {
1336
+ return positions
1337
+ .filter(p => p.realizedPnl < 0)
1338
+ .sort((a, b) => a.realizedPnl - b.realizedPnl)
1339
+ .slice(0, limit)
1340
+ .map(p => ({
1341
+ market: p.title,
1342
+ conditionId: p.conditionId,
1343
+ category: this.categorizeMarket(p.title),
1344
+ pnl: p.realizedPnl,
1345
+ volume: p.totalBought,
1346
+ tradeCount: 1,
1347
+ outcome: 'lose',
1348
+ avgPrice: p.avgPrice,
1349
+ closePrice: p.curPrice,
1350
+ }));
1351
+ }
1352
+ /**
1353
+ * Analyze trading patterns
1354
+ */
1355
+ analyzeTradingPatterns(closedPositions, currentPositions) {
1356
+ // Calculate average trades per day/week
1357
+ let avgTradesPerDay = 0;
1358
+ let avgTradesPerWeek = 0;
1359
+ if (closedPositions.length > 0) {
1360
+ const timestamps = closedPositions.map(p => p.timestamp);
1361
+ const firstDate = new Date(Math.min(...timestamps));
1362
+ const lastDate = new Date(Math.max(...timestamps));
1363
+ const totalDays = Math.max(1, Math.ceil((lastDate.getTime() - firstDate.getTime()) / (1000 * 60 * 60 * 24)));
1364
+ avgTradesPerDay = closedPositions.length / totalDays;
1365
+ avgTradesPerWeek = avgTradesPerDay * 7;
1366
+ }
1367
+ // Determine preferred side
1368
+ const yesPositions = currentPositions.filter(p => p.outcome?.toLowerCase() === 'yes' || p.outcome?.toLowerCase() === 'up');
1369
+ const noPositions = currentPositions.filter(p => p.outcome?.toLowerCase() === 'no' || p.outcome?.toLowerCase() === 'down');
1370
+ let preferredSide = 'balanced';
1371
+ if (yesPositions.length > noPositions.length * 1.5) {
1372
+ preferredSide = 'YES';
1373
+ }
1374
+ else if (noPositions.length > yesPositions.length * 1.5) {
1375
+ preferredSide = 'NO';
1376
+ }
1377
+ // Average position size
1378
+ const avgPositionSize = closedPositions.length > 0
1379
+ ? closedPositions.reduce((sum, p) => sum + p.totalBought, 0) / closedPositions.length
1380
+ : 0;
1381
+ // Top categories
1382
+ const categoryStats = this.calculateCategoryDistributionFromClosed(closedPositions);
1383
+ const topCategories = categoryStats
1384
+ .slice(0, 3)
1385
+ .map(c => c.category);
1386
+ // Position concentration (max single position share)
1387
+ const totalValue = currentPositions.reduce((sum, p) => sum + (p.currentValue ?? p.size * (p.curPrice ?? 0)), 0);
1388
+ const maxPositionValue = currentPositions.length > 0
1389
+ ? Math.max(...currentPositions.map(p => p.currentValue ?? p.size * (p.curPrice ?? 0)))
1390
+ : 0;
1391
+ const positionConcentration = totalValue > 0 ? maxPositionValue / totalValue : 0;
1392
+ return {
1393
+ avgTradesPerDay,
1394
+ avgTradesPerWeek,
1395
+ preferredSide,
1396
+ avgPositionSize,
1397
+ avgHoldingDays: 0, // Would need more data to calculate
1398
+ topCategories,
1399
+ positionConcentration,
1400
+ };
1401
+ }
1402
+ /**
1403
+ * Build pie chart data
1404
+ */
1405
+ buildPieChart(name, items, _valueField) {
1406
+ const categoryMap = new Map();
1407
+ for (const item of items) {
1408
+ const category = this.categorizeMarket(item.title);
1409
+ const current = categoryMap.get(category) || 0;
1410
+ categoryMap.set(category, current + item.value);
1411
+ }
1412
+ const total = Array.from(categoryMap.values()).reduce((a, b) => a + b, 0);
1413
+ const data = Array.from(categoryMap.entries())
1414
+ .map(([category, value]) => ({
1415
+ name: CATEGORY_LABELS[category],
1416
+ value,
1417
+ percentage: total > 0 ? (value / total) * 100 : 0,
1418
+ color: CATEGORY_COLORS[category],
1419
+ }))
1420
+ .sort((a, b) => b.value - a.value);
1421
+ return { name, data, total };
1422
+ }
1423
+ /**
1424
+ * Format date as YYYY-MM-DD
1425
+ */
1426
+ formatDate(date) {
1427
+ const year = date.getFullYear();
1428
+ const month = String(date.getMonth() + 1).padStart(2, '0');
1429
+ const day = String(date.getDate()).padStart(2, '0');
1430
+ return `${year}-${month}-${day}`;
1431
+ }
1432
+ // ============================================================================
567
1433
  // Utilities
568
1434
  // ============================================================================
569
1435
  isCacheValid() {