Stop deploy from overwriting manual FORCE_CLOSE settings.
Apply force-close defaults only when keys are missing so system-settings toggles survive pull_and_restart. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,7 +19,7 @@ git pull
|
|||||||
echo ">>> sync common trading env (binance + okx missing keys)"
|
echo ">>> sync common trading env (binance + okx missing keys)"
|
||||||
python3 scripts/sync_common_trading_env.py "${DRY[@]}"
|
python3 scripts/sync_common_trading_env.py "${DRY[@]}"
|
||||||
|
|
||||||
echo ">>> force-close policy: gate=ON, binance/okx=OFF"
|
echo ">>> force-close defaults only if missing (never overwrite manual)"
|
||||||
python3 scripts/sync_common_trading_env.py --apply-force-close-policy "${DRY[@]}"
|
python3 scripts/sync_common_trading_env.py --apply-force-close-policy "${DRY[@]}"
|
||||||
|
|
||||||
if [[ ${#DRY[@]} -gt 0 ]]; then
|
if [[ ${#DRY[@]} -gt 0 ]]; then
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ SHARED_DEFAULTS: dict[str, str] = {
|
|||||||
"AI_TIMEOUT_SECONDS": "120",
|
"AI_TIMEOUT_SECONDS": "120",
|
||||||
}
|
}
|
||||||
|
|
||||||
# 仅 Gate 启用 0 点强制清仓;币安/OKX 须保持关闭
|
# 仅当某实例 .env 缺少 FORCE_CLOSE_* 时补默认:
|
||||||
|
# Gate 默认开 0 点强制清仓;币安/OKX 默认关.已有手调值绝不覆盖.
|
||||||
FORCE_CLOSE_POLICY: dict[str, dict[str, str]] = {
|
FORCE_CLOSE_POLICY: dict[str, dict[str, str]] = {
|
||||||
"crypto_monitor_gate": {
|
"crypto_monitor_gate": {
|
||||||
"FORCE_CLOSE_ENABLED": "true",
|
"FORCE_CLOSE_ENABLED": "true",
|
||||||
@@ -131,7 +132,7 @@ def sync_one(dir_name: str, *, dry_run: bool, force: bool) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def apply_force_close_policy(*, dry_run: bool) -> bool:
|
def apply_force_close_policy(*, dry_run: bool) -> bool:
|
||||||
"""Gate 开启强制清仓;币安/OKX 强制关闭(覆盖已有值)."""
|
"""仅在 FORCE_CLOSE_* 缺失时补默认值;已有手调值绝不覆盖."""
|
||||||
any_changed = False
|
any_changed = False
|
||||||
for dir_name, values in FORCE_CLOSE_POLICY.items():
|
for dir_name, values in FORCE_CLOSE_POLICY.items():
|
||||||
path = os.path.join(REPO, dir_name, ".env")
|
path = os.path.join(REPO, dir_name, ".env")
|
||||||
@@ -139,19 +140,19 @@ def apply_force_close_policy(*, dry_run: bool) -> bool:
|
|||||||
print(f"skip (no .env): {dir_name}")
|
print(f"skip (no .env): {dir_name}")
|
||||||
continue
|
continue
|
||||||
lines = _parse_env(path)
|
lines = _parse_env(path)
|
||||||
changed_keys: list[str] = []
|
added_keys: list[str] = []
|
||||||
for key, val in values.items():
|
for key, val in values.items():
|
||||||
cur = _env_get(lines, key)
|
cur = _env_get(lines, key)
|
||||||
if cur != val:
|
if cur is None:
|
||||||
lines = _upsert(lines, key, val)
|
lines = _upsert(lines, key, val)
|
||||||
changed_keys.append(key)
|
added_keys.append(key)
|
||||||
if not changed_keys:
|
if not added_keys:
|
||||||
print(f"ok (force-close policy): {dir_name}")
|
print(f"ok (force-close unchanged): {dir_name}")
|
||||||
continue
|
continue
|
||||||
any_changed = True
|
any_changed = True
|
||||||
print(f"force-close policy: {dir_name}")
|
print(f"force-close fill-missing: {dir_name}")
|
||||||
for key in changed_keys:
|
for key in added_keys:
|
||||||
print(f" = {key}={values[key]}")
|
print(f" + {key}={values[key]}")
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
text = "\n".join(lines).rstrip() + "\n"
|
text = "\n".join(lines).rstrip() + "\n"
|
||||||
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
with open(path, "w", encoding="utf-8", newline="\n") as f:
|
||||||
@@ -166,7 +167,7 @@ def main() -> None:
|
|||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"--apply-force-close-policy",
|
"--apply-force-close-policy",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Gate 开启 0 点强制清仓,币安/OKX 强制关闭",
|
help="仅补全缺失的 FORCE_CLOSE_* 默认值(不覆盖手调)",
|
||||||
)
|
)
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"--instances",
|
"--instances",
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""FORCE_CLOSE 部署策略:只补缺失,不覆盖手调."""
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from scripts import sync_common_trading_env as sync
|
||||||
|
|
||||||
|
|
||||||
|
class TestForceCloseFillMissing(unittest.TestCase):
|
||||||
|
def test_does_not_overwrite_existing(self):
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
gate = Path(td) / "crypto_monitor_gate"
|
||||||
|
gate.mkdir()
|
||||||
|
(gate / ".env").write_text(
|
||||||
|
"FORCE_CLOSE_ENABLED=false\nFORCE_CLOSE_BJ_HOUR=1\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
with mock.patch.object(sync, "REPO", td):
|
||||||
|
changed = sync.apply_force_close_policy(dry_run=False)
|
||||||
|
self.assertFalse(changed)
|
||||||
|
text = (gate / ".env").read_text(encoding="utf-8")
|
||||||
|
self.assertIn("FORCE_CLOSE_ENABLED=false", text)
|
||||||
|
self.assertIn("FORCE_CLOSE_BJ_HOUR=1", text)
|
||||||
|
|
||||||
|
def test_fills_missing_keys(self):
|
||||||
|
with tempfile.TemporaryDirectory() as td:
|
||||||
|
gate = Path(td) / "crypto_monitor_gate"
|
||||||
|
gate.mkdir()
|
||||||
|
(gate / ".env").write_text("APP_USERNAME=x\n", encoding="utf-8")
|
||||||
|
with mock.patch.object(sync, "REPO", td):
|
||||||
|
changed = sync.apply_force_close_policy(dry_run=False)
|
||||||
|
self.assertTrue(changed)
|
||||||
|
text = (gate / ".env").read_text(encoding="utf-8")
|
||||||
|
self.assertIn("FORCE_CLOSE_ENABLED=true", text)
|
||||||
|
self.assertIn("FORCE_CLOSE_BJ_HOUR=0", text)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user