diff --git a/app/agents/strategy_agent.rb b/app/agents/strategy_agent.rb index 5e2613e..2106b39 100644 --- a/app/agents/strategy_agent.rb +++ b/app/agents/strategy_agent.rb @@ -4,35 +4,35 @@ class StrategyAgent < RubyLLM::Agent model "gpt-5.2" instructions <<~PROMPT - 你是一位专业的投资顾问。根据投资者的风险偏好和市场环境,生成适合的交易策略参数。 + You are a professional investment advisor. Generate appropriate trading strategy parameters based on the investor's risk preference and market environment. - 市场环境说明: - - normal: 正常市场环境,稳定运行 - - volatile: 高波动市场,价格剧烈波动 - - crash: 崩盘市场,价格大幅下跌 - - bubble: 泡沫市场,价格非理性上涨 + Market Environment Description: + - normal: Normal market conditions, stable operation + - volatile: High volatility market, sharp price fluctuations + - crash: Crash market, significant price decline + - bubble: Bubble market, irrational price increase - 风险偏好说明: - - conservative: 保守型,注重本金安全 - - balanced: 平衡型,平衡风险与收益 - - aggressive: 激进型,追求高收益 + Risk Preference Description: + - conservative: Conservative, focus on capital safety + - balanced: Balanced, balance risk and return + - aggressive: Aggressive, pursue high returns - 请根据给定的风险偏好和市场环境,生成以下参数: - 1. 策略名称(简短描述,如"稳健价值投资策略") - 2. 最大持仓数(10个资产) - 3. 买入信号阈值(0.3-0.7,数值越高越严格) - 4. 单个资产最大仓位(0.3-0.7,即30%-70%) - 5. 最小现金保留比例(0.05-0.4,即5%-40%) - 6. 策略说明(1-2句话,针对当前市场环境) + Based on the given risk preference and market environment, generate the following parameters: + 1. Strategy name (brief description, e.g. "Stable Value Investment Strategy") + 2. Maximum positions (2-5 assets) + 3. Buy signal threshold (0.3-0.7, higher value means stricter filtering) + 4. Maximum position size per asset (0.3-0.7, i.e. 30%-70%) + 5. Minimum cash reserve ratio (0.05-0.4, i.e. 5%-40%) + 6. Strategy description (1-2 sentences, specific to current market environment) - 注意: - - 参数必须在合理范围内 - - 保守型投资者:持仓少、阈值高、仓位小、现金多 - - 激进型投资者:持仓多、阈值低、仓位大、现金少 - - 崩盘时:保守型应防守保本,激进型应逆向买入 - - 泡沫时:保守型应获利了结,激进型可趋势跟随 + Notes: + - Parameters must be within reasonable ranges + - Conservative investors: fewer positions, higher threshold, smaller positions, more cash + - Aggressive investors: more positions, lower threshold, larger positions, less cash + - During crash: conservative should defend capital, aggressive should buy contrarian + - During bubble: conservative should take profits, aggressive can follow trend - 请严格按照以下 JSON 格式返回策略参数,不要添加任何 markdown 标记或其他文字: - {"name":"策略名称","max_positions":3,"buy_signal_threshold":0.5,"max_position_size":0.5,"min_cash_reserve":0.2,"description":"策略说明"} + Please return the strategy parameters in the following JSON format only, without any markdown tags or additional text: + {"name":"Strategy Name","max_positions":3,"buy_signal_threshold":0.5,"max_position_size":0.5,"min_cash_reserve":0.2,"description":"Strategy description"} PROMPT end diff --git a/app/agents/trader_reflection_agent.rb b/app/agents/trader_reflection_agent.rb index d623840..29af371 100644 --- a/app/agents/trader_reflection_agent.rb +++ b/app/agents/trader_reflection_agent.rb @@ -4,40 +4,43 @@ class TraderReflectionAgent < RubyLLM::Agent model "gpt-5.2" instructions <<~PROMPT - 你是一位交易复盘与策略微调助手。请基于给定的 trader、策略、交易记录、执行结果、组合表现和当前持仓,生成一份结构化反思报告。 + You are a trading review and strategy adjustment assistant. Generate a structured reflection report based on the given trader, strategy, trading records, execution results, portfolio performance, and current positions. - 目标: - 1. 总结最近一段时间的表现。 - 2. 识别策略执行中的优点、错误、模式和风险问题。 - 3. 只对有限参数给出微调建议,不要直接改写整套策略。 + IMPORTANT: All output must be in ENGLISH language. - 可建议的参数只有: + Goals: + 1. Summarize performance over the recent period. + 2. Identify strengths, mistakes, patterns, and risk issues in strategy execution. + 3. Provide limited adjustment suggestions for specific parameters only, do not rewrite the entire strategy. + + Suggested parameters are limited to: - max_positions - buy_signal_threshold - max_position_size - min_cash_reserve - 输出要求: - - 必须严格返回 JSON - - 不要输出 markdown 代码块 - - 不要输出解释性前后文 - - 如果你认为无需调整参数,suggested_adjustments 返回空数组 + Output requirements: + - Must return valid JSON only + - Do not output markdown code blocks + - Do not output explanatory context + - If you believe no parameter adjustments are needed, return an empty array for suggested_adjustments + - ALL text values must be in English - 返回格式: + Return format: { - "summary": "string", - "strengths": ["string"], - "mistakes": ["string"], - "pattern_findings": ["string"], - "risk_issues": ["string"], + "summary": "string (in English)", + "strengths": ["string (in English)"], + "mistakes": ["string (in English)"], + "pattern_findings": ["string (in English)"], + "risk_issues": ["string (in English)"], "suggested_adjustments": [ { "parameter": "max_positions|buy_signal_threshold|max_position_size|min_cash_reserve", "direction": "increase|decrease|keep", - "reason": "string" + "reason": "string (in English)" } ], - "recommendation": "string" + "recommendation": "string (in English)" } PROMPT end diff --git a/app/controllers/admin/factor_definitions_controller.rb b/app/controllers/admin/factor_definitions_controller.rb index 3bcea3c..3d9f825 100644 --- a/app/controllers/admin/factor_definitions_controller.rb +++ b/app/controllers/admin/factor_definitions_controller.rb @@ -13,21 +13,21 @@ def index def matrix @factors = FactorDefinition.active.ordered - # 获取最新的因子值,按 asset_id 和 factor_definition_id 分组 + # Get latest factor values, grouped by asset_id and factor_definition_id @factor_values = FactorValue.latest.includes(:asset, :factor_definition) @latest_calculated_at = @factor_values.maximum(:calculated_at) @values_by_asset = @factor_values.group_by(&:asset_id).transform_values do |values| values.index_by { |v| v.factor_definition_id } end - # 获取市值前 50 的资产并计算排序 + # Get top 50 assets by market cap and calculate ranking @assets = sort_assets( Asset.where.not(market_cap_rank: nil).where("market_cap_rank <= 50").to_a, @factors, @values_by_asset ) - # 当前排序参数 + # Current sort parameters @sort_by = params[:sort_by] || "composite" @sort_order = params[:sort_order] || "desc" end @@ -35,16 +35,16 @@ def matrix def correlations @factors = FactorDefinition.active.ordered - # 获取最新的因子值 + # Get latest factor values @factor_values = FactorValue.latest.includes(:factor_definition) - # 按因子分组,得到每个因子在所有资产上的值 + # Group by factor to get each factor's values across all assets @values_by_factor = @factor_values.group_by(&:factor_definition_id) - # 计算因子间相关性矩阵 + # Calculate factor correlation matrix @correlation_matrix = calculate_correlation_matrix(@factors, @values_by_factor) - # 识别高相关性因子对 + # Identify highly correlated factor pairs @high_correlations = find_high_correlations(@factors, @correlation_matrix) end @@ -54,7 +54,7 @@ def edit; end def update if @factor.update(factor_params) - redirect_to admin_factor_definition_path(@factor), notice: "因子更新成功" + redirect_to admin_factor_definition_path(@factor), notice: "Factor updated successfully" else render :edit, status: :unprocessable_entity end @@ -62,8 +62,8 @@ def update def toggle @factor.update!(active: !@factor.active) - status_text = @factor.active? ? "启用" : "禁用" - redirect_to admin_factor_definitions_path, notice: "因子已#{status_text}" + status_text = @factor.active? ? "enabled" : "disabled" + redirect_to admin_factor_definitions_path, notice: "Factor #{status_text}" end private @@ -94,11 +94,11 @@ def calculate_correlation_matrix(factors, values_by_factor) if factor_a.id == factor_b.id matrix[factor_a.id][factor_b.id] = 1.0 else - # 获取两个因子的值序列 + # Get value sequences for both factors values_a = values_by_factor[factor_a.id]&.map(&:normalized_value) || [] values_b = values_by_factor[factor_b.id]&.map(&:normalized_value) || [] - # 计算相关系数 + # Calculate correlation coefficient correlation = calculate_pearson_correlation(values_a, values_b) matrix[factor_a.id][factor_b.id] = correlation end @@ -130,7 +130,7 @@ def find_high_correlations(factors, matrix) factors.each_with_index do |factor_a, i| factors.each_with_index do |factor_b, j| - next if i >= j # 只计算上三角,避免重复 + next if i >= j # Only calculate upper triangle, avoid duplicates correlation = matrix[factor_a.id][factor_b.id] if correlation.abs >= threshold @@ -150,11 +150,11 @@ def sort_assets(assets, factors, values_by_asset) sort_by = params[:sort_by] || "composite" sort_order = params[:sort_order] || "desc" - # 计算每个资产的排序值 + # Calculate ranking score for each asset assets_with_scores = assets.map do |asset| asset_values = values_by_asset[asset.id] || {} - # 计算综合评分 + # Calculate composite score total_score = 0 total_weight = 0 factors.each do |factor| @@ -166,7 +166,7 @@ def sort_assets(assets, factors, values_by_asset) end composite_score = total_weight > 0 ? (total_score / total_weight) : 0 - # 获取指定因子的值 + # Get specified factor's value factor_score = if sort_by != "composite" factor = factors.find { |f| f.id.to_s == sort_by } factor_value = asset_values[factor&.id] @@ -182,7 +182,7 @@ def sort_assets(assets, factors, values_by_asset) } end - # 排序 + # Sort sorted = assets_with_scores.sort_by { |item| item[:score] } sorted = sorted.reverse if sort_order == "desc" diff --git a/app/controllers/admin/signal_daily_reports_controller.rb b/app/controllers/admin/signal_daily_reports_controller.rb index e1db993..a973ee8 100644 --- a/app/controllers/admin/signal_daily_reports_controller.rb +++ b/app/controllers/admin/signal_daily_reports_controller.rb @@ -26,7 +26,7 @@ def create @report = DailyReport.new(report_params.merge(report_type: REPORT_TYPE)) if @report.save - redirect_to admin_signal_daily_report_path(@report), notice: '日报创建成功' + redirect_to admin_signal_daily_report_path(@report), notice: 'Daily report created successfully' else render :new, status: :unprocessable_entity end @@ -36,7 +36,7 @@ def edit; end def update if @report.update(report_params) - redirect_to admin_signal_daily_report_path(@report), notice: '日报更新成功' + redirect_to admin_signal_daily_report_path(@report), notice: 'Daily report updated successfully' else render :edit, status: :unprocessable_entity end @@ -44,7 +44,7 @@ def update def destroy @report.destroy - redirect_to admin_signal_daily_reports_path, notice: '日报已删除' + redirect_to admin_signal_daily_reports_path, notice: 'Daily report deleted' end def generate @@ -52,7 +52,7 @@ def generate existing = DailyReport.find_by(report_type: REPORT_TYPE, report_date: date) if existing - redirect_to admin_signal_daily_report_path(existing), alert: '该日期的日报已存在' + redirect_to admin_signal_daily_report_path(existing), alert: 'Daily report for this date already exists' return end @@ -60,9 +60,9 @@ def generate report = DailyReport.find_by(report_type: REPORT_TYPE, report_date: date) if report - redirect_to admin_signal_daily_report_path(report), notice: '日报生成成功' + redirect_to admin_signal_daily_report_path(report), notice: 'Daily report generated successfully' else - redirect_to admin_signal_daily_reports_path, alert: '日报生成失败' + redirect_to admin_signal_daily_reports_path, alert: 'Failed to generate daily report' end end @@ -72,9 +72,9 @@ def regenerate report = DailyReport.find_by(report_type: REPORT_TYPE, report_date: date) if report - redirect_to admin_signal_daily_report_path(report), notice: '日报重新生成成功' + redirect_to admin_signal_daily_report_path(report), notice: 'Daily report regenerated successfully' else - redirect_to admin_signal_daily_report_path(@report), alert: '日报重新生成失败' + redirect_to admin_signal_daily_report_path(report), alert: 'Failed to regenerate daily report' end end diff --git a/app/controllers/admin/trading_signals_controller.rb b/app/controllers/admin/trading_signals_controller.rb index a67c44b..623d555 100644 --- a/app/controllers/admin/trading_signals_controller.rb +++ b/app/controllers/admin/trading_signals_controller.rb @@ -11,16 +11,16 @@ def index .page(params[:page]) .per(20) - # 筛选 + # Filter @signals = @signals.by_signal_type(params[:signal_type]) if params[:signal_type].present? @signals = @signals.where(asset_id: params[:asset_id]) if params[:asset_id].present? - # 获取每个资产的最新信号 + # Get latest signal for each asset @latest_signals = TradingSignal.includes(:asset) .select("DISTINCT ON (asset_id) trading_signals.*") .order("asset_id, generated_at DESC") - # 统计 + # Statistics @stats = { total: TradingSignal.count, buy: TradingSignal.buy_signals.count, @@ -29,7 +29,7 @@ def index high_confidence: TradingSignal.high_confidence.count } - # 获取最新信号日报 + # Get latest signal daily report @latest_report = DailyReport.latest_for_type('signal') end @@ -41,15 +41,15 @@ def generate signal = service.generate_and_save! if signal - redirect_to admin_trading_signal_path(signal), notice: "信号生成成功" + redirect_to admin_trading_signal_path(signal), notice: "Signal generated successfully" else - redirect_to admin_trading_signals_path, alert: "信号生成失败,请检查因子数据" + redirect_to admin_trading_signals_path, alert: "Signal generation failed, please check factor data" end end def generate_all GenerateSignalsJob.perform_later - redirect_to admin_trading_signals_path, notice: "正在后台生成信号,请稍后刷新页面" + redirect_to admin_trading_signals_path, notice: "Signals are being generated in background, please refresh later" end private diff --git a/app/controllers/ai_analysis_controller.rb b/app/controllers/ai_analysis_controller.rb index 7ea79d5..4088961 100644 --- a/app/controllers/ai_analysis_controller.rb +++ b/app/controllers/ai_analysis_controller.rb @@ -15,18 +15,18 @@ def create @assets = @assets_input.split(/[,\s]+/).map(&:strip).map(&:upcase).reject(&:empty?) if @assets.empty? - flash[:alert] = "请至少输入一个资产" + flash[:alert] = "Please enter at least one asset" redirect_to new_ai_analysis_path and return end - # 执行分析(不再依赖 Trader) + # Execute analysis (no longer depends on Trader) @result = perform_analysis(@assets, @analysis_type) - # 将结果存入 Rails.cache 供 show 页面使用 + # Store result in Rails.cache for show page result_cache_key = "ai_analysis_result_#{Time.current.to_i}" Rails.cache.write(result_cache_key, @result, expires_in: 10.minutes) - flash[:notice] = "分析完成!共分析 #{@assets.length} 个资产" + flash[:notice] = "Analysis completed! Analyzed #{@assets.length} assets" redirect_to ai_analysis_result_path(result_key: result_cache_key, assets: @assets.join(",")) end diff --git a/app/controllers/allocation_decisions_controller.rb b/app/controllers/allocation_decisions_controller.rb index f1b5efe..bf43742 100644 --- a/app/controllers/allocation_decisions_controller.rb +++ b/app/controllers/allocation_decisions_controller.rb @@ -17,9 +17,9 @@ def show def execute AllocationExecutionService.new(@allocation_decision).call - redirect_to allocation_decision_path(@allocation_decision), notice: "配置建议已执行" + redirect_to allocation_decision_path(@allocation_decision), notice: "Allocation recommendation executed" rescue StandardError => e - redirect_to allocation_decision_path(@allocation_decision), alert: "执行失败: #{e.message}" + redirect_to allocation_decision_path(@allocation_decision), alert: "Execution failed: #{e.message}" end private diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5614c60..cd840f0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -14,6 +14,6 @@ def current_user end def require_user - redirect_to login_path, alert: "请先登录" unless current_user + redirect_to login_path, alert: "Please log in first" unless current_user end end diff --git a/app/controllers/trader_reflections_controller.rb b/app/controllers/trader_reflections_controller.rb index dccaf8c..0e77319 100644 --- a/app/controllers/trader_reflections_controller.rb +++ b/app/controllers/trader_reflections_controller.rb @@ -7,9 +7,9 @@ class TraderReflectionsController < ApplicationController def create reflection = TraderReflectionService.new(@trader).call - redirect_to trader_trader_reflection_path(@trader, reflection), notice: "反思报告生成成功" + redirect_to trader_trader_reflection_path(@trader, reflection), notice: "Reflection report generated successfully" rescue StandardError => e - redirect_to trader_path(@trader), alert: "反思报告生成失败:#{e.message}" + redirect_to trader_path(@trader), alert: "Failed to generate reflection report: #{e.message}" end def show; end @@ -20,9 +20,9 @@ def apply_adjustment parameter: params[:parameter] ).call - redirect_to trader_trader_reflection_path(@trader, @trader_reflection), notice: "策略参数已应用到当前策略" + redirect_to trader_trader_reflection_path(@trader, @trader_reflection), notice: "Strategy parameters applied to current strategy" rescue StandardError => e - redirect_to trader_trader_reflection_path(@trader, @trader_reflection), alert: "应用建议失败:#{e.message}" + redirect_to trader_trader_reflection_path(@trader, @trader_reflection), alert: "Failed to apply suggestion: #{e.message}" end private diff --git a/app/controllers/traders_controller.rb b/app/controllers/traders_controller.rb index 2250cdd..f757df1 100644 --- a/app/controllers/traders_controller.rb +++ b/app/controllers/traders_controller.rb @@ -30,7 +30,7 @@ def create if @trader.save generate_strategies_for(@trader) - redirect_to @trader, notice: "操盘手创建成功" + redirect_to @trader, notice: "Trader created successfully" else render :new, status: :unprocessable_entity end @@ -39,7 +39,7 @@ def create def update if @trader.update(trader_params) regenerate_strategies_if_needed(@trader) - redirect_to @trader, notice: "操盘手更新成功" + redirect_to @trader, notice: "Trader updated successfully" else render :edit, status: :unprocessable_entity end @@ -47,7 +47,7 @@ def update def destroy @trader.destroy - redirect_to traders_url, notice: "操盘手已删除" + redirect_to traders_url, notice: "Trader deleted" end private diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d578bc2..624de1d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,16 +1,16 @@ module ApplicationHelper - # 渲染 Markdown 内容 + # Render Markdown content def render_markdown(text) return '' if text.nil? require 'kramdown' Kramdown::Document.new(text).to_html.html_safe rescue LoadError - # 如果 kramdown 不可用,使用简单的格式化 + # Use simple formatting if kramdown is not available simple_format(text) end - # 根据资产类型返回对应的样式类 + # Return style class based on asset type def asset_type_badge(type) case type.to_s.downcase when 'crypto' @@ -26,19 +26,19 @@ def asset_type_badge(type) end end - # 根据涨跌幅返回颜色类 + # Return color class based on price change def change_color_class(value) value.to_f >= 0 ? 'text-green-600' : 'text-red-600' end - # 格式化涨跌幅百分比 + # Format percentage change def format_change_percent(value) return '-' unless value.present? sign = value.to_f >= 0 ? '+' : '' "#{sign}#{value.to_f.round(2)}%" end - # Job 执行状态样式类 + # Job execution status style class def status_badge_class(status) case status.to_s.downcase when 'success' @@ -52,7 +52,7 @@ def status_badge_class(status) end end - # 格式化统计值 + # Format statistical value def format_stat_value(value) case value when Hash @@ -60,21 +60,21 @@ def format_stat_value(value) when Array value.join(', ') when TrueClass, FalseClass - value ? '是' : '否' + value ? 'Yes' : 'No' else value.to_s end end - # Job 执行状态标签 + # Job execution status label def status_label(status) case status.to_s.downcase when 'success' - '成功' + 'Success' when 'failed' - '失败' + 'Failed' when 'running' - '运行中' + 'Running' else status end diff --git a/app/helpers/assets_helper.rb b/app/helpers/assets_helper.rb index be6fe8a..26c48a4 100644 --- a/app/helpers/assets_helper.rb +++ b/app/helpers/assets_helper.rb @@ -34,12 +34,12 @@ def format_change_percent(change) # Returns human-readable label for timeframe def timeframe_label(tf) labels = { - "all" => "全部", - "1h" => "1小时", - "6h" => "6小时", - "24h" => "24小时", - "7d" => "7天", - "30d" => "30天" + "all" => "All", + "1h" => "1h", + "6h" => "6h", + "24h" => "24h", + "7d" => "7d", + "30d" => "30d" } labels[tf] || tf end diff --git a/app/javascript/controllers/assets_controller.js b/app/javascript/controllers/assets_controller.js index fa37dc0..fb510b4 100644 --- a/app/javascript/controllers/assets_controller.js +++ b/app/javascript/controllers/assets_controller.js @@ -193,11 +193,11 @@ export default class extends Controller { // Format trend direction formatTrend(trend) { const trends = { - bullish: "看涨 ↗", - bearish: "看跌 ↘", - neutral: "中性 →" + bullish: "Bullish ↗", + bearish: "Bearish ↘", + neutral: "Neutral →" } - return trends[trend] || "未知" + return trends[trend] || "Unknown" } // Get trend color class diff --git a/app/javascript/controllers/recommendation_controller.js b/app/javascript/controllers/recommendation_controller.js index 6b9517e..88c36a4 100644 --- a/app/javascript/controllers/recommendation_controller.js +++ b/app/javascript/controllers/recommendation_controller.js @@ -36,7 +36,7 @@ export default class extends Controller { const statusCell = row.querySelector("td:last-child") if (statusCell) { if (isSelected) { - statusCell.innerHTML = '已选择' + statusCell.innerHTML = 'Selected' } else { statusCell.innerHTML = "" } diff --git a/app/models/factor_definition.rb b/app/models/factor_definition.rb index 89a0357..7636e4a 100644 --- a/app/models/factor_definition.rb +++ b/app/models/factor_definition.rb @@ -7,12 +7,12 @@ class FactorDefinition < ApplicationRecord # Categories CATEGORIES = { - 'technical' => '技术因子', - 'fundamental' => '基本面因子', - 'sentiment' => '情绪因子', - 'momentum' => '动量因子', - 'risk' => '风险因子', - 'volume' => '成交量因子' + 'technical' => 'Technical Factor', + 'fundamental' => 'Fundamental Factor', + 'sentiment' => 'Sentiment Factor', + 'momentum' => 'Momentum Factor', + 'risk' => 'Risk Factor', + 'volume' => 'Volume Factor' }.freeze # Validations @@ -45,7 +45,7 @@ def display_weight end def display_status - active? ? '启用' : '禁用' + active? ? 'Active' : 'Inactive' end def parameter(key) diff --git a/app/models/job_execution.rb b/app/models/job_execution.rb index 6881fc8..15eaf9b 100644 --- a/app/models/job_execution.rb +++ b/app/models/job_execution.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class JobExecution < ApplicationRecord - # 状态常量 + # Status constants STATUS_RUNNING = "running" STATUS_SUCCESS = "success" STATUS_FAILED = "failed" @@ -11,7 +11,7 @@ class JobExecution < ApplicationRecord validates :status, presence: true, inclusion: { in: [ STATUS_RUNNING, STATUS_SUCCESS, STATUS_FAILED ] } validates :started_at, presence: true - # 作用域 + # Scopes scope :by_job_name, ->(name) { where(job_name: name) if name.present? } scope :by_status, ->(status) { where(status: status) if status.present? } scope :recent_first, -> { order(started_at: :desc) } @@ -19,13 +19,13 @@ class JobExecution < ApplicationRecord where(started_at: start_date..end_date) if start_date.present? && end_date.present? } - # 计算执行时长(秒) + # Calculate duration in seconds def duration_seconds return nil unless duration_ms (duration_ms / 1000.0).round(2) end - # 格式化执行时长 + # Format duration for display def formatted_duration return "-" unless duration_ms @@ -40,7 +40,7 @@ def formatted_duration end end - # 格式化参数显示 + # Format arguments for display def formatted_arguments return "-" unless arguments.present? @@ -54,7 +54,7 @@ def formatted_arguments end end - # 标记为成功 + # Mark as success def mark_success! now = Time.current update!( @@ -64,7 +64,7 @@ def mark_success! ) end - # 标记为失败 + # Mark as failed def mark_failed!(error_message) now = Time.current update!( diff --git a/app/models/trader.rb b/app/models/trader.rb index bd16034..6d4a610 100644 --- a/app/models/trader.rb +++ b/app/models/trader.rb @@ -48,11 +48,11 @@ def profit_loss_percent end def display_status - active? ? "启用" : "停用" + active? ? "Active" : "Inactive" end def display_risk_level - { "conservative" => "保守", "balanced" => "平衡", "aggressive" => "激进" }[risk_level] + { "conservative" => "Conservative", "balanced" => "Balanced", "aggressive" => "Aggressive" }[risk_level] end # Get strategy for a specific market condition diff --git a/app/models/trading_signal.rb b/app/models/trading_signal.rb index dbd533d..4c43b17 100644 --- a/app/models/trading_signal.rb +++ b/app/models/trading_signal.rb @@ -17,7 +17,7 @@ class TradingSignal < ApplicationRecord scope :hold_signals, -> { where(signal_type: "hold") } scope :high_confidence, -> { where("confidence >= ?", 0.7) } - # 获取每个资产的最新信号 + # Get latest signal for each asset def self.latest_for_all_assets select("DISTINCT ON (asset_id) *").order("asset_id, generated_at DESC") end @@ -53,9 +53,9 @@ def confidence_level def signal_type_label { - "buy" => "买入", - "sell" => "卖出", - "hold" => "持有" + "buy" => "Buy", + "sell" => "Sell", + "hold" => "Hold" }[signal_type] || signal_type end end diff --git a/app/models/trading_strategy.rb b/app/models/trading_strategy.rb index 5f7a593..c7f1d0a 100644 --- a/app/models/trading_strategy.rb +++ b/app/models/trading_strategy.rb @@ -28,91 +28,91 @@ class TradingStrategy < ApplicationRecord STRATEGY_MATRIX = { # Normal market conservative_normal: { - name: "稳健配置策略", + name: "Stable Allocation Strategy", max_positions: 2, buy_signal_threshold: 0.60, max_position_size: 0.40, min_cash_reserve: 0.30, - description: "注重本金保护,持仓集中,严格筛选买入信号,保留充足现金" + description: "Focus on capital preservation, concentrated holdings, strict buy signal filtering, maintain ample cash reserves" }, balanced_normal: { - name: "均衡配置策略", + name: "Balanced Allocation Strategy", max_positions: 3, buy_signal_threshold: 0.50, max_position_size: 0.50, min_cash_reserve: 0.20, - description: "平衡风险与收益,适度分散持仓,灵活调整仓位" + description: "Balance risk and return, moderately diversified holdings, flexible position adjustments" }, aggressive_normal: { - name: "积极成长策略", + name: "Growth Strategy", max_positions: 4, buy_signal_threshold: 0.40, max_position_size: 0.60, min_cash_reserve: 0.10, - description: "追求高收益,分散持仓,积极捕捉机会,保持高仓位运作" + description: "Pursue high returns, diversified holdings, actively capture opportunities, maintain high position levels" }, # Volatile market conservative_volatile: { - name: "减仓观望策略", + name: "Wait and See Strategy", max_positions: 2, buy_signal_threshold: 0.65, max_position_size: 0.30, min_cash_reserve: 0.40, - description: "高波动期减少持仓,提高买入门槛,保留更多现金等待机会" + description: "Reduce holdings during high volatility, raise buy threshold, preserve more cash for opportunities" }, balanced_volatile: { - name: "适度防御策略", + name: "Moderate Defense Strategy", max_positions: 3, buy_signal_threshold: 0.55, max_position_size: 0.40, min_cash_reserve: 0.30, - description: "适度降低仓位,提高选股标准,保持防御姿态" + description: "Moderately reduce positions, raise selection standards, maintain defensive posture" }, aggressive_volatile: { - name: "波段操作策略", + name: "Swing Trading Strategy", max_positions: 4, buy_signal_threshold: 0.45, max_position_size: 0.50, min_cash_reserve: 0.20, - description: "利用波动进行波段操作,快进快出,灵活应对" + description: "Utilize volatility for swing trading, quick entry and exit, flexible response" }, # Crash market conservative_crash: { - name: "防守保本策略", + name: "Capital Preservation Strategy", max_positions: 2, buy_signal_threshold: 0.70, max_position_size: 0.25, min_cash_reserve: 0.50, - description: "崩盘时期以保本为主,极低仓位,等待市场企稳" + description: "Prioritize capital preservation during market crash, very low positions, wait for market stabilization" }, balanced_crash: { - name: "小幅抄底策略", + name: "Moderate Dip Buying Strategy", max_positions: 3, buy_signal_threshold: 0.50, max_position_size: 0.40, min_cash_reserve: 0.30, - description: "适度参与抄底,分批建仓,控制风险敞口" + description: "Moderately participate in dip buying, build positions in batches, control risk exposure" }, aggressive_crash: { - name: "逆向买入策略", + name: "Contrarian Buying Strategy", max_positions: 5, buy_signal_threshold: 0.35, max_position_size: 0.65, min_cash_reserve: 0.05, - description: "逆向投资,在恐慌中积极买入优质资产,追求超额收益" + description: "Contrarian investment, actively buy quality assets during panic, pursue excess returns" }, # Bubble market conservative_bubble: { - name: "获利了结策略", + name: "Profit Taking Strategy", max_positions: 2, buy_signal_threshold: 0.70, max_position_size: 0.30, min_cash_reserve: 0.45, - description: "泡沫期逐步获利了结,降低仓位,锁定收益" + description: "Gradually take profits during bubble phase, reduce positions, lock in gains" }, balanced_bubble: { - name: "逐步减仓策略", + name: "Gradual Position Reduction Strategy", max_positions: 3, buy_signal_threshold: 0.60, max_position_size: 0.40, min_cash_reserve: 0.35, - description: "逐步降低仓位,提高现金比例,防范回调风险" + description: "Gradually reduce positions, increase cash ratio, guard against pullback risk" }, aggressive_bubble: { - name: "趋势跟随策略", + name: "Trend Following Strategy", max_positions: 4, buy_signal_threshold: 0.40, max_position_size: 0.55, min_cash_reserve: 0.15, - description: "顺势而为,跟随趋势但设置严格止损,及时止盈" + description: "Follow the trend, set strict stop-loss, take profits timely" } }.freeze # Market condition display names MARKET_CONDITION_DISPLAY = { - "normal" => "正常市场", - "volatile" => "高波动市场", - "crash" => "崩盘市场", - "bubble" => "泡沫市场" + "normal" => "Normal Market", + "volatile" => "Volatile Market", + "crash" => "Crash Market", + "bubble" => "Bubble Market" }.freeze # Class methods @@ -146,6 +146,6 @@ def display_market_condition end def display_generated_by - { "llm" => "AI 生成", "manual" => "手动配置", "default_template" => "默认模板", "matrix" => "矩阵策略" }[generated_by] + { "llm" => "AI Generated", "manual" => "Manual", "default_template" => "Default Template", "matrix" => "Matrix Strategy" }[generated_by] end end diff --git a/app/services/allocation_execution_service.rb b/app/services/allocation_execution_service.rb index c699b68..b702065 100644 --- a/app/services/allocation_execution_service.rb +++ b/app/services/allocation_execution_service.rb @@ -15,7 +15,7 @@ def call run_on: @run_at.to_date, status: :running, started_at: @run_at, - summary: "开始执行 allocation decision ##{@allocation_decision.id}" + summary: "Starting execution of allocation decision ##{@allocation_decision.id}" ) ActiveRecord::Base.transaction do @@ -61,7 +61,7 @@ def call @task&.update( status: :failed, error_message: e.message, - summary: "执行失败: #{e.message}", + summary: "Execution failed: #{e.message}", completed_at: Time.current ) raise @@ -185,7 +185,7 @@ def deactivate_non_target_positions!(allocations, existing_positions) previous_value: previous_value.to_f, target_value: 0.0, action: "sell", - reason: "资产不在最新 recommendation 目标组合中", + reason: "Asset not in latest recommendation target portfolio", trade_quantity: previous_quantity.to_f, trade_price: price.to_f, trade_amount: previous_value.to_f @@ -229,7 +229,7 @@ def update_position_metrics!(position, quantity:, average_cost:, price:, active: end def build_summary(updates, portfolio_value) - "执行 #{updates.size} 个目标仓位更新,最新组合净值 #{portfolio_value.to_f.round(2)}。" + "Executed #{updates.size} target position updates, latest portfolio value #{portfolio_value.to_f.round(2)}." end def build_execution_payload(strategy, allocations, updates, starting_cash, ending_cash) diff --git a/app/services/factor_llm_service.rb b/app/services/factor_llm_service.rb index 3c8acde..58640a7 100644 --- a/app/services/factor_llm_service.rb +++ b/app/services/factor_llm_service.rb @@ -1,24 +1,24 @@ # frozen_string_literal: true -# LLM 服务 - 复用 AiChatService +# LLM Service - reuse AiChatService class FactorLlmService SYSTEM_PROMPT = <<~PROMPT - 你是 SmartTrader 量化交易系统的 AI 分析师。 - - 你的职责: - - 分析交易因子数据 - - 生成交易信号和投资建议 - - 识别风险和异常 - - 撰写专业分析报告 - - 你的回答应该: - - 专业、准确、有依据 - - 简洁明了,避免冗余 - - 考虑风险因素 - - 基于数据而非猜测 + You are the AI analyst for SmartTrader quantitative trading system. + + Your responsibilities: + - Analyze trading factor data + - Generate trading signals and investment recommendations + - Identify risks and anomalies + - Write professional analysis reports + + Your responses should be: + - Professional, accurate, and well-founded + - Concise and clear, avoid redundancy + - Consider risk factors + - Based on data, not speculation PROMPT - # 普通文本回答 + # Plain text response def self.ask(prompt, instructions: nil) service = AiChatService.new( instructions: instructions || SYSTEM_PROMPT, @@ -28,12 +28,12 @@ def self.ask(prompt, instructions: nil) service.ask(prompt) end - # JSON 格式回答 + # JSON format response def self.ask_json(prompt, instructions: nil) json_instructions = <<~PROMPT #{instructions || SYSTEM_PROMPT} - 重要:你必须返回有效的 JSON 格式,不要包含 markdown 代码块标记,不要包含其他解释文字。 + IMPORTANT: You must return valid JSON format, do not include markdown code block markers, do not include other explanatory text. PROMPT service = AiChatService.new( @@ -46,13 +46,13 @@ def self.ask_json(prompt, instructions: nil) parse_json(response) end - # 因子解读 + # Factor interpretation def self.interpret_factors(asset, factor_values) prompt = build_interpretation_prompt(asset, factor_values) ask(prompt) end - # 生成交易信号 + # Generate trading signal def self.generate_signal(asset, factor_values, strategy = nil) prompt = build_signal_prompt(asset, factor_values, strategy) ask_json(prompt) @@ -77,72 +77,72 @@ def self.parse_json(response) def self.build_interpretation_prompt(asset, factor_values) <<~PROMPT - 你是一位专业的量化分析师。请分析以下资产的因子数据,给出简洁的解读。 + You are a professional quantitative analyst. Please analyze the following asset's factor data and provide a concise interpretation. - 资产信息: - - 名称:#{asset.name} (#{asset.symbol}) + Asset Information: + - Name: #{asset.name} (#{asset.symbol}) - 因子数据: + Factor Data: #{format_factor_values(factor_values)} - 请回答: - 1. 这个资产目前的主要特征是什么?(用1-2句话概括) - 2. 哪些因子表现突出?意味着什么? - 3. 综合来看,这个资产处于什么状态?(强势/弱势/震荡) + Please answer: + 1. What are the main characteristics of this asset currently? (Summarize in 1-2 sentences) + 2. Which factors stand out? What does this mean? + 3. Overall, what state is this asset in? (Strong/Weak/Volatile) - 请用简洁专业的语言回答,不要超过100字。 + Please respond in concise professional language, no more than 100 words. PROMPT end def self.build_signal_prompt(asset, factor_values, strategy) - strategy_info = strategy ? build_strategy_info(strategy) : "使用默认策略参数" + strategy_info = strategy ? build_strategy_info(strategy) : "Using default strategy parameters" <<~PROMPT - 你是一位专业的交易信号分析师。根据以下因子数据,生成交易信号。 + You are a professional trading signal analyst. Generate trading signals based on the following factor data. - ## 策略信息 + ## Strategy Information #{strategy_info} - ## 资产信息 - - 资产:#{asset.name} (#{asset.symbol}) + ## Asset Information + - Asset: #{asset.name} (#{asset.symbol}) - ## 因子数据 + ## Factor Data #{format_factor_values(factor_values)} - ## 任务 - 根据因子数据生成交易信号。 + ## Task + Generate a trading signal based on factor data. - 返回 JSON 格式: + Return JSON format: { "signal_type": "buy|sell|hold", "confidence": 0.0-1.0, - "reasoning": "简要说明信号原因(50字以内)", - "key_factors": ["主要驱动因子1", "主要驱动因子2"], - "risk_warning": "风险提示(如有,可选)" + "reasoning": "Brief explanation of signal reason (within 50 words)", + "key_factors": ["Key driving factor 1", "Key driving factor 2"], + "risk_warning": "Risk warning (if any, optional)" } - 注意: - - 综合考虑所有因子,不要只看单一因子 - - 因子之间可能存在矛盾,需要权衡判断 - - 考虑因子的绝对值和相对变化 + Notes: + - Consider all factors comprehensively, not just a single factor + - Factors may conflict, need to weigh and judge + - Consider both absolute and relative changes of factors PROMPT end def self.build_strategy_info(strategy) <<~INFO - - 策略名称:#{strategy.name} - - 风险偏好:#{strategy.risk_level} - - 买入信号阈值:#{strategy.buy_signal_threshold} - - 最大仓位:#{(strategy.max_position_size * 100).round(0)}% + - Strategy Name: #{strategy.name} + - Risk Level: #{strategy.risk_level} + - Buy Signal Threshold: #{strategy.buy_signal_threshold} + - Max Position Size: #{(strategy.max_position_size * 100).round(0)}% INFO end def self.format_factor_values(factor_values) - return "暂无因子数据" if factor_values.empty? + return "No factor data available" if factor_values.empty? factor_values.map do |fv| factor = fv.factor_definition - "- #{factor.name}: 得分 #{fv.normalized_value.round(2)} (百分位: #{fv.percentile || 'N/A'}%)" + "- #{factor.name}: Score #{fv.normalized_value.round(2)} (Percentile: #{fv.percentile || 'N/A'}%)" end.join("\n") end end diff --git a/app/services/signal_generator_service.rb b/app/services/signal_generator_service.rb index 83da0a1..fba3f48 100644 --- a/app/services/signal_generator_service.rb +++ b/app/services/signal_generator_service.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -# 信号生成服务 - 调用 LLM 生成交易信号并持久化 +# Signal generation service - calls LLM to generate trading signals and persists them class SignalGeneratorService def initialize(asset, strategy: nil) @asset = asset @strategy = strategy end - # 生成并保存信号 + # Generate and save signal def generate_and_save! signal_data = generate_signal return nil unless signal_data @@ -15,7 +15,7 @@ def generate_and_save! create_trading_signal(signal_data) end - # 仅生成信号(不保存) + # Generate signal only (without saving) def generate_signal factor_values = fetch_factor_values return nil if factor_values.empty? @@ -30,7 +30,7 @@ def generate_signal private def fetch_factor_values - # 获取该资产最新的因子值 + # Get latest factor values for this asset FactorValue.includes(:factor_definition) .where(asset: @asset) .joins(:factor_definition) @@ -41,7 +41,7 @@ def fetch_factor_values def create_trading_signal(signal_data) factor_values = fetch_factor_values - # 构建因子快照 + # Build factor snapshot factor_snapshot = build_factor_snapshot(factor_values) TradingSignal.create!( diff --git a/app/services/strategy_generator_service.rb b/app/services/strategy_generator_service.rb index bd236bf..6a20e89 100644 --- a/app/services/strategy_generator_service.rb +++ b/app/services/strategy_generator_service.rb @@ -40,13 +40,13 @@ def strategy_agent def build_prompt(market_condition) <<~PROMPT - 投资者描述: + Investor Description: "#{@description}" - 风险偏好:#{@risk_level || 'balanced'} - 市场环境:#{market_condition} + Risk Preference: #{@risk_level || 'balanced'} + Market Condition: #{market_condition} - 请返回策略参数。 + Please return the strategy parameters. PROMPT end @@ -73,7 +73,7 @@ def build_strategy_params(data, market_condition) end def sanitize_name(name) - name.to_s.strip[0..99].presence || "AI生成策略" + name.to_s.strip[0..99].presence || "AI Generated Strategy" end def sanitize_risk_level(level) @@ -90,7 +90,7 @@ def sanitize_threshold(value, min_value, max_value) end def sanitize_description(desc) - desc.to_s.strip[0..499].presence || "AI 根据投资风格描述自动生成" + desc.to_s.strip[0..499].presence || "AI auto-generated based on investment style description" end def fallback_strategies diff --git a/app/services/trader_reflection_service.rb b/app/services/trader_reflection_service.rb index 30e0d08..ad95771 100644 --- a/app/services/trader_reflection_service.rb +++ b/app/services/trader_reflection_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class TraderReflectionService - PROMPT_VERSION = "v1" + PROMPT_VERSION = "v3_en_strict" def initialize(trader, period_start: 30.days.ago.to_date, period_end: Date.current) @trader = trader @@ -80,29 +80,29 @@ def fallback_report total_profit_loss = metrics["total_profit_loss"].to_d trade_count = metrics["trade_count"].to_i risk_issues = [] - risk_issues << "近期交易次数偏少,样本有限,结论置信度较低。" if trade_count < 3 - risk_issues << "当前持仓浮亏较大,需复核入场时机与仓位控制。" if metrics["unrealized_pnl"].to_d < -1000 + risk_issues << "Recent trading volume is low, sample size is limited, conclusion confidence is low." if trade_count < 3 + risk_issues << "Current positions have significant unrealized loss, need to review entry timing and position control." if metrics["unrealized_pnl"].to_d < -1000 { summary: if total_profit_loss.positive? - "最近一段时间整体仍有盈利,但需要继续复核持仓质量与风险控制。" + "Overall profitable in the recent period, but need to continue reviewing position quality and risk control." elsif total_profit_loss.zero? - "最近一段时间整体接近打平,说明策略尚未形成明显优势。" + "Overall break-even recently, indicating the strategy has not yet established a clear advantage." else - "最近一段时间整体表现偏弱,应优先复核买入门槛、仓位集中度和现金保留比例。" + "Overall performance is weak recently, should prioritize reviewing buy threshold, position concentration, and cash reserve ratio." end, findings: { "strengths" => [ - "当前策略和交易执行链路完整,能够持续产出 recommendation 并落地执行。" + "Current strategy and trading execution pipeline is complete, able to continuously produce recommendations and execute them." ], "mistakes" => [ - "近期表现说明策略参数与市场环境之间可能存在错配。" + "Recent performance indicates possible mismatch between strategy parameters and market conditions." ], "pattern_findings" => [ - "反思结果基于最近 30 天执行记录、交易流水、组合快照和当前持仓。" + "Reflection results are based on execution records, trading history, portfolio snapshots, and current positions from the last 30 days." ], "risk_issues" => risk_issues, - "recommendation" => "建议先阅读反思报告,再决定是否人工微调现有策略参数。" + "recommendation" => "Recommend reading the reflection report first, then decide whether to manually fine-tune existing strategy parameters." }, suggested_adjustments: suggested_adjustments_from_metrics(metrics) } @@ -115,7 +115,7 @@ def suggested_adjustments_from_metrics(metrics) adjustments << { "parameter" => "buy_signal_threshold", "direction" => "increase", - "reason" => "近期持仓浮亏偏大,建议提高买入门槛,减少边际信号质量不足的入场。" + "reason" => "Recent positions have significant unrealized losses, recommend raising buy threshold to reduce entries with marginal signal quality." } end @@ -123,7 +123,7 @@ def suggested_adjustments_from_metrics(metrics) adjustments << { "parameter" => "min_cash_reserve", "direction" => "increase", - "reason" => "当前现金比例偏低,建议提高现金保留,增强回撤期缓冲能力。" + "reason" => "Current cash ratio is low, recommend increasing cash reserve to enhance buffer capacity during drawdowns." } end @@ -132,25 +132,26 @@ def suggested_adjustments_from_metrics(metrics) def prompt <<~PROMPT - 请基于以下 trader 反思上下文,输出 JSON: + Please output JSON in ENGLISH based on the following trader reflection context. + IMPORTANT: All output must be in English language. #{JSON.pretty_generate(reflection_payload)} - 返回格式: + Return format (all values in English): { - "summary": "string", - "strengths": ["string"], - "mistakes": ["string"], - "pattern_findings": ["string"], - "risk_issues": ["string"], + "summary": "string (in English)", + "strengths": ["string (in English)"], + "mistakes": ["string (in English)"], + "pattern_findings": ["string (in English)"], + "risk_issues": ["string (in English)"], "suggested_adjustments": [ { "parameter": "max_positions|buy_signal_threshold|max_position_size|min_cash_reserve", "direction": "increase|decrease|keep", - "reason": "string" + "reason": "string (in English)" } ], - "recommendation": "string" + "recommendation": "string (in English)" } PROMPT end diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index e93a859..2c9c1e1 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -1,4 +1,4 @@ -<% content_for :title, "Admin 管理面板" %> +<% content_for :title, "Admin Dashboard" %>
系统管理入口
+System Management Portal
管理交易因子,定义因子参数和权重
+Manage trading factors, define parameters and weights
查看 AI 生成的交易信号
+View AI-generated trading signals
查看后台任务执行记录
+View background task execution records
Sidekiq 队列管理界面
+Sidekiq queue management interface