Add Random direction option — picks Buy or Sell once per batch

All accounts in the same batch trade the same resolved direction.
Each new batch (after positions are flat) picks a fresh random direction.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Senofy
2026-03-10 03:35:50 -05:00
co-authored by Claude Sonnet 4.6
parent 8848f90b11
commit a13096ea86
3 changed files with 23 additions and 13 deletions
+14 -10
View File
@@ -65,11 +65,15 @@ function isInNoTradeWindow(): boolean {
// ── core trade logic ──────────────────────────────────────────────────────────
export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
export async function runTrade(action: 'Buy' | 'Sell' | 'Random', symbol: string) {
if (isInNoTradeWindow()) {
console.log('[auto-trade] no-trade window active (35 PM Central) — skipping');
return [];
}
// Resolve Random once per batch so all accounts trade the same direction
const resolvedAction: 'Buy' | 'Sell' = action === 'Random'
? (Math.random() < 0.5 ? 'Buy' : 'Sell')
: action;
const pointValue = POINT_VALUES[symbol];
if (!pointValue) throw new Error(`Unknown symbol: ${symbol}`);
@@ -159,8 +163,8 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
const mnqContract = await client.findFrontMonthContract('MNQ');
if (!mnqContract) throw new Error('MNQ contract not found for extra-day trade');
const fill = await client.sendOrder(acc.id, mnqContract.name, 1, action, 'Market');
const exitAction: 'Buy' | 'Sell' = action === 'Buy' ? 'Sell' : 'Buy';
const fill = await client.sendOrder(acc.id, mnqContract.name, 1, resolvedAction, 'Market');
const exitAction: 'Buy' | 'Sell' = resolvedAction === 'Buy' ? 'Sell' : 'Buy';
const exitFill = await client.sendOrder(acc.id, mnqContract.name, 1, exitAction, 'Market');
// Refresh daily P&L immediately after the extra-day round-trip completes
@@ -168,7 +172,7 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
console.error('[auto-trade] post-fill fetchDaysTraded error:', err)
);
console.log(`[auto-trade] ${acc.name} (${item.firmName}) extra-day: ${action} 1xMNQ @ ${fill.price} | exited @ ${exitFill.price} (market)`);
console.log(`[auto-trade] ${acc.name} (${item.firmName}) extra-day: ${resolvedAction} 1xMNQ @ ${fill.price} | exited @ ${exitFill.price} (market)`);
return {
account: acc.name,
@@ -189,7 +193,7 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
const rawContracts = Math.max(1, Math.ceil(target.amount / 1000));
const contracts = cfg.maxPositionSize > 0 ? Math.min(rawContracts, cfg.maxPositionSize) : rawContracts;
const fill = await client.sendOrder(acc.id, contract.name, contracts, action, 'Market');
const fill = await client.sendOrder(acc.id, contract.name, contracts, resolvedAction, 'Market');
// Wait briefly for the cash balance WebSocket update to reflect entry commission
await new Promise(r => setTimeout(r, 1000));
@@ -202,11 +206,11 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
const targetPoints = grossTarget / (pointValue * contracts);
const ticks = Math.ceil(targetPoints / contract.tickSize);
const exitPrice = action === 'Buy'
const exitPrice = resolvedAction === 'Buy'
? fill.price + (ticks * contract.tickSize)
: fill.price - (ticks * contract.tickSize);
const exitAction: 'Buy' | 'Sell' = action === 'Buy' ? 'Sell' : 'Buy';
const exitAction: 'Buy' | 'Sell' = resolvedAction === 'Buy' ? 'Sell' : 'Buy';
const exitOrder = await client.placeOrderNoWait(acc.id, contract.name, contracts, exitAction, 'Limit', exitPrice);
// Refresh daily P&L as soon as the exit limit order fills
@@ -218,7 +222,7 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
});
}
console.log(`[auto-trade] ${acc.name} (${item.firmName}) ${action} ${contracts}x${symbol} @ ${fill.price} | target $${target.amount} [${target.path}] (+$${totalCommission.toFixed(2)} comm) | exit @ ${exitPrice} (orderId=${exitOrder.orderId})`);
console.log(`[auto-trade] ${acc.name} (${item.firmName}) ${resolvedAction} ${contracts}x${symbol} @ ${fill.price} | target $${target.amount} [${target.path}] (+$${totalCommission.toFixed(2)} comm) | exit @ ${exitPrice} (orderId=${exitOrder.orderId})`);
return {
account: acc.name,
@@ -254,7 +258,7 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
// ── scheduler ─────────────────────────────────────────────────────────────────
interface SchedulerState {
action: 'Buy' | 'Sell';
action: 'Buy' | 'Sell' | 'Random';
symbol: string;
intervalId: ReturnType<typeof setInterval> | null;
lastRun: Date | null;
@@ -271,7 +275,7 @@ function getState(): SchedulerState {
return _global.__autoTrader;
}
export function startScheduler(action: 'Buy' | 'Sell', symbol: string) {
export function startScheduler(action: 'Buy' | 'Sell' | 'Random', symbol: string) {
const state = getState();
// Clear any existing interval