@catalyst-team/poly-sdk 0.1.0 ā 0.2.0
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/LICENSE +21 -0
- package/README.en.md +538 -0
- package/README.md +357 -78
- package/docs/00-design.md +1 -1
- package/docs/02-API.md +21 -21
- package/docs/arb/test-plan.md +387 -0
- package/docs/arb/test-results.md +336 -0
- package/docs/arbitrage.md +754 -0
- package/docs/reports/smart-money-analysis-2025-12-23-cn.md +840 -0
- package/examples/13-arbitrage-service.ts +211 -0
- package/examples/README.md +1 -1
- package/package.json +19 -19
- package/scripts/arb/faze-bo3-arb.ts +385 -0
- package/scripts/arb/settle-position.ts +190 -0
- package/scripts/arb/token-rebalancer.ts +420 -0
- package/scripts/arb-tests/01-unit-tests.ts +495 -0
- package/scripts/arb-tests/02-integration-tests.ts +412 -0
- package/scripts/arb-tests/03-e2e-tests.ts +503 -0
- package/scripts/arb-tests/README.md +109 -0
- package/scripts/verify/verify-all-apis.ts +1 -1
- package/src/clients/clob-api.ts +1 -1
- package/src/clients/gamma-api.ts +1 -1
- package/src/core/cache-adapter-bridge.ts +1 -1
- package/src/core/types.ts +3 -3
- package/src/core/unified-cache.ts +1 -1
- package/src/index.ts +25 -19
- package/src/services/arbitrage-service.ts +1807 -0
- package/.env +0 -0
- 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/bridge-client.integration.test.d.ts +0 -11
- package/dist/__tests__/integration/bridge-client.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/bridge-client.integration.test.js +0 -260
- 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 +0 -17
- package/dist/__tests__/integration/ctf-client.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/ctf-client.integration.test.js +0 -234
- package/dist/__tests__/integration/ctf-client.integration.test.js.map +0 -1
- package/dist/__tests__/integration/data-api.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/data-api.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/data-api.integration.test.js +0 -161
- package/dist/__tests__/integration/data-api.integration.test.js.map +0 -1
- package/dist/__tests__/integration/gamma-api.integration.test.d.ts +0 -9
- package/dist/__tests__/integration/gamma-api.integration.test.d.ts.map +0 -1
- package/dist/__tests__/integration/gamma-api.integration.test.js +0 -170
- package/dist/__tests__/integration/gamma-api.integration.test.js.map +0 -1
- package/dist/__tests__/test-utils.d.ts +0 -92
- package/dist/__tests__/test-utils.d.ts.map +0 -1
- package/dist/__tests__/test-utils.js +0 -143
- package/dist/__tests__/test-utils.js.map +0 -1
- package/dist/clients/bridge-client.d.ts +0 -388
- package/dist/clients/bridge-client.d.ts.map +0 -1
- package/dist/clients/bridge-client.js +0 -587
- package/dist/clients/bridge-client.js.map +0 -1
- package/dist/clients/clob-api.d.ts +0 -318
- package/dist/clients/clob-api.d.ts.map +0 -1
- package/dist/clients/clob-api.js +0 -388
- package/dist/clients/clob-api.js.map +0 -1
- package/dist/clients/ctf-client.d.ts +0 -473
- package/dist/clients/ctf-client.d.ts.map +0 -1
- package/dist/clients/ctf-client.js +0 -915
- package/dist/clients/ctf-client.js.map +0 -1
- package/dist/clients/data-api.d.ts +0 -134
- package/dist/clients/data-api.d.ts.map +0 -1
- package/dist/clients/data-api.js +0 -265
- package/dist/clients/data-api.js.map +0 -1
- package/dist/clients/gamma-api.d.ts +0 -401
- package/dist/clients/gamma-api.d.ts.map +0 -1
- package/dist/clients/gamma-api.js +0 -352
- package/dist/clients/gamma-api.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 -100
- package/dist/clients/websocket-manager.d.ts.map +0 -1
- package/dist/clients/websocket-manager.js +0 -193
- package/dist/clients/websocket-manager.js.map +0 -1
- package/dist/core/cache-adapter-bridge.d.ts +0 -36
- package/dist/core/cache-adapter-bridge.d.ts.map +0 -1
- package/dist/core/cache-adapter-bridge.js +0 -81
- package/dist/core/cache-adapter-bridge.js.map +0 -1
- package/dist/core/cache.d.ts +0 -40
- package/dist/core/cache.d.ts.map +0 -1
- package/dist/core/cache.js +0 -71
- package/dist/core/cache.js.map +0 -1
- package/dist/core/errors.d.ts +0 -38
- package/dist/core/errors.d.ts.map +0 -1
- package/dist/core/errors.js +0 -84
- package/dist/core/errors.js.map +0 -1
- package/dist/core/rate-limiter.d.ts +0 -31
- package/dist/core/rate-limiter.d.ts.map +0 -1
- package/dist/core/rate-limiter.js +0 -70
- package/dist/core/rate-limiter.js.map +0 -1
- package/dist/core/types.d.ts +0 -314
- 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/unified-cache.d.ts +0 -63
- package/dist/core/unified-cache.d.ts.map +0 -1
- package/dist/core/unified-cache.js +0 -114
- package/dist/core/unified-cache.js.map +0 -1
- package/dist/index.d.ts +0 -94
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -258
- package/dist/index.js.map +0 -1
- package/dist/mcp/errors.d.ts +0 -33
- package/dist/mcp/errors.d.ts.map +0 -1
- package/dist/mcp/errors.js +0 -86
- package/dist/mcp/errors.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -62
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -173
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/server.d.ts +0 -17
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/mcp/server.js +0 -155
- package/dist/mcp/server.js.map +0 -1
- package/dist/mcp/tools/guide.d.ts +0 -12
- package/dist/mcp/tools/guide.d.ts.map +0 -1
- package/dist/mcp/tools/guide.js +0 -801
- package/dist/mcp/tools/guide.js.map +0 -1
- package/dist/mcp/tools/index.d.ts +0 -11
- package/dist/mcp/tools/index.d.ts.map +0 -1
- package/dist/mcp/tools/index.js +0 -27
- package/dist/mcp/tools/index.js.map +0 -1
- package/dist/mcp/tools/market.d.ts +0 -11
- package/dist/mcp/tools/market.d.ts.map +0 -1
- package/dist/mcp/tools/market.js +0 -314
- package/dist/mcp/tools/market.js.map +0 -1
- package/dist/mcp/tools/order.d.ts +0 -10
- package/dist/mcp/tools/order.d.ts.map +0 -1
- package/dist/mcp/tools/order.js +0 -258
- package/dist/mcp/tools/order.js.map +0 -1
- package/dist/mcp/tools/trade.d.ts +0 -38
- package/dist/mcp/tools/trade.d.ts.map +0 -1
- package/dist/mcp/tools/trade.js +0 -314
- package/dist/mcp/tools/trade.js.map +0 -1
- package/dist/mcp/tools/trader.d.ts +0 -11
- package/dist/mcp/tools/trader.d.ts.map +0 -1
- package/dist/mcp/tools/trader.js +0 -277
- package/dist/mcp/tools/trader.js.map +0 -1
- package/dist/mcp/tools/wallet.d.ts +0 -274
- package/dist/mcp/tools/wallet.d.ts.map +0 -1
- package/dist/mcp/tools/wallet.js +0 -579
- package/dist/mcp/tools/wallet.js.map +0 -1
- package/dist/mcp/types.d.ts +0 -413
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -5
- package/dist/mcp/types.js.map +0 -1
- package/dist/services/authorization-service.d.ts +0 -97
- package/dist/services/authorization-service.d.ts.map +0 -1
- package/dist/services/authorization-service.js +0 -279
- package/dist/services/authorization-service.js.map +0 -1
- package/dist/services/market-service.d.ts +0 -108
- package/dist/services/market-service.d.ts.map +0 -1
- package/dist/services/market-service.js +0 -458
- package/dist/services/market-service.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 -150
- package/dist/services/realtime-service.js.map +0 -1
- package/dist/services/swap-service.d.ts +0 -217
- package/dist/services/swap-service.d.ts.map +0 -1
- package/dist/services/swap-service.js +0 -695
- package/dist/services/swap-service.js.map +0 -1
- package/dist/services/wallet-service.d.ts +0 -94
- package/dist/services/wallet-service.d.ts.map +0 -1
- package/dist/services/wallet-service.js +0 -173
- package/dist/services/wallet-service.js.map +0 -1
- package/dist/utils/price-utils.d.ts +0 -153
- package/dist/utils/price-utils.d.ts.map +0 -1
- package/dist/utils/price-utils.js +0 -236
- package/dist/utils/price-utils.js.map +0 -1
- package/docs/01-mcp.md +0 -2041
- package/docs/e2e/01-trader-tools.md +0 -159
- package/docs/e2e/02-market-tools.md +0 -180
- package/docs/e2e/03-order-tools.md +0 -166
- package/docs/e2e/04-wallet-tools.md +0 -224
- package/docs/e2e/05-trading-tools.md +0 -327
- package/docs/e2e/06-integration-scenarios.md +0 -481
- package/docs/e2e/coordinator.md +0 -376
- package/scripts/truth.md +0 -440
- package/src/mcp/README.md +0 -380
- package/src/mcp/errors.ts +0 -124
- package/src/mcp/index.ts +0 -309
- package/src/mcp/server.ts +0 -183
- package/src/mcp/tools/guide.ts +0 -821
- package/src/mcp/tools/index.ts +0 -73
- package/src/mcp/tools/market.ts +0 -363
- package/src/mcp/tools/order.ts +0 -326
- package/src/mcp/tools/trade.ts +0 -417
- package/src/mcp/tools/trader.ts +0 -322
- package/src/mcp/tools/wallet.ts +0 -683
- package/src/mcp/types.ts +0 -472
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Example 13: ArbitrageService - Complete Workflow
|
|
4
|
+
*
|
|
5
|
+
* Demonstrates the full arbitrage workflow:
|
|
6
|
+
* 1. Scan markets for opportunities
|
|
7
|
+
* 2. Start real-time monitoring
|
|
8
|
+
* 3. Auto-execute arbitrage
|
|
9
|
+
* 4. Stop and clear positions
|
|
10
|
+
*
|
|
11
|
+
* Environment variables:
|
|
12
|
+
* POLY_PRIVKEY - Private key for trading (optional for scan-only mode)
|
|
13
|
+
*
|
|
14
|
+
* Run with:
|
|
15
|
+
* pnpm example:arb-service
|
|
16
|
+
*
|
|
17
|
+
* Or scan-only (no trading):
|
|
18
|
+
* npx tsx examples/13-arbitrage-service.ts --scan-only
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { ArbitrageService } from '../src/index.js';
|
|
22
|
+
|
|
23
|
+
// Parse arguments
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const SCAN_ONLY = args.includes('--scan-only');
|
|
26
|
+
const RUN_DURATION = parseInt(args.find(a => a.startsWith('--duration='))?.split('=')[1] || '60') * 1000; // default 60s
|
|
27
|
+
|
|
28
|
+
async function main() {
|
|
29
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
30
|
+
console.log('ā ArbitrageService - Complete Workflow ā');
|
|
31
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
32
|
+
console.log();
|
|
33
|
+
|
|
34
|
+
const privateKey = process.env.POLY_PRIVKEY;
|
|
35
|
+
|
|
36
|
+
if (!privateKey && !SCAN_ONLY) {
|
|
37
|
+
console.log('No POLY_PRIVKEY provided. Running in scan-only mode.\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ========== Initialize ArbitrageService ==========
|
|
41
|
+
const arbService = new ArbitrageService({
|
|
42
|
+
privateKey: SCAN_ONLY ? undefined : privateKey,
|
|
43
|
+
profitThreshold: 0.005, // 0.5% minimum profit
|
|
44
|
+
minTradeSize: 5, // $5 minimum
|
|
45
|
+
maxTradeSize: 100, // $100 maximum
|
|
46
|
+
autoExecute: !SCAN_ONLY && !!privateKey,
|
|
47
|
+
enableLogging: true,
|
|
48
|
+
|
|
49
|
+
// Rebalancer config
|
|
50
|
+
enableRebalancer: !SCAN_ONLY && !!privateKey,
|
|
51
|
+
minUsdcRatio: 0.2,
|
|
52
|
+
maxUsdcRatio: 0.8,
|
|
53
|
+
targetUsdcRatio: 0.5,
|
|
54
|
+
imbalanceThreshold: 5,
|
|
55
|
+
rebalanceInterval: 10000,
|
|
56
|
+
rebalanceCooldown: 30000,
|
|
57
|
+
|
|
58
|
+
// Execution safety
|
|
59
|
+
sizeSafetyFactor: 0.8,
|
|
60
|
+
autoFixImbalance: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ========== Set up event listeners ==========
|
|
64
|
+
arbService.on('opportunity', (opp) => {
|
|
65
|
+
console.log(`\nšÆ ${opp.type.toUpperCase()} ARB: ${opp.profitPercent.toFixed(2)}%`);
|
|
66
|
+
console.log(` ${opp.description}`);
|
|
67
|
+
console.log(` Recommended size: ${opp.recommendedSize.toFixed(2)}, Est profit: $${opp.estimatedProfit.toFixed(2)}`);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
arbService.on('execution', (result) => {
|
|
71
|
+
if (result.success) {
|
|
72
|
+
console.log(`\nā
Execution succeeded!`);
|
|
73
|
+
console.log(` Type: ${result.type}, Size: ${result.size.toFixed(2)}`);
|
|
74
|
+
console.log(` Profit: $${result.profit.toFixed(2)}`);
|
|
75
|
+
console.log(` Time: ${result.executionTimeMs}ms`);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(`\nā Execution failed: ${result.error}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
arbService.on('rebalance', (result) => {
|
|
82
|
+
if (result.success) {
|
|
83
|
+
console.log(`\nš Rebalance: ${result.action.type} ${result.action.amount.toFixed(2)}`);
|
|
84
|
+
console.log(` Reason: ${result.action.reason}`);
|
|
85
|
+
} else {
|
|
86
|
+
console.log(`\nā ļø Rebalance failed: ${result.error}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
arbService.on('balanceUpdate', (balance) => {
|
|
91
|
+
console.log(`\nš° Balance: USDC=${balance.usdc.toFixed(2)}, YES=${balance.yesTokens.toFixed(2)}, NO=${balance.noTokens.toFixed(2)}`);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
arbService.on('error', (error) => {
|
|
95
|
+
console.error(`\nšØ Error: ${error.message}`);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ========== Step 1: Scan Markets ==========
|
|
99
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
100
|
+
console.log('Step 1: Scanning markets for arbitrage opportunities...');
|
|
101
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
102
|
+
|
|
103
|
+
const scanResults = await arbService.scanMarkets(
|
|
104
|
+
{ minVolume24h: 5000, limit: 50 },
|
|
105
|
+
0.003 // 0.3% min profit for scanning
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const opportunities = scanResults.filter(r => r.arbType !== 'none');
|
|
109
|
+
|
|
110
|
+
console.log(`\nFound ${opportunities.length} opportunities out of ${scanResults.length} markets scanned\n`);
|
|
111
|
+
|
|
112
|
+
if (opportunities.length > 0) {
|
|
113
|
+
console.log('Top 5 opportunities:');
|
|
114
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
115
|
+
for (const r of opportunities.slice(0, 5)) {
|
|
116
|
+
console.log(`ā ${r.market.name.slice(0, 50).padEnd(50)} ā`);
|
|
117
|
+
console.log(`ā ${r.arbType.toUpperCase()} +${r.profitPercent.toFixed(2)}% Size: ${r.availableSize.toFixed(0)} Vol: $${r.volume24h.toLocaleString().padEnd(10)} ā`);
|
|
118
|
+
console.log(`āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤`);
|
|
119
|
+
}
|
|
120
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (SCAN_ONLY || opportunities.length === 0) {
|
|
124
|
+
console.log('\nā
Scan complete.');
|
|
125
|
+
if (SCAN_ONLY) {
|
|
126
|
+
console.log(' (Running in scan-only mode, not starting arbitrage)');
|
|
127
|
+
}
|
|
128
|
+
if (opportunities.length === 0) {
|
|
129
|
+
console.log(' (No profitable opportunities found)');
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ========== Step 2: Start Arbitrage ==========
|
|
135
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
136
|
+
console.log('Step 2: Starting arbitrage on best market...');
|
|
137
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
138
|
+
|
|
139
|
+
const best = opportunities[0];
|
|
140
|
+
console.log(`Selected: ${best.market.name}`);
|
|
141
|
+
console.log(`Type: ${best.arbType.toUpperCase()}, Profit: +${best.profitPercent.toFixed(2)}%\n`);
|
|
142
|
+
|
|
143
|
+
await arbService.start(best.market);
|
|
144
|
+
|
|
145
|
+
// ========== Step 3: Run for duration ==========
|
|
146
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
147
|
+
console.log(`Step 3: Running for ${RUN_DURATION / 1000} seconds...`);
|
|
148
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
149
|
+
console.log('Monitoring for arbitrage opportunities...');
|
|
150
|
+
console.log('(Press Ctrl+C to stop early)\n');
|
|
151
|
+
|
|
152
|
+
// Handle graceful shutdown
|
|
153
|
+
let stopped = false;
|
|
154
|
+
const shutdown = async () => {
|
|
155
|
+
if (stopped) return;
|
|
156
|
+
stopped = true;
|
|
157
|
+
console.log('\n\nShutting down...');
|
|
158
|
+
await cleanup();
|
|
159
|
+
process.exit(0);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
process.on('SIGINT', shutdown);
|
|
163
|
+
process.on('SIGTERM', shutdown);
|
|
164
|
+
|
|
165
|
+
// Wait for duration
|
|
166
|
+
await new Promise(resolve => setTimeout(resolve, RUN_DURATION));
|
|
167
|
+
|
|
168
|
+
// ========== Step 4: Stop and Clear ==========
|
|
169
|
+
async function cleanup() {
|
|
170
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
171
|
+
console.log('Step 4: Stopping and clearing positions...');
|
|
172
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā\n');
|
|
173
|
+
|
|
174
|
+
await arbService.stop();
|
|
175
|
+
|
|
176
|
+
// Print stats
|
|
177
|
+
const stats = arbService.getStats();
|
|
178
|
+
console.log('Session Statistics:');
|
|
179
|
+
console.log(` Opportunities detected: ${stats.opportunitiesDetected}`);
|
|
180
|
+
console.log(` Executions attempted: ${stats.executionsAttempted}`);
|
|
181
|
+
console.log(` Executions succeeded: ${stats.executionsSucceeded}`);
|
|
182
|
+
console.log(` Total profit: $${stats.totalProfit.toFixed(2)}`);
|
|
183
|
+
console.log(` Running time: ${(stats.runningTimeMs / 1000).toFixed(0)}s`);
|
|
184
|
+
|
|
185
|
+
// Clear positions
|
|
186
|
+
if (privateKey) {
|
|
187
|
+
console.log('\nClearing positions...');
|
|
188
|
+
const clearResult = await arbService.clearPositions(best.market, false);
|
|
189
|
+
|
|
190
|
+
console.log(`\nPosition status:`);
|
|
191
|
+
console.log(` Market status: ${clearResult.marketStatus}`);
|
|
192
|
+
console.log(` YES balance: ${clearResult.yesBalance.toFixed(4)}`);
|
|
193
|
+
console.log(` NO balance: ${clearResult.noBalance.toFixed(4)}`);
|
|
194
|
+
console.log(` Expected recovery: $${clearResult.totalUsdcRecovered.toFixed(2)}`);
|
|
195
|
+
|
|
196
|
+
if (clearResult.actions.length > 0) {
|
|
197
|
+
console.log(`\nPlanned actions:`);
|
|
198
|
+
for (const action of clearResult.actions) {
|
|
199
|
+
console.log(` - ${action.type}: ${action.amount.toFixed(4)} ā ~$${action.usdcResult.toFixed(2)}`);
|
|
200
|
+
}
|
|
201
|
+
console.log('\n(Run with --execute-clear to actually clear positions)');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log('\nā
Done!');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await cleanup();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
main().catch(console.error);
|
package/examples/README.md
CHANGED
|
@@ -41,7 +41,7 @@ pnpm example:smart-money # 02-smart-money.ts
|
|
|
41
41
|
Get started with the SDK. Fetches trending markets and orderbook data.
|
|
42
42
|
|
|
43
43
|
```typescript
|
|
44
|
-
import { PolymarketSDK } from '@
|
|
44
|
+
import { PolymarketSDK } from '@catalyst-team/poly-sdk';
|
|
45
45
|
const sdk = new PolymarketSDK();
|
|
46
46
|
const trending = await sdk.gammaApi.getTrendingMarkets(5);
|
|
47
47
|
```
|
package/package.json
CHANGED
|
@@ -1,36 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-team/poly-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
|
+
"description": "TypeScript SDK for Polymarket - prediction markets trading, smart money analysis, and market data",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.js",
|
|
7
8
|
"types": "./dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
"poly-mcp": "./dist/mcp/server.js"
|
|
10
|
-
},
|
|
11
9
|
"exports": {
|
|
12
10
|
".": {
|
|
13
11
|
"types": "./dist/index.d.ts",
|
|
14
12
|
"import": "./dist/index.js"
|
|
15
|
-
},
|
|
16
|
-
"./mcp": {
|
|
17
|
-
"types": "./dist/mcp/index.d.ts",
|
|
18
|
-
"import": "./dist/mcp/index.js"
|
|
19
|
-
},
|
|
20
|
-
"./mcp/server": {
|
|
21
|
-
"types": "./dist/mcp/server.d.ts",
|
|
22
|
-
"import": "./dist/mcp/server.js"
|
|
23
13
|
}
|
|
24
14
|
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"polymarket",
|
|
17
|
+
"prediction-markets",
|
|
18
|
+
"trading",
|
|
19
|
+
"crypto",
|
|
20
|
+
"defi",
|
|
21
|
+
"smart-money"
|
|
22
|
+
],
|
|
23
|
+
"author": "Catalyst Team",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
25
28
|
"dependencies": {
|
|
26
|
-
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
27
29
|
"@nevuamarkets/poly-websockets": "^0.3.0",
|
|
28
30
|
"@polymarket/clob-client": "4.22.8",
|
|
29
31
|
"@types/ws": "^8.18.1",
|
|
30
32
|
"bottleneck": "^2.19.5",
|
|
31
33
|
"ethers": "5",
|
|
32
34
|
"ws": "^8.18.3",
|
|
33
|
-
"@
|
|
35
|
+
"@catalyst-team/cache": "0.1.0"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"tsx": "^4.7.0",
|
|
@@ -39,8 +41,6 @@
|
|
|
39
41
|
},
|
|
40
42
|
"scripts": {
|
|
41
43
|
"build": "tsc",
|
|
42
|
-
"mcp": "node dist/mcp/server.js",
|
|
43
|
-
"mcp:dev": "tsx src/mcp/server.ts",
|
|
44
44
|
"dev": "tsc --watch",
|
|
45
45
|
"test": "vitest run",
|
|
46
46
|
"test:watch": "vitest",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"example:trading": "tsx examples/08-trading-orders.ts",
|
|
56
56
|
"example:rewards": "tsx examples/09-rewards-tracking.ts",
|
|
57
57
|
"example:ctf": "tsx examples/10-ctf-operations.ts",
|
|
58
|
-
"example:
|
|
59
|
-
"example:
|
|
60
|
-
"example:
|
|
58
|
+
"example:live-arb": "tsx examples/11-live-arbitrage-scan.ts",
|
|
59
|
+
"example:trending-arb": "tsx examples/12-trending-arb-monitor.ts",
|
|
60
|
+
"example:arb-service": "tsx examples/13-arbitrage-service.ts"
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* FaZe vs Passion UA - BO3 Match Winner Arbitrage Monitor (V2 - Instant Merge)
|
|
4
|
+
*
|
|
5
|
+
* V2 ēē„ļ¼é
å token-rebalancer.ts 使ēØ
|
|
6
|
+
* - Long Arb: Buy YES + NO ā ē«å³ Mergeļ¼å 为蓦ę·é¢åäŗ tokensļ¼
|
|
7
|
+
* - Short Arb: ē“ę„ååŗé¢åē tokens
|
|
8
|
+
* - Rebalancer čŖåØč”„å
tokensļ¼ē”®äæéę¶ęęµåØę§
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* POLY_PRIVKEY=0x... npx tsx scripts/v2/faze-bo3-arb.ts
|
|
12
|
+
* POLY_PRIVKEY=0x... npx tsx scripts/v2/faze-bo3-arb.ts --auto-execute
|
|
13
|
+
* POLY_PRIVKEY=0x... npx tsx scripts/v2/faze-bo3-arb.ts --auto-execute --threshold 0.003
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
WebSocketManager,
|
|
18
|
+
CTFClient,
|
|
19
|
+
TradingClient,
|
|
20
|
+
RateLimiter,
|
|
21
|
+
type BookUpdate,
|
|
22
|
+
type TokenIds,
|
|
23
|
+
} from '../../src/index.js';
|
|
24
|
+
|
|
25
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
26
|
+
// MARKET CONFIGURATION - FaZe vs Passion UA BO3 Match Winner
|
|
27
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
28
|
+
|
|
29
|
+
const MARKET_CONFIG = {
|
|
30
|
+
name: 'CS2: FaZe vs Passion UA (BO3)',
|
|
31
|
+
conditionId: '0x5ac2e92a82ea41ec3a4d9332d323d8d198a8c9acc732b7e6373bd61f45e1df49',
|
|
32
|
+
outcomes: ['FaZe', 'Passion UA'],
|
|
33
|
+
yesTokenId: '89062136554637645106569570664838812035010963361832797298254486917225439629146',
|
|
34
|
+
noTokenId: '54956090965819006918015199317329813503156478664679332459223691084797135448319',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
38
|
+
// CONFIGURATION
|
|
39
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
40
|
+
|
|
41
|
+
const PRIVATE_KEY = process.env.POLY_PRIVKEY || process.env.POLYMARKET_PRIVATE_KEY || '';
|
|
42
|
+
const RPC_URL = process.env.POLYGON_RPC_URL || 'https://polygon-rpc.com';
|
|
43
|
+
|
|
44
|
+
const args = process.argv.slice(2);
|
|
45
|
+
const AUTO_EXECUTE = args.includes('--auto-execute');
|
|
46
|
+
const thresholdIdx = args.indexOf('--threshold');
|
|
47
|
+
const PROFIT_THRESHOLD = thresholdIdx !== -1 ? parseFloat(args[thresholdIdx + 1]) : 0.005;
|
|
48
|
+
const MIN_SIZE = 5;
|
|
49
|
+
const MAX_SINGLE_TRADE = 100;
|
|
50
|
+
const MIN_TOKEN_RESERVE = 10;
|
|
51
|
+
|
|
52
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
53
|
+
// STATE
|
|
54
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
55
|
+
|
|
56
|
+
interface OrderbookState {
|
|
57
|
+
yesBids: Array<{ price: number; size: number }>;
|
|
58
|
+
yesAsks: Array<{ price: number; size: number }>;
|
|
59
|
+
noBids: Array<{ price: number; size: number }>;
|
|
60
|
+
noAsks: Array<{ price: number; size: number }>;
|
|
61
|
+
lastUpdate: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface BalanceState {
|
|
65
|
+
usdc: number;
|
|
66
|
+
yesTokens: number;
|
|
67
|
+
noTokens: number;
|
|
68
|
+
lastUpdate: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let orderbook: OrderbookState = {
|
|
72
|
+
yesBids: [],
|
|
73
|
+
yesAsks: [],
|
|
74
|
+
noBids: [],
|
|
75
|
+
noAsks: [],
|
|
76
|
+
lastUpdate: 0,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
let balance: BalanceState = {
|
|
80
|
+
usdc: 0,
|
|
81
|
+
yesTokens: 0,
|
|
82
|
+
noTokens: 0,
|
|
83
|
+
lastUpdate: 0,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let wsManager: WebSocketManager;
|
|
87
|
+
let ctf: CTFClient | null = null;
|
|
88
|
+
let tradingClient: TradingClient | null = null;
|
|
89
|
+
|
|
90
|
+
let isExecuting = false;
|
|
91
|
+
let lastArbCheck = 0;
|
|
92
|
+
let totalOpportunities = 0;
|
|
93
|
+
let totalExecuted = 0;
|
|
94
|
+
let totalProfit = 0;
|
|
95
|
+
|
|
96
|
+
const tokenIds: TokenIds = {
|
|
97
|
+
yesTokenId: MARKET_CONFIG.yesTokenId,
|
|
98
|
+
noTokenId: MARKET_CONFIG.noTokenId,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
102
|
+
// MAIN
|
|
103
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
104
|
+
|
|
105
|
+
async function main() {
|
|
106
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
107
|
+
console.log('ā FAZE vs PASSION UA - BO3 ARBITRAGE (V2 INSTANT) ā');
|
|
108
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(`Market: ${MARKET_CONFIG.name}`);
|
|
111
|
+
console.log(`Outcomes: ${MARKET_CONFIG.outcomes[0]} (YES) vs ${MARKET_CONFIG.outcomes[1]} (NO)`);
|
|
112
|
+
console.log(`Condition ID: ${MARKET_CONFIG.conditionId.slice(0, 20)}...`);
|
|
113
|
+
console.log(`Profit Threshold: ${(PROFIT_THRESHOLD * 100).toFixed(2)}%`);
|
|
114
|
+
console.log(`Auto Execute: ${AUTO_EXECUTE ? 'YES' : 'NO'}`);
|
|
115
|
+
console.log(`Strategy: INSTANT MERGE (with Rebalancer support)`);
|
|
116
|
+
console.log('');
|
|
117
|
+
|
|
118
|
+
const rateLimiter = new RateLimiter();
|
|
119
|
+
|
|
120
|
+
if (PRIVATE_KEY) {
|
|
121
|
+
ctf = new CTFClient({ privateKey: PRIVATE_KEY, rpcUrl: RPC_URL });
|
|
122
|
+
console.log(`Wallet: ${ctf.getAddress()}`);
|
|
123
|
+
|
|
124
|
+
tradingClient = new TradingClient(rateLimiter, { privateKey: PRIVATE_KEY, chainId: 137 });
|
|
125
|
+
await tradingClient.initialize();
|
|
126
|
+
console.log('Trading client initialized');
|
|
127
|
+
|
|
128
|
+
await updateBalance();
|
|
129
|
+
console.log(`USDC.e Balance: ${balance.usdc.toFixed(2)}`);
|
|
130
|
+
console.log(`YES Tokens: ${balance.yesTokens.toFixed(2)}`);
|
|
131
|
+
console.log(`NO Tokens: ${balance.noTokens.toFixed(2)}`);
|
|
132
|
+
|
|
133
|
+
const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
|
|
134
|
+
if (heldPairs < MIN_TOKEN_RESERVE) {
|
|
135
|
+
console.log(`\nā ļø Token reserve low (${heldPairs.toFixed(0)} < ${MIN_TOKEN_RESERVE})`);
|
|
136
|
+
console.log(` Run token-rebalancer.ts to auto-split tokens`);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
console.log('No wallet configured - monitoring only');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
console.log('');
|
|
143
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
144
|
+
console.log('Connecting to WebSocket...');
|
|
145
|
+
console.log('');
|
|
146
|
+
|
|
147
|
+
wsManager = new WebSocketManager({ enableLogging: false });
|
|
148
|
+
wsManager.on('bookUpdate', handleBookUpdate);
|
|
149
|
+
wsManager.on('connected', ({ assetIds }) => console.log(`WebSocket connected for ${assetIds.length} assets`));
|
|
150
|
+
wsManager.on('error', (error) => console.error('WebSocket error:', error.message));
|
|
151
|
+
|
|
152
|
+
await wsManager.subscribe([MARKET_CONFIG.yesTokenId, MARKET_CONFIG.noTokenId]);
|
|
153
|
+
setInterval(updateBalance, 30000);
|
|
154
|
+
|
|
155
|
+
process.on('SIGINT', async () => {
|
|
156
|
+
console.log('\n\nShutting down...');
|
|
157
|
+
console.log(`Total opportunities: ${totalOpportunities}, Executed: ${totalExecuted}, Profit: $${totalProfit.toFixed(2)}`);
|
|
158
|
+
await wsManager.unsubscribeAll();
|
|
159
|
+
process.exit(0);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
console.log('Monitoring for arbitrage opportunities...\n');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
166
|
+
// BALANCE & ORDERBOOK
|
|
167
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
168
|
+
|
|
169
|
+
async function updateBalance() {
|
|
170
|
+
if (!ctf) return;
|
|
171
|
+
try {
|
|
172
|
+
const [usdcBalance, positions] = await Promise.all([
|
|
173
|
+
ctf.getUsdcBalance(),
|
|
174
|
+
ctf.getPositionBalanceByTokenIds(MARKET_CONFIG.conditionId, tokenIds),
|
|
175
|
+
]);
|
|
176
|
+
balance.usdc = parseFloat(usdcBalance);
|
|
177
|
+
balance.yesTokens = parseFloat(positions.yesBalance);
|
|
178
|
+
balance.noTokens = parseFloat(positions.noBalance);
|
|
179
|
+
balance.lastUpdate = Date.now();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error('Failed to update balance:', (error as Error).message);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleBookUpdate(update: BookUpdate) {
|
|
186
|
+
const { assetId, bids, asks } = update;
|
|
187
|
+
if (assetId === MARKET_CONFIG.yesTokenId) {
|
|
188
|
+
orderbook.yesBids = bids.sort((a, b) => b.price - a.price);
|
|
189
|
+
orderbook.yesAsks = asks.sort((a, b) => a.price - b.price);
|
|
190
|
+
} else if (assetId === MARKET_CONFIG.noTokenId) {
|
|
191
|
+
orderbook.noBids = bids.sort((a, b) => b.price - a.price);
|
|
192
|
+
orderbook.noAsks = asks.sort((a, b) => a.price - b.price);
|
|
193
|
+
}
|
|
194
|
+
orderbook.lastUpdate = Date.now();
|
|
195
|
+
checkArbitrage();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
199
|
+
// ARBITRAGE DETECTION
|
|
200
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
201
|
+
|
|
202
|
+
function checkArbitrage() {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
if (now - lastArbCheck < 10) return;
|
|
205
|
+
lastArbCheck = now;
|
|
206
|
+
|
|
207
|
+
if (orderbook.yesBids.length === 0 || orderbook.noAsks.length === 0) return;
|
|
208
|
+
|
|
209
|
+
const yesBestBid = orderbook.yesBids[0]?.price || 0;
|
|
210
|
+
const yesBestAsk = orderbook.yesAsks[0]?.price || 1;
|
|
211
|
+
const noBestBid = orderbook.noBids[0]?.price || 0;
|
|
212
|
+
const noBestAsk = orderbook.noAsks[0]?.price || 1;
|
|
213
|
+
|
|
214
|
+
const effectiveBuyYes = Math.min(yesBestAsk, 1 - noBestBid);
|
|
215
|
+
const effectiveBuyNo = Math.min(noBestAsk, 1 - yesBestBid);
|
|
216
|
+
const effectiveSellYes = Math.max(yesBestBid, 1 - noBestAsk);
|
|
217
|
+
const effectiveSellNo = Math.max(noBestBid, 1 - yesBestAsk);
|
|
218
|
+
|
|
219
|
+
const longCost = effectiveBuyYes + effectiveBuyNo;
|
|
220
|
+
const longProfit = 1 - longCost;
|
|
221
|
+
const shortRevenue = effectiveSellYes + effectiveSellNo;
|
|
222
|
+
const shortProfit = shortRevenue - 1;
|
|
223
|
+
|
|
224
|
+
const orderbookLongSize = Math.min(orderbook.yesAsks[0]?.size || 0, orderbook.noAsks[0]?.size || 0);
|
|
225
|
+
const orderbookShortSize = Math.min(orderbook.yesBids[0]?.size || 0, orderbook.noBids[0]?.size || 0);
|
|
226
|
+
|
|
227
|
+
const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
|
|
228
|
+
const balanceLongSize = longCost > 0 ? balance.usdc / longCost : 0;
|
|
229
|
+
const longSize = Math.min(orderbookLongSize, balanceLongSize, MAX_SINGLE_TRADE);
|
|
230
|
+
const shortSize = Math.min(orderbookShortSize, heldPairs, MAX_SINGLE_TRADE);
|
|
231
|
+
|
|
232
|
+
const timestamp = new Date().toISOString().split('T')[1].split('.')[0];
|
|
233
|
+
const status = [
|
|
234
|
+
`[${timestamp}]`,
|
|
235
|
+
`${MARKET_CONFIG.outcomes[0]}: ${yesBestBid.toFixed(2)}/${yesBestAsk.toFixed(2)}`,
|
|
236
|
+
`${MARKET_CONFIG.outcomes[1]}: ${noBestBid.toFixed(2)}/${noBestAsk.toFixed(2)}`,
|
|
237
|
+
`Long: ${(longProfit * 100).toFixed(2)}%`,
|
|
238
|
+
`Short: ${(shortProfit * 100).toFixed(2)}%`,
|
|
239
|
+
`Bal: $${balance.usdc.toFixed(0)}`,
|
|
240
|
+
`Pairs: ${heldPairs.toFixed(0)}`,
|
|
241
|
+
].join(' | ');
|
|
242
|
+
|
|
243
|
+
const hasLongArb = longProfit > PROFIT_THRESHOLD && longSize >= MIN_SIZE && balance.usdc >= MIN_SIZE;
|
|
244
|
+
const hasShortArb = shortProfit > PROFIT_THRESHOLD && shortSize >= MIN_SIZE && heldPairs >= MIN_TOKEN_RESERVE;
|
|
245
|
+
|
|
246
|
+
if (hasLongArb || hasShortArb) {
|
|
247
|
+
console.log('\n' + '!'.repeat(75));
|
|
248
|
+
console.log(status);
|
|
249
|
+
|
|
250
|
+
if (hasLongArb) {
|
|
251
|
+
totalOpportunities++;
|
|
252
|
+
console.log(`\nšÆ LONG ARB: Buy ${MARKET_CONFIG.outcomes[0]} @ ${effectiveBuyYes.toFixed(4)} + ${MARKET_CONFIG.outcomes[1]} @ ${effectiveBuyNo.toFixed(4)}`);
|
|
253
|
+
console.log(` Cost: ${longCost.toFixed(4)}, Profit: ${(longProfit * 100).toFixed(2)}%, Size: ${longSize.toFixed(2)}, Est: $${(longProfit * longSize).toFixed(2)}`);
|
|
254
|
+
if (AUTO_EXECUTE && !isExecuting) executeLongArb(effectiveBuyYes, effectiveBuyNo, longSize, longProfit);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (hasShortArb) {
|
|
258
|
+
totalOpportunities++;
|
|
259
|
+
console.log(`\nšÆ SHORT ARB: Sell ${MARKET_CONFIG.outcomes[0]} @ ${effectiveSellYes.toFixed(4)} + ${MARKET_CONFIG.outcomes[1]} @ ${effectiveSellNo.toFixed(4)}`);
|
|
260
|
+
console.log(` Revenue: ${shortRevenue.toFixed(4)}, Profit: ${(shortProfit * 100).toFixed(2)}%, Size: ${shortSize.toFixed(2)}, Est: $${(shortProfit * shortSize).toFixed(2)}`);
|
|
261
|
+
if (AUTO_EXECUTE && !isExecuting) executeShortArb(shortSize, shortProfit * shortSize);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log('!'.repeat(75) + '\n');
|
|
265
|
+
} else {
|
|
266
|
+
process.stdout.write('\r' + status + ' ');
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
271
|
+
// EXECUTION
|
|
272
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
273
|
+
|
|
274
|
+
async function executeLongArb(buyYesPrice: number, buyNoPrice: number, size: number, profitRate: number) {
|
|
275
|
+
if (!ctf || !tradingClient || isExecuting) return;
|
|
276
|
+
isExecuting = true;
|
|
277
|
+
const startTime = Date.now();
|
|
278
|
+
console.log('\nš Executing Long Arb (Buy ā Instant Merge)...');
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const requiredUsdc = (buyYesPrice + buyNoPrice) * size;
|
|
282
|
+
if (balance.usdc < requiredUsdc) {
|
|
283
|
+
console.log(`ā Insufficient USDC.e: have ${balance.usdc.toFixed(2)}, need ${requiredUsdc.toFixed(2)}`);
|
|
284
|
+
isExecuting = false;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(` 1. Buying tokens in parallel...`);
|
|
289
|
+
const orderStartTime = Date.now();
|
|
290
|
+
const [buyYesResult, buyNoResult] = await Promise.all([
|
|
291
|
+
tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.yesTokenId, side: 'BUY', amount: size * buyYesPrice, orderType: 'FOK' }),
|
|
292
|
+
tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.noTokenId, side: 'BUY', amount: size * buyNoPrice, orderType: 'FOK' }),
|
|
293
|
+
]);
|
|
294
|
+
console.log(` ${MARKET_CONFIG.outcomes[0]}: ${buyYesResult.success ? 'ā' : 'ā'}, ${MARKET_CONFIG.outcomes[1]}: ${buyNoResult.success ? 'ā' : 'ā'} (${Date.now() - orderStartTime}ms)`);
|
|
295
|
+
|
|
296
|
+
if (!buyYesResult.success || !buyNoResult.success) {
|
|
297
|
+
console.log(`ā Order(s) failed - will retry on next opportunity`);
|
|
298
|
+
if (buyYesResult.errorMsg) console.log(` YES Error: ${buyYesResult.errorMsg}`);
|
|
299
|
+
if (buyNoResult.errorMsg) console.log(` NO Error: ${buyNoResult.errorMsg}`);
|
|
300
|
+
isExecuting = false;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Immediate merge using pre-held tokens
|
|
305
|
+
const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
|
|
306
|
+
const mergeSize = Math.floor(Math.min(size, heldPairs) * 1e6) / 1e6;
|
|
307
|
+
|
|
308
|
+
if (mergeSize >= MIN_SIZE) {
|
|
309
|
+
console.log(` 2. Merging ${mergeSize.toFixed(2)} pairs from pre-held tokens...`);
|
|
310
|
+
try {
|
|
311
|
+
const mergeResult = await ctf.merge(MARKET_CONFIG.conditionId, mergeSize.toString());
|
|
312
|
+
console.log(` TX: ${mergeResult.txHash}`);
|
|
313
|
+
|
|
314
|
+
const estProfit = profitRate * mergeSize;
|
|
315
|
+
totalProfit += estProfit;
|
|
316
|
+
console.log(`ā
Long Arb completed! Profit: ~$${estProfit.toFixed(2)}. Total: ${Date.now() - startTime}ms`);
|
|
317
|
+
} catch (mergeError: any) {
|
|
318
|
+
console.log(` ā ļø Merge failed: ${mergeError.message}`);
|
|
319
|
+
console.log(` Tokens will be held for later merge by rebalancer`);
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
console.log(` 2. Skipping merge (held pairs ${heldPairs.toFixed(2)} < ${MIN_SIZE})`);
|
|
323
|
+
console.log(` Tokens held - rebalancer will merge later`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
totalExecuted++;
|
|
327
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
328
|
+
await updateBalance();
|
|
329
|
+
console.log(` New balance: USDC=${balance.usdc.toFixed(2)}, YES=${balance.yesTokens.toFixed(2)}, NO=${balance.noTokens.toFixed(2)}`);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.log(`ā Long Arb failed: ${(error as Error).message}`);
|
|
332
|
+
}
|
|
333
|
+
isExecuting = false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function executeShortArb(size: number, estProfit: number) {
|
|
337
|
+
if (!ctf || !tradingClient || isExecuting) return;
|
|
338
|
+
isExecuting = true;
|
|
339
|
+
const startTime = Date.now();
|
|
340
|
+
console.log('\nš Executing Short Arb (Sell Pre-held Tokens)...');
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const heldPairs = Math.min(balance.yesTokens, balance.noTokens);
|
|
344
|
+
if (heldPairs < size) {
|
|
345
|
+
console.log(`ā Insufficient held tokens: have ${heldPairs.toFixed(2)}, need ${size.toFixed(2)}`);
|
|
346
|
+
console.log(` Run token-rebalancer.ts to auto-split more tokens`);
|
|
347
|
+
isExecuting = false;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
console.log(` 1. Selling pre-held tokens in parallel...`);
|
|
352
|
+
const orderStartTime = Date.now();
|
|
353
|
+
const [sellYesResult, sellNoResult] = await Promise.all([
|
|
354
|
+
tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.yesTokenId, side: 'SELL', amount: size, orderType: 'FOK' }),
|
|
355
|
+
tradingClient.createMarketOrder({ tokenId: MARKET_CONFIG.noTokenId, side: 'SELL', amount: size, orderType: 'FOK' }),
|
|
356
|
+
]);
|
|
357
|
+
console.log(` ${MARKET_CONFIG.outcomes[0]}: ${sellYesResult.success ? 'ā' : 'ā'}, ${MARKET_CONFIG.outcomes[1]}: ${sellNoResult.success ? 'ā' : 'ā'} (${Date.now() - orderStartTime}ms)`);
|
|
358
|
+
|
|
359
|
+
if (!sellYesResult.success || !sellNoResult.success) {
|
|
360
|
+
console.log(`ā Order(s) failed`);
|
|
361
|
+
if (sellYesResult.errorMsg) console.log(` YES Error: ${sellYesResult.errorMsg}`);
|
|
362
|
+
if (sellNoResult.errorMsg) console.log(` NO Error: ${sellNoResult.errorMsg}`);
|
|
363
|
+
isExecuting = false;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
console.log(`ā
Short Arb completed! Profit: ~$${estProfit.toFixed(2)}. Total: ${Date.now() - startTime}ms`);
|
|
368
|
+
totalExecuted++;
|
|
369
|
+
totalProfit += estProfit;
|
|
370
|
+
|
|
371
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
372
|
+
await updateBalance();
|
|
373
|
+
console.log(` New balance: USDC=${balance.usdc.toFixed(2)}, YES=${balance.yesTokens.toFixed(2)}, NO=${balance.noTokens.toFixed(2)}`);
|
|
374
|
+
|
|
375
|
+
const newHeldPairs = Math.min(balance.yesTokens, balance.noTokens);
|
|
376
|
+
if (newHeldPairs < MIN_TOKEN_RESERVE) {
|
|
377
|
+
console.log(` ā ļø Token reserve low (${newHeldPairs.toFixed(0)}) - rebalancer should split more`);
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.log(`ā Short Arb failed: ${(error as Error).message}`);
|
|
381
|
+
}
|
|
382
|
+
isExecuting = false;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
main().catch(console.error);
|