Vest: record every trade, the bot's and outside ones, and list them
Vest's position history (GET /v3/positions) gives each position's size, open and close price, P&L, fees, funding and why it closed. The client reads it hourly and a minute after a position closes (or a flat balance moves) into a new trades table, and stores open positions as they appear. Vest can't tell an API order from a click, so the bot marks its own opens and closes as it sends them; an unmarked trade since marking began was someone else's. The dashboard's Vest firm row gets a Trades button listing every trade on its accounts with who opened it and how it closed; the account page shows its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
29eaf64a6b
commit
95cc3bd23f
@@ -0,0 +1,56 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getClients } from '@/lib/clients';
|
||||
import { loadTrades, getSetting, TRADES_SINCE_SETTING } from '@/lib/db';
|
||||
import type { TradeView } from '@/types';
|
||||
|
||||
/**
|
||||
* GET /api/trades?accountId=… | ?firmId=… [&limit=…] — recorded positions,
|
||||
* newest first: one account, a firm's current accounts, or all of them.
|
||||
*
|
||||
* Who opened and closed each one: the bot marks its own orders as it sends
|
||||
* them, so after marking began (TRADES_SINCE_SETTING) anything unmarked was
|
||||
* someone else's. A close Vest calls "user_requested" is an order on the
|
||||
* account — the bot's if it marked it, else outside.
|
||||
*/
|
||||
export async function GET(req: Request) {
|
||||
const url = new URL(req.url);
|
||||
const accountId = Number(url.searchParams.get('accountId'));
|
||||
const firmId = url.searchParams.get('firmId');
|
||||
const limit = Math.min(Math.max(Math.floor(Number(url.searchParams.get('limit'))) || 200, 1), 1000);
|
||||
|
||||
let accountIds: number[] | undefined;
|
||||
if (Number.isFinite(accountId) && accountId > 0) accountIds = [accountId];
|
||||
else if (firmId) accountIds = getClients().get(firmId)?.accountList.map((a) => a.id) ?? [];
|
||||
|
||||
const since = Number(getSetting(TRADES_SINCE_SETTING) ?? NaN);
|
||||
const tracked = (at: number) => Number.isFinite(since) && at >= since;
|
||||
|
||||
const trades: TradeView[] = loadTrades({ accountIds, limit }).map((t) => {
|
||||
const open = t.closedAt === null && t.closeReason === null && t.closedBy === null;
|
||||
const closedBy = open ? null
|
||||
: t.closedBy === 'bot' ? 'bot'
|
||||
: t.closeReason === 'user_requested' ? (tracked(t.closedAt ?? t.openedAt) ? 'outside' : 'user_requested')
|
||||
: t.closeReason ?? 'unknown';
|
||||
const net = open || t.pnl === null ? null
|
||||
: Math.round((t.pnl - Math.abs(t.fee ?? 0) + (t.funding ?? 0)) * 100) / 100;
|
||||
return {
|
||||
positionId: t.positionId,
|
||||
accountId: t.accountId,
|
||||
accountName: t.accountName,
|
||||
symbol: t.symbol,
|
||||
side: t.side,
|
||||
quantity: t.quantity,
|
||||
openPrice: t.openPrice,
|
||||
closePrice: t.closePrice,
|
||||
pnl: t.pnl,
|
||||
fee: t.fee,
|
||||
funding: t.funding,
|
||||
net,
|
||||
openedAt: t.openedAt,
|
||||
closedAt: t.closedAt,
|
||||
openedBy: t.openedBy === 'bot' ? 'bot' : tracked(t.openedAt) ? 'outside' : null,
|
||||
closedBy,
|
||||
};
|
||||
});
|
||||
return NextResponse.json(trades);
|
||||
}
|
||||
Reference in New Issue
Block a user