Allow roll add-ons while position-limit freeze is active.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+16
-1
@@ -93,6 +93,19 @@ def max_active_positions_from_env(default: int = 1) -> int:
|
|||||||
return max(1, default)
|
return max(1, default)
|
||||||
|
|
||||||
|
|
||||||
|
def position_limit_reached(
|
||||||
|
conn,
|
||||||
|
*,
|
||||||
|
max_active_positions: Optional[int] = None,
|
||||||
|
) -> tuple[bool, int, int]:
|
||||||
|
"""(已达上限, 计入上限的活跃数, 上限值)。"""
|
||||||
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
mx = max(1, int(max_active_positions if max_active_positions is not None else max_active_positions_from_env()))
|
||||||
|
ac = count_position_limit_active_monitors(conn)
|
||||||
|
return ac >= mx, ac, mx
|
||||||
|
|
||||||
|
|
||||||
def mood_issues_daily_freeze_enabled() -> bool:
|
def mood_issues_daily_freeze_enabled() -> bool:
|
||||||
return _env_bool("RISK_MOOD_ISSUES_DAILY_FREEZE", True)
|
return _env_bool("RISK_MOOD_ISSUES_DAILY_FREEZE", True)
|
||||||
|
|
||||||
@@ -721,12 +734,14 @@ def apply_position_limit_risk(
|
|||||||
out["status"] = STATUS_FREEZE_POSITION
|
out["status"] = STATUS_FREEZE_POSITION
|
||||||
out["status_label"] = STATUS_LABELS[STATUS_FREEZE_POSITION]
|
out["status_label"] = STATUS_LABELS[STATUS_FREEZE_POSITION]
|
||||||
out["can_trade"] = False
|
out["can_trade"] = False
|
||||||
out["reason"] = f"已达最大持仓数({ac}/{mx},不含趋势回调/顺势加仓),平仓前不可新开"
|
out["can_roll"] = True
|
||||||
|
out["reason"] = f"已达最大持仓数({ac}/{mx}),新开仓已冻结,顺势加仓仍可用"
|
||||||
out["position_limit_frozen"] = True
|
out["position_limit_frozen"] = True
|
||||||
out["freeze_until_ms"] = None
|
out["freeze_until_ms"] = None
|
||||||
out["freeze_remaining_sec"] = 0
|
out["freeze_remaining_sec"] = 0
|
||||||
else:
|
else:
|
||||||
out["position_limit_frozen"] = False
|
out["position_limit_frozen"] = False
|
||||||
|
out["can_roll"] = True
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3173,9 +3173,11 @@ def precheck_risk(conn, symbol, direction):
|
|||||||
return False, risk_reason
|
return False, risk_reason
|
||||||
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 = get_active_position_count(conn)
|
from account_risk_lib import position_limit_reached
|
||||||
if active_count >= MAX_ACTIVE_POSITIONS:
|
|
||||||
return False, f"已达最大持仓数({active_count}/{MAX_ACTIVE_POSITIONS})"
|
reached, active_count, mx = position_limit_reached(conn, max_active_positions=MAX_ACTIVE_POSITIONS)
|
||||||
|
if reached:
|
||||||
|
return False, f"已达最大持仓数({active_count}/{mx})"
|
||||||
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
||||||
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
@@ -6912,11 +6914,14 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
records = []
|
records = []
|
||||||
total = miss_count = rate = occupied_miss_total = 0
|
total = miss_count = rate = occupied_miss_total = 0
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6982,7 +6987,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
auto_transfer_bj_hour=AUTO_TRANSFER_BJ_HOUR,
|
auto_transfer_bj_hour=AUTO_TRANSFER_BJ_HOUR,
|
||||||
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=position_limit_count,
|
||||||
can_trade=can_trade,
|
can_trade=can_trade,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -7075,13 +7080,15 @@ def api_account_snapshot():
|
|||||||
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
||||||
current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
|
current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
|
||||||
recommended_capital = get_recommended_capital(current_capital)
|
recommended_capital = get_recommended_capital(current_capital)
|
||||||
active_count = get_active_position_count(conn)
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
conn.close()
|
conn.close()
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -7093,7 +7100,7 @@ def api_account_snapshot():
|
|||||||
"current_capital": current_capital,
|
"current_capital": current_capital,
|
||||||
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
||||||
"recommended_capital": recommended_capital,
|
"recommended_capital": recommended_capital,
|
||||||
"active_count": active_count,
|
"active_count": position_limit_count,
|
||||||
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||||
"can_trade": can_trade,
|
"can_trade": can_trade,
|
||||||
"opens_today": opens_today,
|
"opens_today": opens_today,
|
||||||
|
|||||||
@@ -2862,9 +2862,11 @@ def precheck_risk(conn, symbol, direction):
|
|||||||
return False, risk_reason
|
return False, risk_reason
|
||||||
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 = get_active_position_count(conn)
|
from account_risk_lib import position_limit_reached
|
||||||
if active_count >= MAX_ACTIVE_POSITIONS:
|
|
||||||
return False, f"已达最大持仓数({active_count}/{MAX_ACTIVE_POSITIONS})"
|
reached, active_count, mx = position_limit_reached(conn, max_active_positions=MAX_ACTIVE_POSITIONS)
|
||||||
|
if reached:
|
||||||
|
return False, f"已达最大持仓数({active_count}/{mx})"
|
||||||
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
||||||
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
@@ -6795,11 +6797,14 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
records = []
|
records = []
|
||||||
total = miss_count = rate = occupied_miss_total = 0
|
total = miss_count = rate = occupied_miss_total = 0
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6862,7 +6867,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
transfer_amount_fmt=format_usdt(AUTO_TRANSFER_AMOUNT),
|
transfer_amount_fmt=format_usdt(AUTO_TRANSFER_AMOUNT),
|
||||||
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=position_limit_count,
|
||||||
can_trade=can_trade,
|
can_trade=can_trade,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6975,13 +6980,15 @@ 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(float(get_recommended_capital(current_capital)), 2)
|
recommended_capital = round(float(get_recommended_capital(current_capital)), 2)
|
||||||
active_count = get_active_position_count(conn)
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
conn.close()
|
conn.close()
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6993,7 +7000,7 @@ def api_account_snapshot():
|
|||||||
"current_capital": current_capital,
|
"current_capital": current_capital,
|
||||||
"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": position_limit_count,
|
||||||
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||||
"can_trade": can_trade,
|
"can_trade": can_trade,
|
||||||
"opens_today": opens_today,
|
"opens_today": opens_today,
|
||||||
|
|||||||
@@ -2862,9 +2862,11 @@ def precheck_risk(conn, symbol, direction):
|
|||||||
return False, risk_reason
|
return False, risk_reason
|
||||||
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 = get_active_position_count(conn)
|
from account_risk_lib import position_limit_reached
|
||||||
if active_count >= MAX_ACTIVE_POSITIONS:
|
|
||||||
return False, f"已达最大持仓数({active_count}/{MAX_ACTIVE_POSITIONS})"
|
reached, active_count, mx = position_limit_reached(conn, max_active_positions=MAX_ACTIVE_POSITIONS)
|
||||||
|
if reached:
|
||||||
|
return False, f"已达最大持仓数({active_count}/{mx})"
|
||||||
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
||||||
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
@@ -6795,11 +6797,14 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
records = []
|
records = []
|
||||||
total = miss_count = rate = occupied_miss_total = 0
|
total = miss_count = rate = occupied_miss_total = 0
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6862,7 +6867,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
transfer_amount_fmt=format_usdt(AUTO_TRANSFER_AMOUNT),
|
transfer_amount_fmt=format_usdt(AUTO_TRANSFER_AMOUNT),
|
||||||
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=position_limit_count,
|
||||||
can_trade=can_trade,
|
can_trade=can_trade,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6971,13 +6976,15 @@ 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(float(get_recommended_capital(current_capital)), 2)
|
recommended_capital = round(float(get_recommended_capital(current_capital)), 2)
|
||||||
active_count = get_active_position_count(conn)
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
conn.close()
|
conn.close()
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6989,7 +6996,7 @@ def api_account_snapshot():
|
|||||||
"current_capital": current_capital,
|
"current_capital": current_capital,
|
||||||
"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": position_limit_count,
|
||||||
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||||
"can_trade": can_trade,
|
"can_trade": can_trade,
|
||||||
"opens_today": opens_today,
|
"opens_today": opens_today,
|
||||||
|
|||||||
+19
-10
@@ -2574,9 +2574,11 @@ def precheck_risk(conn, symbol, direction):
|
|||||||
return False, risk_reason
|
return False, risk_reason
|
||||||
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 = get_active_position_count(conn)
|
from account_risk_lib import position_limit_reached
|
||||||
if active_count >= MAX_ACTIVE_POSITIONS:
|
|
||||||
return False, f"已达最大持仓数({active_count}/{MAX_ACTIVE_POSITIONS})"
|
reached, active_count, mx = position_limit_reached(conn, max_active_positions=MAX_ACTIVE_POSITIONS)
|
||||||
|
if reached:
|
||||||
|
return False, f"已达最大持仓数({active_count}/{mx})"
|
||||||
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
ok_daily, daily_reason, _opens = check_daily_open_hard_limit(
|
||||||
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
conn, get_trading_day(now), DAILY_OPEN_HARD_LIMIT, TRADING_DAY_RESET_HOUR
|
||||||
)
|
)
|
||||||
@@ -6299,13 +6301,16 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
records = []
|
records = []
|
||||||
total = miss_count = rate = occupied_miss_total = 0
|
total = miss_count = rate = occupied_miss_total = 0
|
||||||
active_count = len(order_list)
|
active_count = len(order_list)
|
||||||
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
open_guard_enabled = get_trading_day_reset_open_guard_enabled(conn)
|
open_guard_enabled = get_trading_day_reset_open_guard_enabled(conn)
|
||||||
open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR
|
open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now, conn),
|
time_allows=trading_day_reset_allows_new_open(now, conn),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6368,7 +6373,7 @@ def render_main_page(page="trade", embed_mode=None):
|
|||||||
auto_transfer_bj_hour=AUTO_TRANSFER_BJ_HOUR,
|
auto_transfer_bj_hour=AUTO_TRANSFER_BJ_HOUR,
|
||||||
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=position_limit_count,
|
||||||
can_trade=can_trade,
|
can_trade=can_trade,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
daily_open_hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6476,7 +6481,9 @@ def api_account_snapshot():
|
|||||||
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
funding_usdt = round(funding_capital, FUNDS_DECIMALS) if funding_capital is not None else None
|
||||||
current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
|
current_capital = round(trading_capital, FUNDS_DECIMALS) if trading_capital is not None else round(local_current_capital, FUNDS_DECIMALS)
|
||||||
recommended_capital = get_recommended_capital(current_capital)
|
recommended_capital = get_recommended_capital(current_capital)
|
||||||
active_count = get_active_position_count(conn)
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
open_guard_enabled = get_trading_day_reset_open_guard_enabled(conn)
|
open_guard_enabled = get_trading_day_reset_open_guard_enabled(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
risk_status = hub_account_risk_status(conn)
|
risk_status = hub_account_risk_status(conn)
|
||||||
@@ -6484,7 +6491,7 @@ def api_account_snapshot():
|
|||||||
open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR
|
open_guard_blocks_now = open_guard_enabled and now.hour < TRADING_DAY_RESET_HOUR
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
@@ -6496,7 +6503,7 @@ def api_account_snapshot():
|
|||||||
"current_capital": current_capital,
|
"current_capital": current_capital,
|
||||||
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
"available_trading_usdt": round(available_trading_usdt, FUNDS_DECIMALS) if available_trading_usdt is not None else None,
|
||||||
"recommended_capital": recommended_capital,
|
"recommended_capital": recommended_capital,
|
||||||
"active_count": active_count,
|
"active_count": position_limit_count,
|
||||||
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
"max_active_positions": MAX_ACTIVE_POSITIONS,
|
||||||
"can_trade": can_trade,
|
"can_trade": can_trade,
|
||||||
"opens_today": opens_today,
|
"opens_today": opens_today,
|
||||||
@@ -6525,13 +6532,15 @@ def api_settings_open_guard():
|
|||||||
now = app_now()
|
now = app_now()
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
trading_day = get_trading_day(now)
|
trading_day = get_trading_day(now)
|
||||||
active_count = get_active_position_count(conn)
|
from strategy_trade_labels import count_position_limit_active_monitors
|
||||||
|
|
||||||
|
position_limit_count = count_position_limit_active_monitors(conn)
|
||||||
guard_on = get_trading_day_reset_open_guard_enabled(conn)
|
guard_on = get_trading_day_reset_open_guard_enabled(conn)
|
||||||
opens_today = count_opens_for_trading_day(conn, trading_day)
|
opens_today = count_opens_for_trading_day(conn, trading_day)
|
||||||
conn.close()
|
conn.close()
|
||||||
can_trade = can_trade_new_open(
|
can_trade = can_trade_new_open(
|
||||||
time_allows=trading_day_reset_allows_new_open(now),
|
time_allows=trading_day_reset_allows_new_open(now),
|
||||||
active_count=active_count,
|
active_count=position_limit_count,
|
||||||
max_active_positions=MAX_ACTIVE_POSITIONS,
|
max_active_positions=MAX_ACTIVE_POSITIONS,
|
||||||
opens_today=opens_today,
|
opens_today=opens_today,
|
||||||
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
hard_limit=DAILY_OPEN_HARD_LIMIT,
|
||||||
|
|||||||
@@ -106,7 +106,9 @@ APP_TIMEZONE=Asia/Shanghai
|
|||||||
| `freeze_until_ms` | 倒计时结束时间戳(日冻结为下一交易日切点) |
|
| `freeze_until_ms` | 倒计时结束时间戳(日冻结为下一交易日切点) |
|
||||||
| `freeze_remaining_sec` | 服务端计算的剩余秒数(供调试) |
|
| `freeze_remaining_sec` | 服务端计算的剩余秒数(供调试) |
|
||||||
|
|
||||||
**仓位上限冻结**:当 **计入上限的** 活跃持仓数(不含趋势回调、顺势加仓)≥ 实例 `.env` 的 `MAX_ACTIVE_POSITIONS`(默认 1)且账户无时间类冻结时,徽章显示 **仓位上限冻结**;相关策略单平仓后或仅存在策略持仓时不会触发该冻结。时间冻结(1h/4h/日)优先展示。
|
**仓位上限冻结**:当 **计入上限的** 活跃持仓数(不含趋势回调)≥ 实例 `.env` 的 `MAX_ACTIVE_POSITIONS`(默认 1)且账户无时间类冻结时,徽章显示 **仓位上限冻结**;此时 **新开仓** 被禁止,但 **顺势加仓**(在已有同向监控持仓上加仓)仍可用。仅存在趋势回调持仓时不触发该冻结。时间冻结(1h/4h/日)优先展示。
|
||||||
|
|
||||||
|
`risk_status.can_roll`:仓位上限冻结时为 `true`,表示顺势加仓不受该冻结限制。
|
||||||
|
|
||||||
## 前端倒计时
|
## 前端倒计时
|
||||||
|
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ def _count_active_trends(conn, cfg: dict) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def _roll_preview_response(cfg: dict, data: dict, json_mode: bool = False) -> dict:
|
def _roll_preview_response(cfg: dict, data: dict, json_mode: bool = False) -> dict:
|
||||||
|
"""顺势加仓不占用 MAX_ACTIVE_POSITIONS 新仓名额,故不校验仓位上限冻结。"""
|
||||||
m = cfg.get("app_module")
|
m = cfg.get("app_module")
|
||||||
if m is not None:
|
if m is not None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<strong>仅人工加仓</strong>,程序不会自动触发。须先在「实盘下单」有同向持仓。<br>
|
<strong>仅人工加仓</strong>,程序不会自动触发。须先在「实盘下单」有同向持仓。<br>
|
||||||
做多最多滚仓 <strong>3</strong> 次;止盈<strong>锁定首仓</strong>不变;每次填写<strong>止损偏移%</strong>(相对合并均价,默认 1%),总风险%按「合并持仓打到新止损≈账户风险」反推张数。<br>
|
做多最多滚仓 <strong>3</strong> 次;止盈<strong>锁定首仓</strong>不变;每次填写<strong>止损偏移%</strong>(相对合并均价,默认 1%),总风险%按「合并持仓打到新止损≈账户风险」反推张数。<br>
|
||||||
斐波限价:上沿 H、下沿 L 仅用于算 0.618/0.786 加仓价(多:下沿=止损侧;空:上沿=止损侧)。<br>
|
斐波限价:上沿 H、下沿 L 仅用于算 0.618/0.786 加仓价(多:下沿=止损侧;空:上沿=止损侧)。<br>
|
||||||
|
<strong>仓位上限冻结时仍可顺势加仓</strong>(在已有同向监控持仓上操作,不占用新仓名额)。<br>
|
||||||
{% if roll_trend_active %}<span style="color:#ff8f8f">当前有运行中的趋势回调计划,请先结束后再滚仓。</span>{% endif %}
|
{% if roll_trend_active %}<span style="color:#ff8f8f">当前有运行中的趋势回调计划,请先结束后再滚仓。</span>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -142,27 +142,8 @@ def entry_reason_for_monitor_type(monitor_type: str | None) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def order_monitor_excluded_from_position_limit(conn, row) -> bool:
|
def order_monitor_excluded_from_position_limit(conn, row) -> bool:
|
||||||
"""趋势回调 / 顺势加仓不计入 MAX_ACTIVE_POSITIONS 与仓位上限冻结。"""
|
"""趋势回调不计入 MAX_ACTIVE_POSITIONS;顺势加仓在已有持仓上操作,单独放行。"""
|
||||||
if order_monitor_source_type(row) in (MONITOR_TYPE_TREND_PULLBACK, MONITOR_TYPE_ROLL):
|
return order_monitor_source_type(row) == MONITOR_TYPE_TREND_PULLBACK
|
||||||
return True
|
|
||||||
oid = None
|
|
||||||
try:
|
|
||||||
keys = row.keys() if hasattr(row, "keys") else []
|
|
||||||
if "id" in keys and row["id"] is not None:
|
|
||||||
oid = int(row["id"])
|
|
||||||
except Exception:
|
|
||||||
oid = None
|
|
||||||
if oid and oid > 0:
|
|
||||||
try:
|
|
||||||
hit = conn.execute(
|
|
||||||
"SELECT 1 FROM roll_groups WHERE order_monitor_id=? AND status='active' LIMIT 1",
|
|
||||||
(oid,),
|
|
||||||
).fetchone()
|
|
||||||
if hit is not None:
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def count_position_limit_active_monitors(conn) -> int:
|
def count_position_limit_active_monitors(conn) -> int:
|
||||||
|
|||||||
@@ -499,7 +499,8 @@ class AccountRiskLibTests(unittest.TestCase):
|
|||||||
self.assertEqual(st["status_label"], "仓位上限冻结")
|
self.assertEqual(st["status_label"], "仓位上限冻结")
|
||||||
self.assertFalse(st["can_trade"])
|
self.assertFalse(st["can_trade"])
|
||||||
self.assertIn("2/2", st["reason"])
|
self.assertIn("2/2", st["reason"])
|
||||||
self.assertIn("不含趋势回调", st["reason"])
|
self.assertIn("顺势加仓", st["reason"])
|
||||||
|
self.assertTrue(st.get("can_roll"))
|
||||||
self.assertEqual(st["max_active_positions"], 2)
|
self.assertEqual(st["max_active_positions"], 2)
|
||||||
|
|
||||||
def test_position_limit_normal_when_under_cap(self):
|
def test_position_limit_normal_when_under_cap(self):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import unittest
|
|||||||
|
|
||||||
from strategy_db import init_strategy_tables
|
from strategy_db import init_strategy_tables
|
||||||
from strategy_trade_labels import (
|
from strategy_trade_labels import (
|
||||||
MONITOR_TYPE_ROLL,
|
|
||||||
MONITOR_TYPE_TREND_PULLBACK,
|
MONITOR_TYPE_TREND_PULLBACK,
|
||||||
count_position_limit_active_monitors,
|
count_position_limit_active_monitors,
|
||||||
)
|
)
|
||||||
@@ -47,16 +46,7 @@ class PositionLimitCountTests(unittest.TestCase):
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
self.assertEqual(count_position_limit_active_monitors(conn), 0)
|
self.assertEqual(count_position_limit_active_monitors(conn), 0)
|
||||||
|
|
||||||
def test_roll_monitor_type_excluded(self):
|
def test_active_roll_group_still_counts_regular_monitor(self):
|
||||||
conn = _mem_conn()
|
|
||||||
conn.execute(
|
|
||||||
"INSERT INTO order_monitors (symbol, status, monitor_type) VALUES ('ETH/USDT', 'active', ?)",
|
|
||||||
(MONITOR_TYPE_ROLL,),
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
self.assertEqual(count_position_limit_active_monitors(conn), 0)
|
|
||||||
|
|
||||||
def test_active_roll_group_excludes_monitor(self):
|
|
||||||
conn = _mem_conn()
|
conn = _mem_conn()
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO order_monitors (id, symbol, status, monitor_type) VALUES (1, 'ETH/USDT', 'active', '下单监控')"
|
"INSERT INTO order_monitors (id, symbol, status, monitor_type) VALUES (1, 'ETH/USDT', 'active', '下单监控')"
|
||||||
@@ -67,7 +57,7 @@ class PositionLimitCountTests(unittest.TestCase):
|
|||||||
VALUES (1, 'ETH/USDT', 'long', 'active')"""
|
VALUES (1, 'ETH/USDT', 'long', 'active')"""
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
self.assertEqual(count_position_limit_active_monitors(conn), 0)
|
self.assertEqual(count_position_limit_active_monitors(conn), 1)
|
||||||
|
|
||||||
def test_mixed_monitors(self):
|
def test_mixed_monitors(self):
|
||||||
conn = _mem_conn()
|
conn = _mem_conn()
|
||||||
|
|||||||
Reference in New Issue
Block a user