3340fd11ca
- Initialize Prisma with SQLite and Stock model - Create database service layer with singleton client - Add API routes for stock CRUD operations - Integrate database with analyze page to persist ticker entries - Add Playwright tests for stock database functionality
84 lines
2.3 KiB
TypeScript
84 lines
2.3 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:
|
|
- action: "buy", "sell", or "hold"
|
|
- confidence: a number between 0 and 1
|
|
- reasoning: brief explanation
|
|
|
|
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 based on all available signals." },
|
|
{ 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;
|
|
|
|
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];
|
|
}
|
|
|
|
return {
|
|
action,
|
|
confidence,
|
|
reasoning,
|
|
agentSignals: allSignals,
|
|
debateRounds: debates,
|
|
};
|
|
}
|
|
} |