fix: 修复登录500错误和移除明文密码提示
- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service) - 移除登录页面上的明文密码提示 - 改进前端错误处理,避免数据加载失败导致登录显示错误
This commit is contained in:
@@ -0,0 +1,512 @@
|
||||
"""
|
||||
数据转换工具
|
||||
将 VeighNa 数据对象转换为 API 响应模型
|
||||
处理日期时间格式化和枚举类型转换
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Any
|
||||
|
||||
try:
|
||||
from vnpy.trader.object import (
|
||||
TickData, OrderData, TradeData, PositionData,
|
||||
AccountData, ContractData, QuoteData
|
||||
)
|
||||
from vnpy.trader.constant import Direction, Offset, Status, OrderType, Exchange, Product
|
||||
VNPY_AVAILABLE = True
|
||||
except ImportError:
|
||||
VNPY_AVAILABLE = False
|
||||
|
||||
from ..models import (
|
||||
TickData as TickDataModel,
|
||||
OrderResponse,
|
||||
PositionData as PositionDataModel,
|
||||
AccountData as AccountDataModel,
|
||||
KlineData,
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# 枚举值映射
|
||||
# ============================================
|
||||
|
||||
# VeighNa Direction (LONG/SHORT) -> API (buy/sell)
|
||||
DIRECTION_MAP = {
|
||||
"LONG": "buy",
|
||||
"SHORT": "sell",
|
||||
"NET": "net",
|
||||
}
|
||||
|
||||
# VeighNa OrderType -> API
|
||||
ORDER_TYPE_MAP = {
|
||||
"限价": "limit",
|
||||
"LIMIT": "limit",
|
||||
"市价": "market",
|
||||
"MARKET": "market",
|
||||
"STOP": "stop",
|
||||
"FAK": "fak",
|
||||
"FOK": "fok",
|
||||
}
|
||||
|
||||
# VeighNa Status -> API
|
||||
STATUS_MAP = {
|
||||
"提交中": "submitting",
|
||||
"SUBMITTING": "submitting",
|
||||
"未成交": "not_traded",
|
||||
"NOTTRADED": "not_traded",
|
||||
"部分成交": "part_traded",
|
||||
"PARTTRADED": "part_traded",
|
||||
"全部成交": "all_traded",
|
||||
"ALLTRADED": "all_traded",
|
||||
"已撤销": "cancelled",
|
||||
"CANCELLED": "cancelled",
|
||||
"拒单": "rejected",
|
||||
"REJECTED": "rejected",
|
||||
}
|
||||
|
||||
# VeighNa Exchange -> API
|
||||
EXCHANGE_NAMES = {
|
||||
"CFFEX": "CFFEX",
|
||||
"SHFE": "SHFE",
|
||||
"CZCE": "CZCE",
|
||||
"DCE": "DCE",
|
||||
"INE": "INE",
|
||||
"GFEX": "GFEX",
|
||||
"SSE": "SSE",
|
||||
"SZSE": "SZSE",
|
||||
"BSE": "BSE",
|
||||
"SMART": "SMART",
|
||||
"IBKRATS": "IBKRATS",
|
||||
"LOCAL": "LOCAL",
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# 辅助函数
|
||||
# ============================================
|
||||
|
||||
def format_datetime(dt: Optional[datetime]) -> Optional[datetime]:
|
||||
"""格式化日期时间"""
|
||||
if dt is None:
|
||||
return None
|
||||
return dt
|
||||
|
||||
|
||||
def convert_direction(direction: Any) -> str:
|
||||
"""转换方向枚举"""
|
||||
if direction is None:
|
||||
return "unknown"
|
||||
if isinstance(direction, str):
|
||||
return DIRECTION_MAP.get(direction, direction.lower())
|
||||
return DIRECTION_MAP.get(direction.value, "unknown")
|
||||
|
||||
|
||||
def convert_order_type(order_type: Any) -> str:
|
||||
"""转换订单类型枚举"""
|
||||
if isinstance(order_type, str):
|
||||
return ORDER_TYPE_MAP.get(order_type, order_type.lower())
|
||||
return ORDER_TYPE_MAP.get(order_type.value, "unknown")
|
||||
|
||||
|
||||
def convert_status(status: Any) -> str:
|
||||
"""转换状态枚举"""
|
||||
if isinstance(status, str):
|
||||
return STATUS_MAP.get(status, status.lower())
|
||||
return STATUS_MAP.get(status.value, "unknown")
|
||||
|
||||
|
||||
def convert_exchange(exchange: Any) -> str:
|
||||
"""转换交易所枚举"""
|
||||
if isinstance(exchange, str):
|
||||
return exchange
|
||||
return exchange.value if hasattr(exchange, 'value') else str(exchange)
|
||||
|
||||
|
||||
def safe_float(value: Any, default: float = 0.0) -> float:
|
||||
"""安全转换浮点数"""
|
||||
try:
|
||||
return float(value) if value is not None else default
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def safe_int(value: Any, default: int = 0) -> int:
|
||||
"""安全转换整数"""
|
||||
try:
|
||||
return int(value) if value is not None else default
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
# ============================================
|
||||
# TickData 转换
|
||||
# ============================================
|
||||
|
||||
def convert_tick_data(tick: Any) -> TickDataModel:
|
||||
"""转换 TickData 对象"""
|
||||
if not VNPY_AVAILABLE:
|
||||
# 模拟数据转换
|
||||
return TickDataModel(
|
||||
symbol=getattr(tick, 'symbol', ''),
|
||||
exchange=getattr(tick, 'exchange', ''),
|
||||
datetime=getattr(tick, 'datetime', datetime.now()),
|
||||
name=getattr(tick, 'name', None),
|
||||
last_price=safe_float(getattr(tick, 'last_price', 0)),
|
||||
bid_price_1=safe_float(getattr(tick, 'bid_price_1', None)),
|
||||
ask_price_1=safe_float(getattr(tick, 'ask_price_1', None)),
|
||||
bid_volume_1=safe_float(getattr(tick, 'bid_volume_1', None)),
|
||||
ask_volume_1=safe_float(getattr(tick, 'ask_volume_1', None)),
|
||||
volume=safe_float(getattr(tick, 'volume', None)),
|
||||
open_interest=safe_float(getattr(tick, 'open_interest', None)),
|
||||
)
|
||||
|
||||
# VeighNa TickData 转换
|
||||
return TickDataModel(
|
||||
symbol=tick.symbol,
|
||||
exchange=tick.exchange.value,
|
||||
datetime=tick.datetime,
|
||||
name=tick.name or None,
|
||||
last_price=tick.last_price,
|
||||
bid_price_1=tick.bid_price_1 or None,
|
||||
ask_price_1=tick.ask_price_1 or None,
|
||||
bid_volume_1=tick.bid_volume_1 or None,
|
||||
ask_volume_1=tick.ask_volume_1 or None,
|
||||
volume=tick.volume or None,
|
||||
open_interest=tick.open_interest or None,
|
||||
)
|
||||
|
||||
|
||||
def convert_tick_to_dict(tick: Any) -> dict:
|
||||
"""转换 TickData 为字典(用于缓存和 WebSocket)"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return {
|
||||
"symbol": getattr(tick, 'symbol', ''),
|
||||
"exchange": getattr(tick, 'exchange', ''),
|
||||
"datetime": getattr(tick, 'datetime', datetime.now()).isoformat(),
|
||||
"name": getattr(tick, 'name', ''),
|
||||
"last_price": safe_float(getattr(tick, 'last_price', 0)),
|
||||
"bid_price_1": safe_float(getattr(tick, 'bid_price_1', 0)),
|
||||
"ask_price_1": safe_float(getattr(tick, 'ask_price_1', 0)),
|
||||
"bid_volume_1": safe_float(getattr(tick, 'bid_volume_1', 0)),
|
||||
"ask_volume_1": safe_float(getattr(tick, 'ask_volume_1', 0)),
|
||||
"volume": safe_float(getattr(tick, 'volume', 0)),
|
||||
"open_interest": safe_float(getattr(tick, 'open_interest', 0)),
|
||||
}
|
||||
|
||||
return {
|
||||
"symbol": tick.symbol,
|
||||
"exchange": tick.exchange.value,
|
||||
"datetime": tick.datetime.isoformat(),
|
||||
"name": tick.name,
|
||||
"last_price": tick.last_price,
|
||||
"bid_price_1": tick.bid_price_1,
|
||||
"ask_price_1": tick.ask_price_1,
|
||||
"bid_volume_1": tick.bid_volume_1,
|
||||
"ask_volume_1": tick.ask_volume_1,
|
||||
"volume": tick.volume,
|
||||
"open_interest": tick.open_interest,
|
||||
"open_price": tick.open_price,
|
||||
"high_price": tick.high_price,
|
||||
"low_price": tick.low_price,
|
||||
"pre_close": tick.pre_close,
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# OrderData 转换
|
||||
# ============================================
|
||||
|
||||
def convert_order_data(order: Any) -> OrderResponse:
|
||||
"""转换 OrderData 对象"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return OrderResponse(
|
||||
order_id=getattr(order, 'vt_orderid', ''),
|
||||
symbol=getattr(order, 'symbol', ''),
|
||||
exchange=getattr(order, 'exchange', ''),
|
||||
direction=convert_direction(getattr(order, 'direction', None)),
|
||||
order_type=convert_order_type(getattr(order, 'type', None)),
|
||||
volume=safe_float(getattr(order, 'volume', 0)),
|
||||
price=safe_float(getattr(order, 'price', None)),
|
||||
traded=safe_float(getattr(order, 'traded', 0)),
|
||||
status=convert_status(getattr(order, 'status', None)),
|
||||
time=format_datetime(getattr(order, 'datetime', None)),
|
||||
reference=getattr(order, 'reference', None),
|
||||
)
|
||||
|
||||
return OrderResponse(
|
||||
order_id=order.vt_orderid,
|
||||
symbol=order.symbol,
|
||||
exchange=order.exchange.value,
|
||||
direction=convert_direction(order.direction),
|
||||
order_type=convert_order_type(order.type),
|
||||
volume=order.volume,
|
||||
price=order.price or None,
|
||||
traded=order.traded,
|
||||
status=convert_status(order.status),
|
||||
time=format_datetime(order.datetime),
|
||||
reference=order.reference or None,
|
||||
)
|
||||
|
||||
|
||||
def convert_order_to_dict(order: Any) -> dict:
|
||||
"""转换 OrderData 为字典"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return {
|
||||
"order_id": getattr(order, 'vt_orderid', ''),
|
||||
"symbol": getattr(order, 'symbol', ''),
|
||||
"exchange": getattr(order, 'exchange', ''),
|
||||
"direction": convert_direction(getattr(order, 'direction', None)),
|
||||
"order_type": convert_order_type(getattr(order, 'type', None)),
|
||||
"volume": safe_float(getattr(order, 'volume', 0)),
|
||||
"price": safe_float(getattr(order, 'price', 0)),
|
||||
"traded": safe_float(getattr(order, 'traded', 0)),
|
||||
"status": convert_status(getattr(order, 'status', None)),
|
||||
"time": format_datetime(getattr(order, 'datetime', None)),
|
||||
"reference": getattr(order, 'reference', ''),
|
||||
}
|
||||
|
||||
return {
|
||||
"order_id": order.vt_orderid,
|
||||
"symbol": order.symbol,
|
||||
"exchange": order.exchange.value,
|
||||
"direction": convert_direction(order.direction),
|
||||
"order_type": convert_order_type(order.type),
|
||||
"offset": order.offset.value if order.offset else "",
|
||||
"volume": order.volume,
|
||||
"price": order.price,
|
||||
"traded": order.traded,
|
||||
"status": convert_status(order.status),
|
||||
"time": format_datetime(order.datetime),
|
||||
"reference": order.reference,
|
||||
"gateway_name": order.gateway_name,
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# TradeData 转换
|
||||
# ============================================
|
||||
|
||||
def convert_trade_to_dict(trade: Any) -> dict:
|
||||
"""转换 TradeData 为字典"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return {
|
||||
"trade_id": getattr(trade, 'vt_tradeid', ''),
|
||||
"order_id": getattr(trade, 'vt_orderid', ''),
|
||||
"symbol": getattr(trade, 'symbol', ''),
|
||||
"exchange": getattr(trade, 'exchange', ''),
|
||||
"direction": convert_direction(getattr(trade, 'direction', None)),
|
||||
"offset": getattr(trade, 'offset', ''),
|
||||
"volume": safe_float(getattr(trade, 'volume', 0)),
|
||||
"price": safe_float(getattr(trade, 'price', 0)),
|
||||
"time": format_datetime(getattr(trade, 'datetime', None)),
|
||||
}
|
||||
|
||||
return {
|
||||
"trade_id": trade.vt_tradeid,
|
||||
"order_id": trade.vt_orderid,
|
||||
"symbol": trade.symbol,
|
||||
"exchange": trade.exchange.value,
|
||||
"direction": convert_direction(trade.direction),
|
||||
"offset": trade.offset.value,
|
||||
"volume": trade.volume,
|
||||
"price": trade.price,
|
||||
"time": format_datetime(trade.datetime),
|
||||
"gateway_name": trade.gateway_name,
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# PositionData 转换
|
||||
# ============================================
|
||||
|
||||
def convert_position_data(position: Any) -> PositionDataModel:
|
||||
"""转换 PositionData 对象"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return PositionDataModel(
|
||||
symbol=getattr(position, 'symbol', ''),
|
||||
exchange=getattr(position, 'exchange', ''),
|
||||
direction=convert_direction(getattr(position, 'direction', None)),
|
||||
volume=safe_float(getattr(position, 'volume', 0)),
|
||||
price=safe_float(getattr(position, 'price', 0)),
|
||||
pnl=safe_float(getattr(position, 'pnl', 0)),
|
||||
pnl_ratio=safe_float(getattr(position, 'pnl_ratio', 0)),
|
||||
frozen=safe_float(getattr(position, 'frozen', 0)),
|
||||
yd_volume=safe_float(getattr(position, 'yd_volume', 0)),
|
||||
)
|
||||
|
||||
# 计算 pnl_ratio (VeighNa 的 PositionData 没有 pnl_ratio 属性)
|
||||
pnl_ratio = 0.0
|
||||
if position.volume and position.price:
|
||||
try:
|
||||
pnl_ratio = (position.pnl / (position.volume * position.price * 100)) if position.volume * position.price else 0.0
|
||||
except (ZeroDivisionError, TypeError):
|
||||
pnl_ratio = 0.0
|
||||
|
||||
return PositionDataModel(
|
||||
symbol=position.symbol,
|
||||
exchange=position.exchange.value,
|
||||
direction=convert_direction(position.direction),
|
||||
volume=position.volume,
|
||||
price=position.price,
|
||||
pnl=position.pnl,
|
||||
pnl_ratio=pnl_ratio,
|
||||
frozen=position.frozen,
|
||||
yd_volume=position.yd_volume,
|
||||
)
|
||||
|
||||
|
||||
def convert_position_to_dict(position: Any) -> dict:
|
||||
"""转换 PositionData 为字典"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return {
|
||||
"symbol": getattr(position, 'symbol', ''),
|
||||
"exchange": getattr(position, 'exchange', ''),
|
||||
"direction": convert_direction(getattr(position, 'direction', None)),
|
||||
"volume": safe_float(getattr(position, 'volume', 0)),
|
||||
"price": safe_float(getattr(position, 'price', 0)),
|
||||
"pnl": safe_float(getattr(position, 'pnl', 0)),
|
||||
"pnl_ratio": safe_float(getattr(position, 'pnl_ratio', 0)),
|
||||
"frozen": safe_float(getattr(position, 'frozen', 0)),
|
||||
"yd_volume": safe_float(getattr(position, 'yd_volume', 0)),
|
||||
}
|
||||
|
||||
pnl_ratio = 0.0
|
||||
if position.volume and position.price:
|
||||
try:
|
||||
pnl_ratio = (position.pnl / (position.volume * position.price * 100)) if position.volume * position.price else 0.0
|
||||
except (ZeroDivisionError, TypeError):
|
||||
pnl_ratio = 0.0
|
||||
|
||||
return {
|
||||
"symbol": position.symbol,
|
||||
"exchange": position.exchange.value,
|
||||
"direction": convert_direction(position.direction),
|
||||
"volume": position.volume,
|
||||
"price": position.price,
|
||||
"pnl": position.pnl,
|
||||
"pnl_ratio": pnl_ratio,
|
||||
"frozen": position.frozen,
|
||||
"yd_volume": position.yd_volume,
|
||||
"gateway_name": position.gateway_name,
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# AccountData 转换
|
||||
# ============================================
|
||||
|
||||
def convert_account_data(account: Any) -> AccountDataModel:
|
||||
"""转换 AccountData 对象"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return AccountDataModel(
|
||||
account_id=getattr(account, 'vt_accountid', ''),
|
||||
balance=safe_float(getattr(account, 'balance', 0)),
|
||||
available=safe_float(getattr(account, 'available', 0)),
|
||||
frozen=safe_float(getattr(account, 'frozen', 0)),
|
||||
margin=safe_float(getattr(account, 'margin', 0)),
|
||||
)
|
||||
|
||||
return AccountDataModel(
|
||||
account_id=account.vt_accountid,
|
||||
balance=account.balance,
|
||||
available=account.available,
|
||||
frozen=account.frozen,
|
||||
margin=safe_float(getattr(account, 'margin', 0)),
|
||||
)
|
||||
|
||||
|
||||
def convert_account_to_dict(account: Any) -> dict:
|
||||
"""转换 AccountData 为字典"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return {
|
||||
"account_id": getattr(account, 'vt_accountid', ''),
|
||||
"balance": safe_float(getattr(account, 'balance', 0)),
|
||||
"available": safe_float(getattr(account, 'available', 0)),
|
||||
"frozen": safe_float(getattr(account, 'frozen', 0)),
|
||||
}
|
||||
|
||||
return {
|
||||
"account_id": account.vt_accountid,
|
||||
"balance": account.balance,
|
||||
"available": account.available,
|
||||
"frozen": account.frozen,
|
||||
"gateway_name": account.gateway_name,
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# ContractData 转换
|
||||
# ============================================
|
||||
|
||||
def convert_contract_to_dict(contract: Any) -> dict:
|
||||
"""转换 ContractData 为字典"""
|
||||
if not VNPY_AVAILABLE:
|
||||
return {
|
||||
"symbol": getattr(contract, 'symbol', ''),
|
||||
"exchange": getattr(contract, 'exchange', ''),
|
||||
"name": getattr(contract, 'name', ''),
|
||||
"product": getattr(contract, 'product', ''),
|
||||
"size": safe_float(getattr(contract, 'size', 1)),
|
||||
"pricetick": safe_float(getattr(contract, 'pricetick', 0)),
|
||||
"min_volume": safe_float(getattr(contract, 'min_volume', 1)),
|
||||
"max_volume": safe_float(getattr(contract, 'max_volume', None)),
|
||||
"vt_symbol": getattr(contract, 'vt_symbol', ''),
|
||||
"stop_supported": getattr(contract, 'stop_supported', False),
|
||||
"net_position": getattr(contract, 'net_position', False),
|
||||
}
|
||||
|
||||
return {
|
||||
"symbol": contract.symbol,
|
||||
"exchange": contract.exchange.value,
|
||||
"name": contract.name,
|
||||
"product": contract.product.value,
|
||||
"size": contract.size,
|
||||
"pricetick": contract.pricetick,
|
||||
"min_volume": contract.min_volume,
|
||||
"max_volume": contract.max_volume,
|
||||
"vt_symbol": contract.vt_symbol,
|
||||
"stop_supported": contract.stop_supported,
|
||||
"net_position": contract.net_position,
|
||||
"history_data": contract.history_data,
|
||||
}
|
||||
|
||||
|
||||
# ============================================
|
||||
# 批量转换函数
|
||||
# ============================================
|
||||
|
||||
def convert_tick_list(ticks: List[Any]) -> List[TickDataModel]:
|
||||
"""批量转换 TickData 列表"""
|
||||
return [convert_tick_data(tick) for tick in ticks]
|
||||
|
||||
|
||||
def convert_order_list(orders: List[Any]) -> List[OrderResponse]:
|
||||
"""批量转换 OrderData 列表"""
|
||||
return [convert_order_data(order) for order in orders]
|
||||
|
||||
|
||||
def convert_position_list(positions: List[Any]) -> List[PositionDataModel]:
|
||||
"""批量转换 PositionData 列表"""
|
||||
return [convert_position_data(pos) for pos in positions]
|
||||
|
||||
|
||||
def convert_account_list(accounts: List[Any]) -> List[AccountDataModel]:
|
||||
"""批量转换 AccountData 列表"""
|
||||
return [convert_account_data(acc) for acc in accounts]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"convert_tick_data",
|
||||
"convert_order_data",
|
||||
"convert_position_data",
|
||||
"convert_account_data",
|
||||
"convert_tick_to_dict",
|
||||
"convert_order_to_dict",
|
||||
"convert_trade_to_dict",
|
||||
"convert_position_to_dict",
|
||||
"convert_account_to_dict",
|
||||
"convert_contract_to_dict",
|
||||
"convert_tick_list",
|
||||
"convert_order_list",
|
||||
"convert_position_list",
|
||||
"convert_account_list",
|
||||
]
|
||||
Reference in New Issue
Block a user