This commit is contained in:
aaron 2026-03-03 16:43:32 +08:00
parent 6428a401d0
commit e62d761efb
4 changed files with 155 additions and 36 deletions

View File

@ -60,7 +60,9 @@ class PaperOrder(Base):
exit_price = Column(Float, nullable=True) # 出场价
# 仓位信息
quantity = Column(Float, default=1000) # 仓位大小 (USDT)
quantity = Column(Float, default=1000) # 持仓价值 (USDT)
margin = Column(Float, default=50) # 保证金 (USDT)
leverage = Column(Integer, default=20) # 杠杆倍数
# 信号信息
signal_grade = Column(SQLEnum(SignalGrade), default=SignalGrade.D)
@ -107,7 +109,9 @@ class PaperOrder(Base):
'take_profit': self.take_profit,
'filled_price': self.filled_price,
'exit_price': self.exit_price,
'quantity': self.quantity,
'quantity': self.quantity, # 持仓价值
'margin': getattr(self, 'margin', self.quantity / 20), # 保证金
'leverage': getattr(self, 'leverage', 20), # 杠杆倍数
'signal_grade': self.signal_grade.value if self.signal_grade else None,
'signal_type': self.signal_type,
'confidence': self.confidence,

View File

@ -304,7 +304,9 @@ class PaperTradingService:
stop_loss=signal.get('stop_loss', 0),
take_profit=signal.get('take_profit', 0),
filled_price=filled_price,
quantity=quantity,
quantity=quantity, # 持仓价值
margin=margin, # 保证金
leverage=self.leverage, # 杠杆倍数
signal_grade=SignalGrade(grade),
signal_type=signal.get('signal_type') or signal.get('type', 'swing'),
confidence=signal.get('confidence', 0),
@ -352,10 +354,15 @@ class PaperTradingService:
def _calculate_dynamic_position(self, position_size: str, symbol: str) -> tuple:
"""
根据 LLM 建议的仓位大小计算实际保证金和持仓价值
根据 LLM 建议的仓位大小计算实际保证金和持仓价值复利策略
计算逻辑
- 可用保证金 = 余额 - (持仓 + 挂单占用保证金)
- 根据 position_size 按可用保证金的百分比分配
- 持仓价值 = 保证金 × 杠杆
Args:
position_size: 'heavy' / 'medium' / 'light'
position_size: 'heavy' / 'medium' / 'light' / 'micro'
symbol: 交易对
Returns:
@ -364,49 +371,45 @@ class PaperTradingService:
# 获取当前账户状态
account = self.get_account_status()
balance = account['current_balance']
used_margin = account['used_margin']
max_leverage = self.leverage # 最大杠杆 20x
used_margin = account['used_margin'] # 已用保证金(持仓+挂单)
# 计算可用保证金空间
# 全仓模式下:最大持仓价值 = 余额 × 最大杠杆
max_position_value = balance * max_leverage
current_position_value = account['total_position_value']
available_position_value = max_position_value - current_position_value
# 计算可用保证金(复利核心)
available_margin = balance - used_margin
if available_position_value <= 0:
logger.warning(f"已达最大杠杆限制,无法开仓")
if available_margin <= 0:
logger.warning(f"可用保证金不足,无法开仓(余额: ${balance:.2f}, 已用: ${used_margin:.2f}")
return 0, 0
# 根据 position_size 确定仓位比例
# heavy: 可用空间的 30%
# medium: 可用空间的 15%
# light: 可用空间的 5%
# 根据 position_size 确定保证金比例(按可用保证金百分比)
# micro: 5%, light: 10%, medium: 15%, heavy: 20%
size_ratio = {
'heavy': 0.30,
'micro': 0.05,
'light': 0.10,
'medium': 0.15,
'light': 0.05
}.get(position_size, 0.05)
'heavy': 0.20
}.get(position_size, 0.10)
# 计算目标持仓价值
target_position_value = available_position_value * size_ratio
# 计算目标保证金(直接使用可用保证金的百分比)
target_margin = available_margin * size_ratio
# 设置最小和最大限制
min_position_value = 1000 # 最小持仓价值 1000 USDT
max_single_position = balance * 5 # 单笔最大不超过 5x 杠杆
# 设置最小和最大保证金限制
min_margin = 50 # 最小保证金 50 USDT对应 1000 USDT 持仓价值)
max_single_margin = balance * 0.25 # 单笔最大不超过余额的 25%
position_value = max(min_position_value, min(target_position_value, max_single_position))
margin = max(min_margin, min(target_margin, max_single_margin))
# 确保不超过可用空间
position_value = min(position_value, available_position_value)
# 确保不超过可用保证金
margin = min(margin, available_margin)
# 修正浮点数精度问题,保留 2 位小数
position_value = round(position_value, 2)
margin = round(margin, 2)
# 计算对应的保证金
margin = round(position_value / max_leverage, 2)
# 计算持仓价值(保证金 × 杠杆)
position_value = round(margin * self.leverage, 2)
logger.info(f"动态仓位计算: {position_size} | 可用空间: ${available_position_value:,.0f} | "
f"目标仓位: ${position_value:,.0f} | 保证金: ${margin:,.0f}")
logger.info(f"动态仓位计算: {position_size} | 余额: ${balance:.2f} | 已用保证金: ${used_margin:.2f} | "
f"可用保证金: ${available_margin:.2f} | 目标保证金: ${margin:.2f} ({size_ratio*100:.0f}%) | "
f"持仓价值: ${position_value:.2f}")
return margin, position_value

View File

@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
添加 margin leverage 字段到 paper_orders
运行方式:
cd backend && python scripts/migrate_add_margin_leverage.py
"""
import sys
import os
# 添加父目录到路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.services.db_service import db_service
from sqlalchemy import text, inspect
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def check_column_exists(table_name, column_name):
"""检查列是否存在"""
inspector = inspect(db_service.engine)
columns = [col['name'] for col in inspector.get_columns(table_name)]
return column_name in columns
def migrate():
"""执行迁移"""
try:
with db_service.engine.connect() as conn:
# 检查表是否存在
inspector = inspect(db_service.engine)
if 'paper_orders' not in inspector.get_table_names():
logger.error("❌ paper_orders 表不存在")
return False
logger.info("开始迁移 paper_orders 表...")
# 检查并添加 margin 列
if not check_column_exists('paper_orders', 'margin'):
logger.info(" 添加 margin 列...")
conn.execute(text("""
ALTER TABLE paper_orders
ADD COLUMN margin FLOAT DEFAULT 50
"""))
conn.commit()
logger.info("✅ margin 列添加成功")
# 为现有记录计算并设置 margin 值
logger.info("🔄 为现有记录计算 margin...")
conn.execute(text("""
UPDATE paper_orders
SET margin = ROUND(quantity / 20.0, 2)
WHERE margin IS NULL OR margin = 50
"""))
conn.commit()
logger.info("✅ 现有记录的 margin 已更新")
else:
logger.info("✓ margin 列已存在,跳过")
# 检查并添加 leverage 列
if not check_column_exists('paper_orders', 'leverage'):
logger.info(" 添加 leverage 列...")
conn.execute(text("""
ALTER TABLE paper_orders
ADD COLUMN leverage INTEGER DEFAULT 20
"""))
conn.commit()
logger.info("✅ leverage 列添加成功")
# 为现有记录设置 leverage 值
logger.info("🔄 为现有记录设置 leverage...")
conn.execute(text("""
UPDATE paper_orders
SET leverage = 20
WHERE leverage IS NULL OR leverage = 20
"""))
conn.commit()
logger.info("✅ 现有记录的 leverage 已更新")
else:
logger.info("✓ leverage 列已存在,跳过")
logger.info("\n" + "=" * 50)
logger.info("✅ 迁移完成!")
logger.info("=" * 50)
# 显示更新后的表结构
inspector = inspect(db_service.engine)
columns = inspector.get_columns('paper_orders')
logger.info("\n📊 paper_orders 表结构:")
for col in columns:
if col['name'] in ['quantity', 'margin', 'leverage']:
logger.info(f" - {col['name']}: {col['type']}")
return True
except Exception as e:
logger.error(f"❌ 迁移失败: {e}")
import traceback
logger.error(traceback.format_exc())
return False
if __name__ == "__main__":
success = migrate()
sys.exit(0 if success else 1)

View File

@ -1294,7 +1294,9 @@
<th>浮动盈亏</th>
<th>止损</th>
<th>止盈</th>
<th>仓位</th>
<th>保证金</th>
<th>杠杆</th>
<th>持仓价值</th>
<th>时间</th>
<th>操作</th>
</tr>
@ -1338,7 +1340,9 @@
${{ order.take_profit?.toLocaleString() }}
</span>
</td>
<td>${{ order.quantity }}</td>
<td>${{ order.margin?.toFixed(2) || (order.quantity / 20).toFixed(2) }}</td>
<td>{{ order.leverage || 20 }}x</td>
<td>${{ order.quantity?.toFixed(2) || '-' }}</td>
<td>{{ formatTime(order.status === 'open' ? order.opened_at : order.created_at) }}</td>
<td class="action-cell">
<button class="action-btn danger" @click="closeOrder(order)">