@catalyst-team/poly-sdk 0.4.0 → 0.4.3

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 (264) 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/index.d.ts +15 -4
  29. package/dist/src/index.d.ts.map +1 -1
  30. package/dist/src/index.js +22 -2
  31. package/dist/src/index.js.map +1 -1
  32. package/dist/src/insider-scan/index.d.ts +3 -0
  33. package/dist/src/insider-scan/index.d.ts.map +1 -0
  34. package/dist/src/insider-scan/index.js +3 -0
  35. package/dist/src/insider-scan/index.js.map +1 -0
  36. package/dist/src/insider-scan/insider-scan-service.d.ts +63 -0
  37. package/dist/src/insider-scan/insider-scan-service.d.ts.map +1 -0
  38. package/dist/src/insider-scan/insider-scan-service.js +153 -0
  39. package/dist/src/insider-scan/insider-scan-service.js.map +1 -0
  40. package/dist/src/insider-scan/types.d.ts +205 -0
  41. package/dist/src/insider-scan/types.d.ts.map +1 -0
  42. package/dist/src/insider-scan/types.js +7 -0
  43. package/dist/src/insider-scan/types.js.map +1 -0
  44. package/dist/src/services/market-service.d.ts +61 -0
  45. package/dist/src/services/market-service.d.ts.map +1 -1
  46. package/dist/src/services/market-service.js +58 -0
  47. package/dist/src/services/market-service.js.map +1 -1
  48. package/dist/src/services/onchain-service.d.ts +10 -2
  49. package/dist/src/services/onchain-service.d.ts.map +1 -1
  50. package/dist/src/services/onchain-service.js +8 -0
  51. package/dist/src/services/onchain-service.js.map +1 -1
  52. package/dist/src/services/smart-money-service.d.ts +420 -3
  53. package/dist/src/services/smart-money-service.d.ts.map +1 -1
  54. package/dist/src/services/smart-money-service.js +869 -3
  55. package/dist/src/services/smart-money-service.js.map +1 -1
  56. package/dist/src/services/trading-service.d.ts +5 -0
  57. package/dist/src/services/trading-service.d.ts.map +1 -1
  58. package/dist/src/services/trading-service.js +5 -0
  59. package/dist/src/services/trading-service.js.map +1 -1
  60. package/dist/src/services/wallet-service.d.ts +38 -2
  61. package/dist/src/services/wallet-service.d.ts.map +1 -1
  62. package/dist/src/services/wallet-service.js +72 -5
  63. package/dist/src/services/wallet-service.js.map +1 -1
  64. package/dist/src/signal/index.d.ts +8 -0
  65. package/dist/src/signal/index.d.ts.map +1 -0
  66. package/dist/src/signal/index.js +7 -0
  67. package/dist/src/signal/index.js.map +1 -0
  68. package/dist/src/signal/signal-service.d.ts +89 -0
  69. package/dist/src/signal/signal-service.d.ts.map +1 -0
  70. package/dist/src/signal/signal-service.js +226 -0
  71. package/dist/src/signal/signal-service.js.map +1 -0
  72. package/dist/src/signal/types.d.ts +280 -0
  73. package/dist/src/signal/types.d.ts.map +1 -0
  74. package/dist/src/signal/types.js +7 -0
  75. package/dist/src/signal/types.js.map +1 -0
  76. package/dist/src/wallet-report/index.d.ts +3 -0
  77. package/dist/src/wallet-report/index.d.ts.map +1 -0
  78. package/dist/src/wallet-report/index.js +3 -0
  79. package/dist/src/wallet-report/index.js.map +1 -0
  80. package/dist/src/wallet-report/types.d.ts +187 -0
  81. package/dist/src/wallet-report/types.d.ts.map +1 -0
  82. package/dist/src/wallet-report/types.js +7 -0
  83. package/dist/src/wallet-report/types.js.map +1 -0
  84. package/dist/src/wallet-report/wallet-report-service.d.ts +91 -0
  85. package/dist/src/wallet-report/wallet-report-service.d.ts.map +1 -0
  86. package/dist/src/wallet-report/wallet-report-service.js +208 -0
  87. package/dist/src/wallet-report/wallet-report-service.js.map +1 -0
  88. package/dist/src/wallets/hot-wallet-service.d.ts +162 -0
  89. package/dist/src/wallets/hot-wallet-service.d.ts.map +1 -0
  90. package/dist/src/wallets/hot-wallet-service.js +251 -0
  91. package/dist/src/wallets/hot-wallet-service.js.map +1 -0
  92. package/dist/src/wallets/index.d.ts +15 -0
  93. package/dist/src/wallets/index.d.ts.map +1 -0
  94. package/dist/src/wallets/index.js +26 -0
  95. package/dist/src/wallets/index.js.map +1 -0
  96. package/package.json +5 -5
  97. package/dist/__tests__/clob-api.test.d.ts +0 -5
  98. package/dist/__tests__/clob-api.test.d.ts.map +0 -1
  99. package/dist/__tests__/clob-api.test.js +0 -240
  100. package/dist/__tests__/clob-api.test.js.map +0 -1
  101. package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts +0 -12
  102. package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts.map +0 -1
  103. package/dist/__tests__/integration/arbitrage-service.integration.test.js +0 -267
  104. package/dist/__tests__/integration/arbitrage-service.integration.test.js.map +0 -1
  105. package/dist/__tests__/integration/bridge-client.integration.test.d.ts +0 -11
  106. package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +0 -1
  107. package/dist/__tests__/integration/bridge-client.integration.test.js +0 -260
  108. package/dist/__tests__/integration/bridge-client.integration.test.js.map +0 -1
  109. package/dist/__tests__/integration/clob-api.integration.test.d.ts +0 -13
  110. package/dist/__tests__/integration/clob-api.integration.test.d.ts.map +0 -1
  111. package/dist/__tests__/integration/clob-api.integration.test.js +0 -170
  112. package/dist/__tests__/integration/clob-api.integration.test.js.map +0 -1
  113. package/dist/__tests__/integration/ctf-client.integration.test.d.ts +0 -17
  114. package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +0 -1
  115. package/dist/__tests__/integration/ctf-client.integration.test.js +0 -234
  116. package/dist/__tests__/integration/ctf-client.integration.test.js.map +0 -1
  117. package/dist/__tests__/integration/data-api.integration.test.d.ts +0 -9
  118. package/dist/__tests__/integration/data-api.integration.test.d.ts.map +0 -1
  119. package/dist/__tests__/integration/data-api.integration.test.js +0 -164
  120. package/dist/__tests__/integration/data-api.integration.test.js.map +0 -1
  121. package/dist/__tests__/integration/gamma-api.integration.test.d.ts +0 -9
  122. package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +0 -1
  123. package/dist/__tests__/integration/gamma-api.integration.test.js +0 -170
  124. package/dist/__tests__/integration/gamma-api.integration.test.js.map +0 -1
  125. package/dist/__tests__/integration/market-service.integration.test.d.ts +0 -10
  126. package/dist/__tests__/integration/market-service.integration.test.d.ts.map +0 -1
  127. package/dist/__tests__/integration/market-service.integration.test.js +0 -173
  128. package/dist/__tests__/integration/market-service.integration.test.js.map +0 -1
  129. package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts +0 -10
  130. package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +0 -1
  131. package/dist/__tests__/integration/realtime-service-v2.integration.test.js +0 -307
  132. package/dist/__tests__/integration/realtime-service-v2.integration.test.js.map +0 -1
  133. package/dist/__tests__/integration/trading-service.integration.test.d.ts +0 -10
  134. package/dist/__tests__/integration/trading-service.integration.test.d.ts.map +0 -1
  135. package/dist/__tests__/integration/trading-service.integration.test.js +0 -58
  136. package/dist/__tests__/integration/trading-service.integration.test.js.map +0 -1
  137. package/dist/__tests__/test-utils.d.ts +0 -92
  138. package/dist/__tests__/test-utils.d.ts.map +0 -1
  139. package/dist/__tests__/test-utils.js +0 -143
  140. package/dist/__tests__/test-utils.js.map +0 -1
  141. package/dist/clients/bridge-client.d.ts +0 -388
  142. package/dist/clients/bridge-client.d.ts.map +0 -1
  143. package/dist/clients/bridge-client.js +0 -587
  144. package/dist/clients/bridge-client.js.map +0 -1
  145. package/dist/clients/clob-api.d.ts +0 -391
  146. package/dist/clients/clob-api.d.ts.map +0 -1
  147. package/dist/clients/clob-api.js +0 -448
  148. package/dist/clients/clob-api.js.map +0 -1
  149. package/dist/clients/ctf-client.d.ts +0 -475
  150. package/dist/clients/ctf-client.d.ts.map +0 -1
  151. package/dist/clients/ctf-client.js +0 -915
  152. package/dist/clients/ctf-client.js.map +0 -1
  153. package/dist/clients/data-api.d.ts +0 -452
  154. package/dist/clients/data-api.d.ts.map +0 -1
  155. package/dist/clients/data-api.js +0 -637
  156. package/dist/clients/data-api.js.map +0 -1
  157. package/dist/clients/gamma-api.d.ts +0 -406
  158. package/dist/clients/gamma-api.d.ts.map +0 -1
  159. package/dist/clients/gamma-api.js +0 -354
  160. package/dist/clients/gamma-api.js.map +0 -1
  161. package/dist/clients/subgraph.d.ts +0 -196
  162. package/dist/clients/subgraph.d.ts.map +0 -1
  163. package/dist/clients/subgraph.js +0 -332
  164. package/dist/clients/subgraph.js.map +0 -1
  165. package/dist/clients/trading-client.d.ts +0 -252
  166. package/dist/clients/trading-client.d.ts.map +0 -1
  167. package/dist/clients/trading-client.js +0 -543
  168. package/dist/clients/trading-client.js.map +0 -1
  169. package/dist/clients/websocket-manager.d.ts +0 -103
  170. package/dist/clients/websocket-manager.d.ts.map +0 -1
  171. package/dist/clients/websocket-manager.js +0 -200
  172. package/dist/clients/websocket-manager.js.map +0 -1
  173. package/dist/core/cache-adapter-bridge.d.ts +0 -36
  174. package/dist/core/cache-adapter-bridge.d.ts.map +0 -1
  175. package/dist/core/cache-adapter-bridge.js +0 -81
  176. package/dist/core/cache-adapter-bridge.js.map +0 -1
  177. package/dist/core/cache.d.ts +0 -43
  178. package/dist/core/cache.d.ts.map +0 -1
  179. package/dist/core/cache.js +0 -76
  180. package/dist/core/cache.js.map +0 -1
  181. package/dist/core/errors.d.ts +0 -39
  182. package/dist/core/errors.d.ts.map +0 -1
  183. package/dist/core/errors.js +0 -86
  184. package/dist/core/errors.js.map +0 -1
  185. package/dist/core/rate-limiter.d.ts +0 -33
  186. package/dist/core/rate-limiter.d.ts.map +0 -1
  187. package/dist/core/rate-limiter.js +0 -82
  188. package/dist/core/rate-limiter.js.map +0 -1
  189. package/dist/core/types.d.ts +0 -506
  190. package/dist/core/types.d.ts.map +0 -1
  191. package/dist/core/types.js +0 -49
  192. package/dist/core/types.js.map +0 -1
  193. package/dist/core/types.test.d.ts +0 -7
  194. package/dist/core/types.test.d.ts.map +0 -1
  195. package/dist/core/types.test.js +0 -122
  196. package/dist/core/types.test.js.map +0 -1
  197. package/dist/core/unified-cache.d.ts +0 -63
  198. package/dist/core/unified-cache.d.ts.map +0 -1
  199. package/dist/core/unified-cache.js +0 -114
  200. package/dist/core/unified-cache.js.map +0 -1
  201. package/dist/index.d.ts +0 -159
  202. package/dist/index.d.ts.map +0 -1
  203. package/dist/index.js +0 -262
  204. package/dist/index.js.map +0 -1
  205. package/dist/services/arbitrage-service.d.ts +0 -409
  206. package/dist/services/arbitrage-service.d.ts.map +0 -1
  207. package/dist/services/arbitrage-service.js +0 -1450
  208. package/dist/services/arbitrage-service.js.map +0 -1
  209. package/dist/services/authorization-service.d.ts +0 -97
  210. package/dist/services/authorization-service.d.ts.map +0 -1
  211. package/dist/services/authorization-service.js +0 -279
  212. package/dist/services/authorization-service.js.map +0 -1
  213. package/dist/services/binance-service.d.ts +0 -154
  214. package/dist/services/binance-service.d.ts.map +0 -1
  215. package/dist/services/binance-service.js +0 -266
  216. package/dist/services/binance-service.js.map +0 -1
  217. package/dist/services/dip-arb-service.d.ts +0 -209
  218. package/dist/services/dip-arb-service.d.ts.map +0 -1
  219. package/dist/services/dip-arb-service.js +0 -1602
  220. package/dist/services/dip-arb-service.js.map +0 -1
  221. package/dist/services/dip-arb-types.d.ts +0 -553
  222. package/dist/services/dip-arb-types.d.ts.map +0 -1
  223. package/dist/services/dip-arb-types.js +0 -164
  224. package/dist/services/dip-arb-types.js.map +0 -1
  225. package/dist/services/market-service.d.ts +0 -367
  226. package/dist/services/market-service.d.ts.map +0 -1
  227. package/dist/services/market-service.js +0 -1187
  228. package/dist/services/market-service.js.map +0 -1
  229. package/dist/services/onchain-service.d.ts +0 -309
  230. package/dist/services/onchain-service.d.ts.map +0 -1
  231. package/dist/services/onchain-service.js +0 -417
  232. package/dist/services/onchain-service.js.map +0 -1
  233. package/dist/services/realtime-service-v2.d.ts +0 -362
  234. package/dist/services/realtime-service-v2.d.ts.map +0 -1
  235. package/dist/services/realtime-service-v2.js +0 -858
  236. package/dist/services/realtime-service-v2.js.map +0 -1
  237. package/dist/services/realtime-service.d.ts +0 -82
  238. package/dist/services/realtime-service.d.ts.map +0 -1
  239. package/dist/services/realtime-service.js +0 -182
  240. package/dist/services/realtime-service.js.map +0 -1
  241. package/dist/services/smart-money-service.d.ts +0 -352
  242. package/dist/services/smart-money-service.d.ts.map +0 -1
  243. package/dist/services/smart-money-service.js +0 -582
  244. package/dist/services/smart-money-service.js.map +0 -1
  245. package/dist/services/swap-service.d.ts +0 -217
  246. package/dist/services/swap-service.d.ts.map +0 -1
  247. package/dist/services/swap-service.js +0 -695
  248. package/dist/services/swap-service.js.map +0 -1
  249. package/dist/services/trading-service.d.ts +0 -177
  250. package/dist/services/trading-service.d.ts.map +0 -1
  251. package/dist/services/trading-service.js +0 -422
  252. package/dist/services/trading-service.js.map +0 -1
  253. package/dist/services/wallet-service.d.ts +0 -316
  254. package/dist/services/wallet-service.d.ts.map +0 -1
  255. package/dist/services/wallet-service.js +0 -681
  256. package/dist/services/wallet-service.js.map +0 -1
  257. package/dist/utils/price-utils.d.ts +0 -153
  258. package/dist/utils/price-utils.d.ts.map +0 -1
  259. package/dist/utils/price-utils.js +0 -236
  260. package/dist/utils/price-utils.js.map +0 -1
  261. package/dist/utils/price-utils.test.d.ts +0 -5
  262. package/dist/utils/price-utils.test.d.ts.map +0 -1
  263. package/dist/utils/price-utils.test.js +0 -192
  264. 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() {