526076fe6e
- Rename project to polymarket-terminal - Add Market Maker bot (src/mm.js) with on-chain CTF split/merge/redeem via Gnosis Safe - Add Orderbook Sniper bot (src/sniper.js) with multi-asset GTC low-price orders - Add WebSocket watcher (src/services/wsWatcher.js) for real-time RTDS trade events - Add terminal dashboard UI (src/ui/dashboard.js) using blessed - Add CTF contract helpers (src/services/ctf.js) for splitPosition, mergePositions, redeemPositions - Add mmDetector, mmExecutor, sniperDetector, sniperExecutor services - Add simStats utility for dry-run P&L tracking - Translate all Indonesian-language strings to professional English across all files - Rewrite README.md in English with full setup guide, configuration reference, and architecture overview - Rewrite AGENT.MD in English as comprehensive AI agent and developer reference - Update package.json name, description, scripts, and keywords Co-Authored-By: direkturcrypto <direkturcrypto.x@mail3.me>
92 lines
3.6 KiB
JavaScript
92 lines
3.6 KiB
JavaScript
/**
|
||
* sniperExecutor.js
|
||
* Places GTC limit BUY orders at a very low price on both sides of a market.
|
||
*
|
||
* Strategy:
|
||
* - For each market detected by sniperDetector, place two GTC BUY orders:
|
||
* UP token at $SNIPER_PRICE × SNIPER_SHARES shares
|
||
* DOWN token at $SNIPER_PRICE × SNIPER_SHARES shares
|
||
* - Orders sit in the orderbook. If someone panic-dumps below the price,
|
||
* the order fills and becomes redeemable if that side wins.
|
||
* - GTC orders expire automatically when the market closes — no cleanup needed.
|
||
*
|
||
* Cost per market: SNIPER_PRICE × SNIPER_SHARES × 2 sides
|
||
* e.g. $0.01 × 5 × 2 = $0.10 per market, $0.30 for 3 assets per 5-min slot
|
||
*/
|
||
|
||
import { Side, OrderType } from '@polymarket/clob-client';
|
||
import config from '../config/index.js';
|
||
import { getClient } from './client.js';
|
||
import logger from '../utils/logger.js';
|
||
|
||
// In-memory tracking of placed snipe orders (for TUI status panel)
|
||
const activeSnipes = []; // { asset, side, question, orderId, price, shares, cost, potentialPayout }
|
||
|
||
export function getActiveSnipes() {
|
||
return [...activeSnipes];
|
||
}
|
||
|
||
export async function executeSnipe(market) {
|
||
const { asset, conditionId, question, yesTokenId, noTokenId, tickSize, negRisk } = market;
|
||
const label = question.slice(0, 40);
|
||
const sim = config.dryRun ? '[SIM] ' : '';
|
||
|
||
const sides = [
|
||
{ name: 'UP', tokenId: yesTokenId },
|
||
{ name: 'DOWN', tokenId: noTokenId },
|
||
];
|
||
|
||
logger.info(`SNIPER: ${sim}${asset.toUpperCase()} — "${label}" | $${config.sniperPrice} × ${config.sniperShares}sh each side`);
|
||
|
||
for (const { name, tokenId } of sides) {
|
||
if (config.dryRun) {
|
||
const cost = config.sniperPrice * config.sniperShares;
|
||
logger.trade(`SNIPER[SIM]: ${asset.toUpperCase()} ${name} @ $${config.sniperPrice} × ${config.sniperShares}sh | cost $${cost.toFixed(3)} | payout $${config.sniperShares} if wins`);
|
||
activeSnipes.push({
|
||
asset: asset.toUpperCase(),
|
||
side: name,
|
||
question: label,
|
||
orderId: `sim-${Date.now()}-${tokenId.slice(-6)}`,
|
||
price: config.sniperPrice,
|
||
shares: config.sniperShares,
|
||
cost,
|
||
potentialPayout: config.sniperShares,
|
||
});
|
||
continue;
|
||
}
|
||
|
||
const client = getClient();
|
||
try {
|
||
const res = await client.createAndPostOrder(
|
||
{
|
||
tokenID: tokenId,
|
||
side: Side.BUY,
|
||
price: config.sniperPrice,
|
||
size: config.sniperShares,
|
||
},
|
||
{ tickSize, negRisk },
|
||
OrderType.GTC,
|
||
);
|
||
|
||
if (res?.success) {
|
||
const cost = config.sniperPrice * config.sniperShares;
|
||
logger.trade(`SNIPER: ${asset.toUpperCase()} ${name} @ $${config.sniperPrice} × ${config.sniperShares}sh | cost $${cost.toFixed(3)} | order ${res.orderID}`);
|
||
activeSnipes.push({
|
||
asset: asset.toUpperCase(),
|
||
side: name,
|
||
question: label,
|
||
orderId: res.orderID,
|
||
price: config.sniperPrice,
|
||
shares: config.sniperShares,
|
||
cost,
|
||
potentialPayout: config.sniperShares,
|
||
});
|
||
} else {
|
||
logger.warn(`SNIPER: ${asset.toUpperCase()} ${name} order failed — ${res?.errorMsg || 'unknown'}`);
|
||
}
|
||
} catch (err) {
|
||
logger.error(`SNIPER: ${asset.toUpperCase()} ${name} error — ${err.message}`);
|
||
}
|
||
}
|
||
}
|