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:
co-authored by
Claude Sonnet 4.6
parent
8848f90b11
commit
a13096ea86
@@ -4,7 +4,7 @@ import { POINT_VALUES } from '@/lib/trading-logic';
|
|||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
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;
|
const { action, symbol } = body;
|
||||||
|
|
||||||
if (!action || !symbol) {
|
if (!action || !symbol) {
|
||||||
|
|||||||
+8
-2
@@ -247,7 +247,7 @@ function FirmRows({ state, firm, deleteMode, selected, onToggle, hideDead, priva
|
|||||||
|
|
||||||
interface SchedulerStatus {
|
interface SchedulerStatus {
|
||||||
running: boolean;
|
running: boolean;
|
||||||
action: 'Buy' | 'Sell';
|
action: 'Buy' | 'Sell' | 'Random';
|
||||||
symbol: string;
|
symbol: string;
|
||||||
lastRun: string | null;
|
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 [scheduler, setScheduler] = useState<SchedulerStatus>({ running: false, action: 'Buy', symbol: 'NQ', lastRun: null });
|
||||||
const [enabledSymbols, setEnabledSymbols] = useState<string[]>([]);
|
const [enabledSymbols, setEnabledSymbols] = useState<string[]>([]);
|
||||||
const [tradeSymbol, setTradeSymbol] = useState('');
|
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 [tradeLoading, setTradeLoading] = useState(false);
|
||||||
|
|
||||||
const handleSort = (col: SortKey) => {
|
const handleSort = (col: SortKey) => {
|
||||||
@@ -487,6 +487,12 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
Sell
|
Sell
|
||||||
</button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
+14
-10
@@ -65,11 +65,15 @@ function isInNoTradeWindow(): boolean {
|
|||||||
|
|
||||||
// ── core trade logic ──────────────────────────────────────────────────────────
|
// ── core trade logic ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
|
export async function runTrade(action: 'Buy' | 'Sell' | 'Random', symbol: string) {
|
||||||
if (isInNoTradeWindow()) {
|
if (isInNoTradeWindow()) {
|
||||||
console.log('[auto-trade] no-trade window active (3–5 PM Central) — skipping');
|
console.log('[auto-trade] no-trade window active (3–5 PM Central) — skipping');
|
||||||
return [];
|
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];
|
const pointValue = POINT_VALUES[symbol];
|
||||||
if (!pointValue) throw new Error(`Unknown symbol: ${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');
|
const mnqContract = await client.findFrontMonthContract('MNQ');
|
||||||
if (!mnqContract) throw new Error('MNQ contract not found for extra-day trade');
|
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 fill = await client.sendOrder(acc.id, mnqContract.name, 1, resolvedAction, 'Market');
|
||||||
const exitAction: 'Buy' | 'Sell' = action === 'Buy' ? 'Sell' : 'Buy';
|
const exitAction: 'Buy' | 'Sell' = resolvedAction === 'Buy' ? 'Sell' : 'Buy';
|
||||||
const exitFill = await client.sendOrder(acc.id, mnqContract.name, 1, exitAction, 'Market');
|
const exitFill = await client.sendOrder(acc.id, mnqContract.name, 1, exitAction, 'Market');
|
||||||
|
|
||||||
// Refresh daily P&L immediately after the extra-day round-trip completes
|
// 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.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 {
|
return {
|
||||||
account: acc.name,
|
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 rawContracts = Math.max(1, Math.ceil(target.amount / 1000));
|
||||||
const contracts = cfg.maxPositionSize > 0 ? Math.min(rawContracts, cfg.maxPositionSize) : rawContracts;
|
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
|
// Wait briefly for the cash balance WebSocket update to reflect entry commission
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
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 targetPoints = grossTarget / (pointValue * contracts);
|
||||||
const ticks = Math.ceil(targetPoints / contract.tickSize);
|
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)
|
||||||
: 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);
|
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
|
// 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 {
|
return {
|
||||||
account: acc.name,
|
account: acc.name,
|
||||||
@@ -254,7 +258,7 @@ export async function runTrade(action: 'Buy' | 'Sell', symbol: string) {
|
|||||||
// ── scheduler ─────────────────────────────────────────────────────────────────
|
// ── scheduler ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
interface SchedulerState {
|
interface SchedulerState {
|
||||||
action: 'Buy' | 'Sell';
|
action: 'Buy' | 'Sell' | 'Random';
|
||||||
symbol: string;
|
symbol: string;
|
||||||
intervalId: ReturnType<typeof setInterval> | null;
|
intervalId: ReturnType<typeof setInterval> | null;
|
||||||
lastRun: Date | null;
|
lastRun: Date | null;
|
||||||
@@ -271,7 +275,7 @@ function getState(): SchedulerState {
|
|||||||
return _global.__autoTrader;
|
return _global.__autoTrader;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startScheduler(action: 'Buy' | 'Sell', symbol: string) {
|
export function startScheduler(action: 'Buy' | 'Sell' | 'Random', symbol: string) {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
// Clear any existing interval
|
// Clear any existing interval
|
||||||
|
|||||||
Reference in New Issue
Block a user