update
This commit is contained in:
parent
17995c0a0b
commit
161f9feda0
@ -8,6 +8,7 @@ from pydantic import BaseModel
|
||||
|
||||
from app.services.paper_trading_service import get_paper_trading_service
|
||||
from app.services.price_monitor_service import get_price_monitor_service
|
||||
from app.services.binance_service import binance_service
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
@ -195,16 +196,55 @@ async def get_statistics_by_symbol():
|
||||
|
||||
@router.get("/monitor/status")
|
||||
async def get_monitor_status():
|
||||
"""获取价格监控状态"""
|
||||
"""获取价格监控状态和实时价格"""
|
||||
try:
|
||||
monitor = get_price_monitor_service()
|
||||
paper_trading = get_paper_trading_service()
|
||||
|
||||
# 获取活跃订单的交易对
|
||||
active_orders = paper_trading.get_active_orders()
|
||||
symbols_needed = set(order.get('symbol') for order in active_orders if order.get('symbol'))
|
||||
|
||||
# 获取价格 - 优先使用监控服务的缓存价格
|
||||
latest_prices = dict(monitor.latest_prices)
|
||||
|
||||
# 如果监控服务没有价格数据,直接从 Binance 获取
|
||||
for symbol in symbols_needed:
|
||||
if symbol not in latest_prices or latest_prices[symbol] is None:
|
||||
price = binance_service.get_current_price(symbol)
|
||||
if price:
|
||||
latest_prices[symbol] = price
|
||||
|
||||
# 注意:止盈止损检查由后台任务 (main.py price_monitor_loop) 处理
|
||||
# 这里只返回价格数据供前端显示
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"running": monitor.is_running(),
|
||||
"subscribed_symbols": monitor.get_subscribed_symbols(),
|
||||
"latest_prices": monitor.latest_prices
|
||||
"running": True, # 后台任务始终运行
|
||||
"subscribed_symbols": list(symbols_needed),
|
||||
"latest_prices": latest_prices
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"获取监控状态失败: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/reset")
|
||||
async def reset_paper_trading():
|
||||
"""
|
||||
重置所有模拟交易数据
|
||||
|
||||
警告:此操作将删除所有订单记录,不可恢复!
|
||||
"""
|
||||
try:
|
||||
service = get_paper_trading_service()
|
||||
result = service.reset_all_data()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"模拟交易数据已重置,删除 {result['deleted_count']} 条订单",
|
||||
"result": result
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"重置模拟交易数据失败: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
"""
|
||||
FastAPI主应用
|
||||
"""
|
||||
import asyncio
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
@ -12,13 +13,112 @@ from app.api import chat, stock, skills, llm, auth, admin, paper_trading
|
||||
import os
|
||||
|
||||
|
||||
# 后台价格监控任务
|
||||
_price_monitor_task = None
|
||||
|
||||
|
||||
async def price_monitor_loop():
|
||||
"""后台价格监控循环 - 检查止盈止损"""
|
||||
from app.services.paper_trading_service import get_paper_trading_service
|
||||
from app.services.binance_service import binance_service
|
||||
from app.services.feishu_service import get_feishu_service
|
||||
from app.services.telegram_service import get_telegram_service
|
||||
|
||||
logger.info("后台价格监控任务已启动")
|
||||
|
||||
while True:
|
||||
try:
|
||||
paper_trading = get_paper_trading_service()
|
||||
feishu = get_feishu_service()
|
||||
telegram = get_telegram_service()
|
||||
|
||||
# 获取活跃订单
|
||||
active_orders = paper_trading.get_active_orders()
|
||||
if not active_orders:
|
||||
await asyncio.sleep(10) # 没有活跃订单时,10秒检查一次
|
||||
continue
|
||||
|
||||
# 获取所有需要的交易对
|
||||
symbols = set(order.get('symbol') for order in active_orders if order.get('symbol'))
|
||||
|
||||
# 获取价格并检查止盈止损
|
||||
for symbol in symbols:
|
||||
try:
|
||||
price = binance_service.get_current_price(symbol)
|
||||
if not price:
|
||||
continue
|
||||
|
||||
# 检查止盈止损
|
||||
triggered = paper_trading.check_price_triggers(symbol, price)
|
||||
|
||||
# 发送通知
|
||||
for result in triggered:
|
||||
status = result.get('status', '')
|
||||
is_win = result.get('is_win', False)
|
||||
|
||||
if status == 'closed_tp':
|
||||
emoji = "🎯"
|
||||
status_text = "止盈平仓"
|
||||
elif status == 'closed_sl':
|
||||
emoji = "🛑"
|
||||
status_text = "止损平仓"
|
||||
else:
|
||||
emoji = "📤"
|
||||
status_text = "平仓"
|
||||
|
||||
win_text = "盈利" if is_win else "亏损"
|
||||
side_text = "做多" if result.get('side') == 'long' else "做空"
|
||||
|
||||
message = f"""{emoji} 订单{status_text}
|
||||
|
||||
交易对: {result.get('symbol')}
|
||||
方向: {side_text}
|
||||
入场: ${result.get('entry_price', 0):,.2f}
|
||||
出场: ${result.get('exit_price', 0):,.2f}
|
||||
{win_text}: {result.get('pnl_percent', 0):+.2f}% (${result.get('pnl_amount', 0):+.2f})
|
||||
持仓时间: {result.get('hold_duration', 'N/A')}"""
|
||||
|
||||
# 发送通知
|
||||
await feishu.send_text(message)
|
||||
await telegram.send_message(message)
|
||||
logger.info(f"后台监控触发平仓: {result.get('order_id')} | {symbol}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"检查 {symbol} 价格失败: {e}")
|
||||
|
||||
# 每 3 秒检查一次
|
||||
await asyncio.sleep(3)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"价格监控循环出错: {e}")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""应用生命周期管理"""
|
||||
global _price_monitor_task
|
||||
|
||||
# 启动时执行
|
||||
logger.info("应用启动")
|
||||
|
||||
# 启动后台价格监控任务
|
||||
settings = get_settings()
|
||||
if getattr(settings, 'paper_trading_enabled', True):
|
||||
_price_monitor_task = asyncio.create_task(price_monitor_loop())
|
||||
logger.info("后台价格监控任务已创建")
|
||||
|
||||
yield
|
||||
|
||||
# 关闭时执行
|
||||
if _price_monitor_task:
|
||||
_price_monitor_task.cancel()
|
||||
try:
|
||||
await _price_monitor_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
logger.info("后台价格监控任务已停止")
|
||||
|
||||
logger.info("应用关闭")
|
||||
|
||||
|
||||
|
||||
@ -442,6 +442,39 @@ class PaperTradingService:
|
||||
}
|
||||
return result
|
||||
|
||||
def reset_all_data(self) -> Dict[str, Any]:
|
||||
"""
|
||||
重置所有模拟交易数据
|
||||
|
||||
Returns:
|
||||
重置结果,包含删除的订单数量
|
||||
"""
|
||||
db = db_service.get_session()
|
||||
try:
|
||||
# 统计删除前的数量
|
||||
total_count = db.query(PaperOrder).count()
|
||||
active_count = len(self.active_orders)
|
||||
|
||||
# 删除所有订单
|
||||
db.query(PaperOrder).delete()
|
||||
db.commit()
|
||||
|
||||
# 清空内存缓存
|
||||
self.active_orders.clear()
|
||||
|
||||
logger.info(f"模拟交易数据已重置,删除 {total_count} 条订单")
|
||||
|
||||
return {
|
||||
'deleted_count': total_count,
|
||||
'active_orders_cleared': active_count
|
||||
}
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"重置模拟交易数据失败: {e}")
|
||||
raise
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# 全局单例
|
||||
_paper_trading_service: Optional[PaperTradingService] = None
|
||||
|
||||
@ -344,6 +344,21 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* 重置按钮 */
|
||||
.reset-btn {
|
||||
padding: 8px 16px;
|
||||
background: transparent;
|
||||
border: 1px solid #ff4444;
|
||||
color: #ff4444;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.reset-btn:hover {
|
||||
background: rgba(255, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.action-btn {
|
||||
padding: 4px 8px;
|
||||
@ -441,12 +456,13 @@
|
||||
<!-- 头部 -->
|
||||
<div class="trading-header">
|
||||
<h1 class="trading-title">模拟交易 <span>Paper Trading</span></h1>
|
||||
<div style="display: flex; align-items: center; gap: 20px;">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<div class="monitor-status">
|
||||
<div class="monitor-dot" :class="{ running: monitorRunning }"></div>
|
||||
<span>{{ monitorRunning ? '监控中' : '未启动' }}</span>
|
||||
</div>
|
||||
<button class="refresh-btn" @click="refreshData">刷新数据</button>
|
||||
<button class="reset-btn" @click="resetData">重置数据</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -747,6 +763,27 @@
|
||||
}
|
||||
},
|
||||
|
||||
async resetData() {
|
||||
if (!confirm('确定要重置所有模拟交易数据吗?\n\n此操作将删除所有订单记录,不可恢复!')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/paper-trading/reset', {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
alert(data.message);
|
||||
this.refreshData();
|
||||
} else {
|
||||
alert('重置失败: ' + (data.detail || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('重置失败: ' + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
async fetchActiveOrders() {
|
||||
const response = await fetch('/api/paper-trading/orders/active');
|
||||
const data = await response.json();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user