polly-gamba 1.0.37 → 1.0.38
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/dist/claude-trader.js +1 -1
- package/dist/lib/paper-trade.js +4 -6
- package/dist/test/stress.js +18 -21
- package/package.json +1 -1
- package/service.log +211 -0
- package/src/claude-trader.ts +1 -1
- package/src/lib/paper-trade.ts +4 -6
- package/src/test/stress.ts +18 -21
package/dist/claude-trader.js
CHANGED
|
@@ -115,7 +115,7 @@ RULES:
|
|
|
115
115
|
|
|
116
116
|
## POSITION DISCIPLINE:
|
|
117
117
|
- Max $100 per market (20% of $500 budget). The MCP enforces this — don't fight it.
|
|
118
|
-
-
|
|
118
|
+
- Same-direction re-entry is NEVER allowed while a position is open. You CANNOT average down into an existing open position. The MCP enforces this hard block. If you still believe in the thesis after a price drop, close the existing position first, then re-enter fresh next scan.
|
|
119
119
|
- exit_trigger is required on every trade. Be specific: "Exit when price hits 0.X" or "Exit when [specific news event]" — not "when narrative converges."
|
|
120
120
|
- Call get_budget_status AND get_positions at the start of each scan to know available capital and your current open positions. get_positions returns {"positions": [...], "closed_markets": [...]}. You MUST review BOTH before trading to apply concentration AND closed-market discipline correctly.`
|
|
121
121
|
}
|
package/dist/lib/paper-trade.js
CHANGED
|
@@ -33,14 +33,12 @@ function validatePlaceOrder(params, openPositions) {
|
|
|
33
33
|
if (sameMarketPositions.length > 0 && !params.new_catalyst) {
|
|
34
34
|
return `Error: Position already exists for this market. Provide new_catalyst (specific new information from last 24h) to add.`;
|
|
35
35
|
}
|
|
36
|
-
// Check 1b: Same-outcome re-entry
|
|
36
|
+
// Check 1b: Same-outcome re-entry is NEVER allowed while position is open.
|
|
37
|
+
// Averaging down compounds losses (see: Wembanyama -$7.18 incident).
|
|
38
|
+
// Close the existing position first, then re-enter if still compelling.
|
|
37
39
|
const sameOutcomePositions = sameMarketPositions.filter(p => String(p.outcome).toLowerCase() === String(params.outcome).toLowerCase());
|
|
38
40
|
if (sameOutcomePositions.length > 0) {
|
|
39
|
-
|
|
40
|
-
const requiredPrice = lastEntry * 0.80;
|
|
41
|
-
if (params.price > requiredPrice) {
|
|
42
|
-
return `Error: Re-entry blocked. Last ${params.outcome} entry at ${lastEntry.toFixed(4)}. Require price ≤ ${requiredPrice.toFixed(4)} (20% better) to add same-direction position. Current price ${params.price.toFixed(4)} is too close to last entry. Wait for a larger dislocation before averaging.`;
|
|
43
|
-
}
|
|
41
|
+
return `Error: Same-direction position already open for ${params.market_id} ${params.outcome}. Cannot average into an existing open position — this compounds losses. Close the current position first before re-entering.`;
|
|
44
42
|
}
|
|
45
43
|
// Check 2: $500 total budget cap
|
|
46
44
|
const totalDeployed = openPositions.reduce((s, p) => s + (p.size_usdc || 0), 0);
|
package/dist/test/stress.js
CHANGED
|
@@ -182,29 +182,27 @@ async function runDuplicateTests() {
|
|
|
182
182
|
assert(err !== null, 'expected same-market error for NO when YES exists');
|
|
183
183
|
assert(err.includes('new_catalyst'), `expected new_catalyst required, got: ${err}`);
|
|
184
184
|
});
|
|
185
|
-
await test('D3: re-entry with catalyst
|
|
185
|
+
await test('D3: same-outcome re-entry always blocked (even with catalyst, any discount)', async () => {
|
|
186
186
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })];
|
|
187
|
-
// 0.5 × 0.80 = 0.40; price 0.45 is not ≤ 0.40
|
|
188
187
|
const err = (0, paper_trade_1.validatePlaceOrder)({ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.45, new_catalyst: 'Breaking news' }, openPositions);
|
|
189
|
-
assert(err !== null, 'expected
|
|
190
|
-
assert(err.includes('
|
|
191
|
-
assert(err.includes('0.4000'), `expected required price 0.4000, got: ${err}`);
|
|
188
|
+
assert(err !== null, 'expected same-direction block');
|
|
189
|
+
assert(err.includes('Same-direction position already open'), `expected same-direction error, got: ${err}`);
|
|
192
190
|
});
|
|
193
|
-
await test('D4: re-entry
|
|
191
|
+
await test('D4: same-outcome re-entry blocked even with 24% discount + catalyst', async () => {
|
|
194
192
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })];
|
|
195
|
-
// 0.5 × 0.80 = 0.40; price 0.38 is ≤ 0.40 ✓
|
|
196
193
|
const err = (0, paper_trade_1.validatePlaceOrder)({ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.38, new_catalyst: 'Breaking news' }, openPositions);
|
|
197
|
-
assert(err
|
|
194
|
+
assert(err !== null, `expected block on same-direction open position, got: ${err}`);
|
|
195
|
+
assert(err.includes('Same-direction position already open'), `expected same-direction error, got: ${err}`);
|
|
198
196
|
});
|
|
199
|
-
await test('D5: re-entry at exactly 20% discount (
|
|
197
|
+
await test('D5: same-outcome re-entry at exactly 20% discount — blocked (no longer allowed)', async () => {
|
|
200
198
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })];
|
|
201
199
|
const err = (0, paper_trade_1.validatePlaceOrder)({ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.40, new_catalyst: 'News' }, openPositions);
|
|
202
|
-
assert(err
|
|
200
|
+
assert(err !== null, `expected block: averaging down is never allowed, got: ${err}`);
|
|
203
201
|
});
|
|
204
|
-
await test('D6: re-entry at
|
|
202
|
+
await test('D6: same-outcome re-entry at any price — blocked', async () => {
|
|
205
203
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })];
|
|
206
|
-
const err = (0, paper_trade_1.validatePlaceOrder)({ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.
|
|
207
|
-
assert(err !== null, `expected block
|
|
204
|
+
const err = (0, paper_trade_1.validatePlaceOrder)({ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.10, new_catalyst: 'News' }, openPositions);
|
|
205
|
+
assert(err !== null, `expected block regardless of price, got: ${err}`);
|
|
208
206
|
});
|
|
209
207
|
await test('D7: opposite-outcome (NO) with catalyst — no price check needed', async () => {
|
|
210
208
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })];
|
|
@@ -378,25 +376,24 @@ async function runConcentrationTests() {
|
|
|
378
376
|
}
|
|
379
377
|
async function runTypeCoercionTests() {
|
|
380
378
|
console.log('\n## I) Type Coercion — market_id string vs number');
|
|
381
|
-
await test('I1: numeric market_id in params matches string market_id in position', async () => {
|
|
379
|
+
await test('I1: numeric market_id in params matches string market_id in position — same-direction blocked', async () => {
|
|
382
380
|
// Simulates Claude passing market_id as a number (e.g., 564166) while
|
|
383
381
|
// Redis stores it as a string ("564166"). Without String() coercion,
|
|
384
|
-
// sameMarketPositions would be empty →
|
|
382
|
+
// sameMarketPositions would be empty → same-direction block bypassed.
|
|
385
383
|
const openPositions = [
|
|
386
384
|
makePosition({ market_id: '564166', outcome: 'YES', price: 0.30, ts: 1 })
|
|
387
385
|
];
|
|
388
386
|
const err = (0, paper_trade_1.validatePlaceOrder)({ market_id: 564166, outcome: 'YES', size_usdc: 10, price: 0.29, new_catalyst: 'New result' }, openPositions);
|
|
389
|
-
|
|
390
|
-
assert(err
|
|
391
|
-
assert(err.includes('Re-entry blocked'), `expected Re-entry blocked error, got: ${err}`);
|
|
387
|
+
assert(err !== null, 'expected same-direction block: numeric market_id must match string in position');
|
|
388
|
+
assert(err.includes('Same-direction position already open'), `expected same-direction error, got: ${err}`);
|
|
392
389
|
});
|
|
393
|
-
await test('I2: numeric market_id
|
|
390
|
+
await test('I2: numeric market_id same-direction re-entry always blocked regardless of discount', async () => {
|
|
394
391
|
const openPositions = [
|
|
395
392
|
makePosition({ market_id: '564166', outcome: 'YES', price: 0.30, ts: 1 })
|
|
396
393
|
];
|
|
397
394
|
const err = (0, paper_trade_1.validatePlaceOrder)({ market_id: 564166, outcome: 'YES', size_usdc: 10, price: 0.23, new_catalyst: 'Sportsbook update' }, openPositions);
|
|
398
|
-
//
|
|
399
|
-
assert(err
|
|
395
|
+
// Same-direction averaging is never allowed, any discount
|
|
396
|
+
assert(err !== null, `expected block on same-direction open position, got: ${err}`);
|
|
400
397
|
});
|
|
401
398
|
}
|
|
402
399
|
// ─── Main ─────────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
package/service.log
CHANGED
|
@@ -2803,3 +2803,214 @@ npm warn deprecated prebuild-install@7.1.3: No longer maintained. Please contact
|
|
|
2803
2803
|
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2804
2804
|
[gamma] Loaded 493 markets (filtered from 500)
|
|
2805
2805
|
[scan] 16 high-quality markets for autonomous review
|
|
2806
|
+
[polly-gamba] Starting paper trading service
|
|
2807
|
+
[polly-gamba] Claude cwd: /Users/feral/polly-gamba
|
|
2808
|
+
[polly-gamba] Expiring trader cwd: /Users/feral/polly-gamba-expiring
|
|
2809
|
+
[coinbase-ws] Connecting to wss://ws-feed.exchange.coinbase.com
|
|
2810
|
+
[polly-gamba] Listening for BTC/ETH price signals (threshold: 0.5% in 60s)...
|
|
2811
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2812
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2813
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2814
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2815
|
+
[position-monitor] checked=1 review_candidates=0 hard_stops=0 (moved>5% or <72h expiry)
|
|
2816
|
+
[coinbase-ws] Connected
|
|
2817
|
+
[gamma] Loaded 493 markets (filtered from 500)
|
|
2818
|
+
[scan] 16 high-quality markets for autonomous review
|
|
2819
|
+
[gamma] Loaded 493 markets (filtered from 500)
|
|
2820
|
+
[expiring] 0 expiring markets for review
|
|
2821
|
+
[expiring] no expiring markets found this cycle
|
|
2822
|
+
[claude-trader:anthropic] ready
|
|
2823
|
+
[claude-trader:ollama] ready
|
|
2824
|
+
[claude-trader:expiring] ready
|
|
2825
|
+
[polly-gamba] Starting paper trading service
|
|
2826
|
+
[polly-gamba] Claude cwd: /Users/feral/polly-gamba
|
|
2827
|
+
[polly-gamba] Expiring trader cwd: /Users/feral/polly-gamba-expiring
|
|
2828
|
+
[coinbase-ws] Connecting to wss://ws-feed.exchange.coinbase.com
|
|
2829
|
+
[polly-gamba] Listening for BTC/ETH price signals (threshold: 0.5% in 60s)...
|
|
2830
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2831
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2832
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2833
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2834
|
+
[gamma] Loaded 493 markets (filtered from 500)
|
|
2835
|
+
[expiring] 0 expiring markets for review
|
|
2836
|
+
[expiring] no expiring markets found this cycle
|
|
2837
|
+
[gamma] Loaded 493 markets (filtered from 500)
|
|
2838
|
+
[scan] 16 high-quality markets for autonomous review
|
|
2839
|
+
[coinbase-ws] Connected
|
|
2840
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2841
|
+
[claude-trader:anthropic] ready
|
|
2842
|
+
[claude-trader:ollama] ready
|
|
2843
|
+
[claude-trader:expiring] ready
|
|
2844
|
+
[polly-gamba] Starting paper trading service
|
|
2845
|
+
[polly-gamba] Claude cwd: /Users/feral/polly-gamba
|
|
2846
|
+
[polly-gamba] Expiring trader cwd: /Users/feral/polly-gamba-expiring
|
|
2847
|
+
[coinbase-ws] Connecting to wss://ws-feed.exchange.coinbase.com
|
|
2848
|
+
[polly-gamba] Listening for BTC/ETH price signals (threshold: 0.5% in 60s)...
|
|
2849
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2850
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2851
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2852
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2853
|
+
[gamma] Loaded 493 markets (filtered from 500)
|
|
2854
|
+
[expiring] 0 expiring markets for review
|
|
2855
|
+
[expiring] no expiring markets found this cycle
|
|
2856
|
+
[gamma] Loaded 493 markets (filtered from 500)
|
|
2857
|
+
[scan] 16 high-quality markets for autonomous review
|
|
2858
|
+
[coinbase-ws] Connected
|
|
2859
|
+
[claude-trader:anthropic] ready
|
|
2860
|
+
[claude-trader:ollama] ready
|
|
2861
|
+
[claude-trader:expiring] ready
|
|
2862
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2863
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2864
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2865
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2866
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2867
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2868
|
+
[scan] 16 high-quality markets for autonomous review
|
|
2869
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2870
|
+
[expiring] 0 expiring markets for review
|
|
2871
|
+
[expiring] no expiring markets found this cycle
|
|
2872
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2873
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2874
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2875
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2876
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2877
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2878
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2879
|
+
[expiring] 0 expiring markets for review
|
|
2880
|
+
[expiring] no expiring markets found this cycle
|
|
2881
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2882
|
+
[scan] 15 high-quality markets for autonomous review
|
|
2883
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2884
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2885
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2886
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2887
|
+
[expiring] failed to fetch markets: fetch failed
|
|
2888
|
+
[position-monitor] WARNING: no price data for 553830 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2889
|
+
[position-monitor] WARNING: no price data for 558934 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2890
|
+
[position-monitor] WARNING: no price data for 566140 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2891
|
+
[position-monitor] WARNING: no price data for 540819 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2892
|
+
[position-monitor] WARNING: no price data for 568629 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2893
|
+
[position-monitor] WARNING: no price data for 566142 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2894
|
+
[position-monitor] WARNING: no price data for 562828 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2895
|
+
[position-monitor] WARNING: no price data for 559651 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2896
|
+
[position-monitor] WARNING: no price data for 561975 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2897
|
+
[position-monitor] WARNING: no price data for 567561 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2898
|
+
[position-monitor] WARNING: no price data for 567687 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2899
|
+
[position-monitor] checked=24 review_candidates=3 hard_stops=0 (moved>5% or <72h expiry)
|
|
2900
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2901
|
+
[scan] 15 high-quality markets for autonomous review
|
|
2902
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2903
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2904
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2905
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2906
|
+
[scan] 15 high-quality markets for autonomous review
|
|
2907
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2908
|
+
[expiring] 0 expiring markets for review
|
|
2909
|
+
[expiring] no expiring markets found this cycle
|
|
2910
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2911
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2912
|
+
[scan] failed to fetch markets: fetch failed
|
|
2913
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2914
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2915
|
+
[expiring] failed to fetch markets: fetch failed
|
|
2916
|
+
[position-monitor] WARNING: no price data for 566140 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2917
|
+
[position-monitor] WARNING: no price data for 540819 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2918
|
+
[position-monitor] WARNING: no price data for 568629 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2919
|
+
[position-monitor] WARNING: no price data for 562828 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2920
|
+
[position-monitor] WARNING: no price data for 561974 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2921
|
+
[position-monitor] checked=24 review_candidates=5 hard_stops=0 (moved>5% or <72h expiry)
|
|
2922
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2923
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2924
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2925
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2926
|
+
[scan] 15 high-quality markets for autonomous review
|
|
2927
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2928
|
+
[expiring] 0 expiring markets for review
|
|
2929
|
+
[expiring] no expiring markets found this cycle
|
|
2930
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2931
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2932
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2933
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2934
|
+
[expiring] failed to fetch markets: fetch failed
|
|
2935
|
+
[scan] failed to fetch markets: fetch failed
|
|
2936
|
+
[position-monitor] WARNING: no price data for 553830 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2937
|
+
[position-monitor] WARNING: no price data for 558934 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2938
|
+
[position-monitor] WARNING: no price data for 566140 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2939
|
+
[position-monitor] WARNING: no price data for 540819 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2940
|
+
[position-monitor] WARNING: no price data for 568629 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2941
|
+
[position-monitor] WARNING: no price data for 559652 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2942
|
+
[position-monitor] WARNING: no price data for 558936 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2943
|
+
[position-monitor] WARNING: no price data for 553866 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2944
|
+
[position-monitor] WARNING: no price data for 567689 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2945
|
+
[position-monitor] WARNING: no price data for 566142 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2946
|
+
[position-monitor] checked=24 review_candidates=4 hard_stops=0 (moved>5% or <72h expiry)
|
|
2947
|
+
[signal] ETH down -0.50% @ $2,148.31 over 59s
|
|
2948
|
+
[signal] ETH down 0.50% @ $2,148.31
|
|
2949
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2950
|
+
[error] failed to fetch markets: fetch failed
|
|
2951
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2952
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2953
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2954
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2955
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2956
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2957
|
+
[scan] 15 high-quality markets for autonomous review
|
|
2958
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2959
|
+
[expiring] 0 expiring markets for review
|
|
2960
|
+
[expiring] no expiring markets found this cycle
|
|
2961
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2962
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2963
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2964
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2965
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2966
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2967
|
+
[scan] 15 high-quality markets for autonomous review
|
|
2968
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
2969
|
+
[expiring] 0 expiring markets for review
|
|
2970
|
+
[expiring] no expiring markets found this cycle
|
|
2971
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2972
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2973
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2974
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2975
|
+
[expiring] failed to fetch markets: fetch failed
|
|
2976
|
+
[scan] failed to fetch markets: fetch failed
|
|
2977
|
+
[position-monitor] WARNING: no price data for 558934 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2978
|
+
[position-monitor] WARNING: no price data for 566140 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2979
|
+
[position-monitor] WARNING: no price data for 540819 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2980
|
+
[position-monitor] WARNING: no price data for 568629 YES — first miss recorded, watching (market may be resolved or delisted)
|
|
2981
|
+
[position-monitor] WARNING: no price data for 553866 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2982
|
+
[position-monitor] WARNING: no price data for 567689 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2983
|
+
[position-monitor] WARNING: no price data for 566142 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2984
|
+
[position-monitor] WARNING: no price data for 562828 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2985
|
+
[position-monitor] WARNING: no price data for 566188 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2986
|
+
[position-monitor] WARNING: no price data for 559651 NO — first miss recorded, watching (market may be resolved or delisted)
|
|
2987
|
+
[position-monitor] checked=24 review_candidates=5 hard_stops=0 (moved>5% or <72h expiry)
|
|
2988
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
|
2989
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
2990
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2991
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
2992
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
2993
|
+
[gamma] Loaded 495 markets (filtered from 500)
|
|
2994
|
+
[scan] 15 high-quality markets for autonomous review
|
|
2995
|
+
[gamma] Loaded 495 markets (filtered from 500)
|
|
2996
|
+
[expiring] 0 expiring markets for review
|
|
2997
|
+
[expiring] no expiring markets found this cycle
|
|
2998
|
+
[polly-gamba] Starting paper trading service
|
|
2999
|
+
[polly-gamba] Claude cwd: /Users/feral/polly-gamba
|
|
3000
|
+
[polly-gamba] Expiring trader cwd: /Users/feral/polly-gamba-expiring
|
|
3001
|
+
[coinbase-ws] Connecting to wss://ws-feed.exchange.coinbase.com
|
|
3002
|
+
[polly-gamba] Listening for BTC/ETH price signals (threshold: 0.5% in 60s)...
|
|
3003
|
+
[scan] fetching high-quality markets (vol24h>$50k, liq>$50k, price 0.10-0.90)
|
|
3004
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
3005
|
+
[expiring] fetching expiring markets (closing within 72h, vol24h>$10k, liq>$10k, price 0.05-0.95)
|
|
3006
|
+
[gamma] Fetching active markets from https://gamma-api.polymarket.com/markets?active=true&closed=false&limit=500
|
|
3007
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
3008
|
+
[expiring] 0 expiring markets for review
|
|
3009
|
+
[expiring] no expiring markets found this cycle
|
|
3010
|
+
[coinbase-ws] Connected
|
|
3011
|
+
[gamma] Loaded 494 markets (filtered from 500)
|
|
3012
|
+
[scan] 15 high-quality markets for autonomous review
|
|
3013
|
+
[claude-trader:anthropic] ready
|
|
3014
|
+
[claude-trader:ollama] ready
|
|
3015
|
+
[claude-trader:expiring] ready
|
|
3016
|
+
[position-monitor] checked=24 review_candidates=6 hard_stops=0 (moved>5% or <72h expiry)
|
package/src/claude-trader.ts
CHANGED
|
@@ -132,7 +132,7 @@ RULES:
|
|
|
132
132
|
|
|
133
133
|
## POSITION DISCIPLINE:
|
|
134
134
|
- Max $100 per market (20% of $500 budget). The MCP enforces this — don't fight it.
|
|
135
|
-
-
|
|
135
|
+
- Same-direction re-entry is NEVER allowed while a position is open. You CANNOT average down into an existing open position. The MCP enforces this hard block. If you still believe in the thesis after a price drop, close the existing position first, then re-enter fresh next scan.
|
|
136
136
|
- exit_trigger is required on every trade. Be specific: "Exit when price hits 0.X" or "Exit when [specific news event]" — not "when narrative converges."
|
|
137
137
|
- Call get_budget_status AND get_positions at the start of each scan to know available capital and your current open positions. get_positions returns {"positions": [...], "closed_markets": [...]}. You MUST review BOTH before trading to apply concentration AND closed-market discipline correctly.`
|
|
138
138
|
}
|
package/src/lib/paper-trade.ts
CHANGED
|
@@ -67,16 +67,14 @@ export function validatePlaceOrder(
|
|
|
67
67
|
return `Error: Position already exists for this market. Provide new_catalyst (specific new information from last 24h) to add.`
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Check 1b: Same-outcome re-entry
|
|
70
|
+
// Check 1b: Same-outcome re-entry is NEVER allowed while position is open.
|
|
71
|
+
// Averaging down compounds losses (see: Wembanyama -$7.18 incident).
|
|
72
|
+
// Close the existing position first, then re-enter if still compelling.
|
|
71
73
|
const sameOutcomePositions = sameMarketPositions.filter(p =>
|
|
72
74
|
String(p.outcome).toLowerCase() === String(params.outcome).toLowerCase()
|
|
73
75
|
)
|
|
74
76
|
if (sameOutcomePositions.length > 0) {
|
|
75
|
-
|
|
76
|
-
const requiredPrice = lastEntry * 0.80
|
|
77
|
-
if (params.price > requiredPrice) {
|
|
78
|
-
return `Error: Re-entry blocked. Last ${params.outcome} entry at ${lastEntry.toFixed(4)}. Require price ≤ ${requiredPrice.toFixed(4)} (20% better) to add same-direction position. Current price ${params.price.toFixed(4)} is too close to last entry. Wait for a larger dislocation before averaging.`
|
|
79
|
-
}
|
|
77
|
+
return `Error: Same-direction position already open for ${params.market_id} ${params.outcome}. Cannot average into an existing open position — this compounds losses. Close the current position first before re-entering.`
|
|
80
78
|
}
|
|
81
79
|
|
|
82
80
|
// Check 2: $500 total budget cap
|
package/src/test/stress.ts
CHANGED
|
@@ -234,44 +234,42 @@ async function runDuplicateTests(): Promise<void> {
|
|
|
234
234
|
assert(err!.includes('new_catalyst'), `expected new_catalyst required, got: ${err}`)
|
|
235
235
|
})
|
|
236
236
|
|
|
237
|
-
await test('D3: re-entry with catalyst
|
|
237
|
+
await test('D3: same-outcome re-entry always blocked (even with catalyst, any discount)', async () => {
|
|
238
238
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })]
|
|
239
|
-
// 0.5 × 0.80 = 0.40; price 0.45 is not ≤ 0.40
|
|
240
239
|
const err = validatePlaceOrder(
|
|
241
240
|
{ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.45, new_catalyst: 'Breaking news' },
|
|
242
241
|
openPositions
|
|
243
242
|
)
|
|
244
|
-
assert(err !== null, 'expected
|
|
245
|
-
assert(err!.includes('
|
|
246
|
-
assert(err!.includes('0.4000'), `expected required price 0.4000, got: ${err}`)
|
|
243
|
+
assert(err !== null, 'expected same-direction block')
|
|
244
|
+
assert(err!.includes('Same-direction position already open'), `expected same-direction error, got: ${err}`)
|
|
247
245
|
})
|
|
248
246
|
|
|
249
|
-
await test('D4: re-entry
|
|
247
|
+
await test('D4: same-outcome re-entry blocked even with 24% discount + catalyst', async () => {
|
|
250
248
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })]
|
|
251
|
-
// 0.5 × 0.80 = 0.40; price 0.38 is ≤ 0.40 ✓
|
|
252
249
|
const err = validatePlaceOrder(
|
|
253
250
|
{ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.38, new_catalyst: 'Breaking news' },
|
|
254
251
|
openPositions
|
|
255
252
|
)
|
|
256
|
-
assert(err
|
|
253
|
+
assert(err !== null, `expected block on same-direction open position, got: ${err}`)
|
|
254
|
+
assert(err!.includes('Same-direction position already open'), `expected same-direction error, got: ${err}`)
|
|
257
255
|
})
|
|
258
256
|
|
|
259
|
-
await test('D5: re-entry at exactly 20% discount (
|
|
257
|
+
await test('D5: same-outcome re-entry at exactly 20% discount — blocked (no longer allowed)', async () => {
|
|
260
258
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })]
|
|
261
259
|
const err = validatePlaceOrder(
|
|
262
260
|
{ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.40, new_catalyst: 'News' },
|
|
263
261
|
openPositions
|
|
264
262
|
)
|
|
265
|
-
assert(err
|
|
263
|
+
assert(err !== null, `expected block: averaging down is never allowed, got: ${err}`)
|
|
266
264
|
})
|
|
267
265
|
|
|
268
|
-
await test('D6: re-entry at
|
|
266
|
+
await test('D6: same-outcome re-entry at any price — blocked', async () => {
|
|
269
267
|
const openPositions = [makePosition({ market_id: 'dup_mkt', outcome: 'YES', price: 0.5 })]
|
|
270
268
|
const err = validatePlaceOrder(
|
|
271
|
-
{ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.
|
|
269
|
+
{ market_id: 'dup_mkt', outcome: 'YES', size_usdc: 10, price: 0.10, new_catalyst: 'News' },
|
|
272
270
|
openPositions
|
|
273
271
|
)
|
|
274
|
-
assert(err !== null, `expected block
|
|
272
|
+
assert(err !== null, `expected block regardless of price, got: ${err}`)
|
|
275
273
|
})
|
|
276
274
|
|
|
277
275
|
await test('D7: opposite-outcome (NO) with catalyst — no price check needed', async () => {
|
|
@@ -511,10 +509,10 @@ async function runConcentrationTests(): Promise<void> {
|
|
|
511
509
|
async function runTypeCoercionTests(): Promise<void> {
|
|
512
510
|
console.log('\n## I) Type Coercion — market_id string vs number')
|
|
513
511
|
|
|
514
|
-
await test('I1: numeric market_id in params matches string market_id in position', async () => {
|
|
512
|
+
await test('I1: numeric market_id in params matches string market_id in position — same-direction blocked', async () => {
|
|
515
513
|
// Simulates Claude passing market_id as a number (e.g., 564166) while
|
|
516
514
|
// Redis stores it as a string ("564166"). Without String() coercion,
|
|
517
|
-
// sameMarketPositions would be empty →
|
|
515
|
+
// sameMarketPositions would be empty → same-direction block bypassed.
|
|
518
516
|
const openPositions = [
|
|
519
517
|
makePosition({ market_id: '564166', outcome: 'YES', price: 0.30, ts: 1 })
|
|
520
518
|
]
|
|
@@ -522,12 +520,11 @@ async function runTypeCoercionTests(): Promise<void> {
|
|
|
522
520
|
{ market_id: 564166 as any, outcome: 'YES', size_usdc: 10, price: 0.29, new_catalyst: 'New result' },
|
|
523
521
|
openPositions
|
|
524
522
|
)
|
|
525
|
-
|
|
526
|
-
assert(err
|
|
527
|
-
assert(err!.includes('Re-entry blocked'), `expected Re-entry blocked error, got: ${err}`)
|
|
523
|
+
assert(err !== null, 'expected same-direction block: numeric market_id must match string in position')
|
|
524
|
+
assert(err!.includes('Same-direction position already open'), `expected same-direction error, got: ${err}`)
|
|
528
525
|
})
|
|
529
526
|
|
|
530
|
-
await test('I2: numeric market_id
|
|
527
|
+
await test('I2: numeric market_id same-direction re-entry always blocked regardless of discount', async () => {
|
|
531
528
|
const openPositions = [
|
|
532
529
|
makePosition({ market_id: '564166', outcome: 'YES', price: 0.30, ts: 1 })
|
|
533
530
|
]
|
|
@@ -535,8 +532,8 @@ async function runTypeCoercionTests(): Promise<void> {
|
|
|
535
532
|
{ market_id: 564166 as any, outcome: 'YES', size_usdc: 10, price: 0.23, new_catalyst: 'Sportsbook update' },
|
|
536
533
|
openPositions
|
|
537
534
|
)
|
|
538
|
-
//
|
|
539
|
-
assert(err
|
|
535
|
+
// Same-direction averaging is never allowed, any discount
|
|
536
|
+
assert(err !== null, `expected block on same-direction open position, got: ${err}`)
|
|
540
537
|
})
|
|
541
538
|
}
|
|
542
539
|
|