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
+1 -1
View File
@@ -4,7 +4,7 @@ import { POINT_VALUES } from '@/lib/trading-logic';
export async function POST(req: NextRequest) {
try {
const body = await req.json() as { action: 'Buy' | 'Sell'; symbol: string };
const body = await req.json() as { action: 'Buy' | 'Sell' | 'Random'; symbol: string };
const { action, symbol } = body;
if (!action || !symbol) {
+8 -2
View File
@@ -247,7 +247,7 @@ function FirmRows({ state, firm, deleteMode, selected, onToggle, hideDead, priva
interface SchedulerStatus {
running: boolean;
action: 'Buy' | 'Sell';
action: 'Buy' | 'Sell' | 'Random';
symbol: string;
lastRun: string | null;
}
@@ -267,7 +267,7 @@ export default function Home() {
const [scheduler, setScheduler] = useState<SchedulerStatus>({ running: false, action: 'Buy', symbol: 'NQ', lastRun: null });
const [enabledSymbols, setEnabledSymbols] = useState<string[]>([]);
const [tradeSymbol, setTradeSymbol] = useState('');
const [tradeAction, setTradeAction] = useState<'Buy' | 'Sell'>('Buy');
const [tradeAction, setTradeAction] = useState<'Buy' | 'Sell' | 'Random'>('Buy');
const [tradeLoading, setTradeLoading] = useState(false);
const handleSort = (col: SortKey) => {
@@ -487,6 +487,12 @@ export default function Home() {
>
Sell
</button>
<button
onClick={() => setTradeAction('Random')}
className={`px-3 py-1.5 transition-colors border-l border-slate-200 ${tradeAction === 'Random' ? 'bg-purple-500 text-white' : 'text-slate-500 hover:bg-slate-50'}`}
>
Random
</button>
</div>
</div>
<button
+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