From 434acb04706cec215c878a6359ff4c83dcb7d7e3 Mon Sep 17 00:00:00 2001 From: fish Date: Mon, 27 Jul 2026 12:57:34 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E4=BB=93=E4=B8=8A=E9=99=90=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8F=AF=E9=85=8D=E7=BD=AE=EF=BC=8C=E9=BB=98=E8=AE=A4?= =?UTF-8?q?3=E6=AC=A1=E4=B8=8D=E5=8F=98=EF=BC=8C=E6=AD=A2=E7=9B=88?= =?UTF-8?q?=E6=8C=89=E9=92=AE=E5=B7=A6=E4=BE=A7=E5=A2=9E=E5=8A=A0=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- ft-app/app/engine/lock_strategy.py | 6 ++--- ft-app/app/models.py | 1 + ft-app/app/routers/positions.py | 13 +++++++++ ft-app/app/seed.py | 16 +++++++++++ ft-app/app/templates/positions.html | 40 +++++++++++++++++++++++++--- ft-app/data/ft.db | Bin 5881856 -> 5881856 bytes 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/ft-app/app/engine/lock_strategy.py b/ft-app/app/engine/lock_strategy.py index ce4b2d6..32e492b 100644 --- a/ft-app/app/engine/lock_strategy.py +++ b/ft-app/app/engine/lock_strategy.py @@ -28,6 +28,6 @@ def check_lock( return (current_price - open_price) >= lock_threshold -def should_meltdown(lock_count: int) -> bool: - """3锁熔断""" - return lock_count >= 3 +def should_meltdown(lock_count: int, max_locks: int = 3) -> bool: + """锁仓次数达到上限时熔断""" + return lock_count >= max_locks diff --git a/ft-app/app/models.py b/ft-app/app/models.py index 99dffb0..41e021f 100644 --- a/ft-app/app/models.py +++ b/ft-app/app/models.py @@ -95,6 +95,7 @@ class Round(Base): id: Mapped[int] = mapped_column(primary_key=True) contract_code: Mapped[str] = mapped_column(String(10), index=True) daily_hands: Mapped[int] = mapped_column(Integer, default=1) + max_locks: Mapped[int] = mapped_column(Integer, default=3) status: Mapped[str] = mapped_column(String(20), default="active") started_at: Mapped[date] = mapped_column(Date) diff --git a/ft-app/app/routers/positions.py b/ft-app/app/routers/positions.py index a16e63a..1f54211 100644 --- a/ft-app/app/routers/positions.py +++ b/ft-app/app/routers/positions.py @@ -139,6 +139,19 @@ def update_lock_price( return RedirectResponse("/positions/", status_code=303) +@router.post("/{round_id}/max-locks") +def update_max_locks( + round_id: int, + max_locks: int = Form(...), + db: Session = Depends(get_db), +): + r = db.query(Round).filter(Round.id == round_id).first() + if r and r.status == "active" and 1 <= max_locks <= 10: + r.max_locks = max_locks + db.commit() + return RedirectResponse("/positions/", status_code=303) + + @router.post("/{round_id}/close") def close_round( request: Request, diff --git a/ft-app/app/seed.py b/ft-app/app/seed.py index 9c7a657..03108cf 100644 --- a/ft-app/app/seed.py +++ b/ft-app/app/seed.py @@ -27,8 +27,24 @@ SEED_BARS: list[dict] = [ {"date": "2026-07-24", "open": 900, "close": 908, "high": 912, "low": 891}, ] +def _migrate(engine): + """Add missing columns to existing tables (SQLite doesn't auto-migrate).""" + import sqlite3 + conn = engine.raw_connection() + try: + cur = conn.cursor() + cur.execute("PRAGMA table_info(rounds)") + cols = {row[1] for row in cur.fetchall()} + if "max_locks" not in cols: + cur.execute("ALTER TABLE rounds ADD COLUMN max_locks INTEGER DEFAULT 3") + conn.commit() + finally: + conn.close() + + def seed(): Base.metadata.create_all(bind=engine) + _migrate(engine) db = SessionLocal() try: diff --git a/ft-app/app/templates/positions.html b/ft-app/app/templates/positions.html index 7f94803..d49ab77 100644 --- a/ft-app/app/templates/positions.html +++ b/ft-app/app/templates/positions.html @@ -58,11 +58,13 @@ {{ round.daily_hands }} 手/天 已开 {{ round.positions|length }} 天 开始 {{ round.started_at }} - - 锁仓 {{ locks }}/3 - {% if locks >= 3 %} ⚠ 熔断{% endif %} + + 锁仓 {{ locks }}/{{ round.max_locks }} + {% if locks >= round.max_locks %} ⚠ 熔断{% endif %} +
@@ -180,6 +182,24 @@ function filterContracts() { }); } } + function showMaxLocksModal(rid, currentMax) { + document.getElementById('maxLocksRid').value = rid; + document.getElementById('maxLocksInput').value = currentMax; + document.getElementById('maxLocksModal').style.display = 'block'; + document.getElementById('maxLocksModalOverlay').style.display = 'block'; + document.getElementById('maxLocksInput').focus(); + } + function hideMaxLocksModal() { + document.getElementById('maxLocksModal').style.display = 'none'; + document.getElementById('maxLocksModalOverlay').style.display = 'none'; + } + function doUpdateMaxLocks() { + var rid = document.getElementById('maxLocksRid').value; + var fd = new FormData(); + fd.append('max_locks', document.getElementById('maxLocksInput').value); + fetch('/positions/' + rid + '/max-locks', {method:'POST', body:fd}) + .then(function() { location.reload(); }); + } function showLockConfirm(pid, lockPrice) { document.getElementById('lockPid').value = pid; document.getElementById('lockPriceInput').value = lockPrice; @@ -200,6 +220,20 @@ function filterContracts() { } + + +