1
This commit is contained in:
parent
345416e32f
commit
777f9ff703
|
|
@ -79,7 +79,8 @@ async def get_trades(
|
||||||
trade_type: Optional[str] = Query(None, description="交易类型筛选: 'buy', 'sell'"),
|
trade_type: Optional[str] = Query(None, description="交易类型筛选: 'buy', 'sell'"),
|
||||||
exit_reason: Optional[str] = Query(None, description="平仓原因筛选: 'stop_loss', 'take_profit', 'trailing_stop', 'manual', 'sync'"),
|
exit_reason: Optional[str] = Query(None, description="平仓原因筛选: 'stop_loss', 'take_profit', 'trailing_stop', 'manual', 'sync'"),
|
||||||
status: Optional[str] = Query(None, description="状态筛选: 'open', 'closed', 'cancelled'"),
|
status: Optional[str] = Query(None, description="状态筛选: 'open', 'closed', 'cancelled'"),
|
||||||
limit: int = Query(100, ge=1, le=1000, description="返回记录数限制")
|
limit: int = Query(100, ge=1, le=1000, description="返回记录数限制"),
|
||||||
|
only_system_orders: bool = Query(False, description="仅返回本系统开仓的记录(有开仓订单号),排除同步/手动录入的仓位"),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取交易记录
|
获取交易记录
|
||||||
|
|
@ -125,7 +126,7 @@ async def get_trades(
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.warning(f"无效的结束日期格式: {end_date}")
|
logger.warning(f"无效的结束日期格式: {end_date}")
|
||||||
|
|
||||||
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, status, trade_type, exit_reason, account_id=account_id)
|
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, status, trade_type, exit_reason, account_id=account_id, only_system_orders=only_system_orders)
|
||||||
logger.info(f"查询到 {len(trades)} 条交易记录")
|
logger.info(f"查询到 {len(trades)} 条交易记录")
|
||||||
|
|
||||||
# 格式化交易记录,添加平仓类型的中文显示
|
# 格式化交易记录,添加平仓类型的中文显示
|
||||||
|
|
@ -166,7 +167,8 @@ async def get_trades(
|
||||||
"end_date": datetime.fromtimestamp(end_timestamp).strftime('%Y-%m-%d %H:%M:%S') if end_timestamp else None,
|
"end_date": datetime.fromtimestamp(end_timestamp).strftime('%Y-%m-%d %H:%M:%S') if end_timestamp else None,
|
||||||
"period": period,
|
"period": period,
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"status": status
|
"status": status,
|
||||||
|
"only_system_orders": only_system_orders
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,11 +185,12 @@ async def get_trade_stats(
|
||||||
start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
||||||
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
|
||||||
period: Optional[str] = Query(None, description="快速时间段筛选: '1d', '7d', '30d', 'today', 'week', 'month'"),
|
period: Optional[str] = Query(None, description="快速时间段筛选: '1d', '7d', '30d', 'today', 'week', 'month'"),
|
||||||
symbol: Optional[str] = Query(None, description="交易对筛选")
|
symbol: Optional[str] = Query(None, description="交易对筛选"),
|
||||||
|
only_system_orders: bool = Query(False, description="仅统计本系统开仓的记录"),
|
||||||
):
|
):
|
||||||
"""获取交易统计"""
|
"""获取交易统计"""
|
||||||
try:
|
try:
|
||||||
logger.info(f"获取交易统计请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}")
|
logger.info(f"获取交易统计请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}, only_system_orders={only_system_orders}")
|
||||||
|
|
||||||
start_timestamp = None
|
start_timestamp = None
|
||||||
end_timestamp = None
|
end_timestamp = None
|
||||||
|
|
@ -220,7 +223,7 @@ async def get_trade_stats(
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.warning(f"无效的结束日期格式: {end_date}")
|
logger.warning(f"无效的结束日期格式: {end_date}")
|
||||||
|
|
||||||
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id)
|
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id, only_system_orders=only_system_orders)
|
||||||
closed_trades = [t for t in trades if t['status'] == 'closed']
|
closed_trades = [t for t in trades if t['status'] == 'closed']
|
||||||
|
|
||||||
# 辅助函数:计算净盈亏(优先使用 realized_pnl - commission)
|
# 辅助函数:计算净盈亏(优先使用 realized_pnl - commission)
|
||||||
|
|
|
||||||
|
|
@ -700,7 +700,7 @@ class Trade:
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None):
|
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None, only_system_orders: bool = False):
|
||||||
"""获取交易记录
|
"""获取交易记录
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -710,6 +710,7 @@ class Trade:
|
||||||
status: 状态(可选)
|
status: 状态(可选)
|
||||||
trade_type: 交易类型(可选)
|
trade_type: 交易类型(可选)
|
||||||
exit_reason: 平仓原因(可选)
|
exit_reason: 平仓原因(可选)
|
||||||
|
only_system_orders: 若为 True,仅返回本系统开仓的记录(entry_order_id 非空)
|
||||||
"""
|
"""
|
||||||
query = "SELECT * FROM trades WHERE 1=1"
|
query = "SELECT * FROM trades WHERE 1=1"
|
||||||
params = []
|
params = []
|
||||||
|
|
@ -722,6 +723,9 @@ class Trade:
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if only_system_orders and _table_has_column("trades", "entry_order_id"):
|
||||||
|
query += " AND entry_order_id IS NOT NULL AND entry_order_id != 0"
|
||||||
|
|
||||||
if start_timestamp is not None:
|
if start_timestamp is not None:
|
||||||
query += " AND created_at >= %s"
|
query += " AND created_at >= %s"
|
||||||
params.append(start_timestamp)
|
params.append(start_timestamp)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ const TradeList = () => {
|
||||||
const [useCustomDate, setUseCustomDate] = useState(false)
|
const [useCustomDate, setUseCustomDate] = useState(false)
|
||||||
const [tradeType, setTradeType] = useState('')
|
const [tradeType, setTradeType] = useState('')
|
||||||
const [exitReason, setExitReason] = useState('')
|
const [exitReason, setExitReason] = useState('')
|
||||||
|
const [onlySystemOrders, setOnlySystemOrders] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData()
|
loadData()
|
||||||
|
|
@ -45,6 +46,7 @@ const TradeList = () => {
|
||||||
if (status) params.status = status
|
if (status) params.status = status
|
||||||
if (tradeType) params.trade_type = tradeType
|
if (tradeType) params.trade_type = tradeType
|
||||||
if (exitReason) params.exit_reason = exitReason
|
if (exitReason) params.exit_reason = exitReason
|
||||||
|
if (onlySystemOrders) params.only_system_orders = true
|
||||||
|
|
||||||
const [tradesData, statsData] = await Promise.all([
|
const [tradesData, statsData] = await Promise.all([
|
||||||
api.getTrades(params),
|
api.getTrades(params),
|
||||||
|
|
@ -80,6 +82,7 @@ const TradeList = () => {
|
||||||
setSymbol('')
|
setSymbol('')
|
||||||
setStatus('')
|
setStatus('')
|
||||||
setUseCustomDate(false)
|
setUseCustomDate(false)
|
||||||
|
setOnlySystemOrders(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 导出当前订单数据(含入场/离场原因、入场思路等完整字段,便于后续分析)
|
// 导出当前订单数据(含入场/离场原因、入场思路等完整字段,便于后续分析)
|
||||||
|
|
@ -417,6 +420,17 @@ const TradeList = () => {
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="filter-section" style={{ alignItems: 'center' }}>
|
||||||
|
<label title="只显示本策略在币安下单的记录(有开仓订单号),排除从币安同步/手动录入的仓位">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={onlySystemOrders}
|
||||||
|
onChange={(e) => setOnlySystemOrders(e.target.checked)}
|
||||||
|
/>
|
||||||
|
{' '}仅本系统开仓
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="filter-actions">
|
<div className="filter-actions">
|
||||||
<button className="btn-primary" onClick={loadData}>
|
<button className="btn-primary" onClick={loadData}>
|
||||||
查询
|
查询
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ DEFAULT_TRADING_CONFIG = {
|
||||||
'TAKE_PROFIT_PERCENT': 0.80, # 第二目标止盈80%(追求大趋势收益)
|
'TAKE_PROFIT_PERCENT': 0.80, # 第二目标止盈80%(追求大趋势收益)
|
||||||
'TAKE_PROFIT_1_PERCENT': 0.30, # 第一目标止盈30%(确保3:1盈亏比)
|
'TAKE_PROFIT_1_PERCENT': 0.30, # 第一目标止盈30%(确保3:1盈亏比)
|
||||||
'MIN_RR_FOR_TP1': 1.5, # 第一目标止盈的最小盈亏比(相对于止损距离)(2026-02-12:1.5 改善盈亏比)
|
'MIN_RR_FOR_TP1': 1.5, # 第一目标止盈的最小盈亏比(相对于止损距离)(2026-02-12:1.5 改善盈亏比)
|
||||||
'MIN_STOP_LOSS_PRICE_PCT': 0.025, # 最小止损价格变动2.5%
|
'MIN_STOP_LOSS_PRICE_PCT': 0.025, # 最小止损价格变动2.5%(交易所/挂单最小距离)
|
||||||
'MIN_TAKE_PROFIT_PRICE_PCT': 0.02, # 最小止盈价格变动2%
|
'MIN_TAKE_PROFIT_PRICE_PCT': 0.02, # 最小止盈价格变动2%
|
||||||
'USE_ATR_STOP_LOSS': True, # 使用ATR动态止损
|
'USE_ATR_STOP_LOSS': True, # 使用ATR动态止损
|
||||||
'ATR_STOP_LOSS_MULTIPLIER': 0.5, # ATR止损倍数0.5(2026-02-14:收紧止损,与ATR_MULTIPLIER_MAX一致)
|
'ATR_STOP_LOSS_MULTIPLIER': 0.5, # ATR止损倍数0.5(2026-02-14:收紧止损,与ATR_MULTIPLIER_MAX一致)
|
||||||
|
|
|
||||||
|
|
@ -2775,112 +2775,119 @@ class PositionManager:
|
||||||
else:
|
else:
|
||||||
logger.info("✓ 持仓状态同步完成,数据库与币安状态一致")
|
logger.info("✓ 持仓状态同步完成,数据库与币安状态一致")
|
||||||
|
|
||||||
# 5. 检查币安有但数据库没有记录的持仓(可能是手动开仓的)
|
# 5. 检查币安有但数据库没有记录的持仓(可能是本策略开仓后未正确落库、或其它来源)
|
||||||
|
# 默认不再自动创建「手动开仓」记录,避免产生大量无 entry_order_id 的怪单(与币安实际订单对不上)
|
||||||
missing_in_db = binance_symbols - db_open_symbols
|
missing_in_db = binance_symbols - db_open_symbols
|
||||||
if missing_in_db:
|
if missing_in_db:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有记录: "
|
f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有 open 记录: "
|
||||||
f"{', '.join(missing_in_db)} (可能是手动开仓)"
|
f"{', '.join(missing_in_db)}"
|
||||||
)
|
)
|
||||||
|
sync_create_manual = config.TRADING_CONFIG.get("SYNC_CREATE_MANUAL_ENTRY_RECORD", False)
|
||||||
# 为手动开仓的持仓创建数据库记录并启动监控
|
if not sync_create_manual:
|
||||||
for symbol in missing_in_db:
|
logger.info(
|
||||||
try:
|
" → 已跳过自动创建交易记录(SYNC_CREATE_MANUAL_ENTRY_RECORD=False)。"
|
||||||
# 获取币安持仓详情
|
" 若确认为本策略开仓可检查开仓时是否保存了 entry_order_id;若为手动开仓且需纳入列表可设该配置为 True。"
|
||||||
binance_position = next(
|
)
|
||||||
(p for p in binance_positions if p['symbol'] == symbol),
|
else:
|
||||||
None
|
# 为手动开仓的持仓创建数据库记录并启动监控(仅当显式开启时)
|
||||||
)
|
for symbol in missing_in_db:
|
||||||
if not binance_position:
|
try:
|
||||||
continue
|
# 获取币安持仓详情
|
||||||
|
binance_position = next(
|
||||||
|
(p for p in binance_positions if p['symbol'] == symbol),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
if not binance_position:
|
||||||
|
continue
|
||||||
|
|
||||||
|
position_amt = binance_position['positionAmt']
|
||||||
|
entry_price = binance_position['entryPrice']
|
||||||
|
quantity = abs(position_amt)
|
||||||
|
side = 'BUY' if position_amt > 0 else 'SELL'
|
||||||
|
|
||||||
|
notional = (float(entry_price) * float(quantity)) if entry_price and quantity else 0
|
||||||
|
if notional < 1.0:
|
||||||
|
logger.debug(f"{symbol} [状态同步] 跳过灰尘持仓 (名义 {notional:.4f} USDT < 1),不创建记录")
|
||||||
|
continue
|
||||||
|
logger.info(
|
||||||
|
f"{symbol} [状态同步] 检测到手动开仓,创建数据库记录... "
|
||||||
|
f"({side} {quantity:.4f} @ {entry_price:.4f})"
|
||||||
|
)
|
||||||
|
# 创建数据库记录(显式传入 account_id,避免多账号混用)
|
||||||
|
trade_id = Trade.create(
|
||||||
|
symbol=symbol,
|
||||||
|
side=side,
|
||||||
|
quantity=quantity,
|
||||||
|
entry_price=entry_price,
|
||||||
|
leverage=binance_position.get('leverage', 10),
|
||||||
|
entry_reason='manual_entry', # 标记为手动开仓
|
||||||
|
notional_usdt=notional,
|
||||||
|
margin_usdt=(notional / float(binance_position.get('leverage', 10) or 10)) if float(binance_position.get('leverage', 10) or 0) > 0 else None,
|
||||||
|
account_id=self.account_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"{symbol} [状态同步] ✓ 数据库记录已创建 (ID: {trade_id})")
|
||||||
|
|
||||||
|
# 创建本地持仓记录(用于监控)
|
||||||
|
ticker = await self.client.get_ticker_24h(symbol)
|
||||||
|
current_price = ticker['price'] if ticker else entry_price
|
||||||
|
|
||||||
|
# 计算止损止盈(基于保证金)
|
||||||
|
leverage = binance_position.get('leverage', 10)
|
||||||
|
stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08)
|
||||||
|
# ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式)
|
||||||
|
if stop_loss_pct_margin is not None and stop_loss_pct_margin > 1:
|
||||||
|
stop_loss_pct_margin = stop_loss_pct_margin / 100.0
|
||||||
|
take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15)
|
||||||
|
# ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式)
|
||||||
|
if take_profit_pct_margin is not None and take_profit_pct_margin > 1:
|
||||||
|
take_profit_pct_margin = take_profit_pct_margin / 100.0
|
||||||
|
# 如果配置中没有设置止盈,则使用止损的2倍作为默认
|
||||||
|
if take_profit_pct_margin is None or take_profit_pct_margin == 0:
|
||||||
|
take_profit_pct_margin = stop_loss_pct_margin * 2.0
|
||||||
|
|
||||||
|
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
||||||
|
entry_price, side, quantity, leverage,
|
||||||
|
stop_loss_pct=stop_loss_pct_margin
|
||||||
|
)
|
||||||
|
take_profit_price = self.risk_manager.get_take_profit_price(
|
||||||
|
entry_price, side, quantity, leverage,
|
||||||
|
take_profit_pct=take_profit_pct_margin
|
||||||
|
)
|
||||||
|
|
||||||
|
position_info = {
|
||||||
|
'symbol': symbol,
|
||||||
|
'side': side,
|
||||||
|
'quantity': quantity,
|
||||||
|
'entryPrice': entry_price,
|
||||||
|
'changePercent': 0, # 手动开仓,无法计算涨跌幅
|
||||||
|
'orderId': None,
|
||||||
|
'tradeId': trade_id,
|
||||||
|
'stopLoss': stop_loss_price,
|
||||||
|
'takeProfit': take_profit_price,
|
||||||
|
'initialStopLoss': stop_loss_price,
|
||||||
|
'leverage': leverage,
|
||||||
|
'entryReason': 'manual_entry',
|
||||||
|
'entryTime': get_beijing_time(), # 补全入场时间
|
||||||
|
'atr': None,
|
||||||
|
'maxProfit': 0.0,
|
||||||
|
'trailingStopActivated': False
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_positions[symbol] = position_info
|
||||||
|
|
||||||
|
# 启动WebSocket监控
|
||||||
|
if self._monitoring_enabled:
|
||||||
|
await self._start_position_monitoring(symbol)
|
||||||
|
logger.info(f"{symbol} [状态同步] ✓ 已启动实时监控")
|
||||||
|
|
||||||
|
logger.info(f"{symbol} [状态同步] ✓ 手动开仓同步完成")
|
||||||
|
|
||||||
position_amt = binance_position['positionAmt']
|
except Exception as e:
|
||||||
entry_price = binance_position['entryPrice']
|
logger.error(f"{symbol} [状态同步] ❌ 处理手动开仓失败: {e}")
|
||||||
quantity = abs(position_amt)
|
import traceback
|
||||||
side = 'BUY' if position_amt > 0 else 'SELL'
|
logger.error(f" 错误详情:\n{traceback.format_exc()}")
|
||||||
|
|
||||||
notional = (float(entry_price) * float(quantity)) if entry_price and quantity else 0
|
|
||||||
if notional < 1.0:
|
|
||||||
logger.debug(f"{symbol} [状态同步] 跳过灰尘持仓 (名义 {notional:.4f} USDT < 1),不创建记录")
|
|
||||||
continue
|
|
||||||
logger.info(
|
|
||||||
f"{symbol} [状态同步] 检测到手动开仓,创建数据库记录... "
|
|
||||||
f"({side} {quantity:.4f} @ {entry_price:.4f})"
|
|
||||||
)
|
|
||||||
# 创建数据库记录(显式传入 account_id,避免多账号混用)
|
|
||||||
trade_id = Trade.create(
|
|
||||||
symbol=symbol,
|
|
||||||
side=side,
|
|
||||||
quantity=quantity,
|
|
||||||
entry_price=entry_price,
|
|
||||||
leverage=binance_position.get('leverage', 10),
|
|
||||||
entry_reason='manual_entry', # 标记为手动开仓
|
|
||||||
notional_usdt=notional,
|
|
||||||
margin_usdt=(notional / float(binance_position.get('leverage', 10) or 10)) if float(binance_position.get('leverage', 10) or 0) > 0 else None,
|
|
||||||
account_id=self.account_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"{symbol} [状态同步] ✓ 数据库记录已创建 (ID: {trade_id})")
|
|
||||||
|
|
||||||
# 创建本地持仓记录(用于监控)
|
|
||||||
ticker = await self.client.get_ticker_24h(symbol)
|
|
||||||
current_price = ticker['price'] if ticker else entry_price
|
|
||||||
|
|
||||||
# 计算止损止盈(基于保证金)
|
|
||||||
leverage = binance_position.get('leverage', 10)
|
|
||||||
stop_loss_pct_margin = config.TRADING_CONFIG.get('STOP_LOSS_PERCENT', 0.08)
|
|
||||||
# ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式)
|
|
||||||
if stop_loss_pct_margin is not None and stop_loss_pct_margin > 1:
|
|
||||||
stop_loss_pct_margin = stop_loss_pct_margin / 100.0
|
|
||||||
take_profit_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.15)
|
|
||||||
# ⚠️ 关键修复:配置值格式转换(兼容百分比形式和比例形式)
|
|
||||||
if take_profit_pct_margin is not None and take_profit_pct_margin > 1:
|
|
||||||
take_profit_pct_margin = take_profit_pct_margin / 100.0
|
|
||||||
# 如果配置中没有设置止盈,则使用止损的2倍作为默认
|
|
||||||
if take_profit_pct_margin is None or take_profit_pct_margin == 0:
|
|
||||||
take_profit_pct_margin = stop_loss_pct_margin * 2.0
|
|
||||||
|
|
||||||
stop_loss_price = self.risk_manager.get_stop_loss_price(
|
|
||||||
entry_price, side, quantity, leverage,
|
|
||||||
stop_loss_pct=stop_loss_pct_margin
|
|
||||||
)
|
|
||||||
take_profit_price = self.risk_manager.get_take_profit_price(
|
|
||||||
entry_price, side, quantity, leverage,
|
|
||||||
take_profit_pct=take_profit_pct_margin
|
|
||||||
)
|
|
||||||
|
|
||||||
position_info = {
|
|
||||||
'symbol': symbol,
|
|
||||||
'side': side,
|
|
||||||
'quantity': quantity,
|
|
||||||
'entryPrice': entry_price,
|
|
||||||
'changePercent': 0, # 手动开仓,无法计算涨跌幅
|
|
||||||
'orderId': None,
|
|
||||||
'tradeId': trade_id,
|
|
||||||
'stopLoss': stop_loss_price,
|
|
||||||
'takeProfit': take_profit_price,
|
|
||||||
'initialStopLoss': stop_loss_price,
|
|
||||||
'leverage': leverage,
|
|
||||||
'entryReason': 'manual_entry',
|
|
||||||
'entryTime': get_beijing_time(), # 补全入场时间
|
|
||||||
'atr': None,
|
|
||||||
'maxProfit': 0.0,
|
|
||||||
'trailingStopActivated': False
|
|
||||||
}
|
|
||||||
|
|
||||||
self.active_positions[symbol] = position_info
|
|
||||||
|
|
||||||
# 启动WebSocket监控
|
|
||||||
if self._monitoring_enabled:
|
|
||||||
await self._start_position_monitoring(symbol)
|
|
||||||
logger.info(f"{symbol} [状态同步] ✓ 已启动实时监控")
|
|
||||||
|
|
||||||
logger.info(f"{symbol} [状态同步] ✓ 手动开仓同步完成")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"{symbol} [状态同步] ❌ 处理手动开仓失败: {e}")
|
|
||||||
import traceback
|
|
||||||
logger.error(f" 错误详情:\n{traceback.format_exc()}")
|
|
||||||
|
|
||||||
# 6. 同步挂单信息 (STOP_MARKET / TAKE_PROFIT_MARKET)
|
# 6. 同步挂单信息 (STOP_MARKET / TAKE_PROFIT_MARKET)
|
||||||
if self.active_positions:
|
if self.active_positions:
|
||||||
|
|
|
||||||
|
|
@ -1119,11 +1119,22 @@ class RiskManager:
|
||||||
final_stop_loss_amount = (final_stop_loss - entry_price) * quantity
|
final_stop_loss_amount = (final_stop_loss - entry_price) * quantity
|
||||||
final_stop_loss_pct_margin = (final_stop_loss_amount / margin * 100) if margin > 0 else 0
|
final_stop_loss_pct_margin = (final_stop_loss_amount / margin * 100) if margin > 0 else 0
|
||||||
|
|
||||||
# ⚠️ 优化:如果使用的是ATR止损或技术止损,允许突破配置的保证金百分比限制
|
# ⚠️ 优化:如果使用的是ATR止损或技术止损,允许突破配置的保证金百分比限制(更宽可以)
|
||||||
# 因为仓位大小已经根据风险进行了调整,所以即使单笔亏损比例大(如80%保证金),总亏损金额仍受控
|
# 因为仓位大小已经根据风险进行了调整,所以即使单笔亏损比例大,总亏损金额仍受控
|
||||||
# 只有在使用默认保证金止损策略时,才强制执行限制
|
|
||||||
is_atr_or_tech = selected_method in ['ATR', '技术分析']
|
is_atr_or_tech = selected_method in ['ATR', '技术分析']
|
||||||
|
|
||||||
|
# 与策略一致:ATR/技术止损不得比配置的 STOP_LOSS_PERCENT 更紧(避免低波动时极窄止损被扫)
|
||||||
|
# 仅用 STOP_LOSS_PERCENT 作为下限,不引入额外参数,避免与 STOP_LOSS_PERCENT/ATR 策略冲突
|
||||||
|
if is_atr_or_tech and margin > 0 and final_stop_loss_pct_margin < (stop_loss_percent * 100):
|
||||||
|
atr_pct_before = final_stop_loss_pct_margin
|
||||||
|
final_stop_loss = stop_loss_price_margin
|
||||||
|
final_stop_loss_pct_margin = stop_loss_percent * 100
|
||||||
|
selected_method = 'ATR/技术→保证金下限'
|
||||||
|
logger.info(
|
||||||
|
f"ℹ️ ATR/技术止损({atr_pct_before:.2f}% 保证金) 紧于配置 STOP_LOSS_PERCENT({stop_loss_percent*100:.1f}%),"
|
||||||
|
f"已采用保证金止损以与策略一致"
|
||||||
|
)
|
||||||
|
|
||||||
# 如果最终止损价对应的保证金百分比超过配置值,且不是ATR/技术止损,则强制使用保证金止损
|
# 如果最终止损价对应的保证金百分比超过配置值,且不是ATR/技术止损,则强制使用保证金止损
|
||||||
if final_stop_loss_pct_margin > (stop_loss_percent * 100) and not is_atr_or_tech:
|
if final_stop_loss_pct_margin > (stop_loss_percent * 100) and not is_atr_or_tech:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user