redesign fully scaffolded and web login works
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Strategy Validation Workflow Configuration
|
||||
|
||||
name: strategy-validation
|
||||
description: Validates trading strategies with code review, backtest, and risk assessment
|
||||
|
||||
# Workflow settings
|
||||
timeout: 300000 # 5 minutes
|
||||
maxRetries: 3
|
||||
requiresApproval: true
|
||||
approvalNodes:
|
||||
- human_approval
|
||||
|
||||
# Validation loop settings
|
||||
maxValidationRetries: 3 # Max times to retry fixing errors
|
||||
minBacktestScore: 0.5 # Minimum Sharpe ratio to pass
|
||||
|
||||
# Model override (optional)
|
||||
model: claude-3-5-sonnet-20241022
|
||||
temperature: 0.3
|
||||
138
gateway/src/harness/workflows/strategy-validation/graph.ts
Normal file
138
gateway/src/harness/workflows/strategy-validation/graph.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { StateGraph } from '@langchain/langgraph';
|
||||
import { BaseWorkflow, type WorkflowConfig } from '../base-workflow.js';
|
||||
import { StrategyValidationState, type StrategyValidationStateType } from './state.js';
|
||||
import {
|
||||
createCodeReviewNode,
|
||||
createFixCodeNode,
|
||||
createBacktestNode,
|
||||
createRiskAssessmentNode,
|
||||
createHumanApprovalNode,
|
||||
createRecommendationNode,
|
||||
} from './nodes.js';
|
||||
import type { FastifyBaseLogger } from 'fastify';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { CodeReviewerSubagent } from '../../subagents/code-reviewer/index.js';
|
||||
|
||||
/**
|
||||
* Strategy Validation Workflow
|
||||
*
|
||||
* Multi-step workflow with validation loop:
|
||||
* 1. Code Review (using CodeReviewerSubagent)
|
||||
* 2. If issues found → Fix Code → Loop back to Code Review
|
||||
* 3. Backtest (using user's MCP server)
|
||||
* 4. If backtest fails → Fix Code → Loop back to Code Review
|
||||
* 5. Risk Assessment
|
||||
* 6. Human Approval (pause for user input)
|
||||
* 7. Final Recommendation
|
||||
*
|
||||
* Features:
|
||||
* - Validation loop with max retries
|
||||
* - Human-in-the-loop approval gate
|
||||
* - Multi-file memory from CodeReviewerSubagent
|
||||
* - Comprehensive state tracking
|
||||
*/
|
||||
export class StrategyValidationWorkflow extends BaseWorkflow<StrategyValidationStateType> {
|
||||
constructor(
|
||||
config: WorkflowConfig,
|
||||
private model: BaseChatModel,
|
||||
private codeReviewer: CodeReviewerSubagent,
|
||||
private mcpBacktestFn: (code: string, ticker: string, timeframe: string) => Promise<Record<string, unknown>>,
|
||||
logger: FastifyBaseLogger
|
||||
) {
|
||||
super(config, logger);
|
||||
}
|
||||
|
||||
buildGraph(): any {
|
||||
const graph = new StateGraph(StrategyValidationState);
|
||||
|
||||
// Create nodes
|
||||
const codeReviewNode = createCodeReviewNode(this.codeReviewer, this.logger);
|
||||
const fixCodeNode = createFixCodeNode(this.model, this.logger);
|
||||
const backtestNode = createBacktestNode(this.mcpBacktestFn, this.logger);
|
||||
const riskAssessmentNode = createRiskAssessmentNode(this.model, this.logger);
|
||||
const humanApprovalNode = createHumanApprovalNode(this.logger);
|
||||
const recommendationNode = createRecommendationNode(this.model, this.logger);
|
||||
|
||||
// Add nodes to graph
|
||||
graph
|
||||
.addNode('code_review', codeReviewNode)
|
||||
.addNode('fix_code', fixCodeNode)
|
||||
.addNode('backtest', backtestNode)
|
||||
.addNode('risk_assessment', riskAssessmentNode)
|
||||
.addNode('human_approval', humanApprovalNode)
|
||||
.addNode('recommendation', recommendationNode);
|
||||
|
||||
// Define edges
|
||||
(graph as any).addEdge('__start__', 'code_review');
|
||||
|
||||
// Conditional: After code review, fix if needed or proceed to backtest
|
||||
(graph as any).addConditionalEdges('code_review', (state: any) => {
|
||||
if (state.needsFixing && state.validationRetryCount < 3) {
|
||||
return 'fix_code';
|
||||
}
|
||||
if (state.needsFixing && state.validationRetryCount >= 3) {
|
||||
return 'recommendation'; // Give up, generate rejection
|
||||
}
|
||||
return 'backtest';
|
||||
});
|
||||
|
||||
// After fixing code, loop back to code review
|
||||
(graph as any).addEdge('fix_code', 'code_review');
|
||||
|
||||
// Conditional: After backtest, fix if failed or proceed to risk assessment
|
||||
(graph as any).addConditionalEdges('backtest', (state: any) => {
|
||||
if (!state.backtestPassed && state.validationRetryCount < 3) {
|
||||
return 'fix_code';
|
||||
}
|
||||
if (!state.backtestPassed && state.validationRetryCount >= 3) {
|
||||
return 'recommendation'; // Give up
|
||||
}
|
||||
return 'risk_assessment';
|
||||
});
|
||||
|
||||
// After risk assessment, go to human approval
|
||||
(graph as any).addEdge('risk_assessment', 'human_approval');
|
||||
|
||||
// Conditional: After human approval, proceed to recommendation or reject
|
||||
(graph as any).addConditionalEdges('human_approval', (state: any) => {
|
||||
return state.humanApproved ? 'recommendation' : '__end__';
|
||||
});
|
||||
|
||||
// Final recommendation is terminal
|
||||
(graph as any).addEdge('recommendation', '__end__');
|
||||
|
||||
return graph;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to create and compile workflow
|
||||
*/
|
||||
export async function createStrategyValidationWorkflow(
|
||||
model: BaseChatModel,
|
||||
codeReviewer: CodeReviewerSubagent,
|
||||
mcpBacktestFn: (code: string, ticker: string, timeframe: string) => Promise<Record<string, unknown>>,
|
||||
logger: FastifyBaseLogger,
|
||||
configPath: string
|
||||
): Promise<StrategyValidationWorkflow> {
|
||||
const { readFile } = await import('fs/promises');
|
||||
const yaml = await import('js-yaml');
|
||||
|
||||
// Load config
|
||||
const configContent = await readFile(configPath, 'utf-8');
|
||||
const config = yaml.load(configContent) as WorkflowConfig;
|
||||
|
||||
// Create workflow
|
||||
const workflow = new StrategyValidationWorkflow(
|
||||
config,
|
||||
model,
|
||||
codeReviewer,
|
||||
mcpBacktestFn,
|
||||
logger
|
||||
);
|
||||
|
||||
// Compile graph
|
||||
workflow.compile();
|
||||
|
||||
return workflow;
|
||||
}
|
||||
233
gateway/src/harness/workflows/strategy-validation/nodes.ts
Normal file
233
gateway/src/harness/workflows/strategy-validation/nodes.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import type { StrategyValidationStateType } from './state.js';
|
||||
import type { FastifyBaseLogger } from 'fastify';
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import type { CodeReviewerSubagent } from '../../subagents/code-reviewer/index.js';
|
||||
import { HumanMessage, SystemMessage } from '@langchain/core/messages';
|
||||
|
||||
/**
|
||||
* Node: Code Review
|
||||
* Reviews strategy code using CodeReviewerSubagent
|
||||
*/
|
||||
export function createCodeReviewNode(
|
||||
codeReviewer: CodeReviewerSubagent,
|
||||
logger: FastifyBaseLogger
|
||||
) {
|
||||
return async (state: StrategyValidationStateType): Promise<Partial<StrategyValidationStateType>> => {
|
||||
logger.info('Strategy validation: Code review');
|
||||
|
||||
const review = await codeReviewer.execute(
|
||||
{ userContext: state.userContext },
|
||||
state.strategyCode
|
||||
);
|
||||
|
||||
// Simple issue detection (in production, parse structured output)
|
||||
const hasIssues = review.toLowerCase().includes('critical') ||
|
||||
review.toLowerCase().includes('reject');
|
||||
|
||||
return {
|
||||
codeReview: review,
|
||||
codeIssues: hasIssues ? ['Issues detected in code review'] : [],
|
||||
needsFixing: hasIssues,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Node: Fix Code Issues
|
||||
* Uses LLM to fix issues identified in code review
|
||||
*/
|
||||
export function createFixCodeNode(
|
||||
model: BaseChatModel,
|
||||
logger: FastifyBaseLogger
|
||||
) {
|
||||
return async (state: StrategyValidationStateType): Promise<Partial<StrategyValidationStateType>> => {
|
||||
logger.info('Strategy validation: Fixing code issues');
|
||||
|
||||
const systemPrompt = `You are a trading strategy developer.
|
||||
Fix the issues identified in the code review while maintaining the strategy's logic.
|
||||
Return only the corrected code without explanation.`;
|
||||
|
||||
const userPrompt = `Original code:
|
||||
\`\`\`typescript
|
||||
${state.strategyCode}
|
||||
\`\`\`
|
||||
|
||||
Code review feedback:
|
||||
${state.codeReview}
|
||||
|
||||
Provide the corrected code:`;
|
||||
|
||||
const response = await model.invoke([
|
||||
new SystemMessage(systemPrompt),
|
||||
new HumanMessage(userPrompt),
|
||||
]);
|
||||
|
||||
const fixedCode = (response.content as string)
|
||||
.replace(/```typescript\n?/g, '')
|
||||
.replace(/```\n?/g, '')
|
||||
.trim();
|
||||
|
||||
return {
|
||||
strategyCode: fixedCode,
|
||||
validationRetryCount: state.validationRetryCount + 1,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Node: Backtest Strategy
|
||||
* Runs backtest using user's MCP server
|
||||
*/
|
||||
export function createBacktestNode(
|
||||
mcpBacktestFn: (code: string, ticker: string, timeframe: string) => Promise<Record<string, unknown>>,
|
||||
logger: FastifyBaseLogger
|
||||
) {
|
||||
return async (state: StrategyValidationStateType): Promise<Partial<StrategyValidationStateType>> => {
|
||||
logger.info('Strategy validation: Running backtest');
|
||||
|
||||
try {
|
||||
const results = await mcpBacktestFn(
|
||||
state.strategyCode,
|
||||
state.ticker,
|
||||
state.timeframe
|
||||
);
|
||||
|
||||
// Check if backtest passed (simplified)
|
||||
const sharpeRatio = (results.sharpeRatio as number) || 0;
|
||||
const passed = sharpeRatio > 0.5;
|
||||
|
||||
return {
|
||||
backtestResults: results,
|
||||
backtestPassed: passed,
|
||||
needsFixing: !passed,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error({ error }, 'Backtest failed');
|
||||
return {
|
||||
backtestResults: { error: (error as Error).message },
|
||||
backtestPassed: false,
|
||||
needsFixing: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Node: Risk Assessment
|
||||
* Analyzes backtest results for risk
|
||||
*/
|
||||
export function createRiskAssessmentNode(
|
||||
model: BaseChatModel,
|
||||
logger: FastifyBaseLogger
|
||||
) {
|
||||
return async (state: StrategyValidationStateType): Promise<Partial<StrategyValidationStateType>> => {
|
||||
logger.info('Strategy validation: Risk assessment');
|
||||
|
||||
const systemPrompt = `You are a risk management expert.
|
||||
Analyze the strategy and backtest results to assess risk level.
|
||||
Provide: risk level (low/medium/high) and detailed assessment.`;
|
||||
|
||||
const userPrompt = `Strategy code:
|
||||
\`\`\`typescript
|
||||
${state.strategyCode}
|
||||
\`\`\`
|
||||
|
||||
Backtest results:
|
||||
${JSON.stringify(state.backtestResults, null, 2)}
|
||||
|
||||
Provide risk assessment in format:
|
||||
RISK_LEVEL: [low/medium/high]
|
||||
ASSESSMENT: [detailed explanation]`;
|
||||
|
||||
const response = await model.invoke([
|
||||
new SystemMessage(systemPrompt),
|
||||
new HumanMessage(userPrompt),
|
||||
]);
|
||||
|
||||
const assessment = response.content as string;
|
||||
|
||||
// Parse risk level (simplified)
|
||||
let riskLevel: 'low' | 'medium' | 'high' = 'medium';
|
||||
if (assessment.includes('RISK_LEVEL: low')) riskLevel = 'low';
|
||||
if (assessment.includes('RISK_LEVEL: high')) riskLevel = 'high';
|
||||
|
||||
return {
|
||||
riskAssessment: assessment,
|
||||
riskLevel,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Node: Human Approval
|
||||
* Pauses workflow for human review
|
||||
*/
|
||||
export function createHumanApprovalNode(logger: FastifyBaseLogger) {
|
||||
return async (state: StrategyValidationStateType): Promise<Partial<StrategyValidationStateType>> => {
|
||||
logger.info('Strategy validation: Awaiting human approval');
|
||||
|
||||
// In real implementation, this would:
|
||||
// 1. Send approval request to user's channel
|
||||
// 2. Store workflow state with interrupt
|
||||
// 3. Wait for user response
|
||||
// 4. Resume with approval decision
|
||||
|
||||
// For now, auto-approve if risk is low/medium and backtest passed
|
||||
const autoApprove = state.backtestPassed &&
|
||||
(state.riskLevel === 'low' || state.riskLevel === 'medium');
|
||||
|
||||
return {
|
||||
humanApproved: autoApprove,
|
||||
approvalComment: autoApprove ? 'Auto-approved: passed validation' : 'Needs manual review',
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Node: Final Recommendation
|
||||
* Generates final recommendation based on all steps
|
||||
*/
|
||||
export function createRecommendationNode(
|
||||
model: BaseChatModel,
|
||||
logger: FastifyBaseLogger
|
||||
) {
|
||||
return async (state: StrategyValidationStateType): Promise<Partial<StrategyValidationStateType>> => {
|
||||
logger.info('Strategy validation: Generating recommendation');
|
||||
|
||||
const systemPrompt = `You are the final decision maker for strategy deployment.
|
||||
Based on all validation steps, provide a clear recommendation: approve, reject, or revise.`;
|
||||
|
||||
const userPrompt = `Strategy validation summary:
|
||||
|
||||
Code Review: ${state.codeIssues.length === 0 ? 'Passed' : 'Issues found'}
|
||||
Backtest: ${state.backtestPassed ? 'Passed' : 'Failed'}
|
||||
Risk Level: ${state.riskLevel}
|
||||
Human Approved: ${state.humanApproved}
|
||||
|
||||
Backtest Results:
|
||||
${JSON.stringify(state.backtestResults, null, 2)}
|
||||
|
||||
Risk Assessment:
|
||||
${state.riskAssessment}
|
||||
|
||||
Provide final recommendation (approve/reject/revise) and reasoning:`;
|
||||
|
||||
const response = await model.invoke([
|
||||
new SystemMessage(systemPrompt),
|
||||
new HumanMessage(userPrompt),
|
||||
]);
|
||||
|
||||
const recommendation = response.content as string;
|
||||
|
||||
// Parse recommendation (simplified)
|
||||
let decision: 'approve' | 'reject' | 'revise' = 'revise';
|
||||
if (recommendation.toLowerCase().includes('approve')) decision = 'approve';
|
||||
if (recommendation.toLowerCase().includes('reject')) decision = 'reject';
|
||||
|
||||
return {
|
||||
recommendation: decision,
|
||||
recommendationReason: recommendation,
|
||||
output: recommendation,
|
||||
};
|
||||
};
|
||||
}
|
||||
78
gateway/src/harness/workflows/strategy-validation/state.ts
Normal file
78
gateway/src/harness/workflows/strategy-validation/state.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Annotation } from '@langchain/langgraph';
|
||||
import { BaseWorkflowState } from '../base-workflow.js';
|
||||
|
||||
/**
|
||||
* Strategy validation workflow state
|
||||
*
|
||||
* Extends base workflow state with strategy-specific fields
|
||||
*/
|
||||
export const StrategyValidationState = Annotation.Root({
|
||||
...BaseWorkflowState.spec,
|
||||
|
||||
// Input
|
||||
strategyCode: Annotation<string>(),
|
||||
ticker: Annotation<string>(),
|
||||
timeframe: Annotation<string>(),
|
||||
|
||||
// Code review step
|
||||
codeReview: Annotation<string | null>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => null,
|
||||
}),
|
||||
codeIssues: Annotation<string[]>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => [],
|
||||
}),
|
||||
|
||||
// Backtest step
|
||||
backtestResults: Annotation<Record<string, unknown> | null>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => null,
|
||||
}),
|
||||
backtestPassed: Annotation<boolean>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => false,
|
||||
}),
|
||||
|
||||
// Risk assessment step
|
||||
riskAssessment: Annotation<string | null>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => null,
|
||||
}),
|
||||
riskLevel: Annotation<'low' | 'medium' | 'high' | null>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => null,
|
||||
}),
|
||||
|
||||
// Human approval step
|
||||
humanApproved: Annotation<boolean>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => false,
|
||||
}),
|
||||
approvalComment: Annotation<string | null>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => null,
|
||||
}),
|
||||
|
||||
// Validation loop control
|
||||
validationRetryCount: Annotation<number>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => 0,
|
||||
}),
|
||||
needsFixing: Annotation<boolean>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => false,
|
||||
}),
|
||||
|
||||
// Final output
|
||||
recommendation: Annotation<'approve' | 'reject' | 'revise' | null>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => null,
|
||||
}),
|
||||
recommendationReason: Annotation<string | null>({
|
||||
value: (left, right) => right ?? left,
|
||||
default: () => null,
|
||||
}),
|
||||
});
|
||||
|
||||
export type StrategyValidationStateType = typeof StrategyValidationState.State;
|
||||
Reference in New Issue
Block a user