a
This commit is contained in:
parent
dee252fadb
commit
186b2f2424
|
|
@ -3,7 +3,7 @@ FastAPI应用主入口
|
||||||
"""
|
"""
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
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
|
import os
|
||||||
from pathlib import Path
|
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(trades.router, prefix="/api/trades", tags=["交易记录"])
|
||||||
app.include_router(stats.router, prefix="/api/stats", tags=["统计分析"])
|
app.include_router(stats.router, prefix="/api/stats", tags=["统计分析"])
|
||||||
app.include_router(dashboard.router, prefix="/api/dashboard", tags=["仪表板"])
|
app.include_router(dashboard.router, prefix="/api/dashboard", tags=["仪表板"])
|
||||||
|
app.include_router(account.router, prefix="/api/account", tags=["账户数据"])
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,29 @@ async def get_performance_stats(days: int = Query(7, ge=1, le=365)):
|
||||||
async def get_dashboard_data():
|
async def get_dashboard_data():
|
||||||
"""获取仪表板数据"""
|
"""获取仪表板数据"""
|
||||||
try:
|
try:
|
||||||
# 最近的账户快照
|
account_data = None
|
||||||
snapshots = AccountSnapshot.get_recent(1)
|
|
||||||
latest_snapshot = snapshots[0] if snapshots else 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)
|
recent_scans = MarketScan.get_recent(10)
|
||||||
|
|
@ -54,10 +71,11 @@ async def get_dashboard_data():
|
||||||
recent_signals = TradingSignal.get_recent(20)
|
recent_signals = TradingSignal.get_recent(20)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"account": latest_snapshot,
|
"account": account_data,
|
||||||
"open_trades": recent_trades,
|
"open_trades": open_trades,
|
||||||
"recent_scans": recent_scans,
|
"recent_scans": recent_scans,
|
||||||
"recent_signals": recent_signals
|
"recent_signals": recent_signals
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
logger.error(f"获取仪表板数据失败: {e}", exc_info=True)
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,16 @@
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 4px;
|
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 {
|
.trade-symbol {
|
||||||
|
|
|
||||||
|
|
@ -70,14 +70,27 @@ const StatsDashboard = () => {
|
||||||
<h3>当前持仓</h3>
|
<h3>当前持仓</h3>
|
||||||
{openTrades.length > 0 ? (
|
{openTrades.length > 0 ? (
|
||||||
<div className="trades-list">
|
<div className="trades-list">
|
||||||
{openTrades.map(trade => (
|
{openTrades.map((trade, index) => (
|
||||||
<div key={trade.id} className="trade-item">
|
<div key={trade.id || trade.symbol || index} className="trade-item">
|
||||||
<div className="trade-symbol">{trade.symbol}</div>
|
<div className="trade-symbol">{trade.symbol}</div>
|
||||||
<div className={`trade-side ${trade.side === 'BUY' ? 'buy' : 'sell'}`}>
|
<div className={`trade-side ${trade.side === 'BUY' ? 'buy' : 'sell'}`}>
|
||||||
{trade.side}
|
{trade.side}
|
||||||
</div>
|
</div>
|
||||||
<div className="trade-pnl">
|
<div className="trade-info">
|
||||||
{parseFloat(trade.pnl).toFixed(2)} USDT
|
<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>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -1,86 +1,111 @@
|
||||||
"""
|
"""
|
||||||
配置文件 - API密钥和交易参数配置
|
配置文件 - API密钥和交易参数配置
|
||||||
支持从数据库读取配置(优先),回退到环境变量和默认值
|
支持从数据库读取配置(优先),回退到环境变量和默认值
|
||||||
|
支持动态重载配置
|
||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
# 尝试从数据库加载配置
|
# 尝试从数据库加载配置
|
||||||
USE_DB_CONFIG = False
|
USE_DB_CONFIG = False
|
||||||
try:
|
_config_manager = None
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
def _init_config_manager():
|
||||||
# 从trading_system目录向上两级到项目根目录,然后找backend
|
"""初始化配置管理器"""
|
||||||
project_root = Path(__file__).parent.parent
|
global USE_DB_CONFIG, _config_manager
|
||||||
backend_path = project_root / 'backend'
|
if _config_manager is not None:
|
||||||
if backend_path.exists():
|
return _config_manager
|
||||||
sys.path.insert(0, str(backend_path))
|
|
||||||
from config_manager import config_manager
|
try:
|
||||||
USE_DB_CONFIG = True
|
import sys
|
||||||
else:
|
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
|
USE_DB_CONFIG = False
|
||||||
except Exception:
|
return None
|
||||||
USE_DB_CONFIG = False
|
|
||||||
|
|
||||||
# 币安API配置(优先从数据库,回退到环境变量和默认值)
|
# 初始化配置管理器
|
||||||
if USE_DB_CONFIG:
|
_init_config_manager()
|
||||||
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'
|
|
||||||
|
|
||||||
# 交易参数配置(优先从数据库读取)
|
def _get_config_value(key, default=None):
|
||||||
if USE_DB_CONFIG:
|
"""获取配置值(支持动态重载)"""
|
||||||
TRADING_CONFIG = config_manager.get_trading_config()
|
if _config_manager:
|
||||||
else:
|
_config_manager.reload() # 每次获取配置时重新加载
|
||||||
TRADING_CONFIG = {
|
return _config_manager.get(key, default) or os.getenv(key, default)
|
||||||
# 仓位控制
|
return os.getenv(key, default)
|
||||||
'MAX_POSITION_PERCENT': 0.05, # 单笔最大仓位:账户余额的5%
|
|
||||||
'MAX_TOTAL_POSITION_PERCENT': 0.30, # 总仓位上限:账户余额的30%
|
|
||||||
'MIN_POSITION_PERCENT': 0.01, # 单笔最小仓位:账户余额的1%
|
|
||||||
|
|
||||||
# 涨跌幅阈值
|
def _get_trading_config():
|
||||||
'MIN_CHANGE_PERCENT': 2.0, # 最小涨跌幅阈值:2%
|
"""获取交易配置(支持动态重载)"""
|
||||||
'TOP_N_SYMBOLS': 10, # 选择前N个货币对
|
if _config_manager:
|
||||||
|
_config_manager.reload() # 每次获取配置时重新加载
|
||||||
# 风险控制
|
return _config_manager.get_trading_config()
|
||||||
'STOP_LOSS_PERCENT': 0.03, # 止损:3%
|
# 回退到默认配置
|
||||||
'TAKE_PROFIT_PERCENT': 0.05, # 止盈:5%
|
return {
|
||||||
|
'MAX_POSITION_PERCENT': 0.05,
|
||||||
# 市场扫描(1小时主周期)
|
'MAX_TOTAL_POSITION_PERCENT': 0.30,
|
||||||
'SCAN_INTERVAL': 3600, # 扫描间隔:1小时(秒)
|
'MIN_POSITION_PERCENT': 0.01,
|
||||||
'KLINE_INTERVAL': '1h', # K线周期:1小时
|
'MIN_CHANGE_PERCENT': 2.0,
|
||||||
'PRIMARY_INTERVAL': '1h', # 主周期:1小时
|
'TOP_N_SYMBOLS': 10,
|
||||||
'CONFIRM_INTERVAL': '4h', # 确认周期:4小时
|
'STOP_LOSS_PERCENT': 0.03,
|
||||||
'ENTRY_INTERVAL': '15m', # 入场周期:15分钟
|
'TAKE_PROFIT_PERCENT': 0.05,
|
||||||
|
|
||||||
# 过滤条件
|
|
||||||
'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(高性能实时数据流)
|
|
||||||
}
|
|
||||||
|
|
||||||
# 如果使用数据库配置,确保包含所有必要的默认值
|
|
||||||
if USE_DB_CONFIG:
|
|
||||||
defaults = {
|
|
||||||
'SCAN_INTERVAL': 3600,
|
'SCAN_INTERVAL': 3600,
|
||||||
'KLINE_INTERVAL': '1h',
|
'KLINE_INTERVAL': '1h',
|
||||||
'PRIMARY_INTERVAL': '1h',
|
'PRIMARY_INTERVAL': '1h',
|
||||||
'CONFIRM_INTERVAL': '4h',
|
'CONFIRM_INTERVAL': '4h',
|
||||||
'ENTRY_INTERVAL': '15m',
|
'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():
|
for key, value in defaults.items():
|
||||||
if key not in TRADING_CONFIG:
|
if key not in TRADING_CONFIG:
|
||||||
TRADING_CONFIG[key] = value
|
TRADING_CONFIG[key] = value
|
||||||
|
|
|
||||||
|
|
@ -175,9 +175,18 @@ class TradingStrategy:
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"记录账户快照失败: {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']} 秒后进行下次扫描...")
|
scan_interval = config.TRADING_CONFIG.get('SCAN_INTERVAL', 3600)
|
||||||
await asyncio.sleep(config.TRADING_CONFIG['SCAN_INTERVAL'])
|
logger.info(f"等待 {scan_interval} 秒后进行下次扫描...")
|
||||||
|
await asyncio.sleep(scan_interval)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"策略执行出错: {e}", exc_info=True)
|
logger.error(f"策略执行出错: {e}", exc_info=True)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user