266 lines
9.6 KiB
Python
266 lines
9.6 KiB
Python
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() |