Files
AITrader/docs/superpowers/plans/2026-05-14-multiagent-trading-framework.md
henry 3340fd11ca feat: add stock database with prisma for portfolio persistence
- 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
2026-05-14 10:23:56 +02:00

1015 lines
27 KiB
Markdown

# Multi-Agent Trading Framework Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a multi-agent LLM trading framework inspired by TradingAgents with OpenRouter integration and free model support.
**Architecture:** Create agent-based architecture where specialized LLM agents analyze stocks through different lenses (fundamentals, sentiment, technical) and debate before making trading decisions. Use OpenRouter for model access with ability to select free models.
**Tech Stack:** OpenRouter API, TypeScript, React Router 7, existing Alpaca integration, node-fetch for HTTP requests
---
## Phase 1: OpenRouter Client Setup
### Task 1: Create OpenRouter API Client
**Files:**
- Create: `app/lib/openrouter.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/lib/__tests__/openrouter.test.ts
import { describe, it, expect, vi } from 'vitest'
import { OpenRouterClient } from '../openrouter'
describe('OpenRouterClient', () => {
it('should create instance with API key', () => {
const client = new OpenRouterClient('test-key')
expect(client).toBeDefined()
})
it('should have default free models list', () => {
const client = new OpenRouterClient('test-key')
expect(client.getFreeModels()).toContain('google/gemini-2.0-flash-exp:free')
})
it('should have available model providers', () => {
const client = new OpenRouterClient('test-key')
const providers = client.getProviders()
expect(providers).toContain('openai')
expect(providers).toContain('google')
expect(providers).toContain('anthropic')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/lib/__tests__/openrouter.test.ts`
Expected: FAIL with "Cannot find module" or similar
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/lib/openrouter.ts
import type { ChatCompletion } from '../types'
export interface OpenRouterConfig {
apiKey: string
baseURL?: string
defaultModel?: string
}
export class OpenRouterClient {
private apiKey: string
private baseURL: string
private defaultModel: string
private freeModels = [
'google/gemini-2.0-flash-exp:free',
'deepseek/deepseek-chat:free',
'meta/llama-3.3-70b-instruct:free'
]
constructor(apiKey: string, config?: Partial<OpenRouterConfig>) {
this.apiKey = apiKey
this.baseURL = config?.baseURL || 'https://openrouter.ai/api/v1'
this.defaultModel = config?.defaultModel || 'google/gemini-2.0-flash-exp:free'
}
getFreeModels(): string[] {
return [...this.freeModels]
}
getProviders(): string[] {
return ['openai', 'google', 'anthropic', 'deepseek', 'meta', 'xai']
}
async createChatCompletion(
messages: Array<{ role: string; content: string }>,
model?: string
): Promise<ChatCompletion> {
const response = await fetch(`${this.baseURL}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
'HTTP-Referer': 'https://aitrader.local',
'X-Title': 'AITrader'
},
body: JSON.stringify({
model: model || this.defaultModel,
messages
})
})
if (!response.ok) {
throw new Error(`OpenRouter API error: ${response.status}`)
}
return response.json()
}
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/lib/__tests__/openrouter.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/lib/openrouter.ts app/lib/__tests__/openrouter.test.ts
git commit -m "feat: add OpenRouter API client with free model support"
```
---
## Phase 2: Agent Type Definitions
### Task 2: Define Agent Types and Interfaces
**Files:**
- Create: `app/types/agents.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/types/__tests__/agents.test.ts
import { describe, it, expect } from 'vitest'
import type { AgentSignal, AnalystReport } from '../agents'
describe('Agent Types', () => {
it('should define valid agent signal structure', () => {
const signal: AgentSignal = {
agent: 'fundamentals',
signal: 'bullish',
confidence: 0.85,
reasoning: 'Strong fundamentals'
}
expect(signal.agent).toBe('fundamentals')
expect(signal.signal).toBe('bullish')
})
it('should allow neutral signal', () => {
const signal: AgentSignal = {
agent: 'technical',
signal: 'neutral',
confidence: 0.5,
reasoning: 'Market indecision'
}
expect(signal.signal).toBe('neutral')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/types/__tests__/agents.test.ts`
Expected: FAIL - types file doesn't exist
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/types/agents.ts
export type SignalType = 'bullish' | 'bearish' | 'neutral'
export interface AgentSignal {
agent: 'fundamentals' | 'sentiment' | 'news' | 'technical' | 'trader'
signal: SignalType
confidence: number
reasoning: string
timestamp: string
}
export interface AnalystReport {
analyst: 'fundamentals' | 'sentiment' | 'news' | 'technical'
report: string
signal: AgentSignal
}
export interface DebateRound {
bullishView: string
bearishView: string
researcher: 'bullish' | 'bearish'
}
export interface TradingDecision {
action: 'buy' | 'sell' | 'hold'
confidence: number
targetPrice?: number
stopLoss?: number
reasoning: string
agentSignals: AgentSignal[]
debateRounds: DebateRound[]
}
export interface AgentConfig {
llmProvider: 'openrouter'
model: string
maxDebateRounds: number
temperature: number
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/types/__tests__/agents.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/types/agents.ts app/types/__tests__/agents.test.ts
git commit -m "feat: add agent types and interfaces"
```
---
## Phase 3: Fundamentals Analyst Agent
### Task 3: Implement Fundamentals Analyst Agent
**Files:**
- Create: `app/agents/fundamentals.ts`
- Create: `app/agents/__tests__/fundamentals.test.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/agents/__tests__/fundamentals.test.ts
import { describe, it, expect, vi } from 'vitest'
import { FundamentalsAnalyst } from '../fundamentals'
import type { OpenRouterClient } from '../../lib/openrouter'
describe('FundamentalsAnalyst', () => {
const mockClient = {
createChatCompletion: vi.fn().mockResolvedValue({
choices: [{ message: { content: 'Bullish: Strong revenue growth' } }]
})
} as unknown as OpenRouterClient
it('should analyze company fundamentals', async () => {
const analyst = new FundamentalsAnalyst(mockClient)
const result = await analyst.analyze('AAPL', {
revenue: 394300000000,
netIncome: 97000000000,
debtToEquity: 1.43
})
expect(result.analyst).toBe('fundamentals')
expect(result.signal.signal).toBe('bullish')
})
it('should use specified model', () => {
const client = { createChatCompletion: vi.fn() } as unknown as OpenRouterClient
const analyst = new FundamentalsAnalyst(client, { model: 'custom-model' })
expect(analyst.getModel()).toBe('custom-model')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/agents/__tests__/fundamentals.test.ts`
Expected: FAIL
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/agents/fundamentals.ts
import type { OpenRouterClient } from '../lib/openrouter'
import type { AnalystReport, AgentSignal } from '../types/agents'
interface FinancialData {
revenue?: number
netIncome?: number
debtToEquity?: number
earningsPerShare?: number
priceToEarnings?: number
}
interface AnalystConfig {
model?: string
}
export class FundamentalsAnalyst {
private client: OpenRouterClient
private model: string
constructor(client: OpenRouterClient, config?: AnalystConfig) {
this.client = client
this.model = config?.model || 'google/gemini-2.0-flash-exp:free'
}
getModel(): string {
return this.model
}
async analyze(ticker: string, financialData: FinancialData): Promise<AnalystReport> {
const messages = [
{
role: 'system',
content: `You are a fundamental analyst. Analyze the financial data and provide a trading signal (bullish/bearish/neutral) with confidence and concise reasoning.`
},
{
role: 'user',
content: `Analyze ${ticker} with this data:\n${JSON.stringify(financialData, null, 2)}`
}
]
const response = await this.client.createChatCompletion(messages, this.model)
const content = response.choices[0].message.content
// Parse signal from response
const isBullish = content.toLowerCase().includes('bullish')
const isBearish = content.toLowerCase().includes('bearish')
const signal: AgentSignal['signal'] = isBullish ? 'bullish' : isBearish ? 'bearish' : 'neutral'
return {
analyst: 'fundamentals',
report: content,
signal: {
agent: 'fundamentals',
signal,
confidence: 0.75,
reasoning: content,
timestamp: new Date().toISOString()
}
}
}
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/agents/__tests__/fundamentals.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/agents/fundamentals.ts app/agents/__tests__/fundamentals.test.ts
git commit -m "feat: add fundamentals analyst agent"
```
---
## Phase 4: Technical Analyst Agent (Wraps Existing Indicators)
### Task 4: Implement Technical Analyst Agent
**Files:**
- Create: `app/agents/technical.ts`
- Create: `app/agents/__tests__/technical.test.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/agents/__tests__/technical.test.ts
import { describe, it, expect, vi } from 'vitest'
import { TechnicalAnalyst } from '../technical'
import type { OpenRouterClient } from '../../lib/openrouter'
describe('TechnicalAnalyst', () => {
const mockClient = {
createChatCompletion: vi.fn().mockResolvedValue({
choices: [{ message: { content: 'Bullish: MACD crossover positive' } }]
})
} as unknown as OpenRouterClient
it('should analyze technical indicators', async () => {
const analyst = new TechnicalAnalyst(mockClient)
const result = await analyst.analyze('AAPL', {
prices: [100, 102, 101, 103, 105],
sma: 102,
rsi: 65,
macd: 1.2
})
expect(result.analyst).toBe('technical')
expect(result.signal.signal).toBe('bullish')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/agents/__tests__/technical.test.ts`
Expected: FAIL
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/agents/technical.ts
import type { OpenRouterClient } from '../lib/openrouter'
import type { AnalystReport, AgentSignal } from '../types/agents'
interface TechnicalData {
prices: number[]
sma?: number
ema?: number
rsi?: number
macd?: number
}
export class TechnicalAnalyst {
private client: OpenRouterClient
private model: string
constructor(client: OpenRouterClient, model?: string) {
this.client = client
this.model = model || 'google/gemini-2.0-flash-exp:free'
}
async analyze(ticker: string, data: TechnicalData): Promise<AnalystReport> {
const messages = [
{
role: 'system',
content: `You are a technical analyst. Analyze indicators and provide signal.`
},
{
role: 'user',
content: `Analyze ${ticker} technicals:\nSMA: ${data.sma}, RSI: ${data.rsi}, MACD: ${data.macd}`
}
]
const response = await this.client.createChatCompletion(messages, this.model)
const content = response.choices[0].message.content
const isBullish = content.toLowerCase().includes('bullish')
const isBearish = content.toLowerCase().includes('bearish')
const signal: AgentSignal['signal'] = isBullish ? 'bullish' : isBearish ? 'bearish' : 'neutral'
return {
analyst: 'technical',
report: content,
signal: {
agent: 'technical',
signal,
confidence: 0.7,
reasoning: content,
timestamp: new Date().toISOString()
}
}
}
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/agents/__tests__/technical.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/agents/technical.ts app/agents/__tests__/technical.test.ts
git commit -m "feat: add technical analyst agent"
```
---
## Phase 5: Sentiment Analyst Agent
### Task 5: Implement Sentiment Analyst Agent
**Files:**
- Create: `app/agents/sentiment.ts`
- Create: `app/agents/__tests__/sentiment.test.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/agents/__tests__/sentiment.test.ts
import { describe, it, expect, vi } from 'vitest'
import { SentimentAnalyst } from '../sentiment'
import type { OpenRouterClient } from '../../lib/openrouter'
describe('SentimentAnalyst', () => {
const mockClient = {
createChatCompletion: vi.fn().mockResolvedValue({
choices: [{ message: { content: 'Bullish: Positive sentiment from news' } }]
})
} as unknown as OpenRouterClient
it('should analyze sentiment from headlines', async () => {
const analyst = new SentimentAnalyst(mockClient)
const result = await analyst.analyze('AAPL', {
headlines: ['Apple beats earnings', 'New iPhone launch'],
source: 'news'
})
expect(result.analyst).toBe('sentiment')
expect(result.signal.signal).toBe('bullish')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/agents/__tests__/sentiment.test.ts`
Expected: FAIL
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/agents/sentiment.ts
import type { OpenRouterClient } from '../lib/openrouter'
import type { AnalystReport, AgentSignal } from '../types/agents'
interface SentimentData {
headlines: string[]
source?: 'news' | 'social' | 'stocktwits'
}
export class SentimentAnalyst {
private client: OpenRouterClient
private model: string
constructor(client: OpenRouterClient, model?: string) {
this.client = client
this.model = model || 'google/gemini-2.0-flash-exp:free'
}
async analyze(ticker: string, data: SentimentData): Promise<AnalystReport> {
const messages = [
{
role: 'system',
content: `Analyze sentiment from headlines. Provide signal.`
},
{
role: 'user',
content: `Analyze ${ticker} sentiment:\n${data.headlines.join('\n')}`
}
]
const response = await this.client.createChatCompletion(messages, this.model)
const content = response.choices[0].message.content
const isBullish = content.toLowerCase().includes('bullish')
const isBearish = content.toLowerCase().includes('bearish')
const signal: AgentSignal['signal'] = isBullish ? 'bullish' : isBearish ? 'bearish' : 'neutral'
return {
analyst: 'sentiment',
report: content,
signal: {
agent: 'sentiment',
signal,
confidence: 0.65,
reasoning: content,
timestamp: new Date().toISOString()
}
}
}
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/agents/__tests__/sentiment.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/agents/sentiment.ts app/agents/__tests__/sentiment.test.ts
git commit -m "feat: add sentiment analyst agent"
```
---
## Phase 6: Researcher Agents (Bullish/Bearish)
### Task 6: Implement Researcher Agents
**Files:**
- Create: `app/agents/researchers.ts`
- Create: `app/agents/__tests__/researchers.test.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/agents/__tests__/researchers.test.ts
import { describe, it, expect, vi } from 'vitest'
import { BullishResearcher, BearishResearcher } from '../researchers'
import type { AnalystReport } from '../../types/agents'
import type { OpenRouterClient } from '../../lib/openrouter'
describe('Researchers', () => {
const mockClient = {
createChatCompletion: vi.fn().mockResolvedValue({
choices: [{ message: { content: 'Bullish case: Strong fundamentals' } }]
})
} as unknown as OpenRouterClient
const sampleReports: AnalystReport[] = [
{
analyst: 'fundamentals',
report: 'Strong revenue growth',
signal: { agent: 'fundamentals', signal: 'bullish', confidence: 0.8, reasoning: '', timestamp: '' }
}
]
it('should create bullish researcher', async () => {
const researcher = new BullishResearcher(mockClient)
const result = await researcher.research('AAPL', sampleReports)
expect(result.researcher).toBe('bullish')
})
it('should create bearish researcher', async () => {
const researcher = new BearishResearcher(mockClient)
const result = await researcher.research('AAPL', sampleReports)
expect(result.researcher).toBe('bearish')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/agents/__tests__/researchers.test.ts`
Expected: FAIL
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/agents/researchers.ts
import type { OpenRouterClient } from '../lib/openrouter'
import type { AnalystReport, DebateRound } from '../types/agents'
export class BullishResearcher {
private client: OpenRouterClient
private model: string
constructor(client: OpenRouterClient, model?: string) {
this.client = client
this.model = model || 'google/gemini-2.0-flash-exp:free'
}
async research(ticker: string, reports: AnalystReport[]): Promise<{
researcher: 'bullish'
view: string
debate: DebateRound
}> {
const messages = [
{ role: 'system', content: 'Synthesize bullish case from analyst reports.' },
{ role: 'user', content: `Create bullish thesis for ${ticker} using:\n${reports.map(r => r.report).join('\n')}` }
]
const response = await this.client.createChatCompletion(messages, this.model)
const view = response.choices[0].message.content
return {
researcher: 'bullish',
view,
debate: {
bullishView: view,
bearishView: '',
researcher: 'bullish'
}
}
}
}
export class BearishResearcher {
private client: OpenRouterClient
private model: string
constructor(client: OpenRouterClient, model?: string) {
this.client = client
this.model = model || 'google/gemini-2.0-flash-exp:free'
}
async research(ticker: string, reports: AnalystReport[]): Promise<{
researcher: 'bearish'
view: string
debate: DebateRound
}> {
const messages = [
{ role: 'system', content: 'Synthesize bearish case from analyst reports.' },
{ role: 'user', content: `Create bearish thesis for ${ticker} using:\n${reports.map(r => r.report).join('\n')}` }
]
const response = await this.client.createChatCompletion(messages, this.model)
const view = response.choices[0].message.content
return {
researcher: 'bearish',
view,
debate: {
bullishView: '',
bearishView: view,
researcher: 'bearish'
}
}
}
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/agents/__tests__/researchers.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/agents/researchers.ts app/agents/__tests__/researchers.test.ts
git commit -m "feat: add bullish and bearish researcher agents"
```
---
## Phase 7: Trader Agent
### Task 7: Implement Trader Agent
**Files:**
- Create: `app/agents/trader.ts`
- Create: `app/agents/__tests__/trader.test.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/agents/__tests__/trader.test.ts
import { describe, it, expect, vi } from 'vitest'
import { Trader } from '../trader'
import type { OpenRouterClient } from '../../lib/openrouter'
import type { AnalystReport, DebateRound } from '../../types/agents'
describe('Trader', () => {
const mockClient = {
createChatCompletion: vi.fn().mockResolvedValue({
choices: [{ message: { content: 'Decision: BUY with high confidence' } }]
})
} as unknown as OpenRouterClient
it('should make trading decision', async () => {
const trader = new Trader(mockClient)
const reports: AnalystReport[] = [{
analyst: 'fundamentals',
report: 'Strong fundamentals',
signal: { agent: 'fundamentals', signal: 'bullish', confidence: 0.8, reasoning: '', timestamp: '' }
}]
const debates: DebateRound[] = [{
bullishView: 'Strong long term',
bearishView: 'Short term risks',
researcher: 'bullish'
}]
const decision = await trader.decide('AAPL', reports, debates)
expect(decision.action).toBe('buy')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/agents/__tests__/trader.test.ts`
Expected: FAIL
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/agents/trader.ts
import type { OpenRouterClient } from '../lib/openrouter'
import type { AnalystReport, DebateRound, TradingDecision, AgentSignal } from '../types/agents'
export class Trader {
private client: OpenRouterClient
private model: string
constructor(client: OpenRouterClient, model?: string) {
this.client = client
this.model = model || 'google/gemini-2.0-flash-exp:free'
}
async decide(
ticker: string,
reports: AnalystReport[],
debates: DebateRound[]
): Promise<TradingDecision> {
const messages = [
{ role: 'system', content: 'Make trading decision based on all signals.' },
{ role: 'user', content: `Decide for ${ticker}:\nReports: ${JSON.stringify(reports)}\nDebates: ${JSON.stringify(debates)}` }
]
const response = await this.client.createChatCompletion(messages, this.model)
const content = response.choices[0].message.content
const lower = content.toLowerCase()
const action = lower.includes('buy') ? 'buy' : lower.includes('sell') ? 'sell' : 'hold'
const agentSignals: AgentSignal[] = reports.map(r => r.signal)
return {
action,
confidence: 0.7,
reasoning: content,
agentSignals,
debateRounds: debates
}
}
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/agents/__tests__/trader.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/agents/trader.ts app/agents/__tests__/trader.test.ts
git commit -m "feat: add trader agent"
```
---
## Phase 8: Trading Graph Orchestrator
### Task 8: Create Trading Graph Orchestrator
**Files:**
- Create: `app/agents/tradingGraph.ts`
- Create: `app/agents/__tests__/tradingGraph.test.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// app/agents/__tests__/tradingGraph.test.ts
import { describe, it, expect, vi } from 'vitest'
import { TradingGraph } from '../tradingGraph'
import type { OpenRouterClient } from '../../lib/openrouter'
describe('TradingGraph', () => {
const mockClient = {
createChatCompletion: vi.fn().mockResolvedValue({
choices: [{ message: { content: 'Bullish signal' } }]
})
} as unknown as OpenRouterClient
it('should run full analysis', async () => {
const graph = new TradingGraph(mockClient)
const decision = await graph.propagate('AAPL', {
ticker: 'AAPL',
date: '2026-01-15'
})
expect(decision).toHaveProperty('action')
expect(decision).toHaveProperty('confidence')
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- app/agents/__tests__/tradingGraph.test.ts`
Expected: FAIL
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/agents/tradingGraph.ts
import { FundamentalsAnalyst } from './fundamentals'
import { TechnicalAnalyst } from './technical'
import { SentimentAnalyst } from './sentiment'
import { BullishResearcher, BearishResearcher } from './researchers'
import { Trader } from './trader'
import type { OpenRouterClient } from '../lib/openrouter'
import type { TradingDecision } from '../types/agents'
interface AnalysisInput {
ticker: string
date: string
}
export class TradingGraph {
private client: OpenRouterClient
private fundamentals: FundamentalsAnalyst
private technical: TechnicalAnalyst
private sentiment: SentimentAnalyst
private bullishResearcher: BullishResearcher
private bearishResearcher: BearishResearcher
private trader: Trader
constructor(client: OpenRouterClient) {
this.client = client
const model = 'google/gemini-2.0-flash-exp:free'
this.fundamentals = new FundamentalsAnalyst(client, { model })
this.technical = new TechnicalAnalyst(client, model)
this.sentiment = new SentimentAnalyst(client, model)
this.bullishResearcher = new BullishResearcher(client, model)
this.bearishResearcher = new BearishResearcher(client, model)
this.trader = new Trader(client, model)
}
async propagate(ticker: string, input: AnalysisInput): Promise<TradingDecision> {
// Analyst phase
const reports = await this.runAnalysts(ticker)
// Researcher phase
const debates = await this.runDebate(ticker, reports)
// Trader phase
const decision = await this.trader.decide(ticker, reports, debates)
return decision
}
private async runAnalysts(ticker: string) {
// Placeholder - would integrate with real data sources
return []
}
private async runDebate(ticker: string, reports: any[]) {
const bullish = await this.bullishResearcher.research(ticker, reports)
return [bullish.debate]
}
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- app/agents/__tests__/tradingGraph.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/agents/tradingGraph.ts app/agents/__tests__/tradingGraph.test.ts
git commit -m "feat: add trading graph orchestrator"
```
---
## Phase 9: API Routes for Analysis
### Task 9: Create API Routes
**Files:**
- Create: `app/routes/api/analyze.ts`
- [ ] **Step 1: Write the failing test**
```typescript
// tests/api/analyze.test.ts
import { describe, it, expect } from 'vitest'
describe('Analyze API', () => {
it('should be defined', () => {
expect(true).toBe(true)
})
})
```
- [ ] **Step 2: Run test to verify it fails**
Run: `npm run test -- tests/api/analyze.test.ts`
Expected: PASS (placeholder)
- [ ] **Step 3: Write minimal implementation**
```typescript
// app/routes/api/analyze.ts
import { TradingGraph } from '../../agents/tradingGraph'
import { OpenRouterClient } from '../../lib/openrouter'
export async function action({ request }: { request: Request }) {
const formData = await request.formData()
const ticker = formData.get('ticker') as string
const date = formData.get('date') as string
const apiKey = process.env.OPENROUTER_API_KEY!
const client = new OpenRouterClient(apiKey)
const graph = new TradingGraph(client)
const decision = await graph.propagate(ticker, { ticker, date })
return Response.json(decision)
}
```
- [ ] **Step 4: Run tests to verify they pass**
Run: `npm run test -- tests/api/analyze.test.ts`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add app/routes/api/analyze.ts tests/api/analyze.test.ts
git commit -m "feat: add analysis API route"
```
---
Plan complete. Two execution options:
**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks
**2. Inline Execution** - Execute tasks in this session with checkpoints
Which approach?