bot修改开仓限制
This commit is contained in:
@@ -79,6 +79,14 @@ GATE_TPSL_PRICE_TYPE=0
|
|||||||
# 页面与浏览器标签展示的交易所名称(多环境区分时可改成例如 Gate·模拟)
|
# 页面与浏览器标签展示的交易所名称(多环境区分时可改成例如 Gate·模拟)
|
||||||
# EXCHANGE_DISPLAY_NAME=Gate.io
|
# EXCHANGE_DISPLAY_NAME=Gate.io
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 交易执行 / 开仓限制(与 crypto_monitor_gate 主站一致)
|
||||||
|
# =============================================================================
|
||||||
|
# 【最大同时持仓】active 下单监控数达到该值后禁止再开仓(默认 1=单仓)
|
||||||
|
MAX_ACTIVE_POSITIONS=1
|
||||||
|
# 整点前禁止新开仓:true=启用(默认),false=关闭(交易日划分仍用 TRADING_DAY_RESET_HOUR)
|
||||||
|
# TRADING_DAY_RESET_OPEN_GUARD_ENABLED=true
|
||||||
|
|
||||||
# 关键位监控:5m收线突破过滤参数
|
# 关键位监控:5m收线突破过滤参数
|
||||||
KLINE_TIMEFRAME=5m
|
KLINE_TIMEFRAME=5m
|
||||||
KEY_BREAKOUT_LIMIT_PCT=1.5
|
KEY_BREAKOUT_LIMIT_PCT=1.5
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ DAILY_LOSS_CAPITAL = float(os.getenv("DAILY_LOSS_CAPITAL", "20"))
|
|||||||
DAILY_PROFIT_CAPITAL = float(os.getenv("DAILY_PROFIT_CAPITAL", "50"))
|
DAILY_PROFIT_CAPITAL = float(os.getenv("DAILY_PROFIT_CAPITAL", "50"))
|
||||||
BTC_LEVERAGE = int(os.getenv("BTC_LEVERAGE", "10"))
|
BTC_LEVERAGE = int(os.getenv("BTC_LEVERAGE", "10"))
|
||||||
ALT_LEVERAGE = int(os.getenv("ALT_LEVERAGE", "5"))
|
ALT_LEVERAGE = int(os.getenv("ALT_LEVERAGE", "5"))
|
||||||
|
# 与 Gate 主站一致:最大同时 active 下单监控数(默认 1=单仓)
|
||||||
|
MAX_ACTIVE_POSITIONS = max(1, int(os.getenv("MAX_ACTIVE_POSITIONS", "1")))
|
||||||
# 交易日滚动与「可开仓」整点:按应用本地时区 wall clock(默认北京时间 UTC+8)
|
# 交易日滚动与「可开仓」整点:按应用本地时区 wall clock(默认北京时间 UTC+8)
|
||||||
TRADING_DAY_RESET_HOUR = int(os.getenv("TRADING_DAY_RESET_HOUR", "8"))
|
TRADING_DAY_RESET_HOUR = int(os.getenv("TRADING_DAY_RESET_HOUR", "8"))
|
||||||
# false 时关闭「整点前禁止新开仓」守卫(交易日划分仍用 TRADING_DAY_RESET_HOUR)
|
# false 时关闭「整点前禁止新开仓」守卫(交易日划分仍用 TRADING_DAY_RESET_HOUR)
|
||||||
@@ -2635,13 +2637,18 @@ def trading_day_reset_allows_new_open(now):
|
|||||||
return now.hour >= TRADING_DAY_RESET_HOUR
|
return now.hour >= TRADING_DAY_RESET_HOUR
|
||||||
|
|
||||||
|
|
||||||
|
def get_active_position_count(conn):
|
||||||
|
"""与 Gate 主站一致:仅统计 order_monitors.status=active。"""
|
||||||
|
return int(conn.execute("SELECT COUNT(*) FROM order_monitors WHERE status='active'").fetchone()[0])
|
||||||
|
|
||||||
|
|
||||||
def precheck_risk(conn, symbol, direction):
|
def precheck_risk(conn, symbol, direction):
|
||||||
now = app_now()
|
now = app_now()
|
||||||
if not trading_day_reset_allows_new_open(now):
|
if not trading_day_reset_allows_new_open(now):
|
||||||
return False, f"北京时间 {TRADING_DAY_RESET_HOUR}:00 前不允许持仓"
|
return False, f"北京时间 {TRADING_DAY_RESET_HOUR}:00 前不允许持仓"
|
||||||
active_count = conn.execute("SELECT COUNT(*) FROM order_monitors WHERE status='active'").fetchone()[0]
|
active_count = get_active_position_count(conn)
|
||||||
if active_count > 0:
|
if active_count >= MAX_ACTIVE_POSITIONS:
|
||||||
return False, "一次只能持有一个仓位"
|
return False, f"已达最大持仓数({active_count}/{MAX_ACTIVE_POSITIONS})"
|
||||||
trend_n = conn.execute(
|
trend_n = conn.execute(
|
||||||
"SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
|
"SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
@@ -2659,13 +2666,16 @@ def precheck_risk(conn, symbol, direction):
|
|||||||
|
|
||||||
|
|
||||||
def precheck_trend_pullback_start(conn):
|
def precheck_trend_pullback_start(conn):
|
||||||
"""趋势回调启动前:不与机器人单仓监控并存。"""
|
"""趋势回调启动前:不与机器人下单监控达持仓上限并存。"""
|
||||||
now = app_now()
|
now = app_now()
|
||||||
if not trading_day_reset_allows_new_open(now):
|
if not trading_day_reset_allows_new_open(now):
|
||||||
return False, f"北京时间 {TRADING_DAY_RESET_HOUR}:00 前不允许持仓"
|
return False, f"北京时间 {TRADING_DAY_RESET_HOUR}:00 前不允许持仓"
|
||||||
active_count = conn.execute("SELECT COUNT(*) FROM order_monitors WHERE status='active'").fetchone()[0]
|
active_count = get_active_position_count(conn)
|
||||||
if active_count > 0:
|
if active_count >= MAX_ACTIVE_POSITIONS:
|
||||||
return False, "请先结束「机器人下单监控」中的持仓,再启动趋势回调"
|
return False, (
|
||||||
|
f"已达最大持仓数({active_count}/{MAX_ACTIVE_POSITIONS}),"
|
||||||
|
"请先结束「机器人下单监控」中的持仓,再启动趋势回调"
|
||||||
|
)
|
||||||
trend_n = conn.execute(
|
trend_n = conn.execute(
|
||||||
"SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
|
"SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
@@ -5112,7 +5122,7 @@ def render_main_page(page="trade"):
|
|||||||
and ("持仓占用" in str(r.get("effective_miss_reason") or ""))
|
and ("持仓占用" in str(r.get("effective_miss_reason") or ""))
|
||||||
)
|
)
|
||||||
rate = round(win/total*100,2) if total else 0
|
rate = round(win/total*100,2) if total else 0
|
||||||
active_count = len(order_list)
|
active_count = get_active_position_count(conn)
|
||||||
trend_active = conn.execute(
|
trend_active = conn.execute(
|
||||||
"SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
|
"SELECT COUNT(*) FROM trend_pullback_plans WHERE status='active'"
|
||||||
).fetchone()[0]
|
).fetchone()[0]
|
||||||
@@ -5143,7 +5153,7 @@ def render_main_page(page="trade"):
|
|||||||
preview_snapshots.append(sd)
|
preview_snapshots.append(sd)
|
||||||
can_trade = (
|
can_trade = (
|
||||||
trading_day_reset_allows_new_open(now)
|
trading_day_reset_allows_new_open(now)
|
||||||
and active_count == 0
|
and active_count < MAX_ACTIVE_POSITIONS
|
||||||
and int(trend_active or 0) == 0
|
and int(trend_active or 0) == 0
|
||||||
)
|
)
|
||||||
trend_preview = None
|
trend_preview = None
|
||||||
@@ -5201,6 +5211,7 @@ def render_main_page(page="trade"):
|
|||||||
full_margin_buffer_ratio=FULL_MARGIN_BUFFER_RATIO,
|
full_margin_buffer_ratio=FULL_MARGIN_BUFFER_RATIO,
|
||||||
price_refresh_seconds=PRICE_REFRESH_SECONDS,
|
price_refresh_seconds=PRICE_REFRESH_SECONDS,
|
||||||
active_count=active_count,
|
active_count=active_count,
|
||||||
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
can_trade=can_trade,
|
can_trade=can_trade,
|
||||||
trend_plans=trend_plans,
|
trend_plans=trend_plans,
|
||||||
plan_history=plan_history,
|
plan_history=plan_history,
|
||||||
@@ -5294,9 +5305,9 @@ def api_account_snapshot():
|
|||||||
funding_usdt = round(funding_capital, 2) if funding_capital is not None else None
|
funding_usdt = round(funding_capital, 2) if funding_capital is not None else None
|
||||||
current_capital = round(trading_capital, 2) if trading_capital is not None else round(local_current_capital, 2)
|
current_capital = round(trading_capital, 2) if trading_capital is not None else round(local_current_capital, 2)
|
||||||
recommended_capital = round(get_recommended_capital(current_capital), 2)
|
recommended_capital = round(get_recommended_capital(current_capital), 2)
|
||||||
active_count = conn.execute("SELECT COUNT(*) FROM order_monitors WHERE status='active'").fetchone()[0]
|
active_count = get_active_position_count(conn)
|
||||||
conn.close()
|
conn.close()
|
||||||
can_trade = trading_day_reset_allows_new_open(now) and active_count == 0
|
can_trade = trading_day_reset_allows_new_open(now) and active_count < MAX_ACTIVE_POSITIONS
|
||||||
available_trading_usdt = get_available_trading_usdt()
|
available_trading_usdt = get_available_trading_usdt()
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"funding_usdt": funding_usdt,
|
"funding_usdt": funding_usdt,
|
||||||
@@ -5304,6 +5315,7 @@ def api_account_snapshot():
|
|||||||
"available_trading_usdt": round(available_trading_usdt, 2) if available_trading_usdt is not None else None,
|
"available_trading_usdt": round(available_trading_usdt, 2) if available_trading_usdt is not None else None,
|
||||||
"recommended_capital": recommended_capital,
|
"recommended_capital": recommended_capital,
|
||||||
"active_count": active_count,
|
"active_count": active_count,
|
||||||
|
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||||
"can_trade": can_trade,
|
"can_trade": can_trade,
|
||||||
"trading_day": trading_day
|
"trading_day": trading_day
|
||||||
})
|
})
|
||||||
@@ -5743,7 +5755,7 @@ def add_order():
|
|||||||
return redirect("/")
|
return redirect("/")
|
||||||
ok, reason = precheck_risk(conn, symbol, direction)
|
ok, reason = precheck_risk(conn, symbol, direction)
|
||||||
if not ok:
|
if not ok:
|
||||||
if "一次只能持有一个仓位" in reason:
|
if "已达最大持仓数" in reason:
|
||||||
try:
|
try:
|
||||||
tp_raw = parse_positive_float(d.get("tp"))
|
tp_raw = parse_positive_float(d.get("tp"))
|
||||||
sl_raw = parse_positive_float(d.get("sl"))
|
sl_raw = parse_positive_float(d.get("sl"))
|
||||||
@@ -5759,7 +5771,7 @@ def add_order():
|
|||||||
stop_loss=sl_raw or 0,
|
stop_loss=sl_raw or 0,
|
||||||
take_profit=tgt_raw or 0,
|
take_profit=tgt_raw or 0,
|
||||||
result="错过",
|
result="错过",
|
||||||
miss_reason="持仓占用:一次只能持有一个仓位",
|
miss_reason=f"持仓占用:{reason}",
|
||||||
opened_at=app_now_str(),
|
opened_at=app_now_str(),
|
||||||
closed_at=app_now_str(),
|
closed_at=app_now_str(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -250,8 +250,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="rule-tip" id="order-rule-tip">
|
<div class="rule-tip" id="order-rule-tip">
|
||||||
规则:单仓;与「趋势回调」计划互斥;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x;
|
规则:最大同时持仓 {{ max_active_positions }}(与 Gate 主站 MAX_ACTIVE_POSITIONS 一致,当前 active {{ active_count }});与「趋势回调」计划互斥;BTC {{ btc_leverage }}x / 山寨 {{ alt_leverage }}x;
|
||||||
{% if can_trade %}可开仓{% else %}不可开仓(有持仓、有趋势回调计划,或未到北京时间 {{ reset_hour }}:00){% endif %};
|
{% if can_trade %}可开仓{% else %}不可开仓(持仓达上限、有趋势回调计划,或未到北京时间 {{ reset_hour }}:00){% endif %};
|
||||||
按风险比例自动计算仓位
|
按风险比例自动计算仓位
|
||||||
</div>
|
</div>
|
||||||
<div class="rule-tip">
|
<div class="rule-tip">
|
||||||
|
|||||||
Reference in New Issue
Block a user