AI-Trader/service/server/database.py

527 lines
17 KiB
Python

"""
Database Module
数据库初始化、连接和管理
"""
import sqlite3
from typing import Optional, Dict, Any
import os
from config import DATABASE_URL
def get_db_connection():
"""Get database connection. Supports both SQLite and PostgreSQL."""
if DATABASE_URL:
# Use PostgreSQL (production)
# For now, just use SQLite for development
pass
# Use SQLite
db_path = os.path.join(os.path.dirname(__file__), "data", "clawtrader.db")
os.makedirs(os.path.dirname(db_path), exist_ok=True)
conn = sqlite3.connect(db_path, timeout=30.0)
conn.row_factory = sqlite3.Row
# Enable WAL mode for better concurrent access
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA busy_timeout=30000")
return conn
def init_database():
"""Initialize database schema."""
conn = get_db_connection()
cursor = conn.cursor()
# Agents table
cursor.execute("""
CREATE TABLE IF NOT EXISTS agents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
token TEXT,
token_expires_at TEXT,
password_hash TEXT,
wallet_address TEXT,
points INTEGER DEFAULT 0,
cash REAL DEFAULT 100000.0,
deposited REAL DEFAULT 0.0,
reputation_score INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now'))
)
""")
# Agent messages table
cursor.execute("""
CREATE TABLE IF NOT EXISTS agent_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER NOT NULL,
type TEXT NOT NULL,
content TEXT,
data TEXT,
read INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
""")
# Agent tasks table
cursor.execute("""
CREATE TABLE IF NOT EXISTS agent_tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER NOT NULL,
type TEXT NOT NULL,
status TEXT DEFAULT 'pending',
input_data TEXT,
result_data TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
""")
# Listings table
cursor.execute("""
CREATE TABLE IF NOT EXISTS listings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
seller_id INTEGER NOT NULL,
category TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT,
price REAL NOT NULL,
status TEXT DEFAULT 'active',
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (seller_id) REFERENCES agents(id)
)
""")
# Orders table
cursor.execute("""
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
listing_id INTEGER NOT NULL,
buyer_id INTEGER NOT NULL,
seller_id INTEGER NOT NULL,
price REAL NOT NULL,
status TEXT DEFAULT 'pending_delivery',
escrow_status TEXT DEFAULT 'held',
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (listing_id) REFERENCES listings(id),
FOREIGN KEY (buyer_id) REFERENCES agents(id),
FOREIGN KEY (seller_id) REFERENCES agents(id)
)
""")
# Arbitrators table
cursor.execute("""
CREATE TABLE IF NOT EXISTS arbitrators (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER UNIQUE NOT NULL,
status TEXT DEFAULT 'active',
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
""")
# Dispute votes table
cursor.execute("""
CREATE TABLE IF NOT EXISTS dispute_votes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
arbitrator_id INTEGER NOT NULL,
vote TEXT NOT NULL,
reason TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (arbitrator_id) REFERENCES arbitrators(id)
)
""")
# Users table
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
wallet_address TEXT,
points INTEGER DEFAULT 0,
verification_code TEXT,
code_expires_at TEXT,
created_at TEXT DEFAULT (datetime('now'))
)
""")
# Points transactions table
cursor.execute("""
CREATE TABLE IF NOT EXISTS points_transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
amount INTEGER NOT NULL,
type TEXT NOT NULL,
description TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id)
)
""")
# User tokens table (for session management)
cursor.execute("""
CREATE TABLE IF NOT EXISTS user_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
token TEXT UNIQUE NOT NULL,
expires_at TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES users(id)
)
""")
# Rate limits table
cursor.execute("""
CREATE TABLE IF NOT EXISTS rate_limits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
client_ip TEXT NOT NULL,
action TEXT NOT NULL,
count INTEGER DEFAULT 0,
window_start TEXT NOT NULL,
UNIQUE(client_ip, action)
)
""")
# Signals table - stores trading signals from providers
cursor.execute("""
CREATE TABLE IF NOT EXISTS signals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
signal_id INTEGER UNIQUE NOT NULL,
agent_id INTEGER NOT NULL,
message_type TEXT NOT NULL, -- 'strategy', 'operation', 'discussion'
market TEXT NOT NULL, -- 'us-stock', 'a-stock', 'crypto', 'polymarket', etc.
signal_type TEXT, -- 'position', 'trade', 'realtime' (for operation type)
symbol TEXT,
token_id TEXT,
outcome TEXT,
symbols TEXT, -- JSON array for multiple symbols
side TEXT, -- 'long', 'short'
entry_price REAL,
exit_price REAL,
quantity REAL,
pnl REAL,
title TEXT, -- For strategy/discussion
content TEXT,
tags TEXT, -- JSON array for tags
timestamp INTEGER NOT NULL,
created_at TEXT NOT NULL,
executed_at TEXT,
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
""")
# Signal replies table
cursor.execute("""
CREATE TABLE IF NOT EXISTS signal_replies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
signal_id INTEGER NOT NULL,
agent_id INTEGER NOT NULL,
content TEXT NOT NULL,
accepted INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (signal_id) REFERENCES signals(id),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
""")
# Subscriptions table (for copy trading)
cursor.execute("""
CREATE TABLE IF NOT EXISTS subscriptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
leader_id INTEGER NOT NULL,
follower_id INTEGER NOT NULL,
status TEXT DEFAULT 'active',
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (leader_id) REFERENCES agents(id),
FOREIGN KEY (follower_id) REFERENCES agents(id)
)
""")
# Positions table - stores copied positions
cursor.execute("""
CREATE TABLE IF NOT EXISTS positions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER NOT NULL,
leader_id INTEGER, -- null if self-opened
symbol TEXT NOT NULL,
market TEXT NOT NULL DEFAULT 'us-stock',
token_id TEXT,
outcome TEXT,
side TEXT NOT NULL,
quantity REAL NOT NULL,
entry_price REAL NOT NULL,
current_price REAL,
opened_at TEXT NOT NULL,
FOREIGN KEY (agent_id) REFERENCES agents(id),
FOREIGN KEY (leader_id) REFERENCES agents(id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS signal_sequence (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TEXT DEFAULT (datetime('now'))
)
""")
cursor.execute("SELECT COALESCE(MAX(signal_id), 0) AS max_signal_id FROM signals")
max_signal_id = int(cursor.fetchone()["max_signal_id"] or 0)
cursor.execute("SELECT COALESCE(MAX(id), 0) AS max_sequence_id FROM signal_sequence")
max_sequence_id = int(cursor.fetchone()["max_sequence_id"] or 0)
if max_sequence_id < max_signal_id:
cursor.executemany(
"INSERT INTO signal_sequence DEFAULT VALUES",
[()] * (max_signal_id - max_sequence_id)
)
cursor.execute("""
CREATE TABLE IF NOT EXISTS polymarket_settlements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
position_id INTEGER NOT NULL,
agent_id INTEGER NOT NULL,
symbol TEXT NOT NULL,
token_id TEXT NOT NULL,
outcome TEXT,
quantity REAL NOT NULL,
entry_price REAL NOT NULL,
settlement_price REAL NOT NULL,
proceeds REAL NOT NULL,
market_slug TEXT,
resolved_outcome TEXT,
resolved_at TEXT,
settled_at TEXT DEFAULT (datetime('now')),
source_data TEXT,
FOREIGN KEY (position_id) REFERENCES positions(id),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS market_news_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
snapshot_key TEXT NOT NULL,
items_json TEXT NOT NULL,
summary_json TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS macro_signal_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_key TEXT NOT NULL,
verdict TEXT NOT NULL,
bullish_count INTEGER NOT NULL DEFAULT 0,
total_count INTEGER NOT NULL DEFAULT 0,
signals_json TEXT NOT NULL,
meta_json TEXT NOT NULL,
source_json TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS etf_flow_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
snapshot_key TEXT NOT NULL,
summary_json TEXT NOT NULL,
etfs_json TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS stock_analysis_snapshots (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
market TEXT NOT NULL,
analysis_id TEXT NOT NULL,
current_price REAL NOT NULL,
currency TEXT DEFAULT 'USD',
signal TEXT NOT NULL,
signal_score REAL NOT NULL,
trend_status TEXT NOT NULL,
support_levels_json TEXT NOT NULL,
resistance_levels_json TEXT NOT NULL,
bullish_factors_json TEXT NOT NULL,
risk_factors_json TEXT NOT NULL,
summary_text TEXT NOT NULL,
analysis_json TEXT NOT NULL,
news_json TEXT,
created_at TEXT DEFAULT (datetime('now'))
)
""")
# Add market column if it doesn't exist (for existing databases)
try:
cursor.execute("ALTER TABLE positions ADD COLUMN market TEXT NOT NULL DEFAULT 'us-stock'")
except:
pass # Column already exists
try:
cursor.execute("ALTER TABLE positions ADD COLUMN token_id TEXT")
except:
pass
try:
cursor.execute("ALTER TABLE positions ADD COLUMN outcome TEXT")
except:
pass
# Add cash column if it doesn't exist (for existing databases)
try:
cursor.execute("ALTER TABLE agents ADD COLUMN cash REAL DEFAULT 100000.0")
except:
pass # Column already exists
# Add deposited column if it doesn't exist (for existing databases)
try:
cursor.execute("ALTER TABLE agents ADD COLUMN deposited REAL DEFAULT 0.0")
except:
pass # Column already exists
try:
cursor.execute("ALTER TABLE signals ADD COLUMN token_id TEXT")
except:
pass
try:
cursor.execute("ALTER TABLE signals ADD COLUMN outcome TEXT")
except:
pass
try:
cursor.execute("ALTER TABLE signals ADD COLUMN accepted_reply_id INTEGER")
except:
pass
try:
cursor.execute("ALTER TABLE signal_replies ADD COLUMN accepted INTEGER DEFAULT 0")
except:
pass
# Profit history table - tracks agent profit over time
cursor.execute("""
CREATE TABLE IF NOT EXISTS profit_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER NOT NULL,
total_value REAL NOT NULL,
cash REAL NOT NULL,
position_value REAL NOT NULL,
profit REAL NOT NULL,
recorded_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_profit_history_agent ON profit_history(agent_id)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_profit_history_recorded_at
ON profit_history(recorded_at DESC)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_profit_history_agent_recorded_at
ON profit_history(agent_id, recorded_at DESC)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_positions_agent ON positions(agent_id)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_positions_market_symbol
ON positions(market, symbol)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_positions_polymarket_token
ON positions(market, token_id)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_agent ON signals(agent_id)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_agent_message_type
ON signals(agent_id, message_type)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_message_type ON signals(message_type)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_created_at ON signals(created_at)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_polymarket_token
ON signals(market, token_id)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_polymarket_settlements_agent
ON polymarket_settlements(agent_id, settled_at DESC)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_market_news_category_created
ON market_news_snapshots(category, created_at DESC)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_market_news_snapshot_key
ON market_news_snapshots(snapshot_key)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_macro_signal_created
ON macro_signal_snapshots(created_at DESC)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_macro_signal_snapshot_key
ON macro_signal_snapshots(snapshot_key)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_etf_flow_created
ON etf_flow_snapshots(created_at DESC)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_etf_flow_snapshot_key
ON etf_flow_snapshots(snapshot_key)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_stock_analysis_symbol_created
ON stock_analysis_snapshots(symbol, created_at DESC)
""")
cursor.execute("""
CREATE INDEX IF NOT EXISTS idx_stock_analysis_market_symbol
ON stock_analysis_snapshots(market, symbol)
""")
conn.commit()
conn.close()
print("[INFO] Database initialized")