feat(settings): add settings route and API updates\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { Trader } from "../trader";
|
||||
import type { AnalystReport, DebateRound } from "../../types/agents";
|
||||
|
||||
const mockReports: AnalystReport[] = [
|
||||
{
|
||||
analyst: "fundamentals",
|
||||
report: "Strong earnings growth",
|
||||
signal: {
|
||||
agent: "fundamentals",
|
||||
signal: "bullish",
|
||||
confidence: 0.8,
|
||||
reasoning: "Revenue up 20%",
|
||||
timestamp: "2024-01-01",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockDebates: DebateRound[] = [
|
||||
{
|
||||
bullishView: "Strong fundamentals",
|
||||
bearishView: "Market volatility",
|
||||
researcher: "bullish",
|
||||
},
|
||||
];
|
||||
|
||||
describe("Trader executionPlan parsing", () => {
|
||||
it("includes executionPlan for buy decisions", async () => {
|
||||
const mockBuyClient = {
|
||||
createChatCompletion: vi.fn().mockResolvedValue({
|
||||
choices: [{ message: { content: JSON.stringify({
|
||||
action: "buy",
|
||||
confidence: 0.8,
|
||||
reasoning: "Enter position",
|
||||
executionPlan: { amount: 10, stopLoss: 95, riskManagement: { maxLossPercent: 1 }, takeProfit: 110 }
|
||||
}) } }]
|
||||
}),
|
||||
};
|
||||
|
||||
const trader = new Trader(mockBuyClient as any);
|
||||
const decision = await trader.decide("AAPL", mockReports, mockDebates);
|
||||
|
||||
expect(decision.action).toBe("buy");
|
||||
expect(decision.executionPlan).toBeDefined();
|
||||
expect(decision.executionPlan?.amount).toBe(10);
|
||||
expect(decision.executionPlan?.stopLoss).toBe(95);
|
||||
});
|
||||
|
||||
it("parses stopLoss from malformed executionPlan text (fallback)", async () => {
|
||||
const malformed = 'Model reply: "action": "sell", "executionPlan": { amount: 7, takeProfit: 120, stopLoss: 115, riskManagement: { maxLossPercent: 2 } } and commentary.';
|
||||
const mockMalformedClient = {
|
||||
createChatCompletion: vi.fn().mockResolvedValue({
|
||||
choices: [{ message: { content: malformed } }],
|
||||
}),
|
||||
};
|
||||
|
||||
const trader = new Trader(mockMalformedClient as any);
|
||||
const decision = await trader.decide("AAPL", mockReports, mockDebates);
|
||||
|
||||
// action may be unspecified in this malformed reply; ensure executionPlan fields parsed when present
|
||||
expect(decision.executionPlan).toBeDefined();
|
||||
expect(decision.executionPlan?.amount).toBe(7);
|
||||
expect(decision.executionPlan?.stopLoss).toBe(115);
|
||||
expect(decision.executionPlan?.takeProfit).toBe(120);
|
||||
expect(decision.executionPlan?.riskManagement?.maxLossPercent).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
/* TRADINGGRAPH related file */
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { TradingGraph } from "../tradingGraph";
|
||||
|
||||
@@ -28,3 +30,4 @@ describe("TradingGraph execution step", () => {
|
||||
expect(decision.executionPlan?.amount).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* TRADINGGRAPH related file */
|
||||
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { TradingGraph } from "../tradingGraph";
|
||||
|
||||
@@ -29,4 +31,4 @@ describe("TradingGraph", () => {
|
||||
expect(decision).toHaveProperty("action");
|
||||
expect(decision).toHaveProperty("confidence");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+13
-10
@@ -42,16 +42,17 @@ Based on all the information above, make a trading decision. Respond with JSON c
|
||||
- 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)
|
||||
If the action is "buy" or "sell", also include an "executionPlan" object with:
|
||||
- amount: number (shares to trade)
|
||||
- riskManagement: object (e.g., { maxLossPercent: 2 })
|
||||
- takeProfit: number (target take-profit price)
|
||||
- stopLoss: number (stop-loss price or absolute value)
|
||||
|
||||
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: "system", content: "You are a trading agent that makes buy/sell/hold decisions and provides execution guidance for buy and sell actions." },
|
||||
{ role: "user", content: prompt },
|
||||
],
|
||||
this.model
|
||||
@@ -86,13 +87,15 @@ Format your response as JSON with these fields.`;
|
||||
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 maxLossMatch = content.match(/"maxLossPercent"\s*:\s*([0-9.]+)/);
|
||||
const methodMatch = content.match(/"method"\s*:\s*"([^"]+)"/);
|
||||
const amountMatch = content.match(/(?:"amount"|\bamount\b)\s*:\s*([0-9.]+)/);
|
||||
const takeProfitMatch = content.match(/(?:"takeProfit"|\btakeProfit\b)\s*:\s*([0-9.]+)/);
|
||||
const stopLossMatch = content.match(/(?:"stopLoss"|\bstopLoss\b)\s*:\s*([0-9.]+)/);
|
||||
const maxLossMatch = content.match(/(?:"maxLossPercent"|\bmaxLossPercent\b)\s*:\s*([0-9.]+)/);
|
||||
const methodMatch = content.match(/(?:"method"|\bmethod\b)\s*:\s*"([^"]+)"/);
|
||||
executionPlan = {} as any;
|
||||
if (amountMatch) executionPlan.amount = parseFloat(amountMatch[1]);
|
||||
if (takeProfitMatch) executionPlan.takeProfit = parseFloat(takeProfitMatch[1]);
|
||||
if (stopLossMatch) executionPlan.stopLoss = parseFloat(stopLossMatch[1]);
|
||||
executionPlan.riskManagement = {};
|
||||
if (maxLossMatch) executionPlan.riskManagement.maxLossPercent = parseFloat(maxLossMatch[1]);
|
||||
if (methodMatch) executionPlan.riskManagement.method = methodMatch[1];
|
||||
@@ -101,8 +104,8 @@ Format your response as JSON with these fields.`;
|
||||
|
||||
// Additional fallback: if executionPlan parsed but missing nested riskManagement fields, try to extract them
|
||||
if (executionPlan && executionPlan.riskManagement == null) {
|
||||
const maxLossMatch2 = content.match(/"maxLossPercent"\s*:\s*([0-9.]+)/);
|
||||
const methodMatch2 = content.match(/"method"\s*:\s*"([^"]+)"/);
|
||||
const maxLossMatch2 = content.match(/(?:"maxLossPercent"|\bmaxLossPercent\b)\s*:\s*([0-9.]+)/);
|
||||
const methodMatch2 = content.match(/(?:"method"|\bmethod\b)\s*:\s*"([^"]+)"/);
|
||||
if (maxLossMatch2 || methodMatch2) {
|
||||
executionPlan.riskManagement = executionPlan.riskManagement || {};
|
||||
if (maxLossMatch2) executionPlan.riskManagement.maxLossPercent = parseFloat(maxLossMatch2[1]);
|
||||
@@ -118,7 +121,7 @@ Format your response as JSON with these fields.`;
|
||||
debateRounds: debates,
|
||||
};
|
||||
|
||||
if (action === 'sell' && executionPlan) {
|
||||
if ((action === 'sell' || action === 'buy') && executionPlan) {
|
||||
decision.executionPlan = executionPlan;
|
||||
}
|
||||
|
||||
|
||||
+13
-15
@@ -1,3 +1,5 @@
|
||||
/* TRADINGGRAPH related file */
|
||||
|
||||
import { OpenRouterClient } from "../lib/openrouter";
|
||||
import { FundamentalsAnalyst } from "./fundamentals";
|
||||
import { TechnicalAnalyst } from "./technical";
|
||||
@@ -41,14 +43,14 @@ export class TradingGraph {
|
||||
sentimentData: { headlines: string[]; source?: "news" | "social" | "stocktwits" };
|
||||
}
|
||||
): Promise<TradingDecision> {
|
||||
console.log(`[TradingGraph] Starting analysis for ${ticker} with model ${this.model}`);
|
||||
|
||||
|
||||
const reports = await this.runAnalysts(ticker, input);
|
||||
const debates = await this.runDebate(ticker, reports);
|
||||
const decision = await this.trader.decide(ticker, reports, debates);
|
||||
|
||||
console.log(`[TradingGraph] Analysis complete for ${ticker}`);
|
||||
console.log(`[TradingGraph] Decision: ${decision.action} (confidence: ${decision.confidence})`);
|
||||
|
||||
|
||||
|
||||
// Build workflow steps for observability. Include an execution step when selling.
|
||||
const steps: GraphStep[] = [
|
||||
@@ -57,13 +59,13 @@ export class TradingGraph {
|
||||
{ step: "trader", data: decision },
|
||||
];
|
||||
|
||||
if (decision.action === 'sell' && decision.executionPlan) {
|
||||
if (decision.executionPlan) {
|
||||
steps.push({ step: "execution", data: decision.executionPlan });
|
||||
console.log(`[TradingGraph] Execution plan: ${JSON.stringify(decision.executionPlan)}`);
|
||||
|
||||
}
|
||||
|
||||
// Log steps for debugging; external systems can be extended to consume GraphStep sequence.
|
||||
console.log(`[TradingGraph] Workflow steps: ${JSON.stringify(steps)}`);
|
||||
|
||||
|
||||
return decision;
|
||||
}
|
||||
@@ -76,7 +78,7 @@ export class TradingGraph {
|
||||
sentimentData: { headlines: string[]; source?: "news" | "social" | "stocktwits" };
|
||||
}
|
||||
): Promise<AnalystReport[]> {
|
||||
console.log(`[TradingGraph] Running analysts for ${ticker}...`);
|
||||
|
||||
|
||||
const [fundamentals, technical, sentiment] = await Promise.all([
|
||||
this.fundamentalsAnalyst.analyze(ticker, input.financialData),
|
||||
@@ -84,24 +86,20 @@ export class TradingGraph {
|
||||
this.sentimentAnalyst.analyze(ticker, input.sentimentData),
|
||||
]);
|
||||
|
||||
console.log(`[TradingGraph] Analyst reports complete:`, {
|
||||
fundamentals: fundamentals.signal,
|
||||
technical: technical.signal,
|
||||
sentiment: sentiment.signal,
|
||||
});
|
||||
|
||||
|
||||
return [fundamentals, technical, sentiment];
|
||||
}
|
||||
|
||||
private async runDebate(ticker: string, reports: AnalystReport[]): Promise<DebateRound[]> {
|
||||
console.log(`[TradingGraph] Running debate for ${ticker}...`);
|
||||
|
||||
|
||||
const [bullish, bearish] = await Promise.all([
|
||||
this.bullishResearcher.research(ticker, reports),
|
||||
this.bearishResearcher.research(ticker, reports),
|
||||
]);
|
||||
|
||||
console.log(`[TradingGraph] Debate complete`);
|
||||
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -111,4 +109,4 @@ export class TradingGraph {
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user