Files
AITrader/app/agents/trader.ts
T

113 lines
3.5 KiB
TypeScript

import { OpenRouterClient } from "../lib/openrouter";
import type { AnalystReport, DebateRound, TradingDecision } from "../types/agents";
type ChatResponse = {
choices?: Array<{ message?: { content?: string } }>;
};
export class Trader {
private client: OpenRouterClient;
private model: string;
constructor(client: OpenRouterClient, model?: string) {
this.client = client;
this.model = model ?? "openai/gpt-oss-120b:free";
}
async decide(
ticker: string,
reports: AnalystReport[],
debates: DebateRound[]
): Promise<TradingDecision> {
const signalSummaries = reports
.map((r) => `${r.analyst}: ${r.signal.signal} (confidence: ${r.signal.confidence}) - ${r.report}`)
.join("\n");
const debateSummaries = debates
.map((d) => `Bullish: ${d.bullishView}\nBearish: ${d.bearishView}`)
.join("\n");
const allSignals = reports.map((r) => r.signal);
const prompt = `Analyze all signals and debate rounds for ${ticker}:
Signals:
${signalSummaries}
Debate Rounds:
${debateSummaries}
Based on all the information above, make a trading decision. Respond with JSON containing these fields:
- action: "buy", "sell", or "hold"
- confidence: a number between 0 and 1
- reasoning: brief explanation
If the action is "sell", also include an "executionPlan" object with:
- amount: number (shares to sell)
- riskManagement: object (e.g., { maxLossPercent: 2 })
- takeProfit: number (target take-profit price)
Format your response as JSON with these fields.`;
const response = await this.client.createChatCompletion(
[
{ role: "system", content: "You are a trading agent that makes buy/sell/hold decisions and provides execution guidance when selling." },
{ role: "user", content: prompt },
],
this.model
);
const content = ((response as ChatResponse).choices?.[0]?.message?.content) ?? "";
let action: 'buy' | 'sell' | 'hold' = 'hold';
let confidence = 0.5;
let reasoning = content;
let executionPlan: any | undefined;
const actionMatch = content.match(/"action"\s*:\s*"(buy|sell|hold)"/);
if (actionMatch) {
action = actionMatch[1] as 'buy' | 'sell' | 'hold';
}
const confidenceMatch = content.match(/"confidence"\s*:\s*([0-9.]+)/);
if (confidenceMatch) {
confidence = parseFloat(confidenceMatch[1]);
}
const reasoningMatch = content.match(/"reasoning"\s*:\s*"([^"]+)"/);
if (reasoningMatch) {
reasoning = reasoningMatch[1];
}
// Try to parse executionPlan if provided in JSON
const execMatch = content.match(/"executionPlan"\s*:\s*(\{[\s\S]*\})/);
if (execMatch) {
try {
executionPlan = JSON.parse(execMatch[1]);
} catch (err) {
// fallback: try to extract primitive fields
const amountMatch = content.match(/"amount"\s*:\s*([0-9.]+)/);
const takeProfitMatch = content.match(/"takeProfit"\s*:\s*([0-9.]+)/);
const riskMatch = content.match(/"riskManagement"\s*:\s*"([^"]+)"/);
executionPlan = {};
if (amountMatch) executionPlan.amount = parseFloat(amountMatch[1]);
if (takeProfitMatch) executionPlan.takeProfit = parseFloat(takeProfitMatch[1]);
if (riskMatch) executionPlan.riskManagement = { note: riskMatch[1] };
}
}
const decision: TradingDecision = {
action,
confidence,
reasoning,
agentSignals: allSignals,
debateRounds: debates,
};
if (action === 'sell' && executionPlan) {
decision.executionPlan = executionPlan;
}
return decision;
}
}