|
|
"""
|
|
|
期货智析数据库 - 独立存储
|
|
|
"""
|
|
|
import logging
|
|
|
from pathlib import Path
|
|
|
from sqlalchemy import create_engine
|
|
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
|
|
from datetime import datetime
|
|
|
|
|
|
from app.mysql_database import MySQLSessionLocal
|
|
|
from app.storage_manager import get_storage_manager
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# 数据目录
|
|
|
DATA_DIR = Path(__file__).resolve().parent.parent / "data"
|
|
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
ANALYSIS_DB_PATH = DATA_DIR / "futures_analysis.db"
|
|
|
|
|
|
analysis_engine = create_engine(
|
|
|
f"sqlite:///{ANALYSIS_DB_PATH}",
|
|
|
connect_args={"check_same_thread": False},
|
|
|
pool_pre_ping=True,
|
|
|
)
|
|
|
|
|
|
AnalysisSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=analysis_engine)
|
|
|
AnalysisBase = declarative_base()
|
|
|
|
|
|
|
|
|
def get_analysis_db():
|
|
|
"""获取期货智析数据库会话。
|
|
|
|
|
|
根据 MySQL 可用性动态选择后端:
|
|
|
- MySQL 可用时返回 MySQL Session
|
|
|
- MySQL 不可用时返回 SQLite Session(兜底)
|
|
|
"""
|
|
|
storage = get_storage_manager()
|
|
|
|
|
|
if MySQLSessionLocal is not None and storage.check_mysql():
|
|
|
db = None
|
|
|
try:
|
|
|
db = MySQLSessionLocal()
|
|
|
yield db
|
|
|
except Exception as e:
|
|
|
logger.warning(f"创建 MySQL analysis session 失败,降级到 SQLite: {e}")
|
|
|
if db is not None:
|
|
|
try:
|
|
|
db.close()
|
|
|
except Exception:
|
|
|
pass
|
|
|
db = AnalysisSessionLocal()
|
|
|
yield db
|
|
|
finally:
|
|
|
if db is not None:
|
|
|
try:
|
|
|
db.close()
|
|
|
except Exception:
|
|
|
pass
|
|
|
else:
|
|
|
db = AnalysisSessionLocal()
|
|
|
try:
|
|
|
yield db
|
|
|
finally:
|
|
|
db.close()
|
|
|
|
|
|
|
|
|
def init_analysis_db():
|
|
|
"""初始化期货智析数据库表(SQLite 兜底)。"""
|
|
|
# 确保导入所有模型类,使其注册到 AnalysisBase
|
|
|
from app import analysis_models
|
|
|
# 直接导入 analysis_models 模块中的所有类
|
|
|
from app.analysis_models import (
|
|
|
FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings,
|
|
|
AIAnalysisCache, ReviewDate, SymbolRanking, TradingPlan,
|
|
|
SymbolScoreV2, TradingPlanV2, SectorHeat, ReviewPlanV2,
|
|
|
TradeRecord, TradeImportBatch,
|
|
|
TradePair, TradeReflection, DailyReflection, TradeTag, TradeRecordTag, TradeExperience,
|
|
|
TradeAIAnalysis,
|
|
|
)
|
|
|
AnalysisBase.metadata.create_all(bind=analysis_engine)
|
|
|
|
|
|
# 迁移:为已有表添加新增列(SQLite 不支持 IF NOT EXISTS,用异常处理)
|
|
|
_migrate_add_columns()
|
|
|
|
|
|
# 初始化预设标签
|
|
|
_init_preset_tags()
|
|
|
|
|
|
|
|
|
def init_analysis_mysql(mysql_engine):
|
|
|
"""初始化期货智析数据库表(MySQL)。"""
|
|
|
from app import analysis_models
|
|
|
from app.analysis_models import (
|
|
|
FuturesAnalysis, WatchedSymbol, AIModelConfig, AnalysisSettings,
|
|
|
AIAnalysisCache, ReviewDate, SymbolRanking, TradingPlan,
|
|
|
SymbolScoreV2, TradingPlanV2, SectorHeat, ReviewPlanV2,
|
|
|
TradeRecord, TradeImportBatch,
|
|
|
TradePair, TradeReflection, DailyReflection, TradeTag, TradeRecordTag, TradeExperience,
|
|
|
TradeAIAnalysis,
|
|
|
)
|
|
|
AnalysisBase.metadata.create_all(bind=mysql_engine)
|
|
|
logger.info("MySQL analysis 表结构初始化完成")
|
|
|
|
|
|
|
|
|
def _migrate_add_columns():
|
|
|
"""为已有表添加新增列(兼容旧数据库)"""
|
|
|
import sqlite3
|
|
|
conn = sqlite3.connect(str(ANALYSIS_DB_PATH))
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
migrations = [
|
|
|
("symbol_scores_v2", "data_date", "VARCHAR(16)"),
|
|
|
("review_plans_v2", "actual_data_date", "VARCHAR(16)"),
|
|
|
("review_plans_v2", "data_date_matches", "INTEGER"),
|
|
|
]
|
|
|
|
|
|
for table, column, col_type in migrations:
|
|
|
try:
|
|
|
cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}")
|
|
|
except sqlite3.OperationalError:
|
|
|
pass # 列已存在
|
|
|
|
|
|
conn.commit()
|
|
|
conn.close()
|
|
|
|
|
|
|
|
|
def _init_preset_tags():
|
|
|
"""初始化预设标签"""
|
|
|
from app.analysis_models import TradeTag
|
|
|
from sqlalchemy import inspect
|
|
|
|
|
|
session = AnalysisSessionLocal()
|
|
|
try:
|
|
|
# 检查表是否存在
|
|
|
inspector = inspect(session.get_bind())
|
|
|
if 'trade_tags' not in inspector.get_table_names():
|
|
|
return
|
|
|
|
|
|
# 检查是否已有预设标签
|
|
|
existing = session.query(TradeTag).filter_by(is_preset=True).first()
|
|
|
if existing:
|
|
|
return
|
|
|
|
|
|
preset_tags = [
|
|
|
# 操作类
|
|
|
TradeTag(name="追涨杀跌", category="operation", is_preset=True),
|
|
|
TradeTag(name="逆市操作", category="operation", is_preset=True),
|
|
|
TradeTag(name="提前入场", category="operation", is_preset=True),
|
|
|
TradeTag(name="延迟出场", category="operation", is_preset=True),
|
|
|
TradeTag(name="加仓过早", category="operation", is_preset=True),
|
|
|
TradeTag(name="减仓过晚", category="operation", is_preset=True),
|
|
|
# 心态类
|
|
|
TradeTag(name="情绪化交易", category="mindset", is_preset=True),
|
|
|
TradeTag(name="恐惧平仓", category="mindset", is_preset=True),
|
|
|
TradeTag(name="贪婪持仓", category="mindset", is_preset=True),
|
|
|
TradeTag(name="侥幸心理", category="mindset", is_preset=True),
|
|
|
# 纪律类
|
|
|
TradeTag(name="严格执行计划", category="discipline", is_preset=True),
|
|
|
TradeTag(name="违反止损", category="discipline", is_preset=True),
|
|
|
TradeTag(name="超仓交易", category="discipline", is_preset=True),
|
|
|
TradeTag(name="频繁交易", category="discipline", is_preset=True),
|
|
|
# 技术类
|
|
|
TradeTag(name="突破交易", category="technical", is_preset=True),
|
|
|
TradeTag(name="回调交易", category="technical", is_preset=True),
|
|
|
TradeTag(name="趋势跟踪", category="technical", is_preset=True),
|
|
|
TradeTag(name="震荡交易", category="technical", is_preset=True),
|
|
|
TradeTag(name="反转交易", category="technical", is_preset=True),
|
|
|
]
|
|
|
session.add_all(preset_tags)
|
|
|
session.commit()
|
|
|
except Exception:
|
|
|
session.rollback()
|
|
|
finally:
|
|
|
session.close()
|