a
This commit is contained in:
parent
dee252fadb
commit
186b2f2424
|
|
@ -3,7 +3,7 @@ FastAPI应用主入口
|
|||
"""
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from api.routes import config, trades, stats, dashboard
|
||||
from api.routes import config, trades, stats, dashboard, account
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
|
@ -45,6 +45,7 @@ app.include_router(config.router, prefix="/api/config", tags=["配置管理"])
|
|||
app.include_router(trades.router, prefix="/api/trades", tags=["交易记录"])
|
||||
app.include_router(stats.router, prefix="/api/stats", tags=["统计分析"])
|
||||
app.include_router(dashboard.router, prefix="/api/dashboard", tags=["仪表板"])
|
||||
app.include_router(account.router, prefix="/api/account", tags=["账户数据"])
|
||||
|
||||
|
||||
@app.get("/")
|
||||
|
|
|
|||
|
|
@ -40,12 +40,29 @@ async def get_performance_stats(days: int = Query(7, ge=1, le=365)):
|
|||
async def get_dashboard_data():
|
||||
"""获取仪表板数据"""
|
||||
try:
|
||||
# 最近的账户快照
|
||||
snapshots = AccountSnapshot.get_recent(1)
|
||||
latest_snapshot = snapshots[0] if snapshots else None
|
||||
account_data = None
|
||||
|
||||
# 最近的交易
|
||||
recent_trades = Trade.get_all(status='open')[:10]
|
||||
# 优先尝试获取实时账户数据
|
||||
try:
|
||||
from api.routes.account import get_realtime_account_data
|
||||
account_data = await get_realtime_account_data()
|
||||
except Exception as e:
|
||||
logger.warning(f"获取实时账户数据失败,使用数据库快照: {e}")
|
||||
# 回退到数据库快照
|
||||
snapshots = AccountSnapshot.get_recent(1)
|
||||
account_data = snapshots[0] if snapshots else None
|
||||
|
||||
# 获取持仓数据(优先实时,回退到数据库)
|
||||
open_trades = []
|
||||
try:
|
||||
from api.routes.account import get_realtime_positions
|
||||
positions = await get_realtime_positions()
|
||||
# 转换为前端需要的格式
|
||||
open_trades = positions
|
||||
except Exception as e:
|
||||
logger.warning(f"获取实时持仓失败,使用数据库记录: {e}")
|
||||
# 回退到数据库记录
|
||||
open_trades = Trade.get_all(status='open')[:10]
|
||||
|
||||
# 最近的扫描记录
|
||||
recent_scans = MarketScan.get_recent(10)
|
||||
|
|
@ -54,10 +71,11 @@ async def get_dashboard_data():
|
|||
recent_signals = TradingSignal.get_recent(20)
|
||||
|
||||
return {
|
||||
"account": latest_snapshot,
|
||||
"open_trades": recent_trades,
|
||||
"account": account_data,
|
||||
"open_trades": open_trades,
|
||||
"recent_scans": recent_scans,
|
||||
"recent_signals": recent_signals
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取仪表板数据失败: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
|
|
|||
|
|
@ -75,6 +75,16 @@
|
|||
padding: 0.75rem;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.trade-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.85rem;
|
||||
color: #666;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.trade-symbol {
|
||||
|
|
|
|||
|
|
@ -70,14 +70,27 @@ const StatsDashboard = () => {
|
|||
<h3>当前持仓</h3>
|
||||
{openTrades.length > 0 ? (
|
||||
<div className="trades-list">
|
||||
{openTrades.map(trade => (
|
||||
<div key={trade.id} className="trade-item">
|
||||
{openTrades.map((trade, index) => (
|
||||
<div key={trade.id || trade.symbol || index} className="trade-item">
|
||||
<div className="trade-symbol">{trade.symbol}</div>
|
||||
<div className={`trade-side ${trade.side === 'BUY' ? 'buy' : 'sell'}`}>
|
||||
{trade.side}
|
||||
</div>
|
||||
<div className="trade-pnl">
|
||||
{parseFloat(trade.pnl).toFixed(2)} USDT
|
||||
<div className="trade-info">
|
||||
<div>数量: {parseFloat(trade.quantity || 0).toFixed(4)}</div>
|
||||
<div>入场价: {parseFloat(trade.entry_price || 0).toFixed(4)}</div>
|
||||
{trade.mark_price && (
|
||||
<div>标记价: {parseFloat(trade.mark_price).toFixed(4)}</div>
|
||||
)}
|
||||
{trade.leverage && (
|
||||
<div>杠杆: {trade.leverage}x</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={`trade-pnl ${parseFloat(trade.pnl || 0) >= 0 ? 'positive' : 'negative'}`}>
|
||||
{parseFloat(trade.pnl || 0).toFixed(2)} USDT
|
||||
{trade.pnl_percent !== undefined && (
|
||||
<span> ({parseFloat(trade.pnl_percent).toFixed(2)}%)</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,111 @@
|
|||
"""
|
||||
配置文件 - API密钥和交易参数配置
|
||||
支持从数据库读取配置(优先),回退到环境变量和默认值
|
||||
支持动态重载配置
|
||||
"""
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
# 尝试从数据库加载配置
|
||||
USE_DB_CONFIG = False
|
||||
try:
|
||||
import sys
|
||||
from pathlib import Path
|
||||
# 从trading_system目录向上两级到项目根目录,然后找backend
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / 'backend'
|
||||
if backend_path.exists():
|
||||
sys.path.insert(0, str(backend_path))
|
||||
from config_manager import config_manager
|
||||
USE_DB_CONFIG = True
|
||||
else:
|
||||
_config_manager = None
|
||||
|
||||
def _init_config_manager():
|
||||
"""初始化配置管理器"""
|
||||
global USE_DB_CONFIG, _config_manager
|
||||
if _config_manager is not None:
|
||||
return _config_manager
|
||||
|
||||
try:
|
||||
import sys
|
||||
from pathlib import Path
|
||||
# 从trading_system目录向上两级到项目根目录,然后找backend
|
||||
project_root = Path(__file__).parent.parent
|
||||
backend_path = project_root / 'backend'
|
||||
if backend_path.exists():
|
||||
sys.path.insert(0, str(backend_path))
|
||||
from config_manager import config_manager
|
||||
_config_manager = config_manager
|
||||
USE_DB_CONFIG = True
|
||||
return config_manager
|
||||
else:
|
||||
USE_DB_CONFIG = False
|
||||
return None
|
||||
except Exception as e:
|
||||
USE_DB_CONFIG = False
|
||||
except Exception:
|
||||
USE_DB_CONFIG = False
|
||||
return None
|
||||
|
||||
# 币安API配置(优先从数据库,回退到环境变量和默认值)
|
||||
if USE_DB_CONFIG:
|
||||
BINANCE_API_KEY: Optional[str] = config_manager.get('BINANCE_API_KEY') or os.getenv('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN')
|
||||
BINANCE_API_SECRET: Optional[str] = config_manager.get('BINANCE_API_SECRET') or os.getenv('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2')
|
||||
USE_TESTNET: bool = config_manager.get('USE_TESTNET', False) if config_manager.get('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true'
|
||||
else:
|
||||
BINANCE_API_KEY: Optional[str] = os.getenv('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN')
|
||||
BINANCE_API_SECRET: Optional[str] = os.getenv('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2')
|
||||
USE_TESTNET: bool = os.getenv('USE_TESTNET', 'False').lower() == 'true'
|
||||
# 初始化配置管理器
|
||||
_init_config_manager()
|
||||
|
||||
# 交易参数配置(优先从数据库读取)
|
||||
if USE_DB_CONFIG:
|
||||
TRADING_CONFIG = config_manager.get_trading_config()
|
||||
else:
|
||||
TRADING_CONFIG = {
|
||||
# 仓位控制
|
||||
'MAX_POSITION_PERCENT': 0.05, # 单笔最大仓位:账户余额的5%
|
||||
'MAX_TOTAL_POSITION_PERCENT': 0.30, # 总仓位上限:账户余额的30%
|
||||
'MIN_POSITION_PERCENT': 0.01, # 单笔最小仓位:账户余额的1%
|
||||
|
||||
# 涨跌幅阈值
|
||||
'MIN_CHANGE_PERCENT': 2.0, # 最小涨跌幅阈值:2%
|
||||
'TOP_N_SYMBOLS': 10, # 选择前N个货币对
|
||||
|
||||
# 风险控制
|
||||
'STOP_LOSS_PERCENT': 0.03, # 止损:3%
|
||||
'TAKE_PROFIT_PERCENT': 0.05, # 止盈:5%
|
||||
|
||||
# 市场扫描(1小时主周期)
|
||||
'SCAN_INTERVAL': 3600, # 扫描间隔:1小时(秒)
|
||||
'KLINE_INTERVAL': '1h', # K线周期:1小时
|
||||
'PRIMARY_INTERVAL': '1h', # 主周期:1小时
|
||||
'CONFIRM_INTERVAL': '4h', # 确认周期:4小时
|
||||
'ENTRY_INTERVAL': '15m', # 入场周期:15分钟
|
||||
|
||||
# 过滤条件
|
||||
'MIN_VOLUME_24H': 10000000, # 最小24小时成交量:1000万USDT
|
||||
'MIN_VOLATILITY': 0.02, # 最小波动率:2%
|
||||
|
||||
# 高胜率策略参数
|
||||
'MIN_SIGNAL_STRENGTH': 5, # 最小信号强度(0-10),越高越严格,胜率越高
|
||||
'LEVERAGE': 10, # 杠杆倍数
|
||||
'USE_TRAILING_STOP': True, # 是否使用移动止损
|
||||
'TRAILING_STOP_ACTIVATION': 0.01, # 移动止损激活阈值(盈利1%后激活)
|
||||
'TRAILING_STOP_PROTECT': 0.01, # 移动止损保护利润(保护1%利润)
|
||||
|
||||
# Unicorn WebSocket配置
|
||||
'USE_UNICORN_WEBSOCKET': True, # 是否使用Unicorn WebSocket(高性能实时数据流)
|
||||
}
|
||||
def _get_config_value(key, default=None):
|
||||
"""获取配置值(支持动态重载)"""
|
||||
if _config_manager:
|
||||
_config_manager.reload() # 每次获取配置时重新加载
|
||||
return _config_manager.get(key, default) or os.getenv(key, default)
|
||||
return os.getenv(key, default)
|
||||
|
||||
# 如果使用数据库配置,确保包含所有必要的默认值
|
||||
if USE_DB_CONFIG:
|
||||
defaults = {
|
||||
def _get_trading_config():
|
||||
"""获取交易配置(支持动态重载)"""
|
||||
if _config_manager:
|
||||
_config_manager.reload() # 每次获取配置时重新加载
|
||||
return _config_manager.get_trading_config()
|
||||
# 回退到默认配置
|
||||
return {
|
||||
'MAX_POSITION_PERCENT': 0.05,
|
||||
'MAX_TOTAL_POSITION_PERCENT': 0.30,
|
||||
'MIN_POSITION_PERCENT': 0.01,
|
||||
'MIN_CHANGE_PERCENT': 2.0,
|
||||
'TOP_N_SYMBOLS': 10,
|
||||
'STOP_LOSS_PERCENT': 0.03,
|
||||
'TAKE_PROFIT_PERCENT': 0.05,
|
||||
'SCAN_INTERVAL': 3600,
|
||||
'KLINE_INTERVAL': '1h',
|
||||
'PRIMARY_INTERVAL': '1h',
|
||||
'CONFIRM_INTERVAL': '4h',
|
||||
'ENTRY_INTERVAL': '15m',
|
||||
'MIN_VOLUME_24H': 10000000,
|
||||
'MIN_VOLATILITY': 0.02,
|
||||
'MIN_SIGNAL_STRENGTH': 5,
|
||||
'LEVERAGE': 10,
|
||||
'USE_TRAILING_STOP': True,
|
||||
'TRAILING_STOP_ACTIVATION': 0.01,
|
||||
'TRAILING_STOP_PROTECT': 0.01,
|
||||
'USE_UNICORN_WEBSOCKET': True,
|
||||
}
|
||||
|
||||
# 币安API配置(优先从数据库,回退到环境变量和默认值)
|
||||
BINANCE_API_KEY: Optional[str] = _get_config_value('BINANCE_API_KEY', 'pMEXSgISMgpUIpGjyhikMXWQ7K7cCs1FFATyIvNIwWrUIQegoipVBskPUoUuvaVN')
|
||||
BINANCE_API_SECRET: Optional[str] = _get_config_value('BINANCE_API_SECRET', 'RklItVtBCjGV40mIquoSj78xlTGkdUxz0AFyTnsnuzSBfx776VG0S2Vw5BRLRRg2')
|
||||
USE_TESTNET: bool = _get_config_value('USE_TESTNET', False) if _get_config_value('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true'
|
||||
|
||||
# 交易参数配置(优先从数据库读取,支持动态重载)
|
||||
TRADING_CONFIG = _get_trading_config()
|
||||
|
||||
# 确保包含所有必要的默认值
|
||||
defaults = {
|
||||
'SCAN_INTERVAL': 3600,
|
||||
'KLINE_INTERVAL': '1h',
|
||||
'PRIMARY_INTERVAL': '1h',
|
||||
'CONFIRM_INTERVAL': '4h',
|
||||
'ENTRY_INTERVAL': '15m',
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if key not in TRADING_CONFIG:
|
||||
TRADING_CONFIG[key] = value
|
||||
|
||||
# 提供一个函数来重新加载配置
|
||||
def reload_config():
|
||||
"""重新加载配置(供外部调用)"""
|
||||
global TRADING_CONFIG, BINANCE_API_KEY, BINANCE_API_SECRET, USE_TESTNET, _config_manager
|
||||
_init_config_manager() # 重新初始化配置管理器
|
||||
if _config_manager:
|
||||
_config_manager.reload()
|
||||
BINANCE_API_KEY = _get_config_value('BINANCE_API_KEY', BINANCE_API_KEY)
|
||||
BINANCE_API_SECRET = _get_config_value('BINANCE_API_SECRET', BINANCE_API_SECRET)
|
||||
USE_TESTNET = _get_config_value('USE_TESTNET', False) if _get_config_value('USE_TESTNET') is not None else os.getenv('USE_TESTNET', 'False').lower() == 'true'
|
||||
TRADING_CONFIG = _get_trading_config()
|
||||
# 确保默认值
|
||||
for key, value in defaults.items():
|
||||
if key not in TRADING_CONFIG:
|
||||
TRADING_CONFIG[key] = value
|
||||
|
|
|
|||
|
|
@ -175,9 +175,18 @@ class TradingStrategy:
|
|||
except Exception as e:
|
||||
logger.debug(f"记录账户快照失败: {e}")
|
||||
|
||||
# 重新加载配置(确保使用最新配置)
|
||||
try:
|
||||
if hasattr(config, 'reload_config'):
|
||||
config.reload_config()
|
||||
logger.info("配置已重新加载,将使用最新配置进行下次扫描")
|
||||
except Exception as e:
|
||||
logger.debug(f"重新加载配置失败: {e}")
|
||||
|
||||
# 等待下次扫描
|
||||
logger.info(f"等待 {config.TRADING_CONFIG['SCAN_INTERVAL']} 秒后进行下次扫描...")
|
||||
await asyncio.sleep(config.TRADING_CONFIG['SCAN_INTERVAL'])
|
||||
scan_interval = config.TRADING_CONFIG.get('SCAN_INTERVAL', 3600)
|
||||
logger.info(f"等待 {scan_interval} 秒后进行下次扫描...")
|
||||
await asyncio.sleep(scan_interval)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"策略执行出错: {e}", exc_info=True)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user