update
This commit is contained in:
parent
8e55d8ad1f
commit
182afdc26b
@ -50,6 +50,11 @@ class LLMSignalAnalyzer:
|
|||||||
- 趋势交易,大级别趋势确认
|
- 趋势交易,大级别趋势确认
|
||||||
- 风险较低,止损较宽
|
- 风险较低,止损较宽
|
||||||
|
|
||||||
|
## 入场方式
|
||||||
|
你需要明确指定入场方式:
|
||||||
|
- **market**:现价立即入场 - 当前价格就是好的入场点,建议立即开仓
|
||||||
|
- **limit**:挂单等待入场 - 等价格回调/突破到指定位置再入场
|
||||||
|
|
||||||
## 输出格式
|
## 输出格式
|
||||||
请严格按照以下 JSON 格式输出你的分析结果:
|
请严格按照以下 JSON 格式输出你的分析结果:
|
||||||
|
|
||||||
@ -62,6 +67,7 @@ class LLMSignalAnalyzer:
|
|||||||
{
|
{
|
||||||
"type": "short_term/medium_term/long_term",
|
"type": "short_term/medium_term/long_term",
|
||||||
"action": "buy/sell/wait",
|
"action": "buy/sell/wait",
|
||||||
|
"entry_type": "market/limit",
|
||||||
"confidence": 0-100,
|
"confidence": 0-100,
|
||||||
"grade": "A/B/C/D",
|
"grade": "A/B/C/D",
|
||||||
"entry_price": 建议入场价,
|
"entry_price": 建议入场价,
|
||||||
@ -90,7 +96,9 @@ class LLMSignalAnalyzer:
|
|||||||
3. 止损必须明确,风险收益比至少 1:1.5
|
3. 止损必须明确,风险收益比至少 1:1.5
|
||||||
4. 如果市场混乱或数据不足,直接建议观望
|
4. 如果市场混乱或数据不足,直接建议观望
|
||||||
5. reason 字段要具体说明你看到了什么(如"15M RSI 从 25 回升到 35,同时 MACD 金叉,且有大户加仓消息")
|
5. reason 字段要具体说明你看到了什么(如"15M RSI 从 25 回升到 35,同时 MACD 金叉,且有大户加仓消息")
|
||||||
6. 消息面和技术面冲突时,优先考虑技术面,但要在 risk_warning 中提示"""
|
6. 消息面和技术面冲突时,优先考虑技术面,但要在 risk_warning 中提示
|
||||||
|
7. entry_type 必须明确:如果当前价格合适立即入场用 market,如果需要等待更好价位用 limit
|
||||||
|
8. limit 挂单的 entry_price 应该是你期望的入场价位,而不是当前价格"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""初始化分析器"""
|
"""初始化分析器"""
|
||||||
@ -352,11 +360,16 @@ class LLMSignalAnalyzer:
|
|||||||
if signal['action'] == 'wait':
|
if signal['action'] == 'wait':
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 验证置信度
|
# 验证置信度(必须 >= 80 才算有效信号)
|
||||||
confidence = signal.get('confidence', 0)
|
confidence = signal.get('confidence', 0)
|
||||||
if not isinstance(confidence, (int, float)) or confidence < 40:
|
if not isinstance(confidence, (int, float)) or confidence < 80:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# 验证入场类型(默认为 market)
|
||||||
|
entry_type = signal.get('entry_type', 'market')
|
||||||
|
if entry_type not in ['market', 'limit']:
|
||||||
|
signal['entry_type'] = 'market' # 默认现价入场
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _extract_summary(self, text: str) -> str:
|
def _extract_summary(self, text: str) -> str:
|
||||||
@ -420,6 +433,7 @@ class LLMSignalAnalyzer:
|
|||||||
action = action_map.get(signal['action'], signal['action'])
|
action = action_map.get(signal['action'], signal['action'])
|
||||||
grade = signal.get('grade', 'C')
|
grade = signal.get('grade', 'C')
|
||||||
confidence = signal.get('confidence', 0)
|
confidence = signal.get('confidence', 0)
|
||||||
|
entry_type = signal.get('entry_type', 'market')
|
||||||
|
|
||||||
# 等级图标
|
# 等级图标
|
||||||
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(grade, '')
|
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(grade, '')
|
||||||
@ -427,6 +441,10 @@ class LLMSignalAnalyzer:
|
|||||||
# 方向图标
|
# 方向图标
|
||||||
action_icon = '🟢' if signal['action'] == 'buy' else '🔴'
|
action_icon = '🟢' if signal['action'] == 'buy' else '🔴'
|
||||||
|
|
||||||
|
# 入场类型
|
||||||
|
entry_type_text = '现价入场' if entry_type == 'market' else '挂单等待'
|
||||||
|
entry_type_icon = '⚡' if entry_type == 'market' else '⏳'
|
||||||
|
|
||||||
# 计算风险收益比
|
# 计算风险收益比
|
||||||
entry = signal.get('entry_price', 0)
|
entry = signal.get('entry_price', 0)
|
||||||
sl = signal.get('stop_loss', 0)
|
sl = signal.get('stop_loss', 0)
|
||||||
@ -437,6 +455,7 @@ class LLMSignalAnalyzer:
|
|||||||
message = f"""📊 {symbol} {signal_type}信号
|
message = f"""📊 {symbol} {signal_type}信号
|
||||||
|
|
||||||
{action_icon} **方向**: {action}
|
{action_icon} **方向**: {action}
|
||||||
|
{entry_type_icon} **入场**: {entry_type_text}
|
||||||
⭐ **等级**: {grade} {grade_icon}
|
⭐ **等级**: {grade} {grade_icon}
|
||||||
📈 **置信度**: {confidence}%
|
📈 **置信度**: {confidence}%
|
||||||
|
|
||||||
@ -477,16 +496,21 @@ class LLMSignalAnalyzer:
|
|||||||
action = action_map.get(signal['action'], signal['action'])
|
action = action_map.get(signal['action'], signal['action'])
|
||||||
grade = signal.get('grade', 'C')
|
grade = signal.get('grade', 'C')
|
||||||
confidence = signal.get('confidence', 0)
|
confidence = signal.get('confidence', 0)
|
||||||
|
entry_type = signal.get('entry_type', 'market')
|
||||||
|
|
||||||
# 等级图标
|
# 等级图标
|
||||||
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(grade, '')
|
grade_icon = {'A': '⭐⭐⭐', 'B': '⭐⭐', 'C': '⭐', 'D': ''}.get(grade, '')
|
||||||
|
|
||||||
|
# 入场类型
|
||||||
|
entry_type_text = '现价入场' if entry_type == 'market' else '挂单等待'
|
||||||
|
entry_type_icon = '⚡' if entry_type == 'market' else '⏳'
|
||||||
|
|
||||||
# 标题和颜色
|
# 标题和颜色
|
||||||
if signal['action'] == 'buy':
|
if signal['action'] == 'buy':
|
||||||
title = f"🟢 {symbol} {signal_type}做多信号"
|
title = f"🟢 {symbol} {signal_type}做多信号 [{entry_type_text}]"
|
||||||
color = "green"
|
color = "green"
|
||||||
else:
|
else:
|
||||||
title = f"🔴 {symbol} {signal_type}做空信号"
|
title = f"🔴 {symbol} {signal_type}做空信号 [{entry_type_text}]"
|
||||||
color = "red"
|
color = "red"
|
||||||
|
|
||||||
# 计算风险收益比
|
# 计算风险收益比
|
||||||
@ -499,6 +523,7 @@ class LLMSignalAnalyzer:
|
|||||||
# 构建 Markdown 内容
|
# 构建 Markdown 内容
|
||||||
content_parts = [
|
content_parts = [
|
||||||
f"**{signal_type}** | **{grade}**{grade_icon} | **{confidence}%** 置信度",
|
f"**{signal_type}** | **{grade}**{grade_icon} | **{confidence}%** 置信度",
|
||||||
|
f"{entry_type_icon} **入场方式**: {entry_type_text}",
|
||||||
"",
|
"",
|
||||||
f"💰 **入场**: ${entry:,.2f}",
|
f"💰 **入场**: ${entry:,.2f}",
|
||||||
f"🛑 **止损**: ${sl:,.2f} ({sl_percent:+.1f}%)",
|
f"🛑 **止损**: ${sl:,.2f} ({sl_percent:+.1f}%)",
|
||||||
|
|||||||
@ -31,6 +31,12 @@ class SignalGrade(str, Enum):
|
|||||||
D = "D"
|
D = "D"
|
||||||
|
|
||||||
|
|
||||||
|
class EntryType(str, Enum):
|
||||||
|
"""入场类型"""
|
||||||
|
MARKET = "market" # 现价入场
|
||||||
|
LIMIT = "limit" # 挂单入场
|
||||||
|
|
||||||
|
|
||||||
class PaperOrder(Base):
|
class PaperOrder(Base):
|
||||||
"""模拟交易订单表"""
|
"""模拟交易订单表"""
|
||||||
__tablename__ = "paper_orders"
|
__tablename__ = "paper_orders"
|
||||||
@ -59,6 +65,7 @@ class PaperOrder(Base):
|
|||||||
signal_type = Column(String(20), default="swing") # swing / short_term
|
signal_type = Column(String(20), default="swing") # swing / short_term
|
||||||
confidence = Column(Float, default=0) # 置信度 (0-100)
|
confidence = Column(Float, default=0) # 置信度 (0-100)
|
||||||
trend = Column(String(20), nullable=True) # 趋势方向
|
trend = Column(String(20), nullable=True) # 趋势方向
|
||||||
|
entry_type = Column(SQLEnum(EntryType), default=EntryType.MARKET) # 入场类型
|
||||||
|
|
||||||
# 订单状态
|
# 订单状态
|
||||||
status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, index=True)
|
status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, index=True)
|
||||||
@ -98,6 +105,7 @@ class PaperOrder(Base):
|
|||||||
'signal_type': self.signal_type,
|
'signal_type': self.signal_type,
|
||||||
'confidence': self.confidence,
|
'confidence': self.confidence,
|
||||||
'trend': self.trend,
|
'trend': self.trend,
|
||||||
|
'entry_type': self.entry_type.value if self.entry_type else 'market',
|
||||||
'status': self.status.value if self.status else None,
|
'status': self.status.value if self.status else None,
|
||||||
'pnl_amount': self.pnl_amount,
|
'pnl_amount': self.pnl_amount,
|
||||||
'pnl_percent': self.pnl_percent,
|
'pnl_percent': self.pnl_percent,
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import uuid
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional
|
||||||
|
|
||||||
from app.models.paper_trading import PaperOrder, OrderStatus, OrderSide, SignalGrade
|
from app.models.paper_trading import PaperOrder, OrderStatus, OrderSide, SignalGrade, EntryType
|
||||||
from app.services.db_service import db_service
|
from app.services.db_service import db_service
|
||||||
from app.config import get_settings
|
from app.config import get_settings
|
||||||
from app.utils.logger import logger
|
from app.utils.logger import logger
|
||||||
@ -59,7 +59,7 @@ class PaperTradingService:
|
|||||||
finally:
|
finally:
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
def create_order_from_signal(self, signal: Dict[str, Any]) -> Optional[PaperOrder]:
|
def create_order_from_signal(self, signal: Dict[str, Any], current_price: float = None) -> Optional[PaperOrder]:
|
||||||
"""
|
"""
|
||||||
从交易信号创建模拟订单
|
从交易信号创建模拟订单
|
||||||
|
|
||||||
@ -67,14 +67,15 @@ class PaperTradingService:
|
|||||||
signal: 交易信号
|
signal: 交易信号
|
||||||
- symbol: 交易对
|
- symbol: 交易对
|
||||||
- action: 'buy' 或 'sell'
|
- action: 'buy' 或 'sell'
|
||||||
- price: 入场价
|
- entry_type: 'market' 或 'limit'
|
||||||
|
- price / entry_price: 入场价
|
||||||
- stop_loss: 止损价
|
- stop_loss: 止损价
|
||||||
- take_profit: 止盈价
|
- take_profit: 止盈价
|
||||||
- confidence: 置信度
|
- confidence: 置信度
|
||||||
- signal_grade: 信号等级
|
- signal_grade / grade: 信号等级
|
||||||
- signal_type: 信号类型
|
- signal_type / type: 信号类型
|
||||||
- reasons: 入场原因
|
- reason: 入场原因
|
||||||
- indicators: 技术指标
|
current_price: 当前价格(用于市价单)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
创建的订单或 None
|
创建的订单或 None
|
||||||
@ -84,7 +85,7 @@ class PaperTradingService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# 获取信号等级
|
# 获取信号等级
|
||||||
grade = signal.get('signal_grade', 'D')
|
grade = signal.get('signal_grade') or signal.get('grade', 'D')
|
||||||
if grade == 'D':
|
if grade == 'D':
|
||||||
logger.info(f"D级信号不开仓: {signal.get('symbol')}")
|
logger.info(f"D级信号不开仓: {signal.get('symbol')}")
|
||||||
return None
|
return None
|
||||||
@ -97,28 +98,48 @@ class PaperTradingService:
|
|||||||
# 确定订单方向
|
# 确定订单方向
|
||||||
side = OrderSide.LONG if action == 'buy' else OrderSide.SHORT
|
side = OrderSide.LONG if action == 'buy' else OrderSide.SHORT
|
||||||
|
|
||||||
|
# 确定入场类型
|
||||||
|
entry_type_str = signal.get('entry_type', 'market')
|
||||||
|
entry_type = EntryType.LIMIT if entry_type_str == 'limit' else EntryType.MARKET
|
||||||
|
|
||||||
|
# 获取入场价
|
||||||
|
entry_price = signal.get('entry_price') or signal.get('price', 0)
|
||||||
|
|
||||||
# 生成订单ID
|
# 生成订单ID
|
||||||
symbol = signal.get('symbol', 'UNKNOWN')
|
symbol = signal.get('symbol', 'UNKNOWN')
|
||||||
order_id = f"PT-{symbol}-{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:6]}"
|
order_id = f"PT-{symbol}-{datetime.now().strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:6]}"
|
||||||
|
|
||||||
|
# 确定订单状态和成交价
|
||||||
|
if entry_type == EntryType.MARKET:
|
||||||
|
# 现价单:立即开仓
|
||||||
|
status = OrderStatus.OPEN
|
||||||
|
filled_price = current_price if current_price else entry_price
|
||||||
|
opened_at = datetime.utcnow()
|
||||||
|
else:
|
||||||
|
# 挂单:等待触发
|
||||||
|
status = OrderStatus.PENDING
|
||||||
|
filled_price = None
|
||||||
|
opened_at = None
|
||||||
|
|
||||||
db = db_service.get_session()
|
db = db_service.get_session()
|
||||||
try:
|
try:
|
||||||
order = PaperOrder(
|
order = PaperOrder(
|
||||||
order_id=order_id,
|
order_id=order_id,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
side=side,
|
side=side,
|
||||||
entry_price=signal.get('price', 0),
|
entry_price=entry_price,
|
||||||
stop_loss=signal.get('stop_loss', 0),
|
stop_loss=signal.get('stop_loss', 0),
|
||||||
take_profit=signal.get('take_profit', 0),
|
take_profit=signal.get('take_profit', 0),
|
||||||
filled_price=signal.get('price', 0), # 市价成交
|
filled_price=filled_price,
|
||||||
quantity=quantity,
|
quantity=quantity,
|
||||||
signal_grade=SignalGrade(grade),
|
signal_grade=SignalGrade(grade),
|
||||||
signal_type=signal.get('signal_type', 'swing'),
|
signal_type=signal.get('signal_type') or signal.get('type', 'swing'),
|
||||||
confidence=signal.get('confidence', 0),
|
confidence=signal.get('confidence', 0),
|
||||||
trend=signal.get('trend'),
|
trend=signal.get('trend'),
|
||||||
status=OrderStatus.OPEN,
|
entry_type=entry_type,
|
||||||
opened_at=datetime.utcnow(),
|
status=status,
|
||||||
entry_reasons=signal.get('reasons', []),
|
opened_at=opened_at,
|
||||||
|
entry_reasons=[signal.get('reason', '')] if signal.get('reason') else signal.get('reasons', []),
|
||||||
indicators=signal.get('indicators', {})
|
indicators=signal.get('indicators', {})
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -129,7 +150,9 @@ class PaperTradingService:
|
|||||||
# 添加到活跃订单缓存
|
# 添加到活跃订单缓存
|
||||||
self.active_orders[order.order_id] = order
|
self.active_orders[order.order_id] = order
|
||||||
|
|
||||||
logger.info(f"创建模拟订单: {order_id} | {symbol} {side.value} @ ${order.entry_price:,.2f} | 仓位: ${quantity}")
|
entry_type_text = "现价" if entry_type == EntryType.MARKET else "挂单"
|
||||||
|
status_text = "已开仓" if status == OrderStatus.OPEN else "等待触发"
|
||||||
|
logger.info(f"创建模拟订单: {order_id} | {symbol} {side.value} [{entry_type_text}] @ ${entry_price:,.2f} | {status_text} | 仓位: ${quantity}")
|
||||||
return order
|
return order
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -141,22 +164,32 @@ class PaperTradingService:
|
|||||||
|
|
||||||
def check_price_triggers(self, symbol: str, current_price: float) -> List[Dict[str, Any]]:
|
def check_price_triggers(self, symbol: str, current_price: float) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
检查当前价格是否触发止盈止损
|
检查当前价格是否触发挂单入场或止盈止损
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
symbol: 交易对
|
symbol: 交易对
|
||||||
current_price: 当前价格
|
current_price: 当前价格
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
触发的订单结果列表
|
触发的订单结果列表(平仓结果)
|
||||||
"""
|
"""
|
||||||
triggered = []
|
triggered = []
|
||||||
orders_to_check = [
|
|
||||||
|
# 1. 检查挂单是否触发入场
|
||||||
|
pending_orders = [
|
||||||
|
order for order in self.active_orders.values()
|
||||||
|
if order.symbol == symbol and order.status == OrderStatus.PENDING
|
||||||
|
]
|
||||||
|
for order in pending_orders:
|
||||||
|
if self._check_pending_entry(order, current_price):
|
||||||
|
logger.info(f"挂单触发入场: {order.order_id} | {symbol} @ ${current_price:,.2f}")
|
||||||
|
|
||||||
|
# 2. 检查持仓订单是否触发止盈止损
|
||||||
|
open_orders = [
|
||||||
order for order in self.active_orders.values()
|
order for order in self.active_orders.values()
|
||||||
if order.symbol == symbol and order.status == OrderStatus.OPEN
|
if order.symbol == symbol and order.status == OrderStatus.OPEN
|
||||||
]
|
]
|
||||||
|
for order in open_orders:
|
||||||
for order in orders_to_check:
|
|
||||||
result = self._check_order_trigger(order, current_price)
|
result = self._check_order_trigger(order, current_price)
|
||||||
if result:
|
if result:
|
||||||
triggered.append(result)
|
triggered.append(result)
|
||||||
@ -166,6 +199,49 @@ class PaperTradingService:
|
|||||||
|
|
||||||
return triggered
|
return triggered
|
||||||
|
|
||||||
|
def _check_pending_entry(self, order: PaperOrder, current_price: float) -> bool:
|
||||||
|
"""
|
||||||
|
检查挂单是否触发入场
|
||||||
|
|
||||||
|
做多挂单:价格下跌到入场价时触发(买入)
|
||||||
|
做空挂单:价格上涨到入场价时触发(卖出)
|
||||||
|
"""
|
||||||
|
should_trigger = False
|
||||||
|
|
||||||
|
if order.side == OrderSide.LONG:
|
||||||
|
# 做多:价格 <= 入场价 触发
|
||||||
|
if current_price <= order.entry_price:
|
||||||
|
should_trigger = True
|
||||||
|
else:
|
||||||
|
# 做空:价格 >= 入场价 触发
|
||||||
|
if current_price >= order.entry_price:
|
||||||
|
should_trigger = True
|
||||||
|
|
||||||
|
if should_trigger:
|
||||||
|
return self._activate_pending_order(order, current_price)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _activate_pending_order(self, order: PaperOrder, filled_price: float) -> bool:
|
||||||
|
"""激活挂单,转为持仓"""
|
||||||
|
db = db_service.get_session()
|
||||||
|
try:
|
||||||
|
order.status = OrderStatus.OPEN
|
||||||
|
order.filled_price = filled_price
|
||||||
|
order.opened_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.merge(order)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
logger.info(f"挂单已激活: {order.order_id} | {order.symbol} {order.side.value} @ ${filled_price:,.2f}")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"激活挂单失败: {e}")
|
||||||
|
db.rollback()
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
def _check_order_trigger(self, order: PaperOrder, current_price: float) -> Optional[Dict[str, Any]]:
|
def _check_order_trigger(self, order: PaperOrder, current_price: float) -> Optional[Dict[str, Any]]:
|
||||||
"""检查单个订单是否触发"""
|
"""检查单个订单是否触发"""
|
||||||
triggered = False
|
triggered = False
|
||||||
@ -268,14 +344,50 @@ class PaperTradingService:
|
|||||||
order.max_drawdown = current_pnl_percent
|
order.max_drawdown = current_pnl_percent
|
||||||
|
|
||||||
def close_order_manual(self, order_id: str, exit_price: float) -> Optional[Dict[str, Any]]:
|
def close_order_manual(self, order_id: str, exit_price: float) -> Optional[Dict[str, Any]]:
|
||||||
"""手动平仓"""
|
"""手动平仓或取消挂单"""
|
||||||
if order_id not in self.active_orders:
|
if order_id not in self.active_orders:
|
||||||
logger.warning(f"订单不存在或已平仓: {order_id}")
|
logger.warning(f"订单不存在或已平仓: {order_id}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
order = self.active_orders[order_id]
|
order = self.active_orders[order_id]
|
||||||
|
|
||||||
|
# 如果是挂单,取消而不是平仓
|
||||||
|
if order.status == OrderStatus.PENDING:
|
||||||
|
return self._cancel_pending_order(order)
|
||||||
|
|
||||||
return self._close_order(order, OrderStatus.CLOSED_MANUAL, exit_price)
|
return self._close_order(order, OrderStatus.CLOSED_MANUAL, exit_price)
|
||||||
|
|
||||||
|
def _cancel_pending_order(self, order: PaperOrder) -> Dict[str, Any]:
|
||||||
|
"""取消挂单"""
|
||||||
|
db = db_service.get_session()
|
||||||
|
try:
|
||||||
|
order.status = OrderStatus.CANCELLED
|
||||||
|
order.closed_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.merge(order)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# 从活跃订单缓存中移除
|
||||||
|
if order.order_id in self.active_orders:
|
||||||
|
del self.active_orders[order.order_id]
|
||||||
|
|
||||||
|
logger.info(f"挂单已取消: {order.order_id} | {order.symbol}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'order_id': order.order_id,
|
||||||
|
'symbol': order.symbol,
|
||||||
|
'side': order.side.value,
|
||||||
|
'status': 'cancelled',
|
||||||
|
'entry_price': order.entry_price,
|
||||||
|
'message': '挂单已取消'
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"取消挂单失败: {e}")
|
||||||
|
db.rollback()
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
def get_active_orders(self, symbol: Optional[str] = None) -> List[Dict[str, Any]]:
|
def get_active_orders(self, symbol: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||||
"""获取活跃订单"""
|
"""获取活跃订单"""
|
||||||
orders = list(self.active_orders.values())
|
orders = list(self.active_orders.values())
|
||||||
|
|||||||
@ -165,6 +165,11 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-badge.pending {
|
||||||
|
background: rgba(255, 165, 0, 0.1);
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
.status-badge.open {
|
.status-badge.open {
|
||||||
background: rgba(0, 255, 65, 0.1);
|
background: rgba(0, 255, 65, 0.1);
|
||||||
color: #00ff41;
|
color: #00ff41;
|
||||||
@ -567,6 +572,7 @@
|
|||||||
<th>订单ID</th>
|
<th>订单ID</th>
|
||||||
<th>交易对</th>
|
<th>交易对</th>
|
||||||
<th>方向</th>
|
<th>方向</th>
|
||||||
|
<th>状态</th>
|
||||||
<th>等级</th>
|
<th>等级</th>
|
||||||
<th>入场价</th>
|
<th>入场价</th>
|
||||||
<th>现价</th>
|
<th>现价</th>
|
||||||
@ -574,7 +580,7 @@
|
|||||||
<th>止损</th>
|
<th>止损</th>
|
||||||
<th>止盈</th>
|
<th>止盈</th>
|
||||||
<th>仓位</th>
|
<th>仓位</th>
|
||||||
<th>开仓时间</th>
|
<th>时间</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -583,6 +589,11 @@
|
|||||||
<td>{{ order.order_id.slice(-12) }}</td>
|
<td>{{ order.order_id.slice(-12) }}</td>
|
||||||
<td>{{ order.symbol }}</td>
|
<td>{{ order.symbol }}</td>
|
||||||
<td><span class="side-badge" :class="order.side">{{ order.side === 'long' ? '做多' : '做空' }}</span></td>
|
<td><span class="side-badge" :class="order.side">{{ order.side === 'long' ? '做多' : '做空' }}</span></td>
|
||||||
|
<td>
|
||||||
|
<span class="status-badge" :class="order.status">
|
||||||
|
{{ order.status === 'pending' ? '⏳ 挂单' : '✅ 持仓' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td><span class="grade-badge" :class="order.signal_grade">{{ order.signal_grade }}</span></td>
|
<td><span class="grade-badge" :class="order.signal_grade">{{ order.signal_grade }}</span></td>
|
||||||
<td>${{ order.entry_price?.toLocaleString() }}</td>
|
<td>${{ order.entry_price?.toLocaleString() }}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -591,11 +602,16 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<template v-if="order.status === 'open'">
|
||||||
<span class="pnl" :class="getUnrealizedPnl(order).pnl >= 0 ? 'positive' : 'negative'">
|
<span class="pnl" :class="getUnrealizedPnl(order).pnl >= 0 ? 'positive' : 'negative'">
|
||||||
{{ getUnrealizedPnl(order).pnl >= 0 ? '+' : '' }}{{ getUnrealizedPnl(order).percent.toFixed(2) }}%
|
{{ getUnrealizedPnl(order).pnl >= 0 ? '+' : '' }}{{ getUnrealizedPnl(order).percent.toFixed(2) }}%
|
||||||
<br>
|
<br>
|
||||||
<small>(${{ getUnrealizedPnl(order).pnl >= 0 ? '+' : '' }}{{ getUnrealizedPnl(order).pnl.toFixed(2) }})</small>
|
<small>(${{ getUnrealizedPnl(order).pnl >= 0 ? '+' : '' }}{{ getUnrealizedPnl(order).pnl.toFixed(2) }})</small>
|
||||||
</span>
|
</span>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span style="color: var(--text-secondary);">等待入场</span>
|
||||||
|
</template>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span :class="isNearStopLoss(order) ? 'warning-price' : ''">
|
<span :class="isNearStopLoss(order) ? 'warning-price' : ''">
|
||||||
@ -608,9 +624,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>${{ order.quantity }}</td>
|
<td>${{ order.quantity }}</td>
|
||||||
<td>{{ formatTime(order.opened_at) }}</td>
|
<td>{{ formatTime(order.status === 'open' ? order.opened_at : order.created_at) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="action-btn danger" @click="closeOrder(order)">平仓</button>
|
<button class="action-btn danger" @click="closeOrder(order)">
|
||||||
|
{{ order.status === 'pending' ? '取消' : '平仓' }}
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user