@catalyst-team/poly-sdk 0.3.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 (317) hide show
  1. package/README.md +133 -1
  2. package/README.zh-CN.md +2 -0
  3. package/dist/scripts/dip-arb/auto-trade.d.ts +20 -0
  4. package/dist/scripts/dip-arb/auto-trade.d.ts.map +1 -0
  5. package/dist/scripts/dip-arb/auto-trade.js +373 -0
  6. package/dist/scripts/dip-arb/auto-trade.js.map +1 -0
  7. package/dist/scripts/dip-arb/example-basic.d.ts +30 -0
  8. package/dist/scripts/dip-arb/example-basic.d.ts.map +1 -0
  9. package/dist/scripts/dip-arb/example-basic.js +222 -0
  10. package/dist/scripts/dip-arb/example-basic.js.map +1 -0
  11. package/dist/scripts/dip-arb/redeem-positions.d.ts +11 -0
  12. package/dist/scripts/dip-arb/redeem-positions.d.ts.map +1 -0
  13. package/dist/scripts/dip-arb/redeem-positions.js +201 -0
  14. package/dist/scripts/dip-arb/redeem-positions.js.map +1 -0
  15. package/dist/scripts/dip-arb/scan-markets.d.ts +6 -0
  16. package/dist/scripts/dip-arb/scan-markets.d.ts.map +1 -0
  17. package/dist/scripts/dip-arb/scan-markets.js +73 -0
  18. package/dist/scripts/dip-arb/scan-markets.js.map +1 -0
  19. package/dist/src/__tests__/integration/arbitrage-service.integration.test.d.ts.map +1 -0
  20. package/dist/src/__tests__/integration/arbitrage-service.integration.test.js.map +1 -0
  21. package/dist/src/__tests__/integration/bridge-client.integration.test.d.ts.map +1 -0
  22. package/dist/src/__tests__/integration/bridge-client.integration.test.js.map +1 -0
  23. package/dist/src/__tests__/integration/ctf-client.integration.test.d.ts.map +1 -0
  24. package/dist/src/__tests__/integration/ctf-client.integration.test.js.map +1 -0
  25. package/dist/src/__tests__/integration/data-api.integration.test.d.ts.map +1 -0
  26. package/dist/{__tests__ → src/__tests__}/integration/data-api.integration.test.js +21 -18
  27. package/dist/src/__tests__/integration/data-api.integration.test.js.map +1 -0
  28. package/dist/src/__tests__/integration/gamma-api.integration.test.d.ts.map +1 -0
  29. package/dist/src/__tests__/integration/gamma-api.integration.test.js.map +1 -0
  30. package/dist/src/__tests__/integration/market-service.integration.test.d.ts.map +1 -0
  31. package/dist/{__tests__ → src/__tests__}/integration/market-service.integration.test.js +7 -0
  32. package/dist/src/__tests__/integration/market-service.integration.test.js.map +1 -0
  33. package/dist/src/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +1 -0
  34. package/dist/src/__tests__/integration/realtime-service-v2.integration.test.js.map +1 -0
  35. package/dist/src/__tests__/integration/trading-service.integration.test.d.ts.map +1 -0
  36. package/dist/src/__tests__/integration/trading-service.integration.test.js.map +1 -0
  37. package/dist/src/__tests__/test-utils.d.ts.map +1 -0
  38. package/dist/src/__tests__/test-utils.js.map +1 -0
  39. package/dist/src/catalyst/catalyst-query-service.d.ts +109 -0
  40. package/dist/src/catalyst/catalyst-query-service.d.ts.map +1 -0
  41. package/dist/src/catalyst/catalyst-query-service.js +141 -0
  42. package/dist/src/catalyst/catalyst-query-service.js.map +1 -0
  43. package/dist/src/catalyst/catalyst-realtime-service.d.ts +40 -0
  44. package/dist/src/catalyst/catalyst-realtime-service.d.ts.map +1 -0
  45. package/dist/src/catalyst/catalyst-realtime-service.js +125 -0
  46. package/dist/src/catalyst/catalyst-realtime-service.js.map +1 -0
  47. package/dist/src/catalyst/index.d.ts +4 -0
  48. package/dist/src/catalyst/index.d.ts.map +1 -0
  49. package/dist/src/catalyst/index.js +4 -0
  50. package/dist/src/catalyst/index.js.map +1 -0
  51. package/dist/src/catalyst/types.d.ts +178 -0
  52. package/dist/src/catalyst/types.d.ts.map +1 -0
  53. package/dist/src/catalyst/types.js +2 -0
  54. package/dist/src/catalyst/types.js.map +1 -0
  55. package/dist/src/clients/bridge-client.d.ts.map +1 -0
  56. package/dist/src/clients/bridge-client.js.map +1 -0
  57. package/dist/{clients → src/clients}/ctf-client.d.ts +6 -4
  58. package/dist/src/clients/ctf-client.d.ts.map +1 -0
  59. package/dist/src/clients/ctf-client.js.map +1 -0
  60. package/dist/{clients → src/clients}/data-api.d.ts +94 -20
  61. package/dist/src/clients/data-api.d.ts.map +1 -0
  62. package/dist/{clients → src/clients}/data-api.js +119 -52
  63. package/dist/src/clients/data-api.js.map +1 -0
  64. package/dist/{clients → src/clients}/gamma-api.d.ts +74 -0
  65. package/dist/src/clients/gamma-api.d.ts.map +1 -0
  66. package/dist/{clients → src/clients}/gamma-api.js +7 -0
  67. package/dist/src/clients/gamma-api.js.map +1 -0
  68. package/dist/src/clients/subgraph.d.ts.map +1 -0
  69. package/dist/src/clients/subgraph.js.map +1 -0
  70. package/dist/src/core/cache-adapter-bridge.d.ts.map +1 -0
  71. package/dist/src/core/cache-adapter-bridge.js.map +1 -0
  72. package/dist/{core → src/core}/cache.d.ts +2 -0
  73. package/dist/src/core/cache.d.ts.map +1 -0
  74. package/dist/{core → src/core}/cache.js +4 -0
  75. package/dist/src/core/cache.js.map +1 -0
  76. package/dist/src/core/errors.d.ts.map +1 -0
  77. package/dist/src/core/errors.js.map +1 -0
  78. package/dist/{core → src/core}/rate-limiter.d.ts +2 -1
  79. package/dist/src/core/rate-limiter.d.ts.map +1 -0
  80. package/dist/{core → src/core}/rate-limiter.js +7 -0
  81. package/dist/src/core/rate-limiter.js.map +1 -0
  82. package/dist/{core → src/core}/types.d.ts +105 -1
  83. package/dist/src/core/types.d.ts.map +1 -0
  84. package/dist/src/core/types.js +49 -0
  85. package/dist/src/core/types.js.map +1 -0
  86. package/dist/src/core/types.test.d.ts.map +1 -0
  87. package/dist/src/core/types.test.js.map +1 -0
  88. package/dist/src/core/unified-cache.d.ts.map +1 -0
  89. package/dist/src/core/unified-cache.js.map +1 -0
  90. package/dist/{index.d.ts → src/index.d.ts} +24 -5
  91. package/dist/src/index.d.ts.map +1 -0
  92. package/dist/{index.js → src/index.js} +38 -4
  93. package/dist/src/index.js.map +1 -0
  94. package/dist/src/insider-scan/index.d.ts +3 -0
  95. package/dist/src/insider-scan/index.d.ts.map +1 -0
  96. package/dist/src/insider-scan/index.js +3 -0
  97. package/dist/src/insider-scan/index.js.map +1 -0
  98. package/dist/src/insider-scan/insider-scan-service.d.ts +63 -0
  99. package/dist/src/insider-scan/insider-scan-service.d.ts.map +1 -0
  100. package/dist/src/insider-scan/insider-scan-service.js +153 -0
  101. package/dist/src/insider-scan/insider-scan-service.js.map +1 -0
  102. package/dist/src/insider-scan/types.d.ts +205 -0
  103. package/dist/src/insider-scan/types.d.ts.map +1 -0
  104. package/dist/src/insider-scan/types.js +7 -0
  105. package/dist/src/insider-scan/types.js.map +1 -0
  106. package/dist/src/services/arbitrage-service.d.ts.map +1 -0
  107. package/dist/{services → src/services}/arbitrage-service.js +14 -4
  108. package/dist/src/services/arbitrage-service.js.map +1 -0
  109. package/dist/src/services/authorization-service.d.ts.map +1 -0
  110. package/dist/src/services/authorization-service.js.map +1 -0
  111. package/dist/src/services/binance-service.d.ts +154 -0
  112. package/dist/src/services/binance-service.d.ts.map +1 -0
  113. package/dist/src/services/binance-service.js +266 -0
  114. package/dist/src/services/binance-service.js.map +1 -0
  115. package/dist/src/services/dip-arb-service.d.ts +245 -0
  116. package/dist/src/services/dip-arb-service.d.ts.map +1 -0
  117. package/dist/src/services/dip-arb-service.js +1865 -0
  118. package/dist/src/services/dip-arb-service.js.map +1 -0
  119. package/dist/src/services/dip-arb-types.d.ts +553 -0
  120. package/dist/src/services/dip-arb-types.d.ts.map +1 -0
  121. package/dist/src/services/dip-arb-types.js +164 -0
  122. package/dist/src/services/dip-arb-types.js.map +1 -0
  123. package/dist/src/services/market-service.d.ts +431 -0
  124. package/dist/src/services/market-service.d.ts.map +1 -0
  125. package/dist/{services → src/services}/market-service.js +501 -17
  126. package/dist/src/services/market-service.js.map +1 -0
  127. package/dist/{services → src/services}/onchain-service.d.ts +10 -2
  128. package/dist/src/services/onchain-service.d.ts.map +1 -0
  129. package/dist/{services → src/services}/onchain-service.js +8 -0
  130. package/dist/src/services/onchain-service.js.map +1 -0
  131. package/dist/{services → src/services}/realtime-service-v2.d.ts +6 -0
  132. package/dist/src/services/realtime-service-v2.d.ts.map +1 -0
  133. package/dist/{services → src/services}/realtime-service-v2.js +44 -8
  134. package/dist/src/services/realtime-service-v2.js.map +1 -0
  135. package/dist/src/services/smart-money-service.d.ts +769 -0
  136. package/dist/src/services/smart-money-service.d.ts.map +1 -0
  137. package/dist/src/services/smart-money-service.js +1448 -0
  138. package/dist/src/services/smart-money-service.js.map +1 -0
  139. package/dist/src/services/swap-service.d.ts.map +1 -0
  140. package/dist/src/services/swap-service.js.map +1 -0
  141. package/dist/{services → src/services}/trading-service.d.ts +26 -0
  142. package/dist/src/services/trading-service.d.ts.map +1 -0
  143. package/dist/{services → src/services}/trading-service.js +72 -1
  144. package/dist/src/services/trading-service.js.map +1 -0
  145. package/dist/{services → src/services}/wallet-service.d.ts +81 -4
  146. package/dist/src/services/wallet-service.d.ts.map +1 -0
  147. package/dist/{services → src/services}/wallet-service.js +126 -8
  148. package/dist/src/services/wallet-service.js.map +1 -0
  149. package/dist/src/signal/index.d.ts +8 -0
  150. package/dist/src/signal/index.d.ts.map +1 -0
  151. package/dist/src/signal/index.js +7 -0
  152. package/dist/src/signal/index.js.map +1 -0
  153. package/dist/src/signal/signal-service.d.ts +89 -0
  154. package/dist/src/signal/signal-service.d.ts.map +1 -0
  155. package/dist/src/signal/signal-service.js +226 -0
  156. package/dist/src/signal/signal-service.js.map +1 -0
  157. package/dist/src/signal/types.d.ts +280 -0
  158. package/dist/src/signal/types.d.ts.map +1 -0
  159. package/dist/src/signal/types.js +7 -0
  160. package/dist/src/signal/types.js.map +1 -0
  161. package/dist/src/utils/price-utils.d.ts.map +1 -0
  162. package/dist/src/utils/price-utils.js.map +1 -0
  163. package/dist/src/utils/price-utils.test.d.ts.map +1 -0
  164. package/dist/src/utils/price-utils.test.js.map +1 -0
  165. package/dist/src/wallet-report/index.d.ts +3 -0
  166. package/dist/src/wallet-report/index.d.ts.map +1 -0
  167. package/dist/src/wallet-report/index.js +3 -0
  168. package/dist/src/wallet-report/index.js.map +1 -0
  169. package/dist/src/wallet-report/types.d.ts +187 -0
  170. package/dist/src/wallet-report/types.d.ts.map +1 -0
  171. package/dist/src/wallet-report/types.js +7 -0
  172. package/dist/src/wallet-report/types.js.map +1 -0
  173. package/dist/src/wallet-report/wallet-report-service.d.ts +91 -0
  174. package/dist/src/wallet-report/wallet-report-service.d.ts.map +1 -0
  175. package/dist/src/wallet-report/wallet-report-service.js +208 -0
  176. package/dist/src/wallet-report/wallet-report-service.js.map +1 -0
  177. package/dist/src/wallets/hot-wallet-service.d.ts +162 -0
  178. package/dist/src/wallets/hot-wallet-service.d.ts.map +1 -0
  179. package/dist/src/wallets/hot-wallet-service.js +251 -0
  180. package/dist/src/wallets/hot-wallet-service.js.map +1 -0
  181. package/dist/src/wallets/index.d.ts +15 -0
  182. package/dist/src/wallets/index.d.ts.map +1 -0
  183. package/dist/src/wallets/index.js +26 -0
  184. package/dist/src/wallets/index.js.map +1 -0
  185. package/package.json +7 -7
  186. package/dist/__tests__/clob-api.test.d.ts +0 -5
  187. package/dist/__tests__/clob-api.test.d.ts.map +0 -1
  188. package/dist/__tests__/clob-api.test.js +0 -240
  189. package/dist/__tests__/clob-api.test.js.map +0 -1
  190. package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts.map +0 -1
  191. package/dist/__tests__/integration/arbitrage-service.integration.test.js.map +0 -1
  192. package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +0 -1
  193. package/dist/__tests__/integration/bridge-client.integration.test.js.map +0 -1
  194. package/dist/__tests__/integration/clob-api.integration.test.d.ts +0 -13
  195. package/dist/__tests__/integration/clob-api.integration.test.d.ts.map +0 -1
  196. package/dist/__tests__/integration/clob-api.integration.test.js +0 -170
  197. package/dist/__tests__/integration/clob-api.integration.test.js.map +0 -1
  198. package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +0 -1
  199. package/dist/__tests__/integration/ctf-client.integration.test.js.map +0 -1
  200. package/dist/__tests__/integration/data-api.integration.test.d.ts.map +0 -1
  201. package/dist/__tests__/integration/data-api.integration.test.js.map +0 -1
  202. package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +0 -1
  203. package/dist/__tests__/integration/gamma-api.integration.test.js.map +0 -1
  204. package/dist/__tests__/integration/market-service.integration.test.d.ts.map +0 -1
  205. package/dist/__tests__/integration/market-service.integration.test.js.map +0 -1
  206. package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +0 -1
  207. package/dist/__tests__/integration/realtime-service-v2.integration.test.js.map +0 -1
  208. package/dist/__tests__/integration/trading-service.integration.test.d.ts.map +0 -1
  209. package/dist/__tests__/integration/trading-service.integration.test.js.map +0 -1
  210. package/dist/__tests__/test-utils.d.ts.map +0 -1
  211. package/dist/__tests__/test-utils.js.map +0 -1
  212. package/dist/clients/bridge-client.d.ts.map +0 -1
  213. package/dist/clients/bridge-client.js.map +0 -1
  214. package/dist/clients/clob-api.d.ts +0 -391
  215. package/dist/clients/clob-api.d.ts.map +0 -1
  216. package/dist/clients/clob-api.js +0 -448
  217. package/dist/clients/clob-api.js.map +0 -1
  218. package/dist/clients/ctf-client.d.ts.map +0 -1
  219. package/dist/clients/ctf-client.js.map +0 -1
  220. package/dist/clients/data-api.d.ts.map +0 -1
  221. package/dist/clients/data-api.js.map +0 -1
  222. package/dist/clients/gamma-api.d.ts.map +0 -1
  223. package/dist/clients/gamma-api.js.map +0 -1
  224. package/dist/clients/subgraph.d.ts.map +0 -1
  225. package/dist/clients/subgraph.js.map +0 -1
  226. package/dist/clients/trading-client.d.ts +0 -252
  227. package/dist/clients/trading-client.d.ts.map +0 -1
  228. package/dist/clients/trading-client.js +0 -543
  229. package/dist/clients/trading-client.js.map +0 -1
  230. package/dist/clients/websocket-manager.d.ts +0 -103
  231. package/dist/clients/websocket-manager.d.ts.map +0 -1
  232. package/dist/clients/websocket-manager.js +0 -200
  233. package/dist/clients/websocket-manager.js.map +0 -1
  234. package/dist/core/cache-adapter-bridge.d.ts.map +0 -1
  235. package/dist/core/cache-adapter-bridge.js.map +0 -1
  236. package/dist/core/cache.d.ts.map +0 -1
  237. package/dist/core/cache.js.map +0 -1
  238. package/dist/core/errors.d.ts.map +0 -1
  239. package/dist/core/errors.js.map +0 -1
  240. package/dist/core/rate-limiter.d.ts.map +0 -1
  241. package/dist/core/rate-limiter.js.map +0 -1
  242. package/dist/core/types.d.ts.map +0 -1
  243. package/dist/core/types.js +0 -19
  244. package/dist/core/types.js.map +0 -1
  245. package/dist/core/types.test.d.ts.map +0 -1
  246. package/dist/core/types.test.js.map +0 -1
  247. package/dist/core/unified-cache.d.ts.map +0 -1
  248. package/dist/core/unified-cache.js.map +0 -1
  249. package/dist/index.d.ts.map +0 -1
  250. package/dist/index.js.map +0 -1
  251. package/dist/services/arbitrage-service.d.ts.map +0 -1
  252. package/dist/services/arbitrage-service.js.map +0 -1
  253. package/dist/services/authorization-service.d.ts.map +0 -1
  254. package/dist/services/authorization-service.js.map +0 -1
  255. package/dist/services/market-service.d.ts +0 -208
  256. package/dist/services/market-service.d.ts.map +0 -1
  257. package/dist/services/market-service.js.map +0 -1
  258. package/dist/services/onchain-service.d.ts.map +0 -1
  259. package/dist/services/onchain-service.js.map +0 -1
  260. package/dist/services/realtime-service-v2.d.ts.map +0 -1
  261. package/dist/services/realtime-service-v2.js.map +0 -1
  262. package/dist/services/realtime-service.d.ts +0 -82
  263. package/dist/services/realtime-service.d.ts.map +0 -1
  264. package/dist/services/realtime-service.js +0 -182
  265. package/dist/services/realtime-service.js.map +0 -1
  266. package/dist/services/smart-money-service.d.ts +0 -196
  267. package/dist/services/smart-money-service.d.ts.map +0 -1
  268. package/dist/services/smart-money-service.js +0 -358
  269. package/dist/services/smart-money-service.js.map +0 -1
  270. package/dist/services/swap-service.d.ts.map +0 -1
  271. package/dist/services/swap-service.js.map +0 -1
  272. package/dist/services/trading-service.d.ts.map +0 -1
  273. package/dist/services/trading-service.js.map +0 -1
  274. package/dist/services/wallet-service.d.ts.map +0 -1
  275. package/dist/services/wallet-service.js.map +0 -1
  276. package/dist/utils/price-utils.d.ts.map +0 -1
  277. package/dist/utils/price-utils.js.map +0 -1
  278. package/dist/utils/price-utils.test.d.ts.map +0 -1
  279. package/dist/utils/price-utils.test.js.map +0 -1
  280. /package/dist/{__tests__ → src/__tests__}/integration/arbitrage-service.integration.test.d.ts +0 -0
  281. /package/dist/{__tests__ → src/__tests__}/integration/arbitrage-service.integration.test.js +0 -0
  282. /package/dist/{__tests__ → src/__tests__}/integration/bridge-client.integration.test.d.ts +0 -0
  283. /package/dist/{__tests__ → src/__tests__}/integration/bridge-client.integration.test.js +0 -0
  284. /package/dist/{__tests__ → src/__tests__}/integration/ctf-client.integration.test.d.ts +0 -0
  285. /package/dist/{__tests__ → src/__tests__}/integration/ctf-client.integration.test.js +0 -0
  286. /package/dist/{__tests__ → src/__tests__}/integration/data-api.integration.test.d.ts +0 -0
  287. /package/dist/{__tests__ → src/__tests__}/integration/gamma-api.integration.test.d.ts +0 -0
  288. /package/dist/{__tests__ → src/__tests__}/integration/gamma-api.integration.test.js +0 -0
  289. /package/dist/{__tests__ → src/__tests__}/integration/market-service.integration.test.d.ts +0 -0
  290. /package/dist/{__tests__ → src/__tests__}/integration/realtime-service-v2.integration.test.d.ts +0 -0
  291. /package/dist/{__tests__ → src/__tests__}/integration/realtime-service-v2.integration.test.js +0 -0
  292. /package/dist/{__tests__ → src/__tests__}/integration/trading-service.integration.test.d.ts +0 -0
  293. /package/dist/{__tests__ → src/__tests__}/integration/trading-service.integration.test.js +0 -0
  294. /package/dist/{__tests__ → src/__tests__}/test-utils.d.ts +0 -0
  295. /package/dist/{__tests__ → src/__tests__}/test-utils.js +0 -0
  296. /package/dist/{clients → src/clients}/bridge-client.d.ts +0 -0
  297. /package/dist/{clients → src/clients}/bridge-client.js +0 -0
  298. /package/dist/{clients → src/clients}/ctf-client.js +0 -0
  299. /package/dist/{clients → src/clients}/subgraph.d.ts +0 -0
  300. /package/dist/{clients → src/clients}/subgraph.js +0 -0
  301. /package/dist/{core → src/core}/cache-adapter-bridge.d.ts +0 -0
  302. /package/dist/{core → src/core}/cache-adapter-bridge.js +0 -0
  303. /package/dist/{core → src/core}/errors.d.ts +0 -0
  304. /package/dist/{core → src/core}/errors.js +0 -0
  305. /package/dist/{core → src/core}/types.test.d.ts +0 -0
  306. /package/dist/{core → src/core}/types.test.js +0 -0
  307. /package/dist/{core → src/core}/unified-cache.d.ts +0 -0
  308. /package/dist/{core → src/core}/unified-cache.js +0 -0
  309. /package/dist/{services → src/services}/arbitrage-service.d.ts +0 -0
  310. /package/dist/{services → src/services}/authorization-service.d.ts +0 -0
  311. /package/dist/{services → src/services}/authorization-service.js +0 -0
  312. /package/dist/{services → src/services}/swap-service.d.ts +0 -0
  313. /package/dist/{services → src/services}/swap-service.js +0 -0
  314. /package/dist/{utils → src/utils}/price-utils.d.ts +0 -0
  315. /package/dist/{utils → src/utils}/price-utils.js +0 -0
  316. /package/dist/{utils → src/utils}/price-utils.test.d.ts +0 -0
  317. /package/dist/{utils → src/utils}/price-utils.test.js +0 -0
@@ -0,0 +1,1448 @@
1
+ /**
2
+ * SmartMoneyService
3
+ *
4
+ * 聪明钱监控和自动跟单服务
5
+ *
6
+ * 核心功能:
7
+ * 1. 监听指定地址的交易 - subscribeSmartMoneyTrades()
8
+ * 2. 自动跟单 - startAutoCopyTrading()
9
+ * 3. 聪明钱信息获取 - getSmartMoneyList(), getSmartMoneyInfo()
10
+ *
11
+ * ============================================================================
12
+ * 设计决策
13
+ * ============================================================================
14
+ *
15
+ * ## 监控方式
16
+ * 使用 Activity WebSocket,延迟 < 100ms,实测验证有效。
17
+ *
18
+ * ## 下单方式
19
+ * | 方式 | 使用场景 | 特点 |
20
+ * |------|---------|------|
21
+ * | FOK | 小额跟单 | 全部成交或取消 |
22
+ * | FAK | 大额跟单 | 部分成交也接受 |
23
+ *
24
+ * ## 重要限制
25
+ * ⚠️ Activity WebSocket 不会广播用户自己的交易!
26
+ * 验证跟单结果请使用 TradingService.getTrades()
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
+ };
100
+ // ============================================================================
101
+ // SmartMoneyService
102
+ // ============================================================================
103
+ export class SmartMoneyService {
104
+ walletService;
105
+ realtimeService;
106
+ tradingService;
107
+ dataApi;
108
+ config;
109
+ smartMoneyCache = new Map();
110
+ smartMoneySet = new Set();
111
+ cacheTimestamp = 0;
112
+ activeSubscription = null;
113
+ tradeHandlers = new Set();
114
+ constructor(walletService, realtimeService, tradingService, config = {}, dataApi) {
115
+ this.walletService = walletService;
116
+ this.realtimeService = realtimeService;
117
+ this.tradingService = tradingService;
118
+ this.dataApi = dataApi ?? null;
119
+ this.config = {
120
+ minPnl: config.minPnl ?? 1000,
121
+ cacheTtl: config.cacheTtl ?? 300000,
122
+ };
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
+ }
131
+ // ============================================================================
132
+ // Smart Money Info
133
+ // ============================================================================
134
+ /**
135
+ * Get list of Smart Money wallets from leaderboard
136
+ */
137
+ async getSmartMoneyList(limit = 100) {
138
+ if (this.isCacheValid()) {
139
+ return Array.from(this.smartMoneyCache.values());
140
+ }
141
+ const leaderboardPage = await this.walletService.getLeaderboard(0, limit);
142
+ const entries = leaderboardPage.entries;
143
+ const smartMoneyList = [];
144
+ for (let i = 0; i < entries.length; i++) {
145
+ const trader = entries[i];
146
+ if (trader.pnl < this.config.minPnl)
147
+ continue;
148
+ const wallet = {
149
+ address: trader.address.toLowerCase(),
150
+ name: trader.userName,
151
+ pnl: trader.pnl,
152
+ volume: trader.volume,
153
+ score: Math.min(100, Math.round((trader.pnl / 100000) * 50 + (trader.volume / 1000000) * 50)),
154
+ rank: trader.rank ?? i + 1,
155
+ };
156
+ smartMoneyList.push(wallet);
157
+ this.smartMoneyCache.set(wallet.address, wallet);
158
+ this.smartMoneySet.add(wallet.address);
159
+ }
160
+ this.cacheTimestamp = Date.now();
161
+ return smartMoneyList;
162
+ }
163
+ /**
164
+ * Check if an address is Smart Money
165
+ */
166
+ async isSmartMoney(address) {
167
+ const normalized = address.toLowerCase();
168
+ if (this.isCacheValid()) {
169
+ return this.smartMoneySet.has(normalized);
170
+ }
171
+ await this.getSmartMoneyList();
172
+ return this.smartMoneySet.has(normalized);
173
+ }
174
+ /**
175
+ * Get Smart Money info for an address
176
+ */
177
+ async getSmartMoneyInfo(address) {
178
+ const normalized = address.toLowerCase();
179
+ if (this.isCacheValid() && this.smartMoneyCache.has(normalized)) {
180
+ return this.smartMoneyCache.get(normalized);
181
+ }
182
+ await this.getSmartMoneyList();
183
+ return this.smartMoneyCache.get(normalized) || null;
184
+ }
185
+ // ============================================================================
186
+ // Trade Subscription - 监听交易
187
+ // ============================================================================
188
+ /**
189
+ * Subscribe to trades from specific addresses
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * const sub = smartMoneyService.subscribeSmartMoneyTrades(
194
+ * (trade) => {
195
+ * console.log(`${trade.traderName} ${trade.side} ${trade.size} @ ${trade.price}`);
196
+ * },
197
+ * { filterAddresses: ['0x1234...', '0x5678...'] }
198
+ * );
199
+ *
200
+ * // Stop listening
201
+ * sub.unsubscribe();
202
+ * ```
203
+ */
204
+ subscribeSmartMoneyTrades(onTrade, options = {}) {
205
+ this.tradeHandlers.add(onTrade);
206
+ // Ensure cache is populated
207
+ this.getSmartMoneyList().catch(() => { });
208
+ // Start subscription if not active
209
+ if (!this.activeSubscription) {
210
+ this.activeSubscription = this.realtimeService.subscribeAllActivity({
211
+ onTrade: (activityTrade) => {
212
+ this.handleActivityTrade(activityTrade, options);
213
+ },
214
+ onError: (error) => {
215
+ console.error('[SmartMoneyService] Subscription error:', error);
216
+ },
217
+ });
218
+ }
219
+ return {
220
+ id: `smart_money_${Date.now()}`,
221
+ unsubscribe: () => {
222
+ this.tradeHandlers.delete(onTrade);
223
+ if (this.tradeHandlers.size === 0 && this.activeSubscription) {
224
+ this.activeSubscription.unsubscribe();
225
+ this.activeSubscription = null;
226
+ }
227
+ },
228
+ };
229
+ }
230
+ async handleActivityTrade(trade, options) {
231
+ const rawAddress = trade.trader?.address;
232
+ if (!rawAddress)
233
+ return;
234
+ const traderAddress = rawAddress.toLowerCase();
235
+ // Address filter
236
+ if (options.filterAddresses && options.filterAddresses.length > 0) {
237
+ const normalized = options.filterAddresses.map(a => a.toLowerCase());
238
+ if (!normalized.includes(traderAddress))
239
+ return;
240
+ }
241
+ // Size filter
242
+ if (options.minSize && trade.size < options.minSize)
243
+ return;
244
+ // Smart Money filter
245
+ const isSmartMoney = this.smartMoneySet.has(traderAddress);
246
+ if (options.smartMoneyOnly && !isSmartMoney)
247
+ return;
248
+ const smartMoneyTrade = {
249
+ traderAddress,
250
+ traderName: trade.trader?.name,
251
+ conditionId: trade.conditionId,
252
+ marketSlug: trade.marketSlug,
253
+ side: trade.side,
254
+ size: trade.size,
255
+ price: trade.price,
256
+ tokenId: trade.asset,
257
+ outcome: trade.outcome,
258
+ txHash: trade.transactionHash,
259
+ timestamp: trade.timestamp,
260
+ isSmartMoney,
261
+ smartMoneyInfo: this.smartMoneyCache.get(traderAddress),
262
+ };
263
+ for (const handler of this.tradeHandlers) {
264
+ try {
265
+ handler(smartMoneyTrade);
266
+ }
267
+ catch (error) {
268
+ console.error('[SmartMoneyService] Handler error:', error);
269
+ }
270
+ }
271
+ }
272
+ // ============================================================================
273
+ // Auto Copy Trading - 自动跟单
274
+ // ============================================================================
275
+ /**
276
+ * Start auto copy trading - 自动跟单
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * const sub = await smartMoneyService.startAutoCopyTrading({
281
+ * targetAddresses: ['0x1234...'],
282
+ * // 或者跟踪排行榜前N名
283
+ * topN: 10,
284
+ *
285
+ * sizeScale: 0.1, // 10%
286
+ * maxSizePerTrade: 50, // $50
287
+ * maxSlippage: 0.03, // 3%
288
+ * orderType: 'FOK',
289
+ *
290
+ * dryRun: true, // 测试模式
291
+ *
292
+ * onTrade: (trade, result) => console.log(result),
293
+ * });
294
+ *
295
+ * // 停止
296
+ * sub.stop();
297
+ * ```
298
+ */
299
+ async startAutoCopyTrading(options) {
300
+ const startTime = Date.now();
301
+ // Build target list
302
+ let targetAddresses = [];
303
+ if (options.targetAddresses?.length) {
304
+ targetAddresses = options.targetAddresses.map(a => a.toLowerCase());
305
+ }
306
+ if (options.topN && options.topN > 0) {
307
+ const smartMoneyList = await this.getSmartMoneyList(options.topN);
308
+ const topAddresses = smartMoneyList.map(w => w.address);
309
+ targetAddresses = [...new Set([...targetAddresses, ...topAddresses])];
310
+ }
311
+ if (targetAddresses.length === 0) {
312
+ throw new Error('No target addresses. Use targetAddresses or topN.');
313
+ }
314
+ // Stats
315
+ const stats = {
316
+ startTime,
317
+ tradesDetected: 0,
318
+ tradesExecuted: 0,
319
+ tradesSkipped: 0,
320
+ tradesFailed: 0,
321
+ totalUsdcSpent: 0,
322
+ };
323
+ // Config
324
+ const sizeScale = options.sizeScale ?? 0.1;
325
+ const maxSizePerTrade = options.maxSizePerTrade ?? 50;
326
+ const maxSlippage = options.maxSlippage ?? 0.03;
327
+ const orderType = options.orderType ?? 'FOK';
328
+ const minTradeSize = options.minTradeSize ?? 10;
329
+ const sideFilter = options.sideFilter;
330
+ const delay = options.delay ?? 0;
331
+ const dryRun = options.dryRun ?? false;
332
+ // Subscribe
333
+ const subscription = this.subscribeSmartMoneyTrades(async (trade) => {
334
+ stats.tradesDetected++;
335
+ try {
336
+ // Check target
337
+ if (!targetAddresses.includes(trade.traderAddress.toLowerCase())) {
338
+ return;
339
+ }
340
+ // Filters
341
+ const tradeValue = trade.size * trade.price;
342
+ if (tradeValue < minTradeSize) {
343
+ stats.tradesSkipped++;
344
+ return;
345
+ }
346
+ if (sideFilter && trade.side !== sideFilter) {
347
+ stats.tradesSkipped++;
348
+ return;
349
+ }
350
+ // Calculate size
351
+ let copySize = trade.size * sizeScale;
352
+ let copyValue = copySize * trade.price;
353
+ // Enforce max size
354
+ if (copyValue > maxSizePerTrade) {
355
+ copySize = maxSizePerTrade / trade.price;
356
+ copyValue = maxSizePerTrade;
357
+ }
358
+ // Polymarket minimum order is $1
359
+ const MIN_ORDER_SIZE = 1;
360
+ if (copyValue < MIN_ORDER_SIZE) {
361
+ stats.tradesSkipped++;
362
+ return;
363
+ }
364
+ // Delay
365
+ if (delay > 0) {
366
+ await new Promise(resolve => setTimeout(resolve, delay));
367
+ }
368
+ // Token
369
+ const tokenId = trade.tokenId;
370
+ if (!tokenId) {
371
+ stats.tradesSkipped++;
372
+ return;
373
+ }
374
+ // Price with slippage
375
+ const slippagePrice = trade.side === 'BUY'
376
+ ? trade.price * (1 + maxSlippage)
377
+ : trade.price * (1 - maxSlippage);
378
+ const usdcAmount = copyValue; // Already calculated above
379
+ // Execute
380
+ let result;
381
+ if (dryRun) {
382
+ result = { success: true, orderId: `dry_run_${Date.now()}` };
383
+ console.log('[DRY RUN]', {
384
+ trader: trade.traderAddress.slice(0, 10),
385
+ side: trade.side,
386
+ market: trade.marketSlug,
387
+ copy: { size: copySize.toFixed(2), usdc: usdcAmount.toFixed(2) },
388
+ });
389
+ }
390
+ else {
391
+ result = await this.tradingService.createMarketOrder({
392
+ tokenId,
393
+ side: trade.side,
394
+ amount: usdcAmount,
395
+ price: slippagePrice,
396
+ orderType,
397
+ });
398
+ }
399
+ if (result.success) {
400
+ stats.tradesExecuted++;
401
+ stats.totalUsdcSpent += usdcAmount;
402
+ }
403
+ else {
404
+ stats.tradesFailed++;
405
+ }
406
+ options.onTrade?.(trade, result);
407
+ }
408
+ catch (error) {
409
+ stats.tradesFailed++;
410
+ options.onError?.(error instanceof Error ? error : new Error(String(error)));
411
+ }
412
+ }, { filterAddresses: targetAddresses, minSize: minTradeSize });
413
+ return {
414
+ id: subscription.id,
415
+ targetAddresses,
416
+ startTime,
417
+ isActive: true,
418
+ stats,
419
+ stop: () => subscription.unsubscribe(),
420
+ getStats: () => ({ ...stats }),
421
+ };
422
+ }
423
+ // ============================================================================
424
+ // Leaderboard - 排行榜
425
+ // ============================================================================
426
+ /**
427
+ * Get leaderboard by time period
428
+ *
429
+ * @example
430
+ * ```typescript
431
+ * // Get weekly top 100 by PnL
432
+ * const leaderboard = await sdk.smartMoney.getLeaderboard({
433
+ * period: 'week',
434
+ * limit: 100,
435
+ * sortBy: 'pnl'
436
+ * });
437
+ * ```
438
+ */
439
+ async getLeaderboard(options = {}) {
440
+ const period = options.period ?? 'week';
441
+ const limit = Math.min(options.limit ?? 50, 500);
442
+ const sortBy = options.sortBy ?? 'pnl';
443
+ const offset = Math.min(options.offset ?? 0, 10000);
444
+ const result = await this.walletService.fetchLeaderboardByPeriod(period, limit, sortBy, 'OVERALL', offset);
445
+ const entries = result.entries.map(e => ({
446
+ address: e.address,
447
+ rank: e.rank,
448
+ pnl: e.pnl,
449
+ volume: e.volume,
450
+ tradeCount: e.tradeCount,
451
+ userName: e.userName,
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,
466
+ }));
467
+ return {
468
+ entries,
469
+ hasMore: result.hasMore,
470
+ request: result.request,
471
+ };
472
+ }
473
+ // ============================================================================
474
+ // Wallet Report - 钱包报告
475
+ // ============================================================================
476
+ /**
477
+ * Generate comprehensive wallet report
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * const report = await sdk.smartMoney.getWalletReport('0x...');
482
+ * console.log(report.overview.totalPnL);
483
+ * console.log(report.rankings.weekly?.rank);
484
+ * ```
485
+ */
486
+ async getWalletReport(address) {
487
+ // Fetch all data in parallel
488
+ const [profile, positions, activitySummary, dailyPnl, weeklyPnl, monthlyPnl, allTimePnl,] = await Promise.all([
489
+ this.walletService.getWalletProfile(address),
490
+ this.walletService.getWalletPositions(address),
491
+ this.walletService.getWalletActivity(address, 100),
492
+ this.walletService.getUserPeriodPnl(address, 'day').catch(() => null),
493
+ this.walletService.getUserPeriodPnl(address, 'week').catch(() => null),
494
+ this.walletService.getUserPeriodPnl(address, 'month').catch(() => null),
495
+ this.walletService.getUserPeriodPnl(address, 'all').catch(() => null),
496
+ ]);
497
+ // Calculate performance metrics
498
+ const winningPositions = positions.filter(p => (p.cashPnl ?? 0) > 0);
499
+ const losingPositions = positions.filter(p => (p.cashPnl ?? 0) < 0);
500
+ // Use initialValue (cost basis) instead of currentValue (which is 0 for settled markets)
501
+ const avgPositionSize = positions.length > 0
502
+ ? positions.reduce((sum, p) => sum + (p.initialValue ?? (p.size * p.avgPrice)), 0) / positions.length
503
+ : 0;
504
+ const avgWinAmount = winningPositions.length > 0
505
+ ? winningPositions.reduce((sum, p) => sum + (p.cashPnl ?? 0), 0) / winningPositions.length
506
+ : 0;
507
+ const avgLossAmount = losingPositions.length > 0
508
+ ? Math.abs(losingPositions.reduce((sum, p) => sum + (p.cashPnl ?? 0), 0) / losingPositions.length)
509
+ : 0;
510
+ const uniqueMarkets = new Set(positions.map(p => p.conditionId)).size;
511
+ // Category analysis
512
+ const categoryStats = this.analyzeCategories(positions);
513
+ // Recent trades
514
+ const trades = activitySummary.activities.filter(a => a.type === 'TRADE');
515
+ const recentTrades = trades.slice(0, 10);
516
+ // Build rankings
517
+ const toRanking = (entry) => {
518
+ if (!entry)
519
+ return null;
520
+ return { rank: entry.rank, pnl: entry.pnl, volume: entry.volume };
521
+ };
522
+ return {
523
+ address,
524
+ generatedAt: new Date(),
525
+ overview: {
526
+ totalPnL: profile.totalPnL,
527
+ realizedPnL: profile.realizedPnL,
528
+ unrealizedPnL: profile.unrealizedPnL,
529
+ positionCount: positions.length,
530
+ tradeCount: profile.tradeCount,
531
+ smartScore: profile.smartScore,
532
+ lastActiveAt: profile.lastActiveAt,
533
+ },
534
+ rankings: {
535
+ daily: toRanking(dailyPnl),
536
+ weekly: toRanking(weeklyPnl),
537
+ monthly: toRanking(monthlyPnl),
538
+ allTime: toRanking(allTimePnl),
539
+ },
540
+ performance: {
541
+ winRate: positions.length > 0 ? (winningPositions.length / positions.length) * 100 : 0,
542
+ winCount: winningPositions.length,
543
+ lossCount: losingPositions.length,
544
+ avgPositionSize,
545
+ avgWinAmount,
546
+ avgLossAmount,
547
+ uniqueMarkets,
548
+ },
549
+ categoryBreakdown: categoryStats,
550
+ topPositions: positions
551
+ .sort((a, b) => Math.abs(b.cashPnl ?? 0) - Math.abs(a.cashPnl ?? 0))
552
+ .slice(0, 10)
553
+ .map(p => ({
554
+ market: p.title,
555
+ slug: p.slug,
556
+ outcome: p.outcome,
557
+ size: p.size,
558
+ avgPrice: p.avgPrice,
559
+ currentPrice: p.curPrice,
560
+ pnl: p.cashPnl ?? 0,
561
+ percentPnl: p.percentPnl,
562
+ })),
563
+ recentTrades: recentTrades.map(t => ({
564
+ timestamp: t.timestamp,
565
+ side: t.side,
566
+ size: t.size,
567
+ price: t.price,
568
+ usdcSize: t.usdcSize,
569
+ // Include market info for display
570
+ title: t.title,
571
+ slug: t.slug,
572
+ outcome: t.outcome,
573
+ conditionId: t.conditionId,
574
+ })),
575
+ activitySummary: {
576
+ totalBuys: activitySummary.summary.totalBuys,
577
+ totalSells: activitySummary.summary.totalSells,
578
+ buyVolume: activitySummary.summary.buyVolume,
579
+ sellVolume: activitySummary.summary.sellVolume,
580
+ activeMarketsCount: activitySummary.summary.activeMarkets.length,
581
+ },
582
+ };
583
+ }
584
+ /**
585
+ * Analyze position categories based on title keywords
586
+ */
587
+ analyzeCategories(positions) {
588
+ const categoryStats = {};
589
+ for (const pos of positions) {
590
+ const title = (pos.title || '').toLowerCase();
591
+ let category = 'other';
592
+ if (title.includes('trump') || title.includes('biden') || title.includes('election') || title.includes('president') || title.includes('congress')) {
593
+ category = 'politics';
594
+ }
595
+ else if (title.includes('bitcoin') || title.includes('btc') || title.includes('eth') || title.includes('crypto') || title.includes('solana')) {
596
+ category = 'crypto';
597
+ }
598
+ else if (title.includes('nba') || title.includes('nfl') || title.includes('soccer') || title.includes('football') || title.includes('ufc') || title.includes('mlb')) {
599
+ category = 'sports';
600
+ }
601
+ else if (title.includes('fed') || title.includes('inflation') || title.includes('gdp') || title.includes('interest rate') || title.includes('unemployment')) {
602
+ category = 'economy';
603
+ }
604
+ else if (title.includes('ai') || title.includes('openai') || title.includes('google') || title.includes('apple') || title.includes('tesla')) {
605
+ category = 'tech';
606
+ }
607
+ if (!categoryStats[category]) {
608
+ categoryStats[category] = { count: 0, totalPnl: 0 };
609
+ }
610
+ categoryStats[category].count++;
611
+ categoryStats[category].totalPnl += (pos.cashPnl ?? 0);
612
+ }
613
+ return Object.entries(categoryStats)
614
+ .map(([category, stats]) => ({
615
+ category,
616
+ positionCount: stats.count,
617
+ totalPnl: stats.totalPnl,
618
+ }))
619
+ .sort((a, b) => b.positionCount - a.positionCount);
620
+ }
621
+ // ============================================================================
622
+ // Wallet Comparison - 钱包对比
623
+ // ============================================================================
624
+ /**
625
+ * Compare multiple wallets
626
+ *
627
+ * @example
628
+ * ```typescript
629
+ * const comparison = await sdk.smartMoney.compareWallets(
630
+ * ['0x111...', '0x222...', '0x333...'],
631
+ * { period: 'week' }
632
+ * );
633
+ * ```
634
+ */
635
+ async compareWallets(addresses, options = {}) {
636
+ const period = options.period ?? 'week';
637
+ // Fetch data for all wallets in parallel
638
+ const results = await Promise.all(addresses.map(async (address) => {
639
+ const [periodPnl, positions] = await Promise.all([
640
+ this.walletService.getUserPeriodPnl(address, period).catch(() => null),
641
+ this.walletService.getWalletPositions(address).catch(() => []),
642
+ ]);
643
+ const winningPositions = positions.filter(p => (p.cashPnl ?? 0) > 0);
644
+ const winRate = positions.length > 0
645
+ ? (winningPositions.length / positions.length) * 100
646
+ : 0;
647
+ return {
648
+ address,
649
+ userName: periodPnl?.userName,
650
+ rank: periodPnl?.rank ?? null,
651
+ pnl: periodPnl?.pnl ?? 0,
652
+ volume: periodPnl?.volume ?? 0,
653
+ positionCount: positions.length,
654
+ winRate,
655
+ };
656
+ }));
657
+ // Sort by PnL descending
658
+ results.sort((a, b) => b.pnl - a.pnl);
659
+ return {
660
+ period,
661
+ generatedAt: new Date(),
662
+ wallets: results,
663
+ };
664
+ }
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
+ // ============================================================================
1433
+ // Utilities
1434
+ // ============================================================================
1435
+ isCacheValid() {
1436
+ return Date.now() - this.cacheTimestamp < this.config.cacheTtl && this.smartMoneyCache.size > 0;
1437
+ }
1438
+ disconnect() {
1439
+ if (this.activeSubscription) {
1440
+ this.activeSubscription.unsubscribe();
1441
+ this.activeSubscription = null;
1442
+ }
1443
+ this.tradeHandlers.clear();
1444
+ this.smartMoneyCache.clear();
1445
+ this.smartMoneySet.clear();
1446
+ }
1447
+ }
1448
+ //# sourceMappingURL=smart-money-service.js.map