1
This commit is contained in:
parent
9166e4a987
commit
e3bfa597c9
@ -5,6 +5,7 @@ from enum import Enum
|
||||
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.utils.datetime_utils import get_beijing_time
|
||||
|
||||
|
||||
class OrderStatus(str, Enum):
|
||||
@ -88,7 +89,7 @@ class PaperOrder(Base):
|
||||
trailing_stop_base_profit = Column(Float, default=0) # 移动止损触发时的盈利百分比
|
||||
|
||||
# 时间戳
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
created_at = Column(DateTime, default=lambda: get_beijing_time())
|
||||
opened_at = Column(DateTime, nullable=True) # 开仓时间
|
||||
closed_at = Column(DateTime, nullable=True) # 平仓时间
|
||||
|
||||
@ -99,6 +100,12 @@ class PaperOrder(Base):
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典"""
|
||||
# 格式化时间字段(北京时间字符串)
|
||||
def format_time(dt):
|
||||
if dt is None:
|
||||
return None
|
||||
return dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return {
|
||||
'id': self.id,
|
||||
'order_id': self.order_id,
|
||||
@ -125,8 +132,8 @@ class PaperOrder(Base):
|
||||
'breakeven_triggered': self.breakeven_triggered,
|
||||
'trailing_stop_triggered': getattr(self, 'trailing_stop_triggered', 0),
|
||||
'trailing_stop_base_profit': getattr(self, 'trailing_stop_base_profit', 0),
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'opened_at': self.opened_at.isoformat() if self.opened_at else None,
|
||||
'closed_at': self.closed_at.isoformat() if self.closed_at else None,
|
||||
'created_at': format_time(self.created_at),
|
||||
'opened_at': format_time(self.opened_at),
|
||||
'closed_at': format_time(self.closed_at),
|
||||
'entry_reasons': self.entry_reasons,
|
||||
}
|
||||
|
||||
@ -2,25 +2,14 @@
|
||||
模拟交易服务 - 订单管理和盈亏统计
|
||||
"""
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from app.models.paper_trading import PaperOrder, OrderStatus, OrderSide, SignalGrade, EntryType
|
||||
from app.services.db_service import db_service
|
||||
from app.config import get_settings
|
||||
from app.utils.logger import logger
|
||||
|
||||
|
||||
def get_beijing_time() -> datetime:
|
||||
"""
|
||||
获取东八区(北京时间)当前时间
|
||||
|
||||
Returns:
|
||||
东八区的当前时间
|
||||
"""
|
||||
utc_time = datetime.utcnow().replace(tzinfo=timezone.utc)
|
||||
beijing_tz = timezone(timedelta(hours=8))
|
||||
return utc_time.astimezone(beijing_tz).replace(tzinfo=None)
|
||||
from app.utils.datetime_utils import get_beijing_time
|
||||
|
||||
|
||||
class PaperTradingService:
|
||||
|
||||
27
backend/app/utils/datetime_utils.py
Normal file
27
backend/app/utils/datetime_utils.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""
|
||||
时间工具模块
|
||||
提供统一的时间处理函数
|
||||
"""
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
|
||||
def get_beijing_time() -> datetime:
|
||||
"""
|
||||
获取东八区(北京时间)当前时间
|
||||
|
||||
Returns:
|
||||
东八区的当前时间(无时区信息的naive datetime)
|
||||
"""
|
||||
utc_time = datetime.utcnow().replace(tzinfo=timezone.utc)
|
||||
beijing_tz = timezone(timedelta(hours=8))
|
||||
return utc_time.astimezone(beijing_tz).replace(tzinfo=None)
|
||||
|
||||
|
||||
def get_utc_time() -> datetime:
|
||||
"""
|
||||
获取UTC当前时间
|
||||
|
||||
Returns:
|
||||
UTC当前时间(无时区信息的naive datetime)
|
||||
"""
|
||||
return datetime.utcnow()
|
||||
@ -2149,13 +2149,15 @@
|
||||
|
||||
formatTime(timeStr) {
|
||||
if (!timeStr) return '-';
|
||||
const date = new Date(timeStr);
|
||||
return date.toLocaleString('zh-CN', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
// 后端已返回格式化的北京时间字符串,直接显示
|
||||
// 格式: "2026-03-13 18:30:00" -> 显示为 "03-13 18:30"
|
||||
const parts = timeStr.split(' ');
|
||||
if (parts.length >= 2) {
|
||||
const datePart = parts[0].substring(5); // "03-13"
|
||||
const timePart = parts[1].substring(0, 5); // "18:30"
|
||||
return `${datePart} ${timePart}`;
|
||||
}
|
||||
return timeStr;
|
||||
},
|
||||
|
||||
formatStatus(status) {
|
||||
@ -2507,21 +2509,16 @@
|
||||
return (this.totalUnrealizedPnl / this.totalPosition) * 100;
|
||||
},
|
||||
|
||||
// 累计收益率
|
||||
// 累计收益率(使用 account.initial_balance 作为基准,与顶部收益率一致)
|
||||
totalReturn() {
|
||||
if (this.dailyReturns.length === 0) return 0;
|
||||
const firstDay = this.dailyReturns[0];
|
||||
const lastDay = this.dailyReturns[this.dailyReturns.length - 1];
|
||||
if (firstDay.balance === 0) return 0;
|
||||
return ((lastDay.balance - firstDay.balance) / firstDay.balance) * 100;
|
||||
if (!this.account.initial_balance || this.account.initial_balance === 0) return 0;
|
||||
return ((this.account.current_balance - this.account.initial_balance) / this.account.initial_balance) * 100;
|
||||
},
|
||||
|
||||
// 累计收益额
|
||||
totalReturnAmount() {
|
||||
if (this.dailyReturns.length === 0) return 0;
|
||||
const lastDay = this.dailyReturns[this.dailyReturns.length - 1];
|
||||
const firstDay = this.dailyReturns[0];
|
||||
return lastDay.balance - firstDay.balance;
|
||||
if (!this.account.initial_balance) return 0;
|
||||
return this.account.current_balance - this.account.initial_balance;
|
||||
},
|
||||
|
||||
// 盈利天数
|
||||
|
||||
Loading…
Reference in New Issue
Block a user