← back to Bertha
src/app/api/trades/route.js
101 lines
import { NextResponse } from 'next/server';
const { query } = require('../../../lib/db');
const crypto = require('crypto');
export const dynamic = 'force-dynamic';
export async function GET() {
try {
const [orders, trades] = await Promise.all([
query('SELECT * FROM orders ORDER BY submitted_ts DESC LIMIT 100'),
query('SELECT * FROM trades ORDER BY match_time DESC NULLS LAST LIMIT 100'),
]);
return NextResponse.json({ orders: orders.rows, trades: trades.rows });
} catch (err) {
return NextResponse.json({ error: err.message }, { status: 500 });
}
}
export async function POST(req) {
const body = await req.json();
if (body.action === 'paper_order') {
try {
const orderId = `paper_${crypto.randomUUID().substring(0, 12)}`;
const { asset_id, condition_id, side, outcome, price, size_usd, prediction_id } = body;
// Insert order
await query(`
INSERT INTO orders (order_id, asset_id, condition_id, side, outcome, price, size, size_usd, status, paper_trade, prediction_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'filled', TRUE, $9)
`, [orderId, asset_id, condition_id, side, outcome, price, size_usd / price, size_usd, prediction_id]);
// Simulate instant fill (paper trading)
const tradeId = `ptrade_${crypto.randomUUID().substring(0, 12)}`;
const feeRate = 0; // Paper trades have no fees
const feeUsd = 0;
await query(`
INSERT INTO trades (trade_id, order_id, match_time, price, size, size_usd, fee_rate_bps, fee_usd, pnl, paper_trade)
VALUES ($1, $2, NOW(), $3, $4, $5, $6, $7, 0, TRUE)
`, [tradeId, orderId, price, size_usd / price, size_usd, feeRate, feeUsd]);
// Update order status
await query("UPDATE orders SET status = 'filled', filled_ts = NOW() WHERE order_id = $1", [orderId]);
// Log bankroll change
const lastBal = await query('SELECT balance_after FROM bankroll_ledger ORDER BY ts DESC LIMIT 1');
const currentBal = parseFloat(lastBal.rows[0]?.balance_after || 50);
const newBal = currentBal - size_usd; // Debit for position entry
await query(`
INSERT INTO bankroll_ledger (event_type, amount, balance_after, trade_id, note)
VALUES ('trade_pnl', $1, $2, $3, $4)
`, [-size_usd, newBal, tradeId, `Paper ${side} ${outcome} @ ${price}`]);
// Update risk state
await query('UPDATE risk_state SET open_exposure = open_exposure + $1, updated_at = NOW() WHERE id = (SELECT id FROM risk_state ORDER BY id DESC LIMIT 1)', [size_usd]);
return NextResponse.json({
order_id: orderId,
trade_id: tradeId,
status: 'filled',
paper: true,
size_usd,
price,
});
} catch (err) {
return NextResponse.json({ error: err.message }, { status: 500 });
}
}
if (body.action === 'settle') {
// Settle a paper trade (mark outcome)
try {
const { trade_id, outcome_yes } = body;
const tradeRes = await query('SELECT t.*, o.outcome, o.price as order_price, o.size_usd FROM trades t JOIN orders o ON t.order_id = o.order_id WHERE t.trade_id = $1', [trade_id]);
if (!tradeRes.rows[0]) return NextResponse.json({ error: 'Trade not found' }, { status: 404 });
const trade = tradeRes.rows[0];
const won = (trade.outcome === 'YES' && outcome_yes) || (trade.outcome === 'NO' && !outcome_yes);
const pnl = won ? parseFloat(trade.size_usd) * (1 / parseFloat(trade.order_price) - 1) : -parseFloat(trade.size_usd);
await query('UPDATE trades SET pnl = $1 WHERE trade_id = $2', [pnl, trade_id]);
// Update bankroll
const lastBal = await query('SELECT balance_after FROM bankroll_ledger ORDER BY ts DESC LIMIT 1');
const currentBal = parseFloat(lastBal.rows[0]?.balance_after || 50);
const returnAmount = won ? parseFloat(trade.size_usd) + pnl : 0;
await query(`INSERT INTO bankroll_ledger (event_type, amount, balance_after, trade_id, note) VALUES ('trade_pnl', $1, $2, $3, $4)`,
[returnAmount, currentBal + returnAmount, trade_id, `Settlement: ${won ? 'WIN' : 'LOSS'} ${pnl >= 0 ? '+' : ''}$${pnl.toFixed(4)}`]);
return NextResponse.json({ settled: true, trade_id, won, pnl });
} catch (err) {
return NextResponse.json({ error: err.message }, { status: 500 });
}
}
return NextResponse.json({ error: 'Unknown action' }, { status: 400 });
}