feat: full project overhaul — market maker, sniper, WebSocket watcher, terminal UI
- 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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* 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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user