import sqlite3 from datetime import datetime, timezone, timedelta import logging # 设置东八区时区 BEIJING_TZ = timezone(timedelta(hours=8)) def utc_to_beijing(utc_time_str): """将UTC时间字符串转换为东八区时间字符串""" try: # 解析UTC时间字符串 utc_dt = datetime.fromisoformat(utc_time_str.replace('Z', '+00:00')) if utc_dt.tzinfo is None: utc_dt = utc_dt.replace(tzinfo=timezone.utc) # 转换为东八区时间 beijing_dt = utc_dt.astimezone(BEIJING_TZ) return beijing_dt.strftime('%Y-%m-%d %H:%M:%S') except: return utc_time_str def get_utc_time(): """获取UTC时间""" return datetime.now(timezone.utc) class DatabaseManager: def __init__(self, db_path="trading.db"): self.db_path = db_path self.init_database() def init_database(self): """初始化数据库表结构""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # 币种基础信息表 cursor.execute(''' CREATE TABLE IF NOT EXISTS coins ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT UNIQUE NOT NULL, base_asset TEXT NOT NULL, quote_asset TEXT NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # K线数据表 cursor.execute(''' CREATE TABLE IF NOT EXISTS klines ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT NOT NULL, timeframe TEXT NOT NULL, open_time TIMESTAMP NOT NULL, close_time TIMESTAMP NOT NULL, open_price REAL NOT NULL, high_price REAL NOT NULL, low_price REAL NOT NULL, close_price REAL NOT NULL, volume REAL NOT NULL, quote_volume REAL NOT NULL, trades_count INTEGER, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(symbol, timeframe, open_time) ) ''') # 选币结果表 cursor.execute(''' CREATE TABLE IF NOT EXISTS coin_selections ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT NOT NULL, score REAL NOT NULL, reason TEXT NOT NULL, entry_price REAL NOT NULL, stop_loss REAL NOT NULL, take_profit REAL NOT NULL, timeframe TEXT NOT NULL, selection_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status TEXT DEFAULT 'active', actual_entry_price REAL, exit_price REAL, exit_time TIMESTAMP, pnl_percentage REAL, notes TEXT, strategy_type TEXT NOT NULL DEFAULT '中线', holding_period INTEGER NOT NULL DEFAULT 7, risk_reward_ratio REAL NOT NULL DEFAULT 2.0, expiry_time TIMESTAMP, is_expired BOOLEAN DEFAULT FALSE, action_suggestion TEXT DEFAULT '等待回调买入' ) ''') # 检查并添加新列(如果表已存在) try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN strategy_type TEXT NOT NULL DEFAULT '中线'") except: pass try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN holding_period INTEGER NOT NULL DEFAULT 7") except: pass try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN risk_reward_ratio REAL NOT NULL DEFAULT 2.0") except: pass try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN expiry_time TIMESTAMP") except: pass try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN is_expired BOOLEAN DEFAULT FALSE") except: pass try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN action_suggestion TEXT DEFAULT '等待回调买入'") except: pass # 添加多空支持字段 try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN signal_type TEXT DEFAULT 'LONG'") except: pass try: cursor.execute("ALTER TABLE coin_selections ADD COLUMN direction TEXT DEFAULT 'BUY'") except: pass # 技术指标表 cursor.execute(''' CREATE TABLE IF NOT EXISTS technical_indicators ( id INTEGER PRIMARY KEY AUTOINCREMENT, symbol TEXT NOT NULL, timeframe TEXT NOT NULL, indicator_time TIMESTAMP NOT NULL, ma20 REAL, ma50 REAL, ma200 REAL, rsi REAL, macd REAL, macd_signal REAL, macd_hist REAL, bb_upper REAL, bb_middle REAL, bb_lower REAL, volume_ma REAL, fib_618 REAL, fib_382 REAL, support_level REAL, resistance_level REAL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(symbol, timeframe, indicator_time) ) ''') # 创建索引 cursor.execute('CREATE INDEX IF NOT EXISTS idx_klines_symbol_time ON klines(symbol, timeframe, open_time)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_selections_symbol_time ON coin_selections(symbol, selection_time)') cursor.execute('CREATE INDEX IF NOT EXISTS idx_indicators_symbol_time ON technical_indicators(symbol, timeframe, indicator_time)') conn.commit() conn.close() logging.info("数据库初始化完成") def get_connection(self): """获取数据库连接""" return sqlite3.connect(self.db_path) def insert_coin_selection(self, symbol, score, reason, entry_price, stop_loss, take_profit, timeframe, strategy_type, holding_period, risk_reward_ratio, expiry_hours, action_suggestion, signal_type="LONG", direction="BUY"): """插入选币结果 - 支持多空方向""" conn = self.get_connection() cursor = conn.cursor() # 计算过期时间 expiry_time = datetime.now(timezone.utc) + timedelta(hours=expiry_hours) cursor.execute(''' INSERT INTO coin_selections (symbol, score, reason, entry_price, stop_loss, take_profit, timeframe, strategy_type, holding_period, risk_reward_ratio, expiry_time, action_suggestion, signal_type, direction) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (symbol, score, reason, entry_price, stop_loss, take_profit, timeframe, strategy_type, holding_period, risk_reward_ratio, expiry_time, action_suggestion, signal_type, direction)) selection_id = cursor.lastrowid conn.commit() conn.close() return selection_id def get_active_selections(self, limit=20, offset=0): """获取活跃的选币结果,自动标记过期的 - 支持分页""" conn = self.get_connection() cursor = conn.cursor() # 首先标记过期的选币 cursor.execute(''' UPDATE coin_selections SET is_expired = TRUE, status = 'expired' WHERE expiry_time < datetime('now') AND status = 'active' ''') cursor.execute(''' SELECT * FROM coin_selections WHERE status = 'active' AND is_expired = FALSE ORDER BY selection_time DESC, score DESC LIMIT ? OFFSET ? ''', (limit, offset)) results = cursor.fetchall() conn.commit() # 提交过期状态更新 conn.close() return results def check_and_expire_selections(self): """检查并标记过期的选币""" conn = self.get_connection() cursor = conn.cursor() cursor.execute(''' UPDATE coin_selections SET is_expired = TRUE, status = 'expired' WHERE expiry_time < datetime('now') AND status = 'active' ''') expired_count = cursor.rowcount conn.commit() conn.close() if expired_count > 0: logging.info(f"标记了{expired_count}个过期的选币结果") return expired_count def update_selection_status(self, selection_id, status, exit_price=None, pnl_percentage=None): """更新选币结果状态""" conn = self.get_connection() cursor = conn.cursor() if exit_price and pnl_percentage: cursor.execute(''' UPDATE coin_selections SET status = ?, exit_price = ?, exit_time = CURRENT_TIMESTAMP, pnl_percentage = ? WHERE id = ? ''', (status, exit_price, pnl_percentage, selection_id)) else: cursor.execute(''' UPDATE coin_selections SET status = ? WHERE id = ? ''', (status, selection_id)) conn.commit() conn.close()