feat: add buy CL at 10s, multi-duration support, and multi-asset concurrent maker

- CL at 10s before close: cancel unfilled buy orders, place sells for filled
  positions with 3x retry for on-chain settlement delay
- Sell orders stay live until market close (no early cancellation)
- Support multiple durations simultaneously (MAKER_DURATION=5m,15m)
- Queue key changed to asset+duration so BTC/5m and BTC/15m run concurrently
- Update AGENT.MD and README.md with Maker v2 documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
direkturcrypto
2026-03-07 00:58:35 +07:00
co-authored by Claude Opus 4.6
parent 930dffafbd
commit 5f4aa04d13
8 changed files with 244 additions and 79 deletions
+22 -13
View File
@@ -93,13 +93,13 @@ async function buildStatusContent() {
// Maker Config
lines.push('{bold}MAKER CONFIG{/bold}');
lines.push(` Assets : ${config.makerAssets.join(', ').toUpperCase()}`);
lines.push(` Duration : ${config.makerDuration}`);
lines.push(` Duration : ${config.makerDurations.join(', ')}`);
lines.push(` Buy @ : $${config.makerBuyPrice} per share`);
lines.push(` Sell @ : $${config.makerSellPrice} per share`);
lines.push(` Size : ${config.makerTradeSize} shares/side`);
lines.push(` Cost/side: $${(config.makerTradeSize * config.makerBuyPrice).toFixed(2)}`);
lines.push(` Profit : $${((config.makerSellPrice - config.makerBuyPrice) * config.makerTradeSize).toFixed(2)}/cycle`);
lines.push(` No CL : hold to resolution if sell unfilled`);
lines.push(` CL : cancel buys at ${config.makerCLSeconds || 10}s, sell until close`);
lines.push('');
// Active positions
@@ -110,7 +110,7 @@ async function buildStatusContent() {
lines.push(' {gray-fg}Waiting for market...{/gray-fg}');
} else {
for (const pos of positions) {
const assetTag = pos.asset ? `[${pos.asset.toUpperCase()}] ` : '';
const assetTag = pos.asset ? `[${pos.asset.toUpperCase()}/${pos.duration || '5m'}] ` : '';
const label = pos.question.substring(0, 32);
const msLeft = new Date(pos.endTime).getTime() - Date.now();
const secsLeft = Math.max(0, Math.round(msLeft / 1000));
@@ -200,7 +200,14 @@ function startRefresh() {
const pendingByAsset = new Map();
function slotKey(market) {
return `${market.asset}-${market.duration || '5m'}`;
}
async function runStrategy(market) {
const key = slotKey(market);
const tag = `${market.asset?.toUpperCase()}/${market.duration || '5m'}`;
// Connect WebSocket for orderbook visualization in sim mode
if (config.dryRun) {
activeWsTokens = { up: market.yesTokenId, down: market.noTokenId };
@@ -210,7 +217,7 @@ async function runStrategy(market) {
try {
await executeMakerStrategy(market);
} catch (err) {
logger.error(`MAKER strategy error (${market.asset?.toUpperCase()}): ${err.message}`);
logger.error(`MAKER strategy error (${tag}): ${err.message}`);
}
// Disconnect WS after strategy ends
@@ -219,28 +226,30 @@ async function runStrategy(market) {
}
// Process queued market
const queued = pendingByAsset.get(market.asset);
const queued = pendingByAsset.get(key);
if (queued) {
pendingByAsset.delete(market.asset);
pendingByAsset.delete(key);
const endMs = new Date(queued.endTime).getTime();
const secsLeft = Math.round((endMs - Date.now()) / 1000);
if (secsLeft > 30) {
logger.success(`MAKER[${market.asset?.toUpperCase()}]: executing queued market (${secsLeft}s left)`);
logger.success(`MAKER[${tag}]: executing queued market (${secsLeft}s left)`);
runStrategy(queued);
} else {
logger.warn(`MAKER[${market.asset?.toUpperCase()}]: queued market expired (${secsLeft}s left)`);
logger.warn(`MAKER[${tag}]: queued market expired (${secsLeft}s left)`);
}
}
}
async function handleNewMarket(market) {
const key = slotKey(market);
const tag = `${market.asset?.toUpperCase()}/${market.duration || '5m'}`;
const active = getActiveMakerPositions();
const isAssetBusy = active.some((p) => p.asset === market.asset);
const isSlotBusy = active.some((p) => p.asset === market.asset && p.duration === (market.duration || '5m'));
if (isAssetBusy) {
pendingByAsset.set(market.asset, market);
logger.warn(`MAKER[${market.asset?.toUpperCase()}]: queued — will enter after current position clears`);
if (isSlotBusy) {
pendingByAsset.set(key, market);
logger.warn(`MAKER[${tag}]: queued — will enter after current position clears`);
return;
}
@@ -265,7 +274,7 @@ process.on('SIGTERM', shutdown);
const costPerSide = config.makerTradeSize * config.makerBuyPrice;
const profitPerCycle = (config.makerSellPrice - config.makerBuyPrice) * config.makerTradeSize;
logger.info(`MAKER starting — ${config.dryRun ? 'SIMULATION' : 'LIVE'}`);
logger.info(`Assets: ${config.makerAssets.join(', ').toUpperCase()} | BUY @ $${config.makerBuyPrice} → SELL @ $${config.makerSellPrice}`);
logger.info(`Assets: ${config.makerAssets.join(', ').toUpperCase()} | Durations: ${config.makerDurations.join(', ')} | BUY @ $${config.makerBuyPrice} → SELL @ $${config.makerSellPrice}`);
logger.info(`Size: ${config.makerTradeSize} sh/side | Cost: $${costPerSide.toFixed(2)}/side | Profit: $${profitPerCycle.toFixed(2)}/cycle`);
startRefresh();