feat: add autonomous paper-trading and calibration pipeline

This commit is contained in:
ImBenji
2026-08-03 14:03:27 +01:00
parent 5a9a2e4c6d
commit c4028cc394
46 changed files with 2246 additions and 115 deletions
+100
View File
@@ -0,0 +1,100 @@
const os = require('os');
const Database = require('better-sqlite3');
const { initAutonomySchema } = require('../src/autonomy/schema');
const { createOrderIntent } = require('../src/autonomy/orderIntents');
const { createAlpacaPaperClient } = require('../src/brokers/alpacaPaper');
function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
async function runExecutionWorker({ intelligencePath, pollMs = 10000, mode = 'shadow', notional = 100, workerId = `execution-${os.hostname()}-${process.pid}` } = {}) {
if (!['shadow', 'paper'].includes(mode)) throw new Error(`unsupported execution mode: ${mode}`);
const db = new Database(intelligencePath);
db.pragma('journal_mode = WAL');
initAutonomySchema(db);
const paperClient = mode === 'paper'
? createAlpacaPaperClient({ keyId: process.env.ALPACA_PAPER_KEY_ID, secretKey: process.env.ALPACA_PAPER_SECRET_KEY })
: null;
while (true) {
if (paperClient) {
try {
const [account, positions, orders] = await Promise.all([
paperClient.getAccount(), paperClient.getPositions(), paperClient.getOrders(),
]);
db.prepare(`
INSERT INTO autonomy_account_snapshots(broker, account_id, equity, cash, buying_power, payload)
VALUES ('alpaca-paper', ?, ?, ?, ?, ?)
`).run(account.id || null, Number(account.equity), Number(account.cash), Number(account.buying_power), JSON.stringify(account));
const insertPosition = db.prepare(`
INSERT INTO autonomy_position_snapshots(broker, instrument, quantity, market_value, unrealized_pl, payload)
VALUES ('alpaca-paper', ?, ?, ?, ?, ?)
`);
for (const position of positions || []) {
insertPosition.run(position.symbol, Number(position.qty), Number(position.market_value), Number(position.unrealized_pl), JSON.stringify(position));
}
for (const order of orders || []) {
if (!order.client_order_id) continue;
const mapped = { accepted: 'submitted', new: 'submitted', pending_new: 'submitted', partially_filled: 'partially_filled', filled: 'filled', canceled: 'cancelled', cancelled: 'cancelled', rejected: 'rejected' }[order.status];
if (!mapped) continue;
db.prepare(`
UPDATE autonomy_order_intents SET status=?, broker_order_id=?, updated_at=datetime('now')
WHERE client_order_id=?
`).run(mapped, order.id || null, order.client_order_id);
db.prepare(`
INSERT INTO autonomy_broker_events(broker, event_type, broker_id, payload)
VALUES ('alpaca-paper', ?, ?, ?)
`).run(order.status, order.id || null, JSON.stringify(order));
}
} catch (error) {
console.error(`[${workerId}] broker reconciliation:`, error.message);
}
}
const decisions = db.prepare(`
SELECT d.id FROM autonomy_decisions d
LEFT JOIN autonomy_order_intents oi ON oi.decision_id = d.id
WHERE oi.id IS NULL AND d.action IN ('BUY', 'SELL')
ORDER BY d.created_at ASC LIMIT 25
`).all();
for (const decision of decisions) {
try {
const intent = createOrderIntent(db, decision.id, notional, { tradable: true, maxNotional: notional });
if (mode === 'paper') db.prepare("UPDATE autonomy_order_intents SET status='pending', updated_at=datetime('now') WHERE client_order_id=?").run(intent.clientOrderId);
console.log(`[${workerId}] ${mode} intent ${intent.clientOrderId}`);
} catch (error) {
console.error(`[${workerId}] decision ${decision.id}:`, error.message);
}
}
if (paperClient) {
const pending = db.prepare("SELECT * FROM autonomy_order_intents WHERE status='pending' ORDER BY created_at ASC LIMIT 25").all();
for (const intent of pending) {
try {
let order;
try { order = await paperClient.getOrderByClientId(intent.client_order_id); } catch (error) {
if (error.status !== 404) throw error;
}
if (!order) {
order = await paperClient.submitOrder({
symbol: intent.instrument,
notional: String(intent.notional),
side: intent.side,
type: 'market',
time_in_force: 'day',
client_order_id: intent.client_order_id,
});
}
db.prepare(`
UPDATE autonomy_order_intents
SET status = ?, broker_order_id = ?, updated_at = datetime('now')
WHERE id = ?
`).run(order.status === 'filled' ? 'filled' : 'submitted', order.id || null, intent.id);
} catch (error) {
console.error(`[${workerId}] paper order ${intent.client_order_id}:`, error.message);
db.prepare("UPDATE autonomy_order_intents SET attempts=attempts+1, last_error=?, updated_at=datetime('now') WHERE id=?")
.run(String(error.message).slice(0, 1000), intent.id);
}
}
}
await sleep(pollMs);
}
}
module.exports = { runExecutionWorker };