feat: 增加AI分析并调通

master^2
Lxy 1 week ago
parent bff2ab8b61
commit a65cca62f8

@ -33,4 +33,8 @@ def get_analysis_db():
def init_analysis_db():
"""初始化期货智析数据库表"""
# 确保导入所有模型类,使其注册到 AnalysisBase
from app import analysis_models
# 直接导入 analysis_models 模块中的所有类
from app.analysis_models import FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings, AIAnalysisCache
AnalysisBase.metadata.create_all(bind=analysis_engine)

@ -86,3 +86,16 @@ class AnalysisSettings(AnalysisBase):
def __repr__(self):
return f"<AnalysisSettings {self.key}>"
class AIAnalysisCache(AnalysisBase):
"""AI分析缓存表"""
__tablename__ = "ai_analysis_cache"
id = Column(Integer, primary_key=True, autoincrement=True)
symbol = Column(String(32), nullable=False, index=True, comment="品种合约代码")
analysis_data = Column(JSON, nullable=False, comment="AI分析结果数据")
created_at = Column(DateTime, nullable=False, default=datetime.now, index=True, comment="分析时间")
def __repr__(self):
return f"<AIAnalysisCache {self.symbol} {self.created_at}>"

@ -12,7 +12,7 @@ from sqlalchemy.orm import Session
from app.database import get_db
from app.analysis_db import get_analysis_db
from app.analysis_models import FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings
from app.analysis_models import FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings, AIAnalysisCache
from app.services.cache import get_cached_data, get_latest_cached, save_market_data
from app.services.collector import fetch_symbol_data
@ -694,6 +694,7 @@ def delete_ai_model(model_id: int, adb: Session = Depends(get_analysis_db)):
# ==================== 数据刷新接口 ====================
from app.services.cache import needs_refresh, get_symbol_timestamp
from app.services.ai_analysis import AIFuturesAnalyzer
refresh_lock = threading.Lock()
refresh_status = {"running": False, "progress": 0, "total": 0, "message": ""}
@ -786,3 +787,112 @@ def refresh_all_symbols_api(background_tasks: BackgroundTasks):
def get_refresh_status():
"""获取刷新状态"""
return {"success": True, "data": refresh_status}
# ==================== AI智能分析接口 ====================
@router.post("/ai-analysis/{symbol}")
def run_ai_analysis(symbol: str, db: Session = Depends(get_db), analysis_db: Session = Depends(get_analysis_db)):
"""执行AI智能分析"""
try:
analyzer = AIFuturesAnalyzer(db, analysis_db)
result = analyzer.analyze(symbol)
if result.get("success"):
return {
"success": True,
"data": result["data"]
}
else:
return {
"success": False,
"error": result.get("error", "AI分析失败")
}
except Exception as e:
logger.error(f"AI分析失败: {e}")
return {
"success": False,
"error": f"AI分析失败: {str(e)}"
}
@router.get("/ai-analysis/{symbol}")
def get_ai_analysis(symbol: str, force_refresh: bool = False, db: Session = Depends(get_db), analysis_db: Session = Depends(get_analysis_db)):
"""获取AI分析结果可选择是否强制刷新"""
try:
analyzer = AIFuturesAnalyzer(db, analysis_db)
if force_refresh:
result = analyzer.analyze(symbol)
if result.get("success"):
return {
"success": True,
"data": result["data"],
"is_cached": False
}
else:
return {
"success": False,
"error": result.get("error", "AI分析失败")
}
cache = analyzer.get_latest_cache(symbol)
if cache:
return {
"success": True,
"data": {
"id": cache.id,
"symbol": cache.symbol,
"analysis_time": cache.created_at.isoformat(),
"result": cache.analysis_data
},
"is_cached": True
}
result = analyzer.analyze(symbol)
if result.get("success"):
return {
"success": True,
"data": result["data"],
"is_cached": False
}
else:
return {
"success": False,
"error": result.get("error", "未找到分析结果")
}
except Exception as e:
logger.error(f"获取AI分析结果失败: {e}")
return {
"success": False,
"error": f"获取AI分析失败: {str(e)}"
}
@router.get("/ai-analysis/{symbol}/history")
def get_ai_analysis_history(symbol: str, limit: int = 10, analysis_db: Session = Depends(get_analysis_db)):
"""获取AI分析历史记录"""
try:
records = analysis_db.query(AIAnalysisCache).filter(
AIAnalysisCache.symbol == symbol
).order_by(
AIAnalysisCache.created_at.desc()
).limit(limit).all()
return {
"success": True,
"data": [{
"id": r.id,
"symbol": r.symbol,
"analysis_time": r.created_at.isoformat(),
"summary": r.analysis_data.get("summary", ""),
"trading_suggestion": r.analysis_data.get("trading_suggestion", {}),
"confidence": r.analysis_data.get("trading_suggestion", {}).get("confidence", 0)
} for r in records]
}
except Exception as e:
logger.error(f"获取AI分析历史失败: {e}")
return {
"success": False,
"error": f"获取历史记录失败: {str(e)}"
}

@ -0,0 +1,459 @@
"""
AI分析服务 - 期货四维联合分析
"""
import json
import re
import logging
from datetime import datetime
from typing import Dict, List, Optional
from sqlalchemy.orm import Session
from app.analysis_models import AIAnalysisCache
from app.services.cache import get_cached_data, get_latest_cached
from pathlib import Path
CONFIG_DIR = Path(__file__).resolve().parent.parent.parent / "config"
AI_CONFIG_FILE = CONFIG_DIR / "ai_config.json"
logger = logging.getLogger(__name__)
class AIAnalysisPrompt:
"""AI分析提示词管理器"""
SYSTEM_PROMPT = """你是一位拥有20年实战经验的资深金融交易分析师精通A股市场与商品期货的技术分析。
你的核心使命是基于提供的K线数据执行四维联合判断分析法(4D-XV)并提供包含风控红线审查的客观交易策略
你的分析必须遵循以下原则
1. 数据驱动所有结论必须基于输入的JSON数据严禁凭空捏造
2. 四维共振任何交易建议必须经过MACD趋势成交量资金KDJ时机多周期方向的交叉验证
3. 红线否决如果数据触发17条交易红线必须直接给出禁止交易止损的建议
4. 客观中立不使用绝对化表述提供情景预案概率估算
5. 动态切换遇到关键位放量突破时立即切换右侧思维不逆势死扛"""
ANALYSIS_TEMPLATE = """请严格按照以下JSON格式输出分析结果不要输出任何其他内容
{
"summary": "一句话总结当前市场状态",
"four_dimensional": {
"60min": {
"macd": {"trend": "up/down/neutral", "histogram": "放大/缩小/背离", "position": "零轴上/下"},
"volume": {"status": "放量上涨/缩量回调/趋势量能/拐点量能", "ratio": 1.5},
"kdj": {"k": 85, "d": 80, "j": 95, "status": "超买/超卖/中性", "signal": "金叉/死叉/钝化"},
"conclusion": "定大势结论"
},
"30min": {
"macd": {"trend": "up/down/neutral", "histogram": "放大/缩小/背离", "position": "零轴上/下"},
"volume": {"status": "放量/缩量/正常", "ratio": 1.5},
"kdj": {"k": 50, "d": 45, "j": 60, "status": "中性", "signal": "金叉/死叉"},
"conclusion": "找拐点结论"
},
"15min": {
"macd": {"trend": "up/down/neutral", "histogram": "放大/缩小/背离", "position": "零轴上/下"},
"volume": {"status": "放量/缩量/正常", "ratio": 2.0},
"kdj": {"k": 30, "d": 25, "j": 40, "status": "超卖", "signal": "金叉/死叉"},
"conclusion": "择入场结论"
}
},
"kdj_diagnosis": {
"current_status": "超买/超卖/中性区域",
"divergence": "是否存在顶/底背离",
"paralysis": "是否钝化(持续>6根K线",
"recommendation": "KDJ使用建议"
},
"pivot_points": {
"r2": 7500,
"r1": 7350,
"pp": 7200,
"s1": 7050,
"s2": 6900,
"validation": {
"test_count": 3,
"volume_confirmed": true,
"multi_period_resonance": true,
"breakback_confirmed": false
}
},
"red_lines_check": {
"passed": true,
"violated": [],
"warnings": ["暂无红线警告"]
},
"discipline_score": {
"total": 9,
"max": 11,
"details": {
"trend": true,
"position": true,
"signal": true,
"risk": true,
"mindset": true
}
},
"trading_suggestion": {
"direction": "做多/做空/观望",
"confidence": 75,
"entry_range": {"min": 7050, "max": 7100},
"stop_loss": 6950,
"take_profit": [{"price": 7200, "ratio": 50}, {"price": 7350, "ratio": 30}, {"price": 7500, "ratio": 20}],
"position_size": "轻仓/半仓/重仓",
"reason": "做多理由"
},
"scenario_plans": {
"breakthrough": {"probability": 35, "action": "放量突破关键位,跟随右侧思维"},
"consolidation": {"probability": 40, "action": "R1-S1区间内高抛低吸"},
"reversal": {"probability": 15, "action": "MACD顶/底背离+量能不足,立即止损反手"},
"news_impact": {"probability": 10, "action": "减仓50%规避不确定性"}
},
"risk_warnings": [
"技术指标具有滞后性,历史表现不代表未来",
"需结合基本面和市场情绪综合判断"
],
"experience_lessons": [
"警惕缩量创新高,可能是诱多信号",
"KDJ超买钝化中不宜逆势做空"
]
}"""
@classmethod
def build_prompt(cls, symbol: str, data: Dict) -> str:
"""构建完整的AI分析提示词"""
prompt = f"""{cls.SYSTEM_PROMPT}
现在请分析以下期货品种的K线数据
## 品种信息
- 合约代码{symbol}
- 当前价格{data.get('current_price', 'N/A')}
## 多周期K线数据
```json
{json.dumps(data, ensure_ascii=False, indent=2)}
```
{cls.ANALYSIS_TEMPLATE}"""
return prompt
class AIFuturesAnalyzer:
"""AI期货分析器"""
def __init__(self, db: Session, analysis_db: Session = None):
self.db = db
self.analysis_db = analysis_db or db
def get_active_model(self) -> Optional[Dict]:
"""获取当前激活的AI模型配置"""
try:
if not AI_CONFIG_FILE.exists():
return None
with open(AI_CONFIG_FILE, "r", encoding="utf-8") as f:
config = json.load(f)
models = config.get("models", [])
active_model_name = config.get("active_model")
if active_model_name:
for model in models:
if model.get("model_name") == active_model_name and model.get("enabled", True):
return model
for model in models:
if model.get("enabled", True):
logger.warning(f"未找到匹配的激活模型,使用第一个启用的模型: {model.get('model_name')}")
return model
return None
except Exception as e:
logger.error(f"加载AI配置失败: {e}")
return None
def prepare_multi_period_data(self, symbol: str) -> Optional[Dict]:
"""准备多周期数据用于AI分析"""
cached_data = get_cached_data(
self.db,
symbol,
"futures",
["5min", "15min", "30min", "60min", "daily"]
)
if not cached_data or not cached_data.get("timeframes"):
return None
timeframes = cached_data.get("timeframes", {})
current_price = cached_data.get("current_price")
result = {
"symbol": symbol,
"current_price": current_price,
"timeframes": {}
}
for period_name, db_period in [("5min", "5min"), ("15min", "15min"),
("30min", "30min"), ("60min", "60min"),
("daily", "daily")]:
if period_name in timeframes and timeframes[period_name]:
candles = timeframes[period_name]
if len(candles) >= 20:
result["timeframes"][period_name] = self._analyze_timeframe(candles, period_name)
return result
def _analyze_timeframe(self, candles: List[Dict], period: str) -> Dict:
"""分析单个周期的技术指标"""
if not candles or len(candles) < 20:
return {}
closes = [float(c.get("close", 0)) for c in candles]
highs = [float(c.get("high", 0)) for c in candles]
lows = [float(c.get("low", 0)) for c in candles]
volumes = [float(c.get("volume", 0)) for c in candles]
ma10 = sum(closes[-10:]) / 10 if len(closes) >= 10 else None
ma20 = sum(closes[-20:]) / 20 if len(closes) >= 20 else None
macd_data = self._calc_macd(closes)
kdj_data = self._calc_kdj(highs, lows, closes)
avg_volume = sum(volumes[-20:]) / 20 if len(volumes) >= 20 else 0
current_volume = volumes[-1] if volumes else 0
return {
"trend": "up" if closes[-1] > closes[0] else "down",
"ma10": round(ma10, 2) if ma10 else None,
"ma20": round(ma20, 2) if ma20 else None,
"macd_dif": round(macd_data["dif"], 4),
"macd_dea": round(macd_data["dea"], 4),
"macd_histogram": round(macd_data["histogram"], 4),
"kdj_k": kdj_data["k"],
"kdj_d": kdj_data["d"],
"kdj_j": kdj_data["j"],
"volume_avg": round(avg_volume, 2),
"volume_current": round(current_volume, 2),
"volume_ratio": round(current_volume / avg_volume, 2) if avg_volume > 0 else 1,
"candles": candles[-10:] if len(candles) > 10 else candles
}
def _calc_macd(self, closes: List[float]) -> Dict:
"""计算MACD指标"""
if len(closes) < 26:
return {"dif": 0, "dea": 0, "histogram": 0}
ema12 = self._calc_ema(closes, 12)
ema26 = self._calc_ema(closes, 26)
dif = ema12 - ema26
dea = self._calc_ema([dif] * len(closes), 9)
histogram = 2 * (dif - dea)
return {"dif": dif, "dea": dea, "histogram": histogram}
def _calc_ema(self, data: List[float], period: int) -> float:
"""计算EMA"""
if len(data) < period:
return sum(data) / len(data) if data else 0
multiplier = 2 / (period + 1)
ema = sum(data[:period]) / period
for i in range(period, len(data)):
ema = (data[i] - ema) * multiplier + ema
return ema
def _calc_kdj(self, highs: List[float], lows: List[float], closes: List[float]) -> Dict:
"""计算KDJ指标"""
if len(closes) < 9:
return {"k": 50, "d": 50, "j": 50}
period = 9
recent_highs = highs[-period:]
recent_lows = lows[-period:]
recent_closes = closes[-period:]
highest = max(recent_highs)
lowest = min(recent_lows)
current = recent_closes[-1]
if highest == lowest:
rsv = 50
else:
rsv = (current - lowest) / (highest - lowest) * 100
k = rsv * 2 / 3 + 50 / 3
d = k * 2 / 3 + 50 / 3
j = 3 * k - 2 * d
return {"k": round(k, 2), "d": round(d, 2), "j": round(j, 2)}
def call_ai_model(self, prompt: str, model: Dict) -> Optional[str]:
"""调用AI模型"""
try:
import requests
api_base = model.get("api_base", "https://api.openai.com/v1")
api_key = model.get("api_key", "")
model_id = model.get("model_id") or model.get("model_name", "")
logger.info(f"========== AI模型调用开始 ==========")
logger.info(f"API Base: {api_base}")
logger.info(f"API Key: {'已配置' if api_key else '未配置'} ({api_key[:10]}...)" if api_key else "API Key: 未配置")
logger.info(f"Model ID: {model_id}")
logger.info(f"Temperature: {model.get('temperature', 0.7)}")
logger.info(f"Max Tokens: {model.get('max_tokens', 2000)}")
if not api_key:
logger.error("API Key 未配置无法调用AI模型")
return None
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
payload = {
"model": model_id,
"messages": [
{"role": "user", "content": prompt}
],
"temperature": model.get("temperature", 0.7),
"max_tokens": model.get("max_tokens", 2000)
}
url = f"{api_base}/chat/completions"
logger.info(f"请求 URL: {url}")
logger.info(f"请求 Payload 大小: {len(str(payload))} 字符")
response = requests.post(
url,
headers=headers,
json=payload,
timeout=180 # 增加到180秒3分钟超时
)
logger.info(f"响应状态码: {response.status_code}")
if response.status_code == 200:
result = response.json()
content = result["choices"][0]["message"]["content"]
logger.info(f"AI响应成功内容长度: {len(content)} 字符")
logger.info(f"========== AI模型调用成功 ==========")
return content
else:
logger.error(f"AI模型调用失败:")
logger.error(f" 状态码: {response.status_code}")
logger.error(f" 响应内容: {response.text}")
logger.error(f"========== AI模型调用失败 ==========")
return None
except requests.exceptions.Timeout:
logger.error("AI模型调用超时超过60秒")
logger.error(f"========== AI模型调用失败 ==========")
return None
except requests.exceptions.ConnectionError as e:
logger.error(f"AI模型连接错误: {e}")
logger.error(f"========== AI模型调用失败 ==========")
return None
except requests.exceptions.RequestException as e:
logger.error(f"AI模型请求异常: {e}")
logger.error(f"========== AI模型调用失败 ==========")
return None
except Exception as e:
logger.error(f"调用AI模型未知异常: {e}", exc_info=True)
logger.error(f"========== AI模型调用失败 ==========")
return None
def parse_ai_response(self, response: str) -> Optional[Dict]:
"""解析AI返回的JSON响应"""
try:
json_match = re.search(r'\{[\s\S]*\}', response)
if json_match:
return json.loads(json_match.group(0))
return None
except Exception as e:
logger.error(f"解析AI响应失败: {e}")
return None
def save_analysis_cache(self, symbol: str, analysis_data: Dict) -> AIAnalysisCache:
"""保存AI分析结果到缓存"""
cache = AIAnalysisCache(
symbol=symbol,
analysis_data=analysis_data,
created_at=datetime.now()
)
self.analysis_db.add(cache)
self.analysis_db.commit()
self.analysis_db.refresh(cache)
return cache
def get_latest_cache(self, symbol: str) -> Optional[AIAnalysisCache]:
"""获取最新的AI分析缓存"""
return self.analysis_db.query(AIAnalysisCache).filter(
AIAnalysisCache.symbol == symbol
).order_by(AIAnalysisCache.created_at.desc()).first()
def analyze(self, symbol: str) -> Dict:
"""执行完整的AI分析流程"""
logger.info(f"===== 开始AI分析: {symbol} =====")
model = self.get_active_model()
if not model:
logger.error("未找到激活的AI模型配置")
return {
"success": False,
"error": "未配置AI模型或模型未激活"
}
logger.info(f"使用AI模型: {model.get('model_name')}")
data = self.prepare_multi_period_data(symbol)
if not data:
logger.error(f"未找到 {symbol} 的市场数据")
return {
"success": False,
"error": f"未找到 {symbol} 的市场数据"
}
logger.info(f"市场数据准备成功,包含周期: {list(data.get('timeframes', {}).keys())}")
prompt = AIAnalysisPrompt.build_prompt(symbol, data)
logger.info(f"AI提示词生成完成长度: {len(prompt)} 字符")
response = self.call_ai_model(prompt, model)
if not response:
logger.error("AI模型返回空响应")
return {
"success": False,
"error": "AI模型调用失败"
}
logger.info(f"AI模型响应接收成功长度: {len(response)} 字符")
analysis_result = self.parse_ai_response(response)
if not analysis_result:
logger.error(f"AI响应解析失败原始响应前100字符: {response[:100]}")
return {
"success": False,
"error": "AI响应解析失败"
}
logger.info(f"AI响应解析成功")
cache = self.save_analysis_cache(symbol, analysis_result)
logger.info(f"分析结果已保存到缓存ID: {cache.id}")
logger.info(f"===== AI分析完成: {symbol} =====")
return {
"success": True,
"data": {
"id": cache.id,
"symbol": symbol,
"analysis_time": cache.created_at.isoformat(),
"result": analysis_result
}
}

@ -1597,6 +1597,396 @@ body.theme-minimal .sort-select select:hover {
border-color: var(--text-muted);
}
/* ============================================
AI
============================================ */
.panel-header-actions {
display: flex;
gap: 8px;
margin-left: auto;
}
.ai-analyze-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: linear-gradient(135deg, var(--purple), var(--cyan));
border: none;
border-radius: 6px;
color: white;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.ai-analyze-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--purple-glow);
}
.ai-analyze-btn:active {
transform: translateY(0);
}
.ai-analyze-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.ai-analyze-btn i {
font-size: 11px;
}
.ai-analysis-content {
min-height: 120px;
}
.ai-analysis-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
color: var(--text-muted);
}
.ai-analysis-placeholder i {
font-size: 32px;
margin-bottom: 12px;
opacity: 0.5;
}
.ai-analysis-placeholder p {
font-size: 13px;
text-align: center;
}
.ai-analysis-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
gap: 12px;
}
.ai-analysis-loading i {
font-size: 28px;
color: var(--purple);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.5; transform: scale(1); }
50% { opacity: 1; transform: scale(1.1); }
}
.ai-analysis-result {
display: flex;
flex-direction: column;
gap: 12px;
}
.ai-summary {
padding: 12px;
background: rgba(139, 92, 246, 0.08);
border: 1px solid rgba(139, 92, 246, 0.15);
border-radius: 8px;
font-size: 13px;
line-height: 1.6;
color: var(--text-primary);
}
.ai-suggestion-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.03);
border-radius: 6px;
}
.ai-suggestion-direction {
display: flex;
align-items: center;
gap: 8px;
}
.ai-suggestion-direction i {
font-size: 16px;
}
.ai-suggestion-direction.long i {
color: var(--green);
}
.ai-suggestion-direction.short i {
color: var(--red);
}
.ai-suggestion-direction.neutral i {
color: var(--amber);
}
.ai-confidence {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-secondary);
}
.ai-confidence-bar {
width: 60px;
height: 4px;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
overflow: hidden;
}
.ai-confidence-fill {
height: 100%;
background: linear-gradient(90deg, var(--amber), var(--green));
border-radius: 2px;
transition: width 0.5s ease;
}
.ai-key-metrics {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
}
.ai-metric-item {
display: flex;
justify-content: space-between;
padding: 8px 10px;
background: rgba(255, 255, 255, 0.02);
border-radius: 6px;
font-size: 12px;
}
.ai-metric-item .label {
color: var(--text-muted);
}
.ai-metric-item .value {
font-weight: 600;
color: var(--text-primary);
}
.ai-metric-item .value.up {
color: var(--green);
}
.ai-metric-item .value.down {
color: var(--red);
}
.ai-timestamp {
text-align: center;
font-size: 11px;
color: var(--text-muted);
padding-top: 8px;
border-top: 1px solid var(--border-color);
}
/* AI分析详情模态框 */
.modal-large {
max-width: 900px;
width: 90%;
}
.modal-large .modal-body {
max-height: 80vh;
overflow-y: auto;
}
.ai-modal-section {
margin-bottom: 24px;
}
.ai-modal-section:last-child {
margin-bottom: 0;
}
.ai-modal-section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 15px;
font-weight: 600;
color: var(--cyan);
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}
.ai-modal-section-title i {
font-size: 16px;
}
.four-dimensional-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.four-dimensional-table th,
.four-dimensional-table td {
padding: 10px 12px;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
.four-dimensional-table th {
background: rgba(139, 92, 246, 0.08);
color: var(--purple);
font-weight: 600;
font-size: 12px;
}
.four-dimensional-table td {
color: var(--text-secondary);
}
.four-dimensional-table tr:last-child td {
border-bottom: none;
}
.four-dimensional-table .period-cell {
font-weight: 600;
color: var(--text-primary);
}
.scenario-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.scenario-card {
padding: 12px;
background: rgba(255, 255, 255, 0.03);
border-radius: 8px;
border-left: 3px solid var(--cyan);
}
.scenario-card.breakthrough {
border-left-color: var(--green);
}
.scenario-card.consolidation {
border-left-color: var(--amber);
}
.scenario-card.reversal {
border-left-color: var(--red);
}
.scenario-card.news {
border-left-color: var(--purple);
}
.scenario-probability {
display: inline-block;
padding: 2px 8px;
background: rgba(139, 92, 246, 0.15);
border-radius: 4px;
font-size: 11px;
font-weight: 600;
color: var(--purple);
margin-bottom: 6px;
}
.scenario-action {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
}
.red-lines-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.red-line-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: rgba(239, 68, 68, 0.08);
border: 1px solid rgba(239, 68, 68, 0.15);
border-radius: 6px;
font-size: 12px;
color: var(--red);
}
.red-line-item.pass {
background: rgba(16, 185, 129, 0.08);
border-color: rgba(16, 185, 129, 0.15);
color: var(--green);
}
.discipline-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.discipline-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px;
background: rgba(255, 255, 255, 0.03);
border-radius: 6px;
}
.discipline-item i {
font-size: 16px;
}
.discipline-item.pass i {
color: var(--green);
}
.discipline-item.fail i {
color: var(--red);
}
.discipline-label {
font-size: 12px;
color: var(--text-secondary);
}
.ai-experience-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.experience-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 10px 12px;
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.15);
border-radius: 6px;
font-size: 12px;
color: var(--amber);
}
.experience-item i {
margin-top: 2px;
}
body.theme-minimal .sort-select select:focus {
border-color: var(--cyan);
box-shadow: 0 0 0 0.125rem rgba(73, 79, 223, 0.2);

@ -362,6 +362,26 @@
</div>
</div>
<!-- AI智能分析 -->
<div class="panel-card ai-analysis-card" id="ai-analysis-panel">
<div class="panel-header">
<i class="fas fa-brain"></i>
<span>AI 四维分析</span>
<div class="panel-header-actions">
<button class="ai-analyze-btn" id="ai-analyze-btn" onclick="runAIAnalysis()" title="执行AI分析">
<i class="fas fa-play"></i>
<span>智能分析</span>
</button>
</div>
</div>
<div class="ai-analysis-content" id="ai-analysis-content">
<div class="ai-analysis-placeholder">
<i class="fas fa-brain"></i>
<p>点击"智能分析"按钮获取AI分析结果</p>
</div>
</div>
</div>
<!-- 趋势评分 -->
<div class="panel-card score-card">
<div class="panel-header">
@ -385,6 +405,19 @@
</main>
</div>
<!-- AI分析详情对话框 -->
<div class="modal-overlay" id="ai-analysis-modal">
<div class="modal-content modal-large">
<div class="modal-header">
<h3><i class="fas fa-brain"></i> AI 四维联合分析报告</h3>
<button class="modal-close" onclick="closeModal('ai-analysis-modal')"><i class="fas fa-xmark"></i></button>
</div>
<div class="modal-body" id="ai-analysis-modal-body">
<!-- 动态生成 -->
</div>
</div>
</div>
<!-- AI建议详情对话框 -->
<div class="modal-overlay" id="suggestion-modal">
<div class="modal-content">

@ -143,6 +143,7 @@ function showDetailView(symbol) {
loadFuturesDetail(symbol);
loadKlineData(symbol, currentPeriod);
loadHistoryList(symbol);
loadAIAnalysis();
}
async function loadWatchedSymbols() {
@ -530,6 +531,37 @@ async function loadHistoryList(symbol) {
}
}
async function loadAIAnalysis() {
if (!currentSymbol) return;
const content = document.getElementById('ai-analysis-content');
try {
const response = await fetch(`${API_BASE}/ai-analysis/${currentSymbol}`);
const data = await response.json();
if (data.success && data.data) {
currentAIAnalysis = data.data;
displayAIAnalysisResult(data.data);
} else {
content.innerHTML = `
<div class="ai-analysis-placeholder">
<i class="fas fa-brain"></i>
<p>点击"智能分析"按钮获取AI分析结果</p>
</div>
`;
}
} catch (error) {
console.error('加载AI分析失败:', error);
content.innerHTML = `
<div class="ai-analysis-placeholder">
<i class="fas fa-brain"></i>
<p>点击"智能分析"按钮获取AI分析结果</p>
</div>
`;
}
}
function renderHistoryList(records) {
const container = document.getElementById('history-list');
if (!records || records.length === 0) {
@ -1104,3 +1136,319 @@ async function refreshSingleSymbol(symbol, btnElement = null) {
btn.innerHTML = originalContent;
}
}
// ==================== AI智能分析功能 ====================
let currentAIAnalysis = null;
async function runAIAnalysis(forceRefresh = false) {
if (!currentSymbol) {
showToast('warning', '提示', '请先选择一个品种');
return;
}
const btn = document.getElementById('ai-analyze-btn');
const content = document.getElementById('ai-analysis-content');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i><span>分析中...</span>';
content.innerHTML = `
<div class="ai-analysis-loading">
<i class="fas fa-brain"></i>
<span>AI正在分析中...</span>
</div>
`;
try {
const response = await fetch(`${API_BASE}/ai-analysis/${currentSymbol}?force_refresh=${forceRefresh}`);
const data = await response.json();
if (data.success) {
currentAIAnalysis = data.data;
displayAIAnalysisResult(data.data);
showToast('success', '分析完成', `${currentSymbol} AI分析已完成`);
} else {
content.innerHTML = `
<div class="ai-analysis-placeholder">
<i class="fas fa-exclamation-triangle"></i>
<p>${data.error || 'AI分析失败请稍后重试'}</p>
</div>
`;
showToast('error', '分析失败', data.error || 'AI分析失败');
}
} catch (error) {
console.error('AI分析请求失败:', error);
content.innerHTML = `
<div class="ai-analysis-placeholder">
<i class="fas fa-exclamation-triangle"></i>
<p>网络错误请检查网络连接</p>
</div>
`;
showToast('error', '请求失败', '网络错误,请稍后重试');
} finally {
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-play"></i><span>智能分析</span>';
}
}
function displayAIAnalysisResult(data) {
const content = document.getElementById('ai-analysis-content');
const result = data.result;
const timestamp = new Date(data.analysis_time).toLocaleString('zh-CN');
const direction = result.trading_suggestion?.direction || '观望';
const directionClass = direction === '做多' ? 'long' : direction === '做空' ? 'short' : 'neutral';
const directionIcon = direction === '做多' ? 'fa-arrow-up' : direction === '做空' ? 'fa-arrow-down' : 'fa-arrows-left-right';
const confidence = result.trading_suggestion?.confidence || 0;
const entryMin = result.trading_suggestion?.entry_range?.min || '--';
const entryMax = result.trading_suggestion?.entry_range?.max || '--';
const stopLoss = result.trading_suggestion?.stop_loss || '--';
const positionSize = result.trading_suggestion?.position_size || '--';
content.innerHTML = `
<div class="ai-analysis-result">
<div class="ai-summary">${result.summary || '暂无总结'}</div>
<div class="ai-suggestion-row">
<div class="ai-suggestion-direction ${directionClass}">
<i class="fas ${directionIcon}"></i>
<span style="font-weight:600;">${direction}</span>
</div>
<div class="ai-confidence">
<span>置信度</span>
<div class="ai-confidence-bar">
<div class="ai-confidence-fill" style="width: ${confidence}%"></div>
</div>
<span>${confidence}%</span>
</div>
</div>
<div class="ai-key-metrics">
<div class="ai-metric-item">
<span class="label">入场区间</span>
<span class="value">${entryMin}-${entryMax}</span>
</div>
<div class="ai-metric-item">
<span class="label">止损位</span>
<span class="value down">${stopLoss}</span>
</div>
<div class="ai-metric-item">
<span class="label">建议仓位</span>
<span class="value">${positionSize}</span>
</div>
<div class="ai-metric-item">
<span class="label">纪律评分</span>
<span class="value">${result.discipline_score?.total || '--'}/${result.discipline_score?.max || '11'}</span>
</div>
</div>
<div class="ai-timestamp">
<i class="fas fa-clock"></i> : ${timestamp}
<button class="ai-detail-btn" onclick="showAIDetailModal()" style="margin-left: 8px; background: none; border: 1px solid var(--border-color); padding: 4px 12px; border-radius: 4px; color: var(--cyan); cursor: pointer; font-size: 11px;">
查看详情 <i class="fas fa-external-link-alt"></i>
</button>
</div>
</div>
`;
}
function showAIDetailModal() {
if (!currentAIAnalysis) {
showToast('warning', '提示', '暂无AI分析数据');
return;
}
const result = currentAIAnalysis.result;
const modalBody = document.getElementById('ai-analysis-modal-body');
let fourDimensionalHTML = '';
const periods = ['60min', '30min', '15min'];
periods.forEach(period => {
const data = result.four_dimensional?.[period];
if (data) {
fourDimensionalHTML += `
<tr>
<td class="period-cell">${period}</td>
<td>
<div>趋势: ${data.macd?.trend || '--'}</div>
<div>位置: ${data.macd?.position || '--'}</div>
<div>柱状图: ${data.macd?.histogram || '--'}</div>
</td>
<td>
<div>状态: ${data.volume?.status || '--'}</div>
<div>量比: ${data.volume?.ratio || '--'}</div>
</td>
<td>
<div>K: ${data.kdj?.k || '--'} D: ${data.kdj?.d || '--'}</div>
<div>信号: ${data.kdj?.signal || '--'}</div>
<div>状态: ${data.kdj?.status || '--'}</div>
</td>
<td>${data.conclusion || '--'}</td>
</tr>
`;
}
});
let scenariosHTML = '';
const scenarios = result.scenario_plans || {};
const scenarioIcons = {
'breakthrough': 'fa-rocket',
'consolidation': 'fa-exchange-alt',
'reversal': 'fa-undo',
'news_impact': 'fa-newspaper'
};
Object.entries(scenarios).forEach(([key, scenario]) => {
scenariosHTML += `
<div class="scenario-card ${key}">
<div class="scenario-probability">${scenario.probability || 0}%</div>
<div class="scenario-action">${scenario.action || '--'}</div>
</div>
`;
});
let redLinesHTML = '';
if (result.red_lines_check?.violated?.length > 0) {
result.red_lines_check.violated.forEach(line => {
redLinesHTML += `<div class="red-line-item"><i class="fas fa-exclamation-circle"></i> ${line}</div>`;
});
} else {
redLinesHTML = '<div class="red-line-item pass"><i class="fas fa-check-circle"></i> 未触碰交易红线</div>';
}
let disciplineHTML = '';
const discipline = result.discipline_score?.details || {};
const disciplineLabels = {
'trend': '趋势',
'position': '位置',
'signal': '信号',
'risk': '风险',
'mindset': '心态'
};
Object.entries(disciplineLabels).forEach(([key, label]) => {
const isPass = discipline[key];
disciplineHTML += `
<div class="discipline-item ${isPass ? 'pass' : 'fail'}">
<i class="fas ${isPass ? 'fa-check-circle' : 'fa-times-circle'}"></i>
<span class="discipline-label">${label}</span>
</div>
`;
});
let experiencesHTML = '';
const experiences = result.experience_lessons || [];
if (experiences.length > 0) {
experiences.forEach(exp => {
experiencesHTML += `<div class="experience-item"><i class="fas fa-lightbulb"></i> ${exp}</div>`;
});
} else {
experiencesHTML = '<div class="experience-item"><i class="fas fa-info-circle"></i> 暂无经验提醒</div>';
}
modalBody.innerHTML = `
<div class="ai-modal-section">
<div class="ai-modal-section-title">
<i class="fas fa-chart-bar"></i>
四维联合信号表 (4D-XV)
</div>
<table class="four-dimensional-table">
<thead>
<tr>
<th>周期</th>
<th>MACD(趋势)</th>
<th>成交量(资金)</th>
<th>KDJ(时机)</th>
<th>结论</th>
</tr>
</thead>
<tbody>
${fourDimensionalHTML || '<tr><td colspan="5">暂无数据</td></tr>'}
</tbody>
</table>
</div>
<div class="ai-modal-section">
<div class="ai-modal-section-title">
<i class="fas fa-crosshairs"></i>
关键点位 (Pivot Point)
</div>
<div class="ai-key-metrics">
<div class="ai-metric-item">
<span class="label">R2</span>
<span class="value down">${result.pivot_points?.r2 || '--'}</span>
</div>
<div class="ai-metric-item">
<span class="label">R1</span>
<span class="value down">${result.pivot_points?.r1 || '--'}</span>
</div>
<div class="ai-metric-item">
<span class="label">PP</span>
<span class="value" style="color: var(--purple);">${result.pivot_points?.pp || '--'}</span>
</div>
<div class="ai-metric-item">
<span class="label">S1</span>
<span class="value up">${result.pivot_points?.s1 || '--'}</span>
</div>
<div class="ai-metric-item">
<span class="label">S2</span>
<span class="value up">${result.pivot_points?.s2 || '--'}</span>
</div>
</div>
</div>
<div class="ai-modal-section">
<div class="ai-modal-section-title">
<i class="fas fa-balance-scale"></i>
交易红线审查
</div>
<div class="red-lines-list">
${redLinesHTML}
</div>
</div>
<div class="ai-modal-section">
<div class="ai-modal-section-title">
<i class="fas fa-check-double"></i>
11项纪律检查
</div>
<div class="discipline-grid">
${disciplineHTML}
</div>
</div>
<div class="ai-modal-section">
<div class="ai-modal-section-title">
<i class="fas fa-project-diagram"></i>
情景预案
</div>
<div class="scenario-cards">
${scenariosHTML || '<div class="scenario-card"><div class="scenario-action">暂无情景预案</div></div>'}
</div>
</div>
<div class="ai-modal-section">
<div class="ai-modal-section-title">
<i class="fas fa-lightbulb"></i>
经验教训提醒
</div>
<div class="ai-experience-list">
${experiencesHTML}
</div>
</div>
<div class="ai-modal-section">
<div class="ai-modal-section-title">
<i class="fas fa-exclamation-triangle"></i>
风险提示
</div>
<div class="ai-experience-list">
${(result.risk_warnings || []).map(w => `<div class="experience-item"><i class="fas fa-exclamation-circle"></i> ${w}</div>`).join('')}
</div>
</div>
`;
document.getElementById('ai-analysis-modal').classList.add('active');
}

@ -0,0 +1,55 @@
import sqlite3
import os
from pathlib import Path
db_path = Path(__file__).parent / "data" / "futures_analysis.db"
print("=" * 60)
print("数据库信息检查")
print("=" * 60)
print(f"数据库路径: {db_path}")
print(f"文件存在: {db_path.exists()}")
if db_path.exists():
print(f"文件大小: {db_path.stat().st_size} bytes")
print(f"最后修改: {db_path.stat().st_mtime}")
conn = sqlite3.connect(str(db_path))
cursor = conn.cursor()
# 获取所有表
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
print(f"\n数据库表 ({len(tables)}):")
for table in tables:
table_name = table[0]
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
count = cursor.fetchone()[0]
print(f" - {table_name}: {count} 条记录")
# 检查AI模型配置
print("\n" + "=" * 60)
print("AI模型配置检查")
print("=" * 60)
try:
cursor.execute("SELECT id, provider, model_name, is_active, enabled FROM ai_model_configs")
models = cursor.fetchall()
print(f"AI模型数量: {len(models)}")
if models:
for m in models:
print(f" ID: {m[0]}")
print(f" Provider: {m[1]}")
print(f" Model Name: {m[2]}")
print(f" Active: {m[3]}")
print(f" Enabled: {m[4]}")
print()
else:
print(" 没有配置AI模型!")
except sqlite3.OperationalError as e:
print(f" 查询失败: {e}")
conn.close()
else:
print("数据库文件不存在!")
print("=" * 60)

@ -0,0 +1,201 @@
# 📋 AI 期货/股票四维联合分析系统 (Prompt 模板)
> **版本**: v2.0 (全量实战版)
> **核心架构**: 四维联合判断 (4D-XV) + 17 条交易红线 + 多周期共振 + 动态策略切换
> **适用场景**: 期货/股票技术分析、日内/短线交易决策、交易纪律审查
---
## 🧠 角色与任务设定 (System Prompt)
```text
你是一位拥有 20 年实战经验的资深金融交易分析师,精通 A 股市场与商品期货的技术分析。
你的核心使命是基于提供的 K 线数据,执行**「四维联合判断分析法 (4D-XV)」**,并提供包含风控红线审查的客观交易策略。
你的分析必须遵循以下原则:
1. **数据驱动**:所有结论必须基于输入的 JSON 数据,严禁凭空捏造。
2. **四维共振**:任何交易建议必须经过 MACD趋势、成交量资金、KDJ时机、多周期方向的交叉验证。
3. **红线否决**如果数据触发「17 条交易红线」,必须直接给出「禁止交易」或「止损」的建议。
4. **客观中立**:不使用绝对化表述,提供情景预案(概率估算)。
5. **动态切换**:遇到关键位放量突破时,立即切换右侧思维,不逆势死扛。
```
---
## 📥 输入数据规范 (JSON)
AI 模型接收的输入数据必须包含以下结构:
```json
{
"symbol": "AG2606",
"current_price": 7200,
"timeframes": {
"60min": {
"trend": "down",
"ma10": 7250, "ma20": 7300,
"macd_dif": -5.2, "macd_dea": -3.1, "macd_histogram": -2.1,
"kdj_k": 85, "kdj_d": 80, "kdj_j": 95,
"volume_avg": 150000, "volume_current": 200000,
"candles": [{"time": "...", "open": 0, "high": 0, "low": 0, "close": 0, "volume": 0}]
},
"30min": { "... 结构同上 ..." },
"15min": { "... 结构同上 ..." },
"5min": { "... 结构同上 ..." },
"daily": { "... 结构同上 ..." }
},
"fundamentals": "近期库存持续下降,宏观情绪偏暖"
}
```
---
## 🛠️ 核心分析引擎 (执行步骤)
请在代码中要求 AI 严格按以下步骤执行分析:
### 第一步:四维联合信号扫描 (4D-XV)
**60m/30m/15m** 周期进行四维交叉验证,填写诊断表:
| 周期 | MACD(趋势锚点) | 成交量(资金验证) | KDJ(时机狙击) | 四维综合结论 |
|------|----------------|------------------|---------------|--------------|
| **60m** | 零轴上/下,柱状图放大/缩小/背离 | 趋势量能(放量上涨/缩量回调等) | K 值位置,金死叉状态,是否钝化 | 定大势(只做多/只做空/震荡) |
| **30m** | 零轴上/下,柱状图放大/缩小 | 拐点量能(突破/跌破量比≥1.5 | K 值位置,金死叉状态 | 找拐点(验证短期转折) |
| **15m** | 零轴上/下,柱状图放大/缩小 | 入场量能即时量比≥1.5/2.0 | K 值位置,金死叉状态 | 择入场(精确触发点) |
**判定标准**
* ✅ **完美共振**3 维度以上同向 + 量能确认 → 强信号胜率≥80%
* ⚠️ **弱势/矛盾**:指标矛盾或缺少量能验证 → 震荡/观望/轻仓
* ❌ **逆势/背离**量价背离、MACD 与 KDJ 冲突 → **禁止入场**
### 第二步KDJ 深度诊断
* **区域判断**:超买 (>80)、超卖 (<20)、中性区。
* **信号确认**:金叉/死叉有效性(需等待第二次确认,防假信号)。
* **钝化检查**:是否持续超买/超卖 >6 根 K 线?若是,**放弃 KDJ 逆势信号,改用 MACD 趋势跟踪**。
* **背离识别**KDJ 顶/底背离 + MACD 同步背离 = 极高胜率反转信号。
* **参数建议**:日线 (9,3,3)、60m (9,3,3)、30m (6,3,3)、15m (5,3,3)。
### 第三步:关键位计算与验证 (Pivot Point)
使用最近一根完整 K 线的 H、L、C 计算:
`PP = (H+L+C)/3`, `R1 = 2*PP-L`, `S1 = 2*PP-H`, `R2 = PP+(H-L)`, `S2 = PP-(H-L)`
**关键位五维度验证**
1. 测试次数 ≥3 次?
2. 量价配合良好(无量支撑为虚位)?
3. 多周期共振(大周期关键位权重更高)?
4. 基本面匹配(成本线、政策线)?
5. 突破后回踩确认?
### 第四步:交易红线审查 (17 条)
**检查数据是否触碰以下红线(触碰即停)**
* [ ] 逆势重仓
* [ ] 移动止损/加仓摊平
* [ ] 情绪化交易/报复交易
* [ ] 放量突破死扛逆势单(最高红线)
* [ ] 22:00 后开仓/14:30 后隔夜决策
* [ ] 无衰竭信号入场/追涨杀跌
* [ ] 五维度评分 < 9 < 5
### 第五步:动态策略切换 (Plan B)
* **场景**:价格逼近关键阻力位,原判断受阻回落,但出现**放量突破**。
* **验证**15m/30m 成交量 > 近 5 日均量 200%,且伴随实体饱满大阳线。
* **动作**:若持有逆势单(如空单),价格站稳 15 分钟 → **无条件市价止损**,绝不等回调。
* **思维**:立即切换为右侧跟随思维,回踩阻力位(转支撑)时尝试做多。
---
## 📝 输出模板 (Markdown)
要求 AI 严格按照以下格式输出:
```markdown
## 📊 {symbol} 四维联合分析报告
**分析时间**{当前时间} | **当前价**{current_price}
### 一、四维联合信号表 (4D-XV)
| 周期 | MACD(趋势) | 成交量(资金) | KDJ(时机) | 结论 |
|------|------------|-------------|-----------|------|
| 60m | ... | ... | ... | ... |
| 30m | ... | ... | ... | ... |
| 15m | ... | ... | ... | ... |
### 二、KDJ 深度诊断
- **当前状态**...
- **钝化/背离**...
- **参数建议**...
### 三、关键位 (Pivot Point)
- 🔴 **R2**: ... | **R1**: ...
- ⚪ **PP**: ...
- 🟢 **S1**: ... | **S2**: ...
- **有效性验证**(测试次数、量能、周期共振情况)
### 四、11 项纪律检查与红线审查
- **红线检查**:✅ 未触碰 / ❌ 触碰 [具体红线]
- **纪律得分**__ / 11 (≥9 可入场)
- **诊断表**
- A. 趋势 (60m 大势/30m 结构/15m 协同): [✅/❌]
- B. 位置 (关键位/共振): [✅/❌]
- C. 信号 (K 线/量能/背离): [✅/❌]
- D. 风险 (盈亏比/仓位): [✅/❌]
- E. 心态: [✅/❌]
### 五、交易建议
- **方向**:做多 / 做空 / 观望
- **入场区间**...
- **止损位**... (依据:前低/高/关键位)
- **止盈目标**... (盈亏比 ≥ 1:2)
- **建议仓位**... (基于信号强度)
### 六、动态策略预案 (Plan B)
1. **顺势突破 (35-45%)**:放量突破关键位,跟随右侧思维,移动止损。
2. **震荡拉锯 (30-40%)**R1-S1 区间内高抛低吸,突破前观望。
3. **假突破反转 (15-25%)**MACD 顶/底背离 + 量能不足,立即止损反手。
4. **突发消息 (5-10%)**:减仓 50% 规避不确定性。
### ⚠️ 风险提示与经验教训
- **技术局限**:技术指标具有滞后性,历史表现不代表未来。
- **避坑提醒**(根据当前盘面自动提示,如"警惕缩量创新高"、"KDJ 超买钝化中不宜逆势做空"等)
- **红线警告**(如触发红线,在此处高亮警告)
- **免责声明**:本分析仅供参考,不构成投资建议,交易需自负盈亏。
```
---
## 💡 经验教训总结 (供 AI 内部参考)
在分析过程中AI 应时刻检索以下**历史经验教训库**,若数据特征匹配,必须在报告中提示:
| 陷阱名称 | 特征识别 | 应对经验教训 |
|----------|----------|--------------|
| **唯 MACD 论** | MACD 金叉但 KDJ 超买 | 往往是诱多,**禁止单独使用 MACD 入场**。 |
| **唯 KDJ 论** | KDJ 金叉但 MACD 零轴下死叉 | 通常是下跌中继(假反弹),**必须等待 MACD 确认**。 |
| **无量突破** | KDJ+MACD 共振但无量 | 假突破概率 > 80%**放弃或极轻仓试错**。 |
| **大周期否决** | 日线/60m 明确空头 | 小周期任何做多信号均为短线反弹,**仓位必须减半**。 |
| **钝化区逆势** | KDJ 超买但价格继续涨 | 不逆势等钝化解除KDJ 离开超买区 + 价格反向突破)。 |
| **假金叉/死叉** | 金叉后快速死叉 | 等待**第二次确认**,或结合 30m 级别过滤。 |
| **背离假信号** | 多次背离后价格继续原方向 | 背离不是入场理由,**等待价格突破关键位确认**。 |
| **放量突破死扛** | 价格放量突破阻力,仍持有逆势空单 | **最高红线!立即市价止损,绝不等回调。** |
---
## 📦 附录:精简版 Prompt (上下文窗口有限时使用)
```text
你是一位专业交易分析师。请基于以下 JSON 数据,执行【四维联合判断 (4D-XV)】分析。
分析逻辑:
1. MACD 定趋势KDJ 择时机,成交量验证,多周期 (60m→30m→15m) 共振。
2. 严格审查「17 条交易红线」,触碰即禁止交易。
3. 若出现放量突破关键位,执行 Plan B右侧跟随不逆势死扛
输出内容:
1. 四维信号表 (MACD/量/KDJ/结论)。
2. Pivot Point 关键位及有效性验证。
3. 11 项纪律检查评分。
4. 交易建议 (方向/入场/止损/止盈/仓位)。
5. 情景预案 (5 种情景 + 概率)。
6. 风险提示与避坑提醒 (结合经验教训库)。
数据:{{JSON_DATA}}
```

Binary file not shown.

@ -0,0 +1,24 @@
"""
手动初始化期货智析数据库
用于在应用外部创建数据库表
"""
import sys
from pathlib import Path
# 添加项目根目录到 Python 路径
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from app.analysis_db import init_analysis_db
if __name__ == "__main__":
print("开始初始化期货智析数据库...")
init_analysis_db()
print("✅ 数据库初始化完成!")
# 验证数据库文件是否创建
from app.analysis_db import ANALYSIS_DB_PATH
if ANALYSIS_DB_PATH.exists():
print(f"✅ 数据库文件已创建: {ANALYSIS_DB_PATH}")
else:
print(f"❌ 数据库文件未创建: {ANALYSIS_DB_PATH}")

@ -0,0 +1,27 @@
"""
验证数据库表是否正确创建
"""
import sqlite3
db_path = "data/futures_analysis.db"
conn = sqlite3.connect(db_path)
cursor = conn.execute('SELECT name FROM sqlite_master WHERE type="table"')
print("✅ 数据库表列表:")
for row in cursor:
print(f" - {row[0]}")
# 检查 ai_analysis_cache 表是否存在
cursor.execute('SELECT name FROM sqlite_master WHERE type="table" AND name="ai_analysis_cache"')
if cursor.fetchone():
print("\n✅ ai_analysis_cache 表已存在!")
# 显示表结构
cursor.execute("PRAGMA table_info(ai_analysis_cache)")
print("\n表结构:")
for col in cursor:
print(f" {col[1]} ({col[2]})")
else:
print("\n❌ ai_analysis_cache 表不存在!")
conn.close()
Loading…
Cancel
Save