@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.
- package/README.md +133 -1
- package/README.zh-CN.md +2 -0
- package/dist/scripts/dip-arb/auto-trade.d.ts +20 -0
- package/dist/scripts/dip-arb/auto-trade.d.ts.map +1 -0
- package/dist/scripts/dip-arb/auto-trade.js +373 -0
- package/dist/scripts/dip-arb/auto-trade.js.map +1 -0
- package/dist/scripts/dip-arb/example-basic.d.ts +30 -0
- package/dist/scripts/dip-arb/example-basic.d.ts.map +1 -0
- package/dist/scripts/dip-arb/example-basic.js +222 -0
- package/dist/scripts/dip-arb/example-basic.js.map +1 -0
- package/dist/scripts/dip-arb/redeem-positions.d.ts +11 -0
- package/dist/scripts/dip-arb/redeem-positions.d.ts.map +1 -0
- package/dist/scripts/dip-arb/redeem-positions.js +201 -0
- package/dist/scripts/dip-arb/redeem-positions.js.map +1 -0
- package/dist/scripts/dip-arb/scan-markets.d.ts +6 -0
- package/dist/scripts/dip-arb/scan-markets.d.ts.map +1 -0
- package/dist/scripts/dip-arb/scan-markets.js +73 -0
- package/dist/scripts/dip-arb/scan-markets.js.map +1 -0
- package/dist/src/__tests__/integration/arbitrage-service.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/arbitrage-service.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/bridge-client.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/bridge-client.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/ctf-client.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/ctf-client.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/data-api.integration.test.d.ts.map +1 -0
- package/dist/{__tests__ → src/__tests__}/integration/data-api.integration.test.js +21 -18
- package/dist/src/__tests__/integration/data-api.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/gamma-api.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/gamma-api.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/market-service.integration.test.d.ts.map +1 -0
- package/dist/{__tests__ → src/__tests__}/integration/market-service.integration.test.js +7 -0
- package/dist/src/__tests__/integration/market-service.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/realtime-service-v2.integration.test.js.map +1 -0
- package/dist/src/__tests__/integration/trading-service.integration.test.d.ts.map +1 -0
- package/dist/src/__tests__/integration/trading-service.integration.test.js.map +1 -0
- package/dist/src/__tests__/test-utils.d.ts.map +1 -0
- package/dist/src/__tests__/test-utils.js.map +1 -0
- package/dist/src/catalyst/catalyst-query-service.d.ts +109 -0
- package/dist/src/catalyst/catalyst-query-service.d.ts.map +1 -0
- package/dist/src/catalyst/catalyst-query-service.js +141 -0
- package/dist/src/catalyst/catalyst-query-service.js.map +1 -0
- package/dist/src/catalyst/catalyst-realtime-service.d.ts +40 -0
- package/dist/src/catalyst/catalyst-realtime-service.d.ts.map +1 -0
- package/dist/src/catalyst/catalyst-realtime-service.js +125 -0
- package/dist/src/catalyst/catalyst-realtime-service.js.map +1 -0
- package/dist/src/catalyst/index.d.ts +4 -0
- package/dist/src/catalyst/index.d.ts.map +1 -0
- package/dist/src/catalyst/index.js +4 -0
- package/dist/src/catalyst/index.js.map +1 -0
- package/dist/src/catalyst/types.d.ts +178 -0
- package/dist/src/catalyst/types.d.ts.map +1 -0
- package/dist/src/catalyst/types.js +2 -0
- package/dist/src/catalyst/types.js.map +1 -0
- package/dist/src/clients/bridge-client.d.ts.map +1 -0
- package/dist/src/clients/bridge-client.js.map +1 -0
- package/dist/{clients → src/clients}/ctf-client.d.ts +6 -4
- package/dist/src/clients/ctf-client.d.ts.map +1 -0
- package/dist/src/clients/ctf-client.js.map +1 -0
- package/dist/{clients → src/clients}/data-api.d.ts +94 -20
- package/dist/src/clients/data-api.d.ts.map +1 -0
- package/dist/{clients → src/clients}/data-api.js +119 -52
- package/dist/src/clients/data-api.js.map +1 -0
- package/dist/{clients → src/clients}/gamma-api.d.ts +74 -0
- package/dist/src/clients/gamma-api.d.ts.map +1 -0
- package/dist/{clients → src/clients}/gamma-api.js +7 -0
- package/dist/src/clients/gamma-api.js.map +1 -0
- package/dist/src/clients/subgraph.d.ts.map +1 -0
- package/dist/src/clients/subgraph.js.map +1 -0
- package/dist/src/core/cache-adapter-bridge.d.ts.map +1 -0
- package/dist/src/core/cache-adapter-bridge.js.map +1 -0
- package/dist/{core → src/core}/cache.d.ts +2 -0
- package/dist/src/core/cache.d.ts.map +1 -0
- package/dist/{core → src/core}/cache.js +4 -0
- package/dist/src/core/cache.js.map +1 -0
- package/dist/src/core/errors.d.ts.map +1 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/{core → src/core}/rate-limiter.d.ts +2 -1
- package/dist/src/core/rate-limiter.d.ts.map +1 -0
- package/dist/{core → src/core}/rate-limiter.js +7 -0
- package/dist/src/core/rate-limiter.js.map +1 -0
- package/dist/{core → src/core}/types.d.ts +105 -1
- package/dist/src/core/types.d.ts.map +1 -0
- package/dist/src/core/types.js +49 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/types.test.d.ts.map +1 -0
- package/dist/src/core/types.test.js.map +1 -0
- package/dist/src/core/unified-cache.d.ts.map +1 -0
- package/dist/src/core/unified-cache.js.map +1 -0
- package/dist/{index.d.ts → src/index.d.ts} +24 -5
- package/dist/src/index.d.ts.map +1 -0
- package/dist/{index.js → src/index.js} +38 -4
- package/dist/src/index.js.map +1 -0
- package/dist/src/insider-scan/index.d.ts +3 -0
- package/dist/src/insider-scan/index.d.ts.map +1 -0
- package/dist/src/insider-scan/index.js +3 -0
- package/dist/src/insider-scan/index.js.map +1 -0
- package/dist/src/insider-scan/insider-scan-service.d.ts +63 -0
- package/dist/src/insider-scan/insider-scan-service.d.ts.map +1 -0
- package/dist/src/insider-scan/insider-scan-service.js +153 -0
- package/dist/src/insider-scan/insider-scan-service.js.map +1 -0
- package/dist/src/insider-scan/types.d.ts +205 -0
- package/dist/src/insider-scan/types.d.ts.map +1 -0
- package/dist/src/insider-scan/types.js +7 -0
- package/dist/src/insider-scan/types.js.map +1 -0
- package/dist/src/services/arbitrage-service.d.ts.map +1 -0
- package/dist/{services → src/services}/arbitrage-service.js +14 -4
- package/dist/src/services/arbitrage-service.js.map +1 -0
- package/dist/src/services/authorization-service.d.ts.map +1 -0
- package/dist/src/services/authorization-service.js.map +1 -0
- package/dist/src/services/binance-service.d.ts +154 -0
- package/dist/src/services/binance-service.d.ts.map +1 -0
- package/dist/src/services/binance-service.js +266 -0
- package/dist/src/services/binance-service.js.map +1 -0
- package/dist/src/services/dip-arb-service.d.ts +245 -0
- package/dist/src/services/dip-arb-service.d.ts.map +1 -0
- package/dist/src/services/dip-arb-service.js +1865 -0
- package/dist/src/services/dip-arb-service.js.map +1 -0
- package/dist/src/services/dip-arb-types.d.ts +553 -0
- package/dist/src/services/dip-arb-types.d.ts.map +1 -0
- package/dist/src/services/dip-arb-types.js +164 -0
- package/dist/src/services/dip-arb-types.js.map +1 -0
- package/dist/src/services/market-service.d.ts +431 -0
- package/dist/src/services/market-service.d.ts.map +1 -0
- package/dist/{services → src/services}/market-service.js +501 -17
- package/dist/src/services/market-service.js.map +1 -0
- package/dist/{services → src/services}/onchain-service.d.ts +10 -2
- package/dist/src/services/onchain-service.d.ts.map +1 -0
- package/dist/{services → src/services}/onchain-service.js +8 -0
- package/dist/src/services/onchain-service.js.map +1 -0
- package/dist/{services → src/services}/realtime-service-v2.d.ts +6 -0
- package/dist/src/services/realtime-service-v2.d.ts.map +1 -0
- package/dist/{services → src/services}/realtime-service-v2.js +44 -8
- package/dist/src/services/realtime-service-v2.js.map +1 -0
- package/dist/src/services/smart-money-service.d.ts +769 -0
- package/dist/src/services/smart-money-service.d.ts.map +1 -0
- package/dist/src/services/smart-money-service.js +1448 -0
- package/dist/src/services/smart-money-service.js.map +1 -0
- package/dist/src/services/swap-service.d.ts.map +1 -0
- package/dist/src/services/swap-service.js.map +1 -0
- package/dist/{services → src/services}/trading-service.d.ts +26 -0
- package/dist/src/services/trading-service.d.ts.map +1 -0
- package/dist/{services → src/services}/trading-service.js +72 -1
- package/dist/src/services/trading-service.js.map +1 -0
- package/dist/{services → src/services}/wallet-service.d.ts +81 -4
- package/dist/src/services/wallet-service.d.ts.map +1 -0
- package/dist/{services → src/services}/wallet-service.js +126 -8
- package/dist/src/services/wallet-service.js.map +1 -0
- package/dist/src/signal/index.d.ts +8 -0
- package/dist/src/signal/index.d.ts.map +1 -0
- package/dist/src/signal/index.js +7 -0
- package/dist/src/signal/index.js.map +1 -0
- package/dist/src/signal/signal-service.d.ts +89 -0
- package/dist/src/signal/signal-service.d.ts.map +1 -0
- package/dist/src/signal/signal-service.js +226 -0
- package/dist/src/signal/signal-service.js.map +1 -0
- package/dist/src/signal/types.d.ts +280 -0
- package/dist/src/signal/types.d.ts.map +1 -0
- package/dist/src/signal/types.js +7 -0
- package/dist/src/signal/types.js.map +1 -0
- package/dist/src/utils/price-utils.d.ts.map +1 -0
- package/dist/src/utils/price-utils.js.map +1 -0
- package/dist/src/utils/price-utils.test.d.ts.map +1 -0
- package/dist/src/utils/price-utils.test.js.map +1 -0
- package/dist/src/wallet-report/index.d.ts +3 -0
- package/dist/src/wallet-report/index.d.ts.map +1 -0
- package/dist/src/wallet-report/index.js +3 -0
- package/dist/src/wallet-report/index.js.map +1 -0
- package/dist/src/wallet-report/types.d.ts +187 -0
- package/dist/src/wallet-report/types.d.ts.map +1 -0
- package/dist/src/wallet-report/types.js +7 -0
- package/dist/src/wallet-report/types.js.map +1 -0
- package/dist/src/wallet-report/wallet-report-service.d.ts +91 -0
- package/dist/src/wallet-report/wallet-report-service.d.ts.map +1 -0
- package/dist/src/wallet-report/wallet-report-service.js +208 -0
- package/dist/src/wallet-report/wallet-report-service.js.map +1 -0
- package/dist/src/wallets/hot-wallet-service.d.ts +162 -0
- package/dist/src/wallets/hot-wallet-service.d.ts.map +1 -0
- package/dist/src/wallets/hot-wallet-service.js +251 -0
- package/dist/src/wallets/hot-wallet-service.js.map +1 -0
- package/dist/src/wallets/index.d.ts +15 -0
- package/dist/src/wallets/index.d.ts.map +1 -0
- package/dist/src/wallets/index.js +26 -0
- package/dist/src/wallets/index.js.map +1 -0
- package/package.json +7 -7
- package/dist/__tests__/clob-api.test.d.ts +0 -5
- package/dist/__tests__/clob-api.test.d.ts.map +0 -1
- package/dist/__tests__/clob-api.test.js +0 -240
- package/dist/__tests__/clob-api.test.js.map +0 -1
- package/dist/__tests__/integration/arbitrage-service.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/arbitrage-service.integration.test.js.map +0 -1
- package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/bridge-client.integration.test.js.map +0 -1
- package/dist/__tests__/integration/clob-api.integration.test.d.ts +0 -13
- package/dist/__tests__/integration/clob-api.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/clob-api.integration.test.js +0 -170
- package/dist/__tests__/integration/clob-api.integration.test.js.map +0 -1
- package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/ctf-client.integration.test.js.map +0 -1
- package/dist/__tests__/integration/data-api.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/data-api.integration.test.js.map +0 -1
- package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/gamma-api.integration.test.js.map +0 -1
- package/dist/__tests__/integration/market-service.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/market-service.integration.test.js.map +0 -1
- package/dist/__tests__/integration/realtime-service-v2.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/realtime-service-v2.integration.test.js.map +0 -1
- package/dist/__tests__/integration/trading-service.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/trading-service.integration.test.js.map +0 -1
- package/dist/__tests__/test-utils.d.ts.map +0 -1
- package/dist/__tests__/test-utils.js.map +0 -1
- package/dist/clients/bridge-client.d.ts.map +0 -1
- package/dist/clients/bridge-client.js.map +0 -1
- package/dist/clients/clob-api.d.ts +0 -391
- package/dist/clients/clob-api.d.ts.map +0 -1
- package/dist/clients/clob-api.js +0 -448
- package/dist/clients/clob-api.js.map +0 -1
- package/dist/clients/ctf-client.d.ts.map +0 -1
- package/dist/clients/ctf-client.js.map +0 -1
- package/dist/clients/data-api.d.ts.map +0 -1
- package/dist/clients/data-api.js.map +0 -1
- package/dist/clients/gamma-api.d.ts.map +0 -1
- package/dist/clients/gamma-api.js.map +0 -1
- package/dist/clients/subgraph.d.ts.map +0 -1
- package/dist/clients/subgraph.js.map +0 -1
- package/dist/clients/trading-client.d.ts +0 -252
- package/dist/clients/trading-client.d.ts.map +0 -1
- package/dist/clients/trading-client.js +0 -543
- package/dist/clients/trading-client.js.map +0 -1
- package/dist/clients/websocket-manager.d.ts +0 -103
- package/dist/clients/websocket-manager.d.ts.map +0 -1
- package/dist/clients/websocket-manager.js +0 -200
- package/dist/clients/websocket-manager.js.map +0 -1
- package/dist/core/cache-adapter-bridge.d.ts.map +0 -1
- package/dist/core/cache-adapter-bridge.js.map +0 -1
- package/dist/core/cache.d.ts.map +0 -1
- package/dist/core/cache.js.map +0 -1
- package/dist/core/errors.d.ts.map +0 -1
- package/dist/core/errors.js.map +0 -1
- package/dist/core/rate-limiter.d.ts.map +0 -1
- package/dist/core/rate-limiter.js.map +0 -1
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -19
- package/dist/core/types.js.map +0 -1
- package/dist/core/types.test.d.ts.map +0 -1
- package/dist/core/types.test.js.map +0 -1
- package/dist/core/unified-cache.d.ts.map +0 -1
- package/dist/core/unified-cache.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/services/arbitrage-service.d.ts.map +0 -1
- package/dist/services/arbitrage-service.js.map +0 -1
- package/dist/services/authorization-service.d.ts.map +0 -1
- package/dist/services/authorization-service.js.map +0 -1
- package/dist/services/market-service.d.ts +0 -208
- package/dist/services/market-service.d.ts.map +0 -1
- package/dist/services/market-service.js.map +0 -1
- package/dist/services/onchain-service.d.ts.map +0 -1
- package/dist/services/onchain-service.js.map +0 -1
- package/dist/services/realtime-service-v2.d.ts.map +0 -1
- package/dist/services/realtime-service-v2.js.map +0 -1
- package/dist/services/realtime-service.d.ts +0 -82
- package/dist/services/realtime-service.d.ts.map +0 -1
- package/dist/services/realtime-service.js +0 -182
- package/dist/services/realtime-service.js.map +0 -1
- package/dist/services/smart-money-service.d.ts +0 -196
- package/dist/services/smart-money-service.d.ts.map +0 -1
- package/dist/services/smart-money-service.js +0 -358
- package/dist/services/smart-money-service.js.map +0 -1
- package/dist/services/swap-service.d.ts.map +0 -1
- package/dist/services/swap-service.js.map +0 -1
- package/dist/services/trading-service.d.ts.map +0 -1
- package/dist/services/trading-service.js.map +0 -1
- package/dist/services/wallet-service.d.ts.map +0 -1
- package/dist/services/wallet-service.js.map +0 -1
- package/dist/utils/price-utils.d.ts.map +0 -1
- package/dist/utils/price-utils.js.map +0 -1
- package/dist/utils/price-utils.test.d.ts.map +0 -1
- package/dist/utils/price-utils.test.js.map +0 -1
- /package/dist/{__tests__ → src/__tests__}/integration/arbitrage-service.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/arbitrage-service.integration.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/bridge-client.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/bridge-client.integration.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/ctf-client.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/ctf-client.integration.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/data-api.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/gamma-api.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/gamma-api.integration.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/market-service.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/realtime-service-v2.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/realtime-service-v2.integration.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/trading-service.integration.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/integration/trading-service.integration.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/test-utils.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/test-utils.js +0 -0
- /package/dist/{clients → src/clients}/bridge-client.d.ts +0 -0
- /package/dist/{clients → src/clients}/bridge-client.js +0 -0
- /package/dist/{clients → src/clients}/ctf-client.js +0 -0
- /package/dist/{clients → src/clients}/subgraph.d.ts +0 -0
- /package/dist/{clients → src/clients}/subgraph.js +0 -0
- /package/dist/{core → src/core}/cache-adapter-bridge.d.ts +0 -0
- /package/dist/{core → src/core}/cache-adapter-bridge.js +0 -0
- /package/dist/{core → src/core}/errors.d.ts +0 -0
- /package/dist/{core → src/core}/errors.js +0 -0
- /package/dist/{core → src/core}/types.test.d.ts +0 -0
- /package/dist/{core → src/core}/types.test.js +0 -0
- /package/dist/{core → src/core}/unified-cache.d.ts +0 -0
- /package/dist/{core → src/core}/unified-cache.js +0 -0
- /package/dist/{services → src/services}/arbitrage-service.d.ts +0 -0
- /package/dist/{services → src/services}/authorization-service.d.ts +0 -0
- /package/dist/{services → src/services}/authorization-service.js +0 -0
- /package/dist/{services → src/services}/swap-service.d.ts +0 -0
- /package/dist/{services → src/services}/swap-service.js +0 -0
- /package/dist/{utils → src/utils}/price-utils.d.ts +0 -0
- /package/dist/{utils → src/utils}/price-utils.js +0 -0
- /package/dist/{utils → src/utils}/price-utils.test.d.ts +0 -0
- /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
|