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) {
|
||||
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
@@ -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
@@ -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 (3–5 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
|
||||
|
||||
Reference in New Issue
Block a user