110 lines
5.0 KiB
Python
110 lines
5.0 KiB
Python
"""
|
||
实盘交易数据模型
|
||
|
||
与模拟交易使用相同的订单状态和方向枚举
|
||
"""
|
||
from datetime import datetime
|
||
from sqlalchemy import Column, Integer, String, Float, DateTime, JSON, Text, Enum as SQLEnum
|
||
from app.models.database import Base
|
||
from app.models.paper_trading import OrderStatus, OrderSide, SignalGrade, EntryType
|
||
|
||
|
||
class RealOrder(Base):
|
||
"""实盘交易订单表"""
|
||
__tablename__ = "real_orders"
|
||
|
||
id = Column(Integer, primary_key=True, index=True)
|
||
|
||
# 订单标识
|
||
order_id = Column(String(64), unique=True, nullable=False, index=True) # 本地订单ID
|
||
exchange_order_id = Column(String(64), nullable=True, index=True) # 交易所订单ID
|
||
client_order_id = Column(String(64), nullable=True, index=True) # 自定义订单ID
|
||
|
||
# 交易对信息
|
||
symbol = Column(String(20), nullable=False, index=True)
|
||
side = Column(SQLEnum(OrderSide), nullable=False)
|
||
|
||
# 价格信息
|
||
entry_price = Column(Float, nullable=False) # 目标入场价
|
||
stop_loss = Column(Float, nullable=False) # 止损价
|
||
take_profit = Column(Float, nullable=False) # 止盈价
|
||
filled_price = Column(Float, nullable=True) # 实际成交价
|
||
exit_price = Column(Float, nullable=True) # 出场价
|
||
|
||
# 仓位信息
|
||
quantity = Column(Float, default=1000) # 仓位大小 (USDT)
|
||
leverage = Column(Integer, default=10) # 杠杆倍数
|
||
size = Column(Float, nullable=True) # 合约数量(张数)
|
||
|
||
# 信号信息
|
||
signal_grade = Column(SQLEnum(SignalGrade), default=SignalGrade.D)
|
||
signal_type = Column(String(20), default="swing") # swing / short_term
|
||
confidence = Column(Float, default=0) # 置信度 (0-100)
|
||
trend = Column(String(20), nullable=True) # 趋势方向
|
||
entry_type = Column(SQLEnum(EntryType, values_callable=lambda x: [e.value for e in x]), default=EntryType.MARKET)
|
||
|
||
# 订单状态
|
||
status = Column(SQLEnum(OrderStatus), default=OrderStatus.PENDING, index=True)
|
||
|
||
# 盈亏信息
|
||
pnl_amount = Column(Float, default=0) # 盈亏金额 (USDT)
|
||
pnl_percent = Column(Float, default=0) # 盈亏百分比
|
||
fee_amount = Column(Float, default=0) # 手续费
|
||
|
||
# 风险指标
|
||
max_drawdown = Column(Float, default=0) # 持仓期间最大回撤
|
||
max_profit = Column(Float, default=0) # 持仓期间最大盈利
|
||
|
||
# 移动止损相关
|
||
breakeven_triggered = Column(Integer, default=0) # 保本止损是否已触发
|
||
trailing_stop_triggered = Column(Integer, default=0) # 移动止损是否已触发
|
||
trailing_stop_base_profit = Column(Float, default=0) # 移动止损基准盈利
|
||
|
||
# 时间戳
|
||
created_at = Column(DateTime, default=datetime.now, index=True)
|
||
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||
filled_at = Column(DateTime, nullable=True) # 成交时间
|
||
closed_at = Column(DateTime, nullable=True) # 平仓时间
|
||
|
||
# 额外数据
|
||
extra_data = Column(JSON, nullable=True) # 存储额外信息(metadata是保留字)
|
||
notes = Column(Text, nullable=True) # 备注
|
||
|
||
def to_dict(self) -> dict:
|
||
"""转换为字典"""
|
||
return {
|
||
'id': self.id,
|
||
'order_id': self.order_id,
|
||
'exchange_order_id': self.exchange_order_id,
|
||
'client_order_id': self.client_order_id,
|
||
'symbol': self.symbol,
|
||
'side': self.side.value if self.side else None,
|
||
'entry_price': self.entry_price,
|
||
'stop_loss': self.stop_loss,
|
||
'take_profit': self.take_profit,
|
||
'filled_price': self.filled_price,
|
||
'exit_price': self.exit_price,
|
||
'quantity': self.quantity,
|
||
'leverage': self.leverage,
|
||
'size': self.size,
|
||
'signal_grade': self.signal_grade.value if self.signal_grade else None,
|
||
'signal_type': self.signal_type,
|
||
'confidence': self.confidence,
|
||
'trend': self.trend,
|
||
'entry_type': self.entry_type.value if self.entry_type else None,
|
||
'status': self.status.value if self.status else None,
|
||
'pnl_amount': self.pnl_amount,
|
||
'pnl_percent': self.pnl_percent,
|
||
'fee_amount': self.fee_amount,
|
||
'max_drawdown': self.max_drawdown,
|
||
'max_profit': self.max_profit,
|
||
'breakeven_triggered': bool(self.breakeven_triggered),
|
||
'trailing_stop_triggered': bool(self.trailing_stop_triggered),
|
||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||
'filled_at': self.filled_at.isoformat() if self.filled_at else None,
|
||
'closed_at': self.closed_at.isoformat() if self.closed_at else None,
|
||
'extra_data': self.extra_data,
|
||
'notes': self.notes
|
||
}
|