Compare commits
2 Commits
1a70a60dba
...
c40b4b919c
| Author | SHA1 | Date | |
|---|---|---|---|
| c40b4b919c | |||
| 434acb0470 |
@@ -28,6 +28,6 @@ def check_lock(
|
|||||||
return (current_price - open_price) >= lock_threshold
|
return (current_price - open_price) >= lock_threshold
|
||||||
|
|
||||||
|
|
||||||
def should_meltdown(lock_count: int) -> bool:
|
def should_meltdown(lock_count: int, max_locks: int = 3) -> bool:
|
||||||
"""3锁熔断"""
|
"""锁仓次数达到上限时熔断"""
|
||||||
return lock_count >= 3
|
return lock_count >= max_locks
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ class Round(Base):
|
|||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
contract_code: Mapped[str] = mapped_column(String(10), index=True)
|
contract_code: Mapped[str] = mapped_column(String(10), index=True)
|
||||||
daily_hands: Mapped[int] = mapped_column(Integer, default=1)
|
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")
|
status: Mapped[str] = mapped_column(String(20), default="active")
|
||||||
started_at: Mapped[date] = mapped_column(Date)
|
started_at: Mapped[date] = mapped_column(Date)
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,19 @@ def update_lock_price(
|
|||||||
return RedirectResponse("/positions/", status_code=303)
|
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")
|
@router.post("/{round_id}/close")
|
||||||
def close_round(
|
def close_round(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
|||||||
@@ -27,8 +27,24 @@ SEED_BARS: list[dict] = [
|
|||||||
{"date": "2026-07-24", "open": 900, "close": 908, "high": 912, "low": 891},
|
{"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():
|
def seed():
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
_migrate(engine)
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -58,11 +58,13 @@
|
|||||||
<span class="badge badge-up">{{ round.daily_hands }} 手/天</span>
|
<span class="badge badge-up">{{ round.daily_hands }} 手/天</span>
|
||||||
<span style="font-size:0.82rem;color:var(--sub);">已开 {{ round.positions|length }} 天</span>
|
<span style="font-size:0.82rem;color:var(--sub);">已开 {{ round.positions|length }} 天</span>
|
||||||
<span style="font-size:0.82rem;color:var(--sub);">开始 {{ round.started_at }}</span>
|
<span style="font-size:0.82rem;color:var(--sub);">开始 {{ round.started_at }}</span>
|
||||||
<span style="font-size:0.82rem;font-weight:600;{% if locks >= 3 %}color:var(--danger-fg);{% endif %}">
|
<span style="font-size:0.82rem;font-weight:600;{% if locks >= round.max_locks %}color:var(--danger-fg);{% endif %}">
|
||||||
锁仓 {{ locks }}/3
|
锁仓 {{ locks }}/{{ round.max_locks }}
|
||||||
{% if locks >= 3 %} ⚠ 熔断{% endif %}
|
{% if locks >= round.max_locks %} ⚠ 熔断{% endif %}
|
||||||
</span>
|
</span>
|
||||||
<span style="margin-left:auto;display:flex;gap:8px;">
|
<span style="margin-left:auto;display:flex;gap:8px;">
|
||||||
|
<button style="background:var(--surface);color:var(--fg);border:1px solid var(--border);padding:3px 12px;border-radius:4px;cursor:pointer;font-size:0.78rem;"
|
||||||
|
onclick="showMaxLocksModal({{ round.id }}, {{ round.max_locks }})">修改锁仓数</button>
|
||||||
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
|
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
|
||||||
<input type="hidden" name="result" value="profit_taken">
|
<input type="hidden" name="result" value="profit_taken">
|
||||||
<button class="btn" style="background:var(--success);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="confirmDelete(event, '确认 {{ round.contract_code }} 止盈清仓?', this.closest('form').action)">止盈</button>
|
<button class="btn" style="background:var(--success);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="confirmDelete(event, '确认 {{ round.contract_code }} 止盈清仓?', this.closest('form').action)">止盈</button>
|
||||||
@@ -179,6 +181,24 @@ function filterContracts() {
|
|||||||
sel.innerHTML += '<option value="' + c + '">' + display + '</option>';
|
sel.innerHTML += '<option value="' + c + '">' + display + '</option>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
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) {
|
function showLockConfirm(pid, lockPrice) {
|
||||||
document.getElementById('lockPid').value = pid;
|
document.getElementById('lockPid').value = pid;
|
||||||
@@ -200,6 +220,20 @@ function filterContracts() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div id="maxLocksModalOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:159;" onclick="hideMaxLocksModal()"></div>
|
||||||
|
<div id="maxLocksModal" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:160;width:300px;max-width:90vw;">
|
||||||
|
<h3 style="margin-bottom:16px;">修改锁仓上限</h3>
|
||||||
|
<input type="hidden" id="maxLocksRid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>锁仓次数上限(熔断阈值)</label>
|
||||||
|
<input type="number" id="maxLocksInput" min="1" max="10" required style="font-size:1rem;text-align:center;font-weight:600;">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:10px;justify-content:flex-end;margin-top:8px;">
|
||||||
|
<button class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideMaxLocksModal()">取消</button>
|
||||||
|
<button class="btn" style="background:var(--accent);color:#fff;" onclick="doUpdateMaxLocks()">确认修改</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="lockModalOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:149;" onclick="hideLockModal()"></div>
|
<div id="lockModalOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:149;" onclick="hideLockModal()"></div>
|
||||||
<div id="lockModal" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:150;width:300px;max-width:90vw;">
|
<div id="lockModal" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:24px;z-index:150;width:300px;max-width:90vw;">
|
||||||
<h3 style="margin-bottom:16px;">确认锁仓</h3>
|
<h3 style="margin-bottom:16px;">确认锁仓</h3>
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user