@gonzih/polymarket-arb 1.0.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/README.md +70 -0
- package/dist/claude.d.ts +10 -0
- package/dist/claude.d.ts.map +1 -0
- package/dist/claude.js +68 -0
- package/dist/claude.js.map +1 -0
- package/dist/daemon.d.ts +13 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +112 -0
- package/dist/daemon.js.map +1 -0
- package/dist/feeds.d.ts +23 -0
- package/dist/feeds.d.ts.map +1 -0
- package/dist/feeds.js +134 -0
- package/dist/feeds.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/kelly.d.ts +17 -0
- package/dist/kelly.d.ts.map +1 -0
- package/dist/kelly.js +61 -0
- package/dist/kelly.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +20 -0
- package/dist/logger.js.map +1 -0
- package/dist/polymarket.d.ts +16 -0
- package/dist/polymarket.d.ts.map +1 -0
- package/dist/polymarket.js +123 -0
- package/dist/polymarket.js.map +1 -0
- package/dist/signal.d.ts +16 -0
- package/dist/signal.d.ts.map +1 -0
- package/dist/signal.js +48 -0
- package/dist/signal.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# polymarket-arb
|
|
2
|
+
|
|
3
|
+
Claude-powered Polymarket arbitrage bot. Monitors BTC/ETH price feeds, detects 30-second momentum signals, finds matching Polymarket contracts expiring within 20 minutes, and uses Claude AI to assess each trade opportunity before entering.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
1. **Price feeds** — connects to Binance WebSocket (with Coinbase fallback on 451 geo-block) for real-time BTC/ETH prices
|
|
8
|
+
2. **Signal detection** — fires when |30s momentum| > 0.35%
|
|
9
|
+
3. **Contract discovery** — queries Polymarket GraphQL for open BTC/ETH up/down contracts expiring within 20 minutes
|
|
10
|
+
4. **Claude analysis** — sends signal + contract data to `claude-haiku-4-5-20251001` for confidence score, Kelly fraction, and reasoning
|
|
11
|
+
5. **Trade entry** — only enters if Claude confidence > 0.65; sizes position using Kelly Criterion (capped at 10%)
|
|
12
|
+
6. **Risk management** — daily loss limit -20%, total drawdown kill switch -40%
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @gonzih/polymarket-arb
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Paper trading (default, safe)
|
|
24
|
+
ANTHROPIC_API_KEY=sk-... polymarket-arb --paper
|
|
25
|
+
|
|
26
|
+
# Live trading
|
|
27
|
+
ANTHROPIC_API_KEY=sk-... POLYMARKET_API_KEY=... POLYMARKET_SECRET=... polymarket-arb --live
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Environment variables
|
|
31
|
+
|
|
32
|
+
| Variable | Required | Default | Description |
|
|
33
|
+
|----------|----------|---------|-------------|
|
|
34
|
+
| `ANTHROPIC_API_KEY` | Yes | — | Claude API key |
|
|
35
|
+
| `POLYMARKET_API_KEY` | Live only | — | Polymarket CLOB API key |
|
|
36
|
+
| `POLYMARKET_SECRET` | Live only | — | Polymarket API secret |
|
|
37
|
+
| `PAPER_MODE` | No | `true` | Set `false` for live trading |
|
|
38
|
+
| `LOG_DIR` | No | `~/.polymarket-arb` | Log directory |
|
|
39
|
+
|
|
40
|
+
## Logs
|
|
41
|
+
|
|
42
|
+
Structured JSON logs written to `~/.polymarket-arb/trades.jsonl`. Each line is a JSON object with `timestamp`, `level`, and event-specific fields.
|
|
43
|
+
|
|
44
|
+
## macOS daemon (launchd)
|
|
45
|
+
|
|
46
|
+
A launchd plist is included (`com.polymarket-arb.plist`). Install:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Edit REPLACE_ME with your API key first
|
|
50
|
+
cp com.polymarket-arb.plist ~/Library/LaunchAgents/
|
|
51
|
+
launchctl load ~/Library/LaunchAgents/com.polymarket-arb.plist
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Architecture
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
src/
|
|
58
|
+
index.ts — CLI entry point
|
|
59
|
+
feeds.ts — Binance + Coinbase WebSocket managers
|
|
60
|
+
signal.ts — 30s momentum calculation
|
|
61
|
+
polymarket.ts — GraphQL contract discovery + CLOB order placement
|
|
62
|
+
claude.ts — Anthropic SDK integration, trade analysis
|
|
63
|
+
kelly.ts — Kelly Criterion position sizing, risk limits
|
|
64
|
+
logger.ts — Structured JSON logging
|
|
65
|
+
daemon.ts — Main bot loop, kill switch logic
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT
|
package/dist/claude.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MomentumSignal } from "./signal.js";
|
|
2
|
+
import type { Contract } from "./polymarket.js";
|
|
3
|
+
export type TradeAnalysis = {
|
|
4
|
+
confidence: number;
|
|
5
|
+
kelly_fraction: number;
|
|
6
|
+
reasoning: string;
|
|
7
|
+
enter: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function analyzeTradeOpportunity(signal: MomentumSignal, contract: Contract): Promise<TradeAnalysis | null>;
|
|
10
|
+
//# sourceMappingURL=claude.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAKhD,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;CAChB,CAAC;AAaF,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA0D/B"}
|
package/dist/claude.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { log } from "./logger.js";
|
|
3
|
+
const MODEL = "claude-haiku-4-5-20251001";
|
|
4
|
+
let client = null;
|
|
5
|
+
function getClient() {
|
|
6
|
+
if (!client) {
|
|
7
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
8
|
+
if (!apiKey)
|
|
9
|
+
throw new Error("ANTHROPIC_API_KEY is required");
|
|
10
|
+
client = new Anthropic({ apiKey });
|
|
11
|
+
}
|
|
12
|
+
return client;
|
|
13
|
+
}
|
|
14
|
+
export async function analyzeTradeOpportunity(signal, contract) {
|
|
15
|
+
const momentumPct = (signal.momentum * 100).toFixed(3);
|
|
16
|
+
const direction = signal.direction === "down" ? "dropped" : "rose";
|
|
17
|
+
const contractOdds = contract.direction === "down" ? contract.yesPrice : contract.yesPrice;
|
|
18
|
+
const minutesToExpiry = Math.round((contract.expiresAt - Date.now()) / 60_000);
|
|
19
|
+
// Implied real probability based on momentum direction matching contract
|
|
20
|
+
const impliedProb = signal.direction === contract.direction ? 0.75 : 0.35;
|
|
21
|
+
const impliedEdge = Math.abs(impliedProb - contractOdds);
|
|
22
|
+
const prompt = `You are a prediction market arbitrage assistant. Analyze this trade opportunity and respond in JSON only.
|
|
23
|
+
|
|
24
|
+
Signal: ${signal.symbol} ${direction} ${momentumPct}% in 30 seconds on crypto exchange
|
|
25
|
+
Polymarket contract: "${contract.question}" currently priced at ${(contractOdds * 100).toFixed(1)}% (${contractOdds.toFixed(2)})
|
|
26
|
+
Implied edge: ${(impliedEdge * 100).toFixed(1)} percentage points (estimated real probability ~${(impliedProb * 100).toFixed(0)}%)
|
|
27
|
+
Minutes to expiry: ${minutesToExpiry}
|
|
28
|
+
Recent price ticks: [${signal.recentTicks.join(", ")}]
|
|
29
|
+
Current price: ${signal.currentPrice}
|
|
30
|
+
|
|
31
|
+
Assess: Does the momentum signal justify a bet on this contract? Consider momentum strength, edge size, time to expiry, and signal-contract alignment.
|
|
32
|
+
|
|
33
|
+
Respond with JSON only:
|
|
34
|
+
{"confidence": 0.0-1.0, "kelly_fraction": 0.0-0.1, "reasoning": "brief explanation", "enter": true/false}`;
|
|
35
|
+
try {
|
|
36
|
+
const anthropic = getClient();
|
|
37
|
+
const message = await anthropic.messages.create({
|
|
38
|
+
model: MODEL,
|
|
39
|
+
max_tokens: 256,
|
|
40
|
+
messages: [{ role: "user", content: prompt }],
|
|
41
|
+
});
|
|
42
|
+
const content = message.content[0];
|
|
43
|
+
if (content.type !== "text") {
|
|
44
|
+
log("warn", { source: "claude", event: "unexpected_response_type" });
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
// Extract JSON from response (may have surrounding text)
|
|
48
|
+
const jsonMatch = content.text.match(/\{[^}]+\}/s);
|
|
49
|
+
if (!jsonMatch) {
|
|
50
|
+
log("warn", { source: "claude", event: "no_json_in_response", text: content.text });
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const analysis = JSON.parse(jsonMatch[0]);
|
|
54
|
+
log("info", {
|
|
55
|
+
source: "claude",
|
|
56
|
+
event: "analysis",
|
|
57
|
+
symbol: signal.symbol,
|
|
58
|
+
contract: contract.question,
|
|
59
|
+
...analysis,
|
|
60
|
+
});
|
|
61
|
+
return analysis;
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
log("error", { source: "claude", event: "analysis_error", error: String(err) });
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../src/claude.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class TradingDaemon {
|
|
2
|
+
private feeds;
|
|
3
|
+
private signals;
|
|
4
|
+
private risk;
|
|
5
|
+
private paperMode;
|
|
6
|
+
private lastSignal;
|
|
7
|
+
private dayResetTimer;
|
|
8
|
+
constructor(paperMode?: boolean);
|
|
9
|
+
start(): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
private scheduleDayReset;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=daemon.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"AAWA,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAqB;IAClC,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,IAAI,CAAoC;IAChD,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,UAAU,CAAkC;IACpD,OAAO,CAAC,aAAa,CAA+B;gBAExC,SAAS,UAAO;IAK5B,KAAK,IAAI,IAAI;IAwFb,IAAI,IAAI,IAAI;IAMZ,OAAO,CAAC,gBAAgB;CAWzB"}
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { FeedManager } from "./feeds.js";
|
|
2
|
+
import { SignalEngine } from "./signal.js";
|
|
3
|
+
import { fetchContracts, placeOrder } from "./polymarket.js";
|
|
4
|
+
import { analyzeTradeOpportunity } from "./claude.js";
|
|
5
|
+
import { RiskManager } from "./kelly.js";
|
|
6
|
+
import { log } from "./logger.js";
|
|
7
|
+
const CLAUDE_CONFIDENCE_THRESHOLD = 0.65;
|
|
8
|
+
const INITIAL_BALANCE = 1000; // paper trading starts with $1000
|
|
9
|
+
const SIGNAL_COOLDOWN_MS = 60_000; // don't re-fire same symbol within 1 min
|
|
10
|
+
export class TradingDaemon {
|
|
11
|
+
feeds = new FeedManager();
|
|
12
|
+
signals = new SignalEngine();
|
|
13
|
+
risk = new RiskManager(INITIAL_BALANCE);
|
|
14
|
+
paperMode;
|
|
15
|
+
lastSignal = new Map();
|
|
16
|
+
dayResetTimer = null;
|
|
17
|
+
constructor(paperMode = true) {
|
|
18
|
+
this.paperMode = paperMode;
|
|
19
|
+
log("info", { event: "daemon_start", paperMode, balance: INITIAL_BALANCE });
|
|
20
|
+
}
|
|
21
|
+
start() {
|
|
22
|
+
this.feeds.onPrice(async (tick) => {
|
|
23
|
+
const signal = this.signals.addTick(tick);
|
|
24
|
+
if (!signal)
|
|
25
|
+
return;
|
|
26
|
+
// Cooldown check
|
|
27
|
+
const lastFired = this.lastSignal.get(signal.symbol) ?? 0;
|
|
28
|
+
if (Date.now() - lastFired < SIGNAL_COOLDOWN_MS)
|
|
29
|
+
return;
|
|
30
|
+
// Risk limits
|
|
31
|
+
const limits = this.risk.checkLimits();
|
|
32
|
+
if (!limits.ok) {
|
|
33
|
+
log("warn", { event: "trading_halted", reason: limits.reason });
|
|
34
|
+
this.stop();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
this.lastSignal.set(signal.symbol, Date.now());
|
|
38
|
+
log("info", {
|
|
39
|
+
event: "signal_fired",
|
|
40
|
+
symbol: signal.symbol,
|
|
41
|
+
direction: signal.direction,
|
|
42
|
+
momentum: (signal.momentum * 100).toFixed(3) + "%",
|
|
43
|
+
price: signal.currentPrice,
|
|
44
|
+
});
|
|
45
|
+
// Find matching contracts
|
|
46
|
+
const contracts = await fetchContracts(signal.symbol);
|
|
47
|
+
const matching = contracts.filter((c) => c.direction === signal.direction);
|
|
48
|
+
if (matching.length === 0) {
|
|
49
|
+
log("info", { event: "no_matching_contracts", symbol: signal.symbol });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Process best contract (closest expiry)
|
|
53
|
+
const contract = matching.sort((a, b) => a.expiresAt - b.expiresAt)[0];
|
|
54
|
+
// Ask Claude for analysis
|
|
55
|
+
const analysis = await analyzeTradeOpportunity(signal, contract);
|
|
56
|
+
if (!analysis)
|
|
57
|
+
return;
|
|
58
|
+
if (!analysis.enter || analysis.confidence < CLAUDE_CONFIDENCE_THRESHOLD) {
|
|
59
|
+
log("info", {
|
|
60
|
+
event: "trade_skipped",
|
|
61
|
+
reason: "claude_confidence_below_threshold",
|
|
62
|
+
confidence: analysis.confidence,
|
|
63
|
+
threshold: CLAUDE_CONFIDENCE_THRESHOLD,
|
|
64
|
+
reasoning: analysis.reasoning,
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Size the position
|
|
69
|
+
const price = contract.yesPrice;
|
|
70
|
+
const size = this.risk.sizePosition(analysis.kelly_fraction, price);
|
|
71
|
+
if (size <= 0) {
|
|
72
|
+
log("info", { event: "trade_skipped", reason: "zero_size" });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
log("trade", {
|
|
76
|
+
event: "entering_trade",
|
|
77
|
+
symbol: signal.symbol,
|
|
78
|
+
contract: contract.question,
|
|
79
|
+
direction: signal.direction,
|
|
80
|
+
size,
|
|
81
|
+
price,
|
|
82
|
+
confidence: analysis.confidence,
|
|
83
|
+
kellyFraction: analysis.kelly_fraction,
|
|
84
|
+
reasoning: analysis.reasoning,
|
|
85
|
+
expiresAt: new Date(contract.expiresAt).toISOString(),
|
|
86
|
+
paperMode: this.paperMode,
|
|
87
|
+
});
|
|
88
|
+
await placeOrder(contract, "YES", size, price, this.paperMode);
|
|
89
|
+
});
|
|
90
|
+
this.feeds.start();
|
|
91
|
+
// Reset daily P&L at midnight
|
|
92
|
+
this.scheduleDayReset();
|
|
93
|
+
log("info", { event: "feeds_started" });
|
|
94
|
+
}
|
|
95
|
+
stop() {
|
|
96
|
+
this.feeds.stop();
|
|
97
|
+
if (this.dayResetTimer)
|
|
98
|
+
clearTimeout(this.dayResetTimer);
|
|
99
|
+
log("info", { event: "daemon_stopped" });
|
|
100
|
+
}
|
|
101
|
+
scheduleDayReset() {
|
|
102
|
+
const now = new Date();
|
|
103
|
+
const midnight = new Date(now);
|
|
104
|
+
midnight.setHours(24, 0, 0, 0);
|
|
105
|
+
const msUntilMidnight = midnight.getTime() - now.getTime();
|
|
106
|
+
this.dayResetTimer = setTimeout(() => {
|
|
107
|
+
this.risk.resetDay();
|
|
108
|
+
this.scheduleDayReset();
|
|
109
|
+
}, msUntilMidnight);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/feeds.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type PriceTick = {
|
|
2
|
+
symbol: "BTC" | "ETH";
|
|
3
|
+
price: number;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
source: "binance" | "coinbase";
|
|
6
|
+
};
|
|
7
|
+
export type PriceUpdateHandler = (tick: PriceTick) => void;
|
|
8
|
+
export declare class FeedManager {
|
|
9
|
+
private handlers;
|
|
10
|
+
private binanceWs;
|
|
11
|
+
private coinbaseWs;
|
|
12
|
+
private binanceBlocked;
|
|
13
|
+
private binanceRetryTimer;
|
|
14
|
+
private stopped;
|
|
15
|
+
onPrice(handler: PriceUpdateHandler): void;
|
|
16
|
+
private emit;
|
|
17
|
+
start(): void;
|
|
18
|
+
stop(): void;
|
|
19
|
+
private connectBinance;
|
|
20
|
+
private scheduleBindanceRetry;
|
|
21
|
+
private connectCoinbase;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=feeds.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,KAAK,GAAG,KAAK,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,UAAU,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;AAS3D,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,OAAO,CAAS;IAExB,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAI1C,OAAO,CAAC,IAAI;IAIZ,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI;IAOZ,OAAO,CAAC,cAAc;IAoDtB,OAAO,CAAC,qBAAqB;IAQ7B,OAAO,CAAC,eAAe;CA+CxB"}
|
package/dist/feeds.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { log } from "./logger.js";
|
|
3
|
+
const BINANCE_WS = "wss://stream.binance.com:9443/stream?streams=btcusdt@kline_1m/ethusdt@kline_1m";
|
|
4
|
+
const COINBASE_WS = "wss://advanced-trade-ws.coinbase.com";
|
|
5
|
+
// Retry Binance every 6 hours after 451 block
|
|
6
|
+
const BINANCE_RETRY_MS = 6 * 60 * 60 * 1000;
|
|
7
|
+
export class FeedManager {
|
|
8
|
+
handlers = [];
|
|
9
|
+
binanceWs = null;
|
|
10
|
+
coinbaseWs = null;
|
|
11
|
+
binanceBlocked = false;
|
|
12
|
+
binanceRetryTimer = null;
|
|
13
|
+
stopped = false;
|
|
14
|
+
onPrice(handler) {
|
|
15
|
+
this.handlers.push(handler);
|
|
16
|
+
}
|
|
17
|
+
emit(tick) {
|
|
18
|
+
for (const h of this.handlers)
|
|
19
|
+
h(tick);
|
|
20
|
+
}
|
|
21
|
+
start() {
|
|
22
|
+
this.connectBinance();
|
|
23
|
+
this.connectCoinbase();
|
|
24
|
+
}
|
|
25
|
+
stop() {
|
|
26
|
+
this.stopped = true;
|
|
27
|
+
if (this.binanceRetryTimer)
|
|
28
|
+
clearTimeout(this.binanceRetryTimer);
|
|
29
|
+
this.binanceWs?.close();
|
|
30
|
+
this.coinbaseWs?.close();
|
|
31
|
+
}
|
|
32
|
+
connectBinance() {
|
|
33
|
+
if (this.stopped || this.binanceBlocked)
|
|
34
|
+
return;
|
|
35
|
+
const ws = new WebSocket(BINANCE_WS);
|
|
36
|
+
this.binanceWs = ws;
|
|
37
|
+
ws.on("open", () => {
|
|
38
|
+
log("info", { source: "binance", event: "connected" });
|
|
39
|
+
});
|
|
40
|
+
ws.on("message", (raw) => {
|
|
41
|
+
try {
|
|
42
|
+
const msg = JSON.parse(raw.toString());
|
|
43
|
+
const stream = msg.stream ?? "";
|
|
44
|
+
const kline = msg.data?.k;
|
|
45
|
+
if (!kline || !kline.x)
|
|
46
|
+
return; // not closed candle — use last close price anyway
|
|
47
|
+
const symbol = stream.startsWith("btc") ? "BTC" : "ETH";
|
|
48
|
+
const price = parseFloat(kline.c);
|
|
49
|
+
this.emit({ symbol, price, timestamp: Date.now(), source: "binance" });
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// ignore malformed
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
ws.on("error", (err) => {
|
|
56
|
+
// Check for 451 geo-block
|
|
57
|
+
if (err.message?.includes("451") || err.code === "451") {
|
|
58
|
+
log("warn", { source: "binance", event: "geo_blocked_451", message: err.message });
|
|
59
|
+
this.binanceBlocked = true;
|
|
60
|
+
ws.close();
|
|
61
|
+
this.scheduleBindanceRetry();
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
log("warn", { source: "binance", event: "error", message: err.message });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
ws.on("unexpected-response", (_req, res) => {
|
|
68
|
+
if (res.statusCode === 451) {
|
|
69
|
+
log("warn", { source: "binance", event: "geo_blocked_451", statusCode: 451 });
|
|
70
|
+
this.binanceBlocked = true;
|
|
71
|
+
ws.close();
|
|
72
|
+
this.scheduleBindanceRetry();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
ws.on("close", () => {
|
|
76
|
+
if (this.stopped || this.binanceBlocked)
|
|
77
|
+
return;
|
|
78
|
+
log("info", { source: "binance", event: "reconnecting" });
|
|
79
|
+
setTimeout(() => this.connectBinance(), 5000);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
scheduleBindanceRetry() {
|
|
83
|
+
log("info", { source: "binance", event: "retry_scheduled_6h" });
|
|
84
|
+
this.binanceRetryTimer = setTimeout(() => {
|
|
85
|
+
this.binanceBlocked = false;
|
|
86
|
+
this.connectBinance();
|
|
87
|
+
}, BINANCE_RETRY_MS);
|
|
88
|
+
}
|
|
89
|
+
connectCoinbase() {
|
|
90
|
+
if (this.stopped)
|
|
91
|
+
return;
|
|
92
|
+
const ws = new WebSocket(COINBASE_WS);
|
|
93
|
+
this.coinbaseWs = ws;
|
|
94
|
+
ws.on("open", () => {
|
|
95
|
+
log("info", { source: "coinbase", event: "connected" });
|
|
96
|
+
ws.send(JSON.stringify({
|
|
97
|
+
type: "subscribe",
|
|
98
|
+
product_ids: ["BTC-USD", "ETH-USD"],
|
|
99
|
+
channel: "market_trades",
|
|
100
|
+
}));
|
|
101
|
+
});
|
|
102
|
+
ws.on("message", (raw) => {
|
|
103
|
+
try {
|
|
104
|
+
const msg = JSON.parse(raw.toString());
|
|
105
|
+
if (msg.channel !== "market_trades")
|
|
106
|
+
return;
|
|
107
|
+
const events = msg.events ?? [];
|
|
108
|
+
for (const event of events) {
|
|
109
|
+
const trades = event.trades ?? [];
|
|
110
|
+
for (const trade of trades) {
|
|
111
|
+
const symbol = trade.product_id.startsWith("BTC") ? "BTC" : "ETH";
|
|
112
|
+
const price = parseFloat(trade.price);
|
|
113
|
+
if (!isNaN(price)) {
|
|
114
|
+
this.emit({ symbol, price, timestamp: Date.now(), source: "coinbase" });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// ignore malformed
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
ws.on("error", (err) => {
|
|
124
|
+
log("warn", { source: "coinbase", event: "error", message: err.message });
|
|
125
|
+
});
|
|
126
|
+
ws.on("close", () => {
|
|
127
|
+
if (this.stopped)
|
|
128
|
+
return;
|
|
129
|
+
log("info", { source: "coinbase", event: "reconnecting" });
|
|
130
|
+
setTimeout(() => this.connectCoinbase(), 5000);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=feeds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feeds.js","sourceRoot":"","sources":["../src/feeds.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { TradingDaemon } from "./daemon.js";
|
|
3
|
+
import { log, logDir } from "./logger.js";
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
6
|
+
console.log(`polymarket-arb — Claude-powered prediction market arbitrage bot
|
|
7
|
+
|
|
8
|
+
Usage: polymarket-arb [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--paper Paper trading mode (default, no real orders)
|
|
12
|
+
--live Live trading mode (requires POLYMARKET_API_KEY + POLYMARKET_SECRET)
|
|
13
|
+
--log-dir Override log directory (default: ~/.polymarket-arb)
|
|
14
|
+
--help Show this help
|
|
15
|
+
|
|
16
|
+
Environment variables:
|
|
17
|
+
ANTHROPIC_API_KEY Required — Claude API key for trade analysis
|
|
18
|
+
POLYMARKET_API_KEY Required for live trading
|
|
19
|
+
POLYMARKET_SECRET Required for live trading
|
|
20
|
+
PAPER_MODE=true Default paper mode (overridden by --live flag)
|
|
21
|
+
LOG_DIR Log directory path
|
|
22
|
+
|
|
23
|
+
Log directory: ${logDir()}
|
|
24
|
+
`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
// Determine paper mode
|
|
28
|
+
const paperMode = args.includes("--live")
|
|
29
|
+
? false
|
|
30
|
+
: args.includes("--paper")
|
|
31
|
+
? true
|
|
32
|
+
: process.env.PAPER_MODE !== "false";
|
|
33
|
+
if (!paperMode && !process.env.POLYMARKET_API_KEY) {
|
|
34
|
+
console.error("ERROR: --live mode requires POLYMARKET_API_KEY environment variable");
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
38
|
+
console.error("ERROR: ANTHROPIC_API_KEY environment variable is required");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
log("info", {
|
|
42
|
+
event: "startup",
|
|
43
|
+
mode: paperMode ? "paper" : "live",
|
|
44
|
+
logDir: logDir(),
|
|
45
|
+
nodeVersion: process.version,
|
|
46
|
+
});
|
|
47
|
+
const daemon = new TradingDaemon(paperMode);
|
|
48
|
+
daemon.start();
|
|
49
|
+
// Graceful shutdown
|
|
50
|
+
process.on("SIGTERM", () => {
|
|
51
|
+
log("info", { event: "sigterm_received" });
|
|
52
|
+
daemon.stop();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
});
|
|
55
|
+
process.on("SIGINT", () => {
|
|
56
|
+
log("info", { event: "sigint_received" });
|
|
57
|
+
daemon.stop();
|
|
58
|
+
process.exit(0);
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;iBAiBG,MAAM,EAAE;CACxB,CAAC,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,uBAAuB;AACvB,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACvC,CAAC,CAAC,KAAK;IACP,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC1B,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,OAAO,CAAC;AAEvC,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;IAClD,OAAO,CAAC,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACrF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;IACnC,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,GAAG,CAAC,MAAM,EAAE;IACV,KAAK,EAAE,SAAS;IAChB,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;IAClC,MAAM,EAAE,MAAM,EAAE;IAChB,WAAW,EAAE,OAAO,CAAC,OAAO;CAC7B,CAAC,CAAC;AAEH,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,SAAS,CAAC,CAAC;AAC5C,MAAM,CAAC,KAAK,EAAE,CAAC;AAEf,oBAAoB;AACpB,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACzB,GAAG,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;IACxB,GAAG,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC1C,MAAM,CAAC,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/kelly.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare class RiskManager {
|
|
2
|
+
private portfolio;
|
|
3
|
+
private startOfDayPortfolio;
|
|
4
|
+
private peakPortfolio;
|
|
5
|
+
private halted;
|
|
6
|
+
constructor(initialBalance: number);
|
|
7
|
+
isHalted(): boolean;
|
|
8
|
+
getPortfolio(): number;
|
|
9
|
+
checkLimits(): {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
reason?: string;
|
|
12
|
+
};
|
|
13
|
+
sizePosition(kellyFraction: number, price: number): number;
|
|
14
|
+
recordTrade(pnl: number): void;
|
|
15
|
+
resetDay(): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=kelly.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kelly.d.ts","sourceRoot":"","sources":["../src/kelly.ts"],"names":[],"mappings":"AAOA,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,cAAc,EAAE,MAAM;IAMlC,QAAQ,IAAI,OAAO;IAInB,YAAY,IAAI,MAAM;IAItB,WAAW,IAAI;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAiB/C,YAAY,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM;IAW1D,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAa9B,QAAQ,IAAI,IAAI;CAIjB"}
|
package/dist/kelly.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { log } from "./logger.js";
|
|
2
|
+
const MAX_KELLY = 0.10; // cap at 10%
|
|
3
|
+
const MAX_POSITION = 0.08; // 8% of portfolio
|
|
4
|
+
const DAILY_LOSS_LIMIT = -0.20; // -20%
|
|
5
|
+
const TOTAL_DRAWDOWN_LIMIT = -0.40; // -40%
|
|
6
|
+
export class RiskManager {
|
|
7
|
+
portfolio;
|
|
8
|
+
startOfDayPortfolio;
|
|
9
|
+
peakPortfolio;
|
|
10
|
+
halted = false;
|
|
11
|
+
constructor(initialBalance) {
|
|
12
|
+
this.portfolio = initialBalance;
|
|
13
|
+
this.startOfDayPortfolio = initialBalance;
|
|
14
|
+
this.peakPortfolio = initialBalance;
|
|
15
|
+
}
|
|
16
|
+
isHalted() {
|
|
17
|
+
return this.halted;
|
|
18
|
+
}
|
|
19
|
+
getPortfolio() {
|
|
20
|
+
return this.portfolio;
|
|
21
|
+
}
|
|
22
|
+
checkLimits() {
|
|
23
|
+
const dailyReturn = (this.portfolio - this.startOfDayPortfolio) / this.startOfDayPortfolio;
|
|
24
|
+
const drawdown = (this.portfolio - this.peakPortfolio) / this.peakPortfolio;
|
|
25
|
+
if (dailyReturn <= DAILY_LOSS_LIMIT) {
|
|
26
|
+
this.halted = true;
|
|
27
|
+
return { ok: false, reason: `Daily loss limit hit: ${(dailyReturn * 100).toFixed(1)}%` };
|
|
28
|
+
}
|
|
29
|
+
if (drawdown <= TOTAL_DRAWDOWN_LIMIT) {
|
|
30
|
+
this.halted = true;
|
|
31
|
+
return { ok: false, reason: `Total drawdown kill switch: ${(drawdown * 100).toFixed(1)}%` };
|
|
32
|
+
}
|
|
33
|
+
return { ok: true };
|
|
34
|
+
}
|
|
35
|
+
sizePosition(kellyFraction, price) {
|
|
36
|
+
const cappedKelly = Math.min(kellyFraction, MAX_KELLY);
|
|
37
|
+
const maxByPortfolio = this.portfolio * MAX_POSITION;
|
|
38
|
+
const kellySize = this.portfolio * cappedKelly;
|
|
39
|
+
const size = Math.min(kellySize, maxByPortfolio);
|
|
40
|
+
// Convert dollars to number of contracts (each contract is $1 face value at price)
|
|
41
|
+
const contracts = price > 0 ? size / price : 0;
|
|
42
|
+
return Math.floor(contracts * 100) / 100; // round to 2 decimal places
|
|
43
|
+
}
|
|
44
|
+
recordTrade(pnl) {
|
|
45
|
+
this.portfolio += pnl;
|
|
46
|
+
if (this.portfolio > this.peakPortfolio) {
|
|
47
|
+
this.peakPortfolio = this.portfolio;
|
|
48
|
+
}
|
|
49
|
+
log("info", {
|
|
50
|
+
event: "portfolio_update",
|
|
51
|
+
portfolio: this.portfolio,
|
|
52
|
+
pnl,
|
|
53
|
+
peakPortfolio: this.peakPortfolio,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
resetDay() {
|
|
57
|
+
this.startOfDayPortfolio = this.portfolio;
|
|
58
|
+
log("info", { event: "day_reset", portfolio: this.portfolio });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=kelly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kelly.js","sourceRoot":"","sources":["../src/kelly.ts"],"names":[],"mappings":"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"}
|
package/dist/logger.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAcA,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAKnG;AAED,wBAAgB,MAAM,IAAI,MAAM,CAE/B"}
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
const LOG_DIR = process.env.LOG_DIR
|
|
5
|
+
? path.resolve(process.env.LOG_DIR)
|
|
6
|
+
: path.join(os.homedir(), ".polymarket-arb");
|
|
7
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
8
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
const logFile = path.join(LOG_DIR, "trades.jsonl");
|
|
11
|
+
export function log(level, data) {
|
|
12
|
+
const entry = { timestamp: new Date().toISOString(), level, ...data };
|
|
13
|
+
const line = JSON.stringify(entry);
|
|
14
|
+
console.log(line);
|
|
15
|
+
fs.appendFileSync(logFile, line + "\n");
|
|
16
|
+
}
|
|
17
|
+
export function logDir() {
|
|
18
|
+
return LOG_DIR;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO;IACjC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;IACnC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,iBAAiB,CAAC,CAAC;AAE/C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;AAEnD,MAAM,UAAU,GAAG,CAAC,KAA0C,EAAE,IAA6B;IAC3F,MAAM,KAAK,GAAG,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,CAAC;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClB,EAAE,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,MAAM;IACpB,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type Contract = {
|
|
2
|
+
id: string;
|
|
3
|
+
question: string;
|
|
4
|
+
expiresAt: number;
|
|
5
|
+
yesPrice: number;
|
|
6
|
+
noPrice: number;
|
|
7
|
+
symbol: "BTC" | "ETH";
|
|
8
|
+
direction: "up" | "down";
|
|
9
|
+
};
|
|
10
|
+
export type OrderResult = {
|
|
11
|
+
orderId: string;
|
|
12
|
+
status: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function fetchContracts(symbol: "BTC" | "ETH"): Promise<Contract[]>;
|
|
15
|
+
export declare function placeOrder(contract: Contract, side: "YES" | "NO", size: number, price: number, paperMode: boolean): Promise<OrderResult | null>;
|
|
16
|
+
//# sourceMappingURL=polymarket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polymarket.d.ts","sourceRoot":"","sources":["../src/polymarket.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,KAAK,GAAG,KAAK,CAAC;IACtB,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AA6BF,wBAAsB,cAAc,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CA0D/E;AAED,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,KAAK,GAAG,IAAI,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAmD7B"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { log } from "./logger.js";
|
|
2
|
+
const GAMMA_API = "https://gamma-api.polymarket.com";
|
|
3
|
+
const CLOB_API = "https://clob.polymarket.com";
|
|
4
|
+
const GRAPHQL_QUERY = `
|
|
5
|
+
query OpenContracts($symbol: String!) {
|
|
6
|
+
markets(
|
|
7
|
+
where: {
|
|
8
|
+
active: true,
|
|
9
|
+
closed: false,
|
|
10
|
+
question_contains: $symbol
|
|
11
|
+
},
|
|
12
|
+
orderBy: endDate,
|
|
13
|
+
orderDirection: asc
|
|
14
|
+
) {
|
|
15
|
+
id
|
|
16
|
+
question
|
|
17
|
+
endDate
|
|
18
|
+
outcomePrices
|
|
19
|
+
outcomes
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`;
|
|
23
|
+
function parseDirection(question) {
|
|
24
|
+
const q = question.toLowerCase();
|
|
25
|
+
if (q.includes("higher") || q.includes("above") || q.includes("exceed"))
|
|
26
|
+
return "up";
|
|
27
|
+
if (q.includes("lower") || q.includes("below") || q.includes("drop") || q.includes("fall"))
|
|
28
|
+
return "down";
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export async function fetchContracts(symbol) {
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
const maxExpiry = now + 20 * 60 * 1000; // 20 min from now
|
|
34
|
+
try {
|
|
35
|
+
const res = await fetch(`${GAMMA_API}/graphql`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
query: GRAPHQL_QUERY,
|
|
40
|
+
variables: { symbol },
|
|
41
|
+
}),
|
|
42
|
+
});
|
|
43
|
+
if (!res.ok) {
|
|
44
|
+
log("warn", { source: "polymarket", event: "graphql_error", status: res.status });
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const data = (await res.json());
|
|
48
|
+
const markets = data?.data?.markets ?? [];
|
|
49
|
+
const contracts = [];
|
|
50
|
+
for (const m of markets) {
|
|
51
|
+
const expiresAt = new Date(m.endDate).getTime();
|
|
52
|
+
if (expiresAt > maxExpiry || expiresAt < now)
|
|
53
|
+
continue;
|
|
54
|
+
const direction = parseDirection(m.question);
|
|
55
|
+
if (!direction)
|
|
56
|
+
continue;
|
|
57
|
+
const prices = m.outcomePrices?.map(Number) ?? [0.5, 0.5];
|
|
58
|
+
contracts.push({
|
|
59
|
+
id: m.id,
|
|
60
|
+
question: m.question,
|
|
61
|
+
expiresAt,
|
|
62
|
+
yesPrice: prices[0] ?? 0.5,
|
|
63
|
+
noPrice: prices[1] ?? 0.5,
|
|
64
|
+
symbol,
|
|
65
|
+
direction,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return contracts;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
log("warn", { source: "polymarket", event: "fetch_error", error: String(err) });
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function placeOrder(contract, side, size, price, paperMode) {
|
|
76
|
+
const order = {
|
|
77
|
+
marketId: contract.id,
|
|
78
|
+
side,
|
|
79
|
+
size,
|
|
80
|
+
price,
|
|
81
|
+
orderType: "LIMIT",
|
|
82
|
+
};
|
|
83
|
+
if (paperMode) {
|
|
84
|
+
log("trade", {
|
|
85
|
+
event: "paper_order",
|
|
86
|
+
contract: contract.question,
|
|
87
|
+
side,
|
|
88
|
+
size,
|
|
89
|
+
price,
|
|
90
|
+
expiresAt: new Date(contract.expiresAt).toISOString(),
|
|
91
|
+
});
|
|
92
|
+
return { orderId: `paper-${Date.now()}`, status: "paper" };
|
|
93
|
+
}
|
|
94
|
+
const apiKey = process.env.POLYMARKET_API_KEY;
|
|
95
|
+
const secret = process.env.POLYMARKET_SECRET;
|
|
96
|
+
if (!apiKey || !secret) {
|
|
97
|
+
log("error", { event: "missing_creds", message: "POLYMARKET_API_KEY and POLYMARKET_SECRET required for live trading" });
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const res = await fetch(`${CLOB_API}/order`, {
|
|
102
|
+
method: "POST",
|
|
103
|
+
headers: {
|
|
104
|
+
"Content-Type": "application/json",
|
|
105
|
+
Authorization: `Bearer ${apiKey}`,
|
|
106
|
+
"X-Secret": secret,
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify(order),
|
|
109
|
+
});
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
log("error", { source: "polymarket", event: "order_error", status: res.status });
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const result = (await res.json());
|
|
115
|
+
log("trade", { event: "order_placed", orderId: result.orderId, ...order });
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
log("error", { source: "polymarket", event: "order_exception", error: String(err) });
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=polymarket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polymarket.js","sourceRoot":"","sources":["../src/polymarket.ts"],"names":[],"mappings":"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"}
|
package/dist/signal.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { PriceTick } from "./feeds.js";
|
|
2
|
+
export type MomentumSignal = {
|
|
3
|
+
symbol: "BTC" | "ETH";
|
|
4
|
+
direction: "up" | "down";
|
|
5
|
+
momentum: number;
|
|
6
|
+
currentPrice: number;
|
|
7
|
+
recentTicks: number[];
|
|
8
|
+
timestamp: number;
|
|
9
|
+
};
|
|
10
|
+
export declare class SignalEngine {
|
|
11
|
+
private ticks;
|
|
12
|
+
addTick(tick: PriceTick): MomentumSignal | null;
|
|
13
|
+
private evaluate;
|
|
14
|
+
getRecentTicks(symbol: "BTC" | "ETH", n?: number): number[];
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../src/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,KAAK,GAAG,KAAK,CAAC;IACtB,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAMF,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAAuC;IAEpD,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,cAAc,GAAG,IAAI;IAe/C,OAAO,CAAC,QAAQ;IA2BhB,cAAc,CAAC,MAAM,EAAE,KAAK,GAAG,KAAK,EAAE,CAAC,SAAI,GAAG,MAAM,EAAE;CAIvD"}
|
package/dist/signal.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const WINDOW_MS = 30_000; // 30 seconds
|
|
2
|
+
const MOMENTUM_THRESHOLD = 0.0035; // 0.35%
|
|
3
|
+
const MAX_TICKS = 20; // keep last N ticks per symbol for history
|
|
4
|
+
export class SignalEngine {
|
|
5
|
+
ticks = new Map();
|
|
6
|
+
addTick(tick) {
|
|
7
|
+
const key = tick.symbol;
|
|
8
|
+
if (!this.ticks.has(key))
|
|
9
|
+
this.ticks.set(key, []);
|
|
10
|
+
const arr = this.ticks.get(key);
|
|
11
|
+
arr.push(tick);
|
|
12
|
+
// trim to keep only recent + last MAX_TICKS
|
|
13
|
+
const cutoff = Date.now() - WINDOW_MS * 2;
|
|
14
|
+
while (arr.length > 0 && arr[0].timestamp < cutoff && arr.length > MAX_TICKS) {
|
|
15
|
+
arr.shift();
|
|
16
|
+
}
|
|
17
|
+
return this.evaluate(tick.symbol);
|
|
18
|
+
}
|
|
19
|
+
evaluate(symbol) {
|
|
20
|
+
const arr = this.ticks.get(symbol);
|
|
21
|
+
if (!arr || arr.length < 2)
|
|
22
|
+
return null;
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
const windowStart = now - WINDOW_MS;
|
|
25
|
+
const recent = arr.filter((t) => t.timestamp >= windowStart);
|
|
26
|
+
const all = arr.slice(-MAX_TICKS);
|
|
27
|
+
if (recent.length < 2)
|
|
28
|
+
return null;
|
|
29
|
+
const oldest = recent[0].price;
|
|
30
|
+
const newest = recent[recent.length - 1].price;
|
|
31
|
+
const momentum = (newest - oldest) / oldest;
|
|
32
|
+
if (Math.abs(momentum) < MOMENTUM_THRESHOLD)
|
|
33
|
+
return null;
|
|
34
|
+
return {
|
|
35
|
+
symbol,
|
|
36
|
+
direction: momentum > 0 ? "up" : "down",
|
|
37
|
+
momentum,
|
|
38
|
+
currentPrice: newest,
|
|
39
|
+
recentTicks: all.map((t) => t.price).slice(-5),
|
|
40
|
+
timestamp: now,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
getRecentTicks(symbol, n = 5) {
|
|
44
|
+
const arr = this.ticks.get(symbol) ?? [];
|
|
45
|
+
return arr.slice(-n).map((t) => t.price);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.js","sourceRoot":"","sources":["../src/signal.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gonzih/polymarket-arb",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Claude-powered Polymarket arbitrage bot — BTC/ETH momentum signal + AI trade analysis",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"polymarket",
|
|
7
|
+
"arbitrage",
|
|
8
|
+
"trading",
|
|
9
|
+
"crypto",
|
|
10
|
+
"defi",
|
|
11
|
+
"btc",
|
|
12
|
+
"eth",
|
|
13
|
+
"claude",
|
|
14
|
+
"anthropic"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/Gonzih/polymarket-arb",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/Gonzih/polymarket-arb.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"bin": {
|
|
24
|
+
"polymarket-arb": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"start": "node dist/index.js",
|
|
30
|
+
"dev": "tsx src/index.ts",
|
|
31
|
+
"test": "node dist/index.js --help"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist/",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@anthropic-ai/sdk": "0.39.0",
|
|
45
|
+
"ws": "8.18.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/ws": "8.5.14",
|
|
49
|
+
"@types/node": "22.13.14",
|
|
50
|
+
"typescript": "5.8.2",
|
|
51
|
+
"tsx": "4.19.3"
|
|
52
|
+
}
|
|
53
|
+
}
|