This commit is contained in:
薇薇安 2026-01-29 23:34:15 +08:00
parent 53396adf26
commit 9490207537
6 changed files with 143 additions and 23 deletions

View File

@ -402,7 +402,19 @@ async def get_global_configs(
"value": 8,
"type": "number",
"category": "scan",
"description": "每次扫描后处理的交易对数量",
"description": "每次扫描后优先处理的交易对数量",
},
"SCAN_EXTRA_SYMBOLS_FOR_SUPPLEMENT": {
"value": 8,
"type": "number",
"category": "scan",
"description": "智能补单:多返回的候选数量。当前 TOP_N 中部分因冷却等被跳过时,仍会尝试这批额外候选,避免无单可下。",
},
"TAKE_PROFIT_1_PERCENT": {
"value": 0.15,
"type": "number",
"category": "strategy",
"description": "分步止盈第一目标(保证金百分比,如 0.15=15%。第一目标触发后了结50%仓位,剩余追求第二目标。",
},
"MIN_VOLUME_24H_STRICT": {
"value": 10000000,
@ -428,6 +440,24 @@ async def get_global_configs(
"category": "strategy",
"description": "智能入场开关。关闭后回归纯限价单模式",
},
"SYMBOL_LOSS_COOLDOWN_ENABLED": {
"value": True,
"type": "boolean",
"category": "strategy",
"description": "是否启用同一交易对连续亏损后的冷却避免连续亏损后继续交易。2026-01-29新增。",
},
"SYMBOL_MAX_CONSECUTIVE_LOSSES": {
"value": 2,
"type": "number",
"category": "strategy",
"description": "最大允许连续亏损次数超过则禁止交易该交易对一段时间。2026-01-29新增。",
},
"SYMBOL_LOSS_COOLDOWN_SEC": {
"value": 3600,
"type": "number",
"category": "strategy",
"description": "连续亏损后的冷却时间默认1小时。2026-01-29新增。",
},
}
for k, meta in ADDITIONAL_STRATEGY_DEFAULTS.items():
if k not in result:

View File

@ -744,6 +744,7 @@ class ConfigManager:
'TRAILING_STOP_PROTECT',
'MIN_VOLATILITY',
'TAKE_PROFIT_PERCENT',
'TAKE_PROFIT_1_PERCENT', # 分步止盈第一目标默认15%
'STOP_LOSS_PERCENT',
'MIN_STOP_LOSS_PRICE_PCT',
'MIN_TAKE_PROFIT_PRICE_PCT',
@ -807,7 +808,8 @@ class ConfigManager:
# - 提高ATR倍数从1.5到2.0),给市场波动更多空间
# - 提高最小价格变动百分比从2%到2.5%),避免止损过紧
'STOP_LOSS_PERCENT': eff_get('STOP_LOSS_PERCENT', 0.12), # 默认12%(保证金百分比)
'TAKE_PROFIT_PERCENT': eff_get('TAKE_PROFIT_PERCENT', 0.10), # 默认10%2026-01-27优化进一步降低止盈目标更容易触发提升止盈单比例
'TAKE_PROFIT_PERCENT': eff_get('TAKE_PROFIT_PERCENT', 0.10), # 默认10%(第二目标/单目标止盈)
'TAKE_PROFIT_1_PERCENT': eff_get('TAKE_PROFIT_1_PERCENT', 0.15), # 默认15%(分步止盈第一目标,提高整体盈亏比)
'MIN_STOP_LOSS_PRICE_PCT': eff_get('MIN_STOP_LOSS_PRICE_PCT', 0.025), # 默认2.5%2026-01-29优化从2%提高到2.5%,给波动更多空间)
'MIN_TAKE_PROFIT_PRICE_PCT': eff_get('MIN_TAKE_PROFIT_PRICE_PCT', 0.02), # 默认2%防止ATR过小时计算出不切实际的微小止盈距离
'USE_ATR_STOP_LOSS': eff_get('USE_ATR_STOP_LOSS', True), # 是否使用ATR动态止损
@ -825,7 +827,8 @@ class ConfigManager:
# 市场扫描30分钟主周期
'SCAN_INTERVAL': eff_get('SCAN_INTERVAL', scan_interval_default), # 30分钟增加交易机会
'TOP_N_SYMBOLS': eff_get('TOP_N_SYMBOLS', 8), # 每次扫描后处理的交易对数量增加到8给更多选择余地
'TOP_N_SYMBOLS': eff_get('TOP_N_SYMBOLS', 8), # 每次扫描后优先处理的交易对数量
'SCAN_EXTRA_SYMBOLS_FOR_SUPPLEMENT': eff_get('SCAN_EXTRA_SYMBOLS_FOR_SUPPLEMENT', 8), # 智能补单:多返回的候选数量,冷却时仍可尝试后续交易对
'MAX_SCAN_SYMBOLS': eff_get('MAX_SCAN_SYMBOLS', 250), # 扫描的最大交易对数量增加到250提升覆盖率到46%
'EXCLUDE_MAJOR_COINS': eff_get('EXCLUDE_MAJOR_COINS', True), # 是否排除主流币BTC、ETH、BNB等专注于山寨币
'KLINE_INTERVAL': eff_get('KLINE_INTERVAL', '1h'),
@ -889,6 +892,11 @@ class ConfigManager:
# 当前交易预设(让 trading_system 能知道是哪种模式)
'TRADING_PROFILE': profile,
# ⚠️ 2026-01-29新增同一交易对连续亏损过滤避免连续亏损后继续交易
'SYMBOL_LOSS_COOLDOWN_ENABLED': eff_get('SYMBOL_LOSS_COOLDOWN_ENABLED', True),
'SYMBOL_MAX_CONSECUTIVE_LOSSES': eff_get('SYMBOL_MAX_CONSECUTIVE_LOSSES', 2),
'SYMBOL_LOSS_COOLDOWN_SEC': eff_get('SYMBOL_LOSS_COOLDOWN_SEC', 3600),
}
def _sync_to_redis(self):

View File

@ -199,11 +199,13 @@ def _get_trading_config():
'MIN_POSITION_PERCENT': 0.01, # 最小仓位1%
'MIN_MARGIN_USDT': 5.0, # 最小保证金5美元
'MIN_CHANGE_PERCENT': 0.5, # 最小价格变动0.5%
'TOP_N_SYMBOLS': 8, # 选择信号最强的8个给更多选择余地避免错过好机会
'TOP_N_SYMBOLS': 8, # 选择信号最强的8个优先处理
'SCAN_EXTRA_SYMBOLS_FOR_SUPPLEMENT': 8, # 智能补单多返回8个候选冷却时仍可尝试后续交易对
'MAX_SCAN_SYMBOLS': 250, # 扫描前250个增加覆盖率从27.6%提升到46.0%
'EXCLUDE_MAJOR_COINS': True, # 排除主流币BTC、ETH、BNB等专注于山寨币
'STOP_LOSS_PERCENT': 0.12, # 止损12%(保证金百分比)
'TAKE_PROFIT_PERCENT': 0.10, # 止盈10%2026-01-27优化进一步降低止盈目标更容易触发提升止盈单比例
'TAKE_PROFIT_PERCENT': 0.10, # 第二目标/单目标止盈10%
'TAKE_PROFIT_1_PERCENT': 0.15, # 分步止盈第一目标15%,提高整体盈亏比
'MIN_STOP_LOSS_PRICE_PCT': 0.025, # 最小止损价格变动2.5%2026-01-29优化从2%提高到2.5%,给波动更多空间)
'MIN_TAKE_PROFIT_PRICE_PCT': 0.02, # 最小止盈价格变动2%
'USE_ATR_STOP_LOSS': True, # 使用ATR动态止损
@ -284,6 +286,11 @@ def _get_trading_config():
'SMART_ENTRY_ENABLED': True, # 开启智能入场,提高成交率
'SMART_ENTRY_STRONG_SIGNAL': 8, # 强信号阈值≥82026-01-29优化与MIN_SIGNAL_STRENGTH保持一致
'ENTRY_SYMBOL_COOLDOWN_SEC': 1800, # 同一币种冷却30分钟1800秒快速验证模式缩短冷却以增加交易频率
# ===== 同一交易对连续亏损过滤(避免连续亏损后继续交易)=====
'SYMBOL_LOSS_COOLDOWN_ENABLED': True, # 是否启用同一交易对连续亏损后的冷却
'SYMBOL_MAX_CONSECUTIVE_LOSSES': 2, # 最大允许连续亏损次数(超过则禁止交易)
'SYMBOL_LOSS_COOLDOWN_SEC': 3600, # 连续亏损后的冷却时间默认1小时
'ENTRY_TIMEOUT_SEC': 180, # 智能入场总预算(秒)(限价/追价逻辑内部使用)
'ENTRY_STEP_WAIT_SEC': 15, # 每步等待成交时间(秒)
'ENTRY_CHASE_MAX_STEPS': 4, # 最多追价步数(逐步减少 offset

View File

@ -161,7 +161,11 @@ class MarketScanner:
reverse=True
)
top_n = sorted_results[:cfg.get('TOP_N_SYMBOLS', config.TRADING_CONFIG['TOP_N_SYMBOLS'])]
# 智能补单:返回 TOP_N + 额外候选数,当前 TOP_N 中部分因冷却等被跳过时,策略仍会尝试后续交易对,避免无单可下
top_n_val = cfg.get('TOP_N_SYMBOLS', config.TRADING_CONFIG['TOP_N_SYMBOLS'])
extra = cfg.get('SCAN_EXTRA_SYMBOLS_FOR_SUPPLEMENT', config.TRADING_CONFIG.get('SCAN_EXTRA_SYMBOLS_FOR_SUPPLEMENT', 8))
take_count = min(len(sorted_results), top_n_val + extra)
top_n = sorted_results[:take_count]
self.top_symbols = top_n

View File

@ -589,20 +589,19 @@ class PositionManager:
margin_usdt = None
# 分步止盈(基于"实际成交价 + 已计算的止损/止盈"
# ⚠️ 关键修复:第一目标应该使用 TAKE_PROFIT_PERCENT10%固定止盈而不是盈亏比1:1
# 这样第一目标更容易触发保证拿到10%盈利然后剩余50%追求更高收益
# 第一目标和触发条件必须一致,都使用 TAKE_PROFIT_PERCENT
take_profit_1_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10)
# ⚠️ 第一目标使用 TAKE_PROFIT_1_PERCENT默认15%),与第二目标 TAKE_PROFIT_PERCENT 分离,提高整体盈亏比
# 第一目标和触发条件必须一致,都使用 TAKE_PROFIT_1_PERCENT
take_profit_1_pct_margin = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.15)
# 兼容百分比形式和比例形式
if take_profit_1_pct_margin is not None and take_profit_1_pct_margin > 1:
take_profit_1_pct_margin = take_profit_1_pct_margin / 100.0
# 计算第一目标止盈价(基于保证金10%
# 计算第一目标止盈价(基于保证金默认15%
if margin_usdt and margin_usdt > 0 and quantity > 0:
take_profit_1_amount = margin_usdt * take_profit_1_pct_margin
if side == 'BUY':
take_profit_1 = entry_price + (take_profit_1_amount / quantity) # 10%固定止盈
take_profit_1 = entry_price + (take_profit_1_amount / quantity) # 第一目标止盈默认15%
else:
take_profit_1 = entry_price - (take_profit_1_amount / quantity) # 10%固定止盈
take_profit_1 = entry_price - (take_profit_1_amount / quantity) # 第一目标止盈默认15%
else:
# 如果无法计算保证金回退到盈亏比1:1
if side == 'BUY':
@ -1713,12 +1712,11 @@ class PositionManager:
partial_profit_taken = position_info.get('partialProfitTaken', False)
remaining_quantity = position_info.get('remainingQuantity', quantity)
# 第一目标:10%固定止盈基于保证金了结50%仓位保证拿到10%盈利
# 第一目标:TAKE_PROFIT_1_PERCENT 止盈默认15%保证金了结50%仓位
# ✅ 已移除时间锁限制,可以立即执行
if not partial_profit_taken and take_profit_1 is not None:
# ⚠️ 关键修复:直接使用配置的 TAKE_PROFIT_PERCENT而不是从止盈价格反推
# 因为第一目标现在已经是基于 TAKE_PROFIT_PERCENT 计算的,直接使用配置值更准确
take_profit_1_pct_margin_config = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10)
# 直接使用配置的 TAKE_PROFIT_1_PERCENT与开仓时计算的第一目标一致
take_profit_1_pct_margin_config = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.15)
# 兼容百分比形式和比例形式
if take_profit_1_pct_margin_config > 1:
take_profit_1_pct_margin_config = take_profit_1_pct_margin_config / 100.0
@ -3010,11 +3008,10 @@ class PositionManager:
take_profit_2 = position_info.get('takeProfit2', position_info.get('takeProfit')) # 第二目标1.5:1
# ⚠️ 注意partial_profit_taken和remaining_quantity已在方法开头初始化这里不需要重复定义
# 第一目标:10%固定止盈基于保证金了结50%仓位保证拿到10%盈利
# 第一目标:TAKE_PROFIT_1_PERCENT 止盈默认15%保证金了结50%仓位
if not partial_profit_taken and take_profit_1 is not None:
# ⚠️ 关键修复:直接使用配置的 TAKE_PROFIT_PERCENT而不是从止盈价格反推
# 因为第一目标现在已经是基于 TAKE_PROFIT_PERCENT 计算的,直接使用配置值更准确
take_profit_1_pct_margin_config = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10)
# 直接使用配置的 TAKE_PROFIT_1_PERCENT与开仓时计算的第一目标一致
take_profit_1_pct_margin_config = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.15)
# 兼容百分比形式和比例形式
if take_profit_1_pct_margin_config > 1:
take_profit_1_pct_margin_config = take_profit_1_pct_margin_config / 100.0
@ -3022,8 +3019,7 @@ class PositionManager:
# 直接比较当前盈亏百分比与第一目标(基于保证金,使用配置值)
if pnl_percent_margin >= take_profit_1_pct_margin:
# ⚠️ 2026-01-27优化动态读取配置值更新日志文案
take_profit_pct_config = config.TRADING_CONFIG.get('TAKE_PROFIT_PERCENT', 0.10)
take_profit_pct_config = config.TRADING_CONFIG.get('TAKE_PROFIT_1_PERCENT', 0.15)
if take_profit_pct_config > 1:
take_profit_pct_config = take_profit_pct_config / 100.0
take_profit_pct_display = take_profit_pct_config * 100

View File

@ -3,6 +3,7 @@
"""
import logging
import os
import time
from datetime import datetime, timezone, timedelta
from typing import Dict, List, Optional
try:
@ -558,8 +559,82 @@ class RiskManager:
logger.info(f"{symbol} 今日开仓次数已达上限:{c}/{max_daily},跳过")
return False
# ⚠️ 2026-01-29新增检查同一交易对连续亏损情况避免连续亏损后继续交易
try:
loss_cooldown_enabled = bool(config.TRADING_CONFIG.get("SYMBOL_LOSS_COOLDOWN_ENABLED", True))
if loss_cooldown_enabled:
max_consecutive_losses = int(config.TRADING_CONFIG.get("SYMBOL_MAX_CONSECUTIVE_LOSSES", 2) or 2)
loss_cooldown_sec = int(config.TRADING_CONFIG.get("SYMBOL_LOSS_COOLDOWN_SEC", 3600) or 3600) # 默认1小时
# 查询该交易对最近的交易记录(仅查询已平仓的)
recent_losses = await self._check_recent_losses(symbol, max_consecutive_losses, loss_cooldown_sec)
if recent_losses >= max_consecutive_losses:
logger.info(
f"{symbol} [连续亏损过滤] 最近{max_consecutive_losses}次交易连续亏损,"
f"禁止交易{loss_cooldown_sec}秒(冷却中)"
)
return False
except Exception as e:
logger.debug(f"{symbol} 检查连续亏损时出错(忽略,允许交易): {e}")
return True
async def _check_recent_losses(self, symbol: str, max_consecutive: int, cooldown_sec: int) -> int:
"""
检查同一交易对最近的连续亏损次数
Args:
symbol: 交易对
max_consecutive: 最大允许连续亏损次数
cooldown_sec: 冷却时间
Returns:
连续亏损次数如果>=max_consecutive则应该禁止交易
"""
try:
# 尝试从数据库查询最近的交易记录
from database.models import Trade
# 查询最近N+1次已平仓的交易多查一次确保能判断是否连续
recent_trades = Trade.get_all(
symbol=symbol,
status='closed',
account_id=int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1)
)
# 按平仓时间倒序排序(最新的在前)
recent_trades = sorted(
recent_trades,
key=lambda x: (x.get('exit_time') or x.get('entry_time') or 0),
reverse=True
)[:max_consecutive + 1] # 只取最近N+1次
if not recent_trades:
return 0
# 检查是否在冷却时间内
now = int(time.time())
latest_trade = recent_trades[0]
latest_exit_time = latest_trade.get('exit_time') or latest_trade.get('entry_time') or 0
# 如果最新交易还在冷却时间内,检查连续亏损
if now - latest_exit_time < cooldown_sec:
consecutive_losses = 0
for trade in recent_trades:
pnl = float(trade.get('pnl', 0) or 0)
if pnl < 0: # 亏损
consecutive_losses += 1
else: # 盈利,中断连续亏损
break
return consecutive_losses
# 如果最新交易已超过冷却时间,不限制
return 0
except Exception as e:
logger.debug(f"查询{symbol}最近交易记录失败(忽略,允许交易): {e}")
return 0
def _daily_entries_key(self) -> str:
try:
aid = int(os.getenv("ATS_ACCOUNT_ID") or os.getenv("ACCOUNT_ID") or 1)