a
This commit is contained in:
parent
53396adf26
commit
9490207537
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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, # 强信号阈值≥8(2026-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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -589,20 +589,19 @@ class PositionManager:
|
|||
margin_usdt = None
|
||||
|
||||
# 分步止盈(基于"实际成交价 + 已计算的止损/止盈")
|
||||
# ⚠️ 关键修复:第一目标应该使用 TAKE_PROFIT_PERCENT(10%固定止盈),而不是盈亏比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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user