Compare commits
10 Commits
58d99259e8
...
7a5795e131
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a5795e131 | |||
| 46654a05b1 | |||
| 085652c2dd | |||
| fb4710b011 | |||
| 5dd58b8297 | |||
| 4724741fdd | |||
| 9b08bd8bfd | |||
| adec664c52 | |||
| e222a93204 | |||
| 51be04e5d2 |
+2
-1
@@ -7,7 +7,7 @@ from starlette.middleware.base import BaseHTTPMiddleware
|
|||||||
from app.database import engine, Base, SessionLocal
|
from app.database import engine, Base, SessionLocal
|
||||||
from app.models import User
|
from app.models import User
|
||||||
from app.seed import seed
|
from app.seed import seed
|
||||||
from app.routers import contracts, admin, auth, positions
|
from app.routers import contracts, admin, auth, positions, trades
|
||||||
|
|
||||||
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
||||||
|
|
||||||
@@ -57,6 +57,7 @@ app.include_router(auth.router)
|
|||||||
app.include_router(contracts.router)
|
app.include_router(contracts.router)
|
||||||
app.include_router(admin.router)
|
app.include_router(admin.router)
|
||||||
app.include_router(positions.router)
|
app.include_router(positions.router)
|
||||||
|
app.include_router(trades.router)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
|
|||||||
@@ -120,3 +120,30 @@ class Position(Base):
|
|||||||
opened_at: Mapped[date] = mapped_column(Date)
|
opened_at: Mapped[date] = mapped_column(Date)
|
||||||
|
|
||||||
round: Mapped["Round"] = relationship(back_populates="positions")
|
round: Mapped["Round"] = relationship(back_populates="positions")
|
||||||
|
|
||||||
|
|
||||||
|
class Trade(Base):
|
||||||
|
__tablename__ = "trades"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
product_code: Mapped[str] = mapped_column(String(10))
|
||||||
|
contract_code: Mapped[str] = mapped_column(String(10), index=True)
|
||||||
|
direction: Mapped[str] = mapped_column(String(4))
|
||||||
|
open_date: Mapped[date] = mapped_column(Date)
|
||||||
|
open_price: Mapped[float] = mapped_column(Float)
|
||||||
|
open_fee: Mapped[float | None] = mapped_column(Float, nullable=True, default=0)
|
||||||
|
close_date: Mapped[date | None] = mapped_column(Date, nullable=True)
|
||||||
|
close_price: Mapped[float | None] = mapped_column(Float, nullable=True)
|
||||||
|
close_fee: Mapped[float | None] = mapped_column(Float, nullable=True, default=0)
|
||||||
|
status: Mapped[str] = mapped_column(String(10), default="open")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pnl(self) -> float | None:
|
||||||
|
if self.close_price is None:
|
||||||
|
return None
|
||||||
|
mul = 20 # glass futures point value
|
||||||
|
if self.direction == "long":
|
||||||
|
result = (self.close_price - self.open_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
|
||||||
|
else:
|
||||||
|
result = (self.open_price - self.close_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
|
||||||
|
return round(result, 2)
|
||||||
@@ -20,11 +20,11 @@ def get_active_contracts(db: Session) -> list[str]:
|
|||||||
|
|
||||||
@router.get("/", response_class=HTMLResponse)
|
@router.get("/", response_class=HTMLResponse)
|
||||||
def positions_page(request: Request, db: Session = Depends(get_db)):
|
def positions_page(request: Request, db: Session = Depends(get_db)):
|
||||||
active_round = (
|
active_rounds = (
|
||||||
db.query(Round)
|
db.query(Round)
|
||||||
.filter(Round.status == "active")
|
.filter(Round.status == "active")
|
||||||
.order_by(Round.started_at.desc())
|
.order_by(Round.started_at.desc())
|
||||||
.first()
|
.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
template = request.app.state.templates.get_template("positions.html")
|
template = request.app.state.templates.get_template("positions.html")
|
||||||
@@ -33,7 +33,7 @@ def positions_page(request: Request, db: Session = Depends(get_db)):
|
|||||||
request=request,
|
request=request,
|
||||||
active_nav="positions",
|
active_nav="positions",
|
||||||
contracts=get_active_contracts(db),
|
contracts=get_active_contracts(db),
|
||||||
active_round=active_round,
|
active_rounds=active_rounds,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,12 +45,15 @@ def create_round(
|
|||||||
daily_hands: int = Form(1),
|
daily_hands: int = Form(1),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
existing = db.query(Round).filter(Round.status == "active").first()
|
code = contract_code.upper()
|
||||||
|
existing = db.query(Round).filter(
|
||||||
|
Round.status == "active", Round.contract_code == code
|
||||||
|
).first()
|
||||||
if existing:
|
if existing:
|
||||||
return RedirectResponse("/positions/?error=已有活跃轮次", status_code=303)
|
return RedirectResponse(f"/positions/?error={code} 已有活跃轮次", status_code=303)
|
||||||
|
|
||||||
r = Round(
|
r = Round(
|
||||||
contract_code=contract_code.upper(),
|
contract_code=code,
|
||||||
daily_hands=daily_hands,
|
daily_hands=daily_hands,
|
||||||
status="active",
|
status="active",
|
||||||
started_at=date.today(),
|
started_at=date.today(),
|
||||||
@@ -60,21 +63,22 @@ def create_round(
|
|||||||
return RedirectResponse("/positions/", status_code=303)
|
return RedirectResponse("/positions/", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/add")
|
@router.post("/{round_id}/add")
|
||||||
def add_position(
|
def add_position(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
round_id: int,
|
||||||
open_price: int = Form(...),
|
open_price: int = Form(...),
|
||||||
amp_threshold: int = Form(...),
|
amp_threshold: int = Form(...),
|
||||||
hands: int = Form(1),
|
hands: int = Form(1),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
active_round = db.query(Round).filter(Round.status == "active").first()
|
r = db.query(Round).filter(Round.id == round_id).first()
|
||||||
if not active_round:
|
if not r or r.status != "active":
|
||||||
return RedirectResponse("/positions/?error=无活跃轮次", status_code=303)
|
return RedirectResponse("/positions/?error=轮次不存在或已结束", status_code=303)
|
||||||
|
|
||||||
lock_price = open_price + amp_threshold
|
lock_price = open_price + amp_threshold
|
||||||
p = Position(
|
p = Position(
|
||||||
round_id=active_round.id,
|
round_id=r.id,
|
||||||
open_price=open_price,
|
open_price=open_price,
|
||||||
amp_threshold=amp_threshold,
|
amp_threshold=amp_threshold,
|
||||||
lock_price=lock_price,
|
lock_price=lock_price,
|
||||||
@@ -104,14 +108,24 @@ def unlock_position(position_id: int, db: Session = Depends(get_db)):
|
|||||||
return RedirectResponse("/positions/", status_code=303)
|
return RedirectResponse("/positions/", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/close")
|
@router.post("/{round_id}/close")
|
||||||
def close_round(
|
def close_round(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
round_id: int,
|
||||||
result: str = Form(...),
|
result: str = Form(...),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
):
|
):
|
||||||
active_round = db.query(Round).filter(Round.status == "active").first()
|
r = db.query(Round).filter(Round.id == round_id).first()
|
||||||
if active_round:
|
if r and r.status == "active":
|
||||||
active_round.status = result
|
r.status = result
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse("/positions/", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{round_id}/delete")
|
||||||
|
def delete_round(round_id: int, db: Session = Depends(get_db)):
|
||||||
|
r = db.query(Round).filter(Round.id == round_id).first()
|
||||||
|
if r:
|
||||||
|
db.delete(r)
|
||||||
db.commit()
|
db.commit()
|
||||||
return RedirectResponse("/positions/", status_code=303)
|
return RedirectResponse("/positions/", status_code=303)
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
from datetime import date
|
||||||
|
from fastapi import APIRouter, Depends, Form, Request
|
||||||
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models import Trade, Contract, Product
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/trades", tags=["trades"])
|
||||||
|
|
||||||
|
DIRECTIONS = [("short", "空"), ("long", "多")]
|
||||||
|
|
||||||
|
|
||||||
|
def get_active_contracts(db: Session) -> list[str]:
|
||||||
|
contracts = (
|
||||||
|
db.query(Contract.code)
|
||||||
|
.filter(Contract.is_active == True)
|
||||||
|
.order_by(Contract.code)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
return [c[0] for c in contracts]
|
||||||
|
|
||||||
|
|
||||||
|
def today_str() -> str:
|
||||||
|
return date.today().isoformat()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_class=HTMLResponse)
|
||||||
|
def trades_page(request: Request, db: Session = Depends(get_db)):
|
||||||
|
open_trades = (
|
||||||
|
db.query(Trade).filter(Trade.status == "open")
|
||||||
|
.order_by(Trade.open_date.desc()).all()
|
||||||
|
)
|
||||||
|
closed_trades = (
|
||||||
|
db.query(Trade).filter(Trade.status == "closed")
|
||||||
|
.order_by(Trade.close_date.desc()).limit(50).all()
|
||||||
|
)
|
||||||
|
|
||||||
|
template = request.app.state.templates.get_template("trades.html")
|
||||||
|
return HTMLResponse(
|
||||||
|
template.render(
|
||||||
|
request=request,
|
||||||
|
active_nav="trades",
|
||||||
|
contracts=get_active_contracts(db),
|
||||||
|
directions=DIRECTIONS,
|
||||||
|
open_trades=open_trades,
|
||||||
|
closed_trades=closed_trades,
|
||||||
|
today=today_str(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/open")
|
||||||
|
def open_trade(
|
||||||
|
request: Request,
|
||||||
|
contract_code: str = Form(...),
|
||||||
|
direction: str = Form(...),
|
||||||
|
open_date: str = Form(...),
|
||||||
|
open_price: float = Form(...),
|
||||||
|
open_fee: float = Form(0.0),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
code = contract_code.upper()
|
||||||
|
contract = db.query(Contract).filter(Contract.code == code).first()
|
||||||
|
product_code = contract.product.code if contract else code[:2]
|
||||||
|
|
||||||
|
t = Trade(
|
||||||
|
product_code=product_code,
|
||||||
|
contract_code=code,
|
||||||
|
direction=direction,
|
||||||
|
open_date=date.fromisoformat(open_date),
|
||||||
|
open_price=open_price,
|
||||||
|
open_fee=open_fee,
|
||||||
|
status="open",
|
||||||
|
)
|
||||||
|
db.add(t)
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse("/trades/", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{trade_id}/close")
|
||||||
|
def close_trade(
|
||||||
|
request: Request,
|
||||||
|
trade_id: int,
|
||||||
|
close_date: str = Form(...),
|
||||||
|
close_price: float = Form(...),
|
||||||
|
close_fee: float = Form(0.0),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
t = db.query(Trade).filter(Trade.id == trade_id).first()
|
||||||
|
if t and t.status == "open":
|
||||||
|
t.close_date = date.fromisoformat(close_date)
|
||||||
|
t.close_price = close_price
|
||||||
|
t.close_fee = close_fee
|
||||||
|
t.status = "closed"
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse("/trades/", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{trade_id}/delete")
|
||||||
|
def delete_trade(trade_id: int, db: Session = Depends(get_db)):
|
||||||
|
t = db.query(Trade).filter(Trade.id == trade_id).first()
|
||||||
|
if t:
|
||||||
|
db.delete(t)
|
||||||
|
db.commit()
|
||||||
|
return RedirectResponse("/trades/", status_code=303)
|
||||||
@@ -59,10 +59,9 @@
|
|||||||
border-left-color: var(--accent); font-weight: 600;
|
border-left-color: var(--accent); font-weight: 600;
|
||||||
}
|
}
|
||||||
.sidebar-nav .icon { font-size: 1.1rem; width: 22px; text-align: center; }
|
.sidebar-nav .icon { font-size: 1.1rem; width: 22px; text-align: center; }
|
||||||
.nav-parent { position: relative; }
|
.nav-group .nav-arrow { font-size: 0.65rem; margin-left: auto; transition: transform .2s; color: var(--sub); }
|
||||||
.nav-arrow { font-size: 0.65rem; margin-left: auto; transition: transform .2s; color: var(--sub); }
|
|
||||||
.nav-group.open .nav-arrow { transform: rotate(-180deg); }
|
.nav-group.open .nav-arrow { transform: rotate(-180deg); }
|
||||||
.nav-children { display: none; }
|
.nav-group .nav-children { display: none; }
|
||||||
.nav-group.open .nav-children { display: block; }
|
.nav-group.open .nav-children { display: block; }
|
||||||
.nav-children a { padding-left: 48px !important; font-size: 0.84rem !important; }
|
.nav-children a { padding-left: 48px !important; font-size: 0.84rem !important; }
|
||||||
.sidebar-footer {
|
.sidebar-footer {
|
||||||
@@ -201,20 +200,15 @@
|
|||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-logo">📊 期货量化</div>
|
<div class="sidebar-logo">📊 期货量化</div>
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
<div class="nav-group{% if active_nav in ('contracts', 'positions') %} open{% endif %}" id="nav-market">
|
|
||||||
<a href="/contracts/" class="nav-parent{% if active_nav in ('contracts', 'positions') %} active{% endif %}" onclick="toggleNavGroup(event, 'nav-market')">
|
|
||||||
<span class="icon">📈</span> 行情数据
|
|
||||||
<span class="nav-arrow">▼</span>
|
|
||||||
</a>
|
|
||||||
<div class="nav-children">
|
|
||||||
<a href="/contracts/" class="{% if active_nav == 'contracts' %}active{% endif %}">
|
<a href="/contracts/" class="{% if active_nav == 'contracts' %}active{% endif %}">
|
||||||
<span class="icon">📋</span> 合约列表
|
<span class="icon">📈</span> 行情数据
|
||||||
</a>
|
</a>
|
||||||
<a href="/positions/" class="{% if active_nav == 'positions' %}active{% endif %}">
|
<a href="/positions/" class="{% if active_nav == 'positions' %}active{% endif %}">
|
||||||
<span class="icon">📐</span> 仓位管理
|
<span class="icon">📐</span> 仓位管理
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<a href="/trades/" class="{% if active_nav == 'trades' %}active{% endif %}">
|
||||||
</div>
|
<span class="icon">📝</span> 交易记录
|
||||||
|
</a>
|
||||||
<a href="/admin/" class="{% if active_nav == 'admin' %}active{% endif %}">
|
<a href="/admin/" class="{% if active_nav == 'admin' %}active{% endif %}">
|
||||||
<span class="icon">⚙️</span> 系统管理
|
<span class="icon">⚙️</span> 系统管理
|
||||||
</a>
|
</a>
|
||||||
@@ -265,6 +259,10 @@ function setTheme(dark) {
|
|||||||
function toggleTheme() {
|
function toggleTheme() {
|
||||||
setTheme(document.documentElement.getAttribute('data-theme') !== 'dark');
|
setTheme(document.documentElement.getAttribute('data-theme') !== 'dark');
|
||||||
}
|
}
|
||||||
|
function toggleNavGroup(e, id) {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById(id).classList.toggle('open');
|
||||||
|
}
|
||||||
(function() {
|
(function() {
|
||||||
try {
|
try {
|
||||||
var s = localStorage.getItem('ft-theme');
|
var s = localStorage.getItem('ft-theme');
|
||||||
@@ -278,10 +276,6 @@ function closeDrawer() {
|
|||||||
document.getElementById('drawer').classList.remove('show');
|
document.getElementById('drawer').classList.remove('show');
|
||||||
if (activeRow) { activeRow.classList.remove('active'); activeRow = null; }
|
if (activeRow) { activeRow.classList.remove('active'); activeRow = null; }
|
||||||
}
|
}
|
||||||
function toggleNavGroup(e, id) {
|
|
||||||
e.preventDefault();
|
|
||||||
document.getElementById(id).classList.toggle('open');
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
<div style="background:var(--danger-bg);color:var(--danger-fg);padding:10px 16px;border-radius:6px;margin-bottom:16px;font-size:0.88rem;">{{ error }}</div>
|
<div style="background:var(--danger-bg);color:var(--danger-fg);padding:10px 16px;border-radius:6px;margin-bottom:16px;font-size:0.88rem;">{{ error }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not active_round %}
|
|
||||||
{# ═══════════════ 新建轮次 ═══════════════ #}
|
{# ═══════════════ 新建轮次 ═══════════════ #}
|
||||||
<div class="section-title">新建轮次</div>
|
<div class="section-title">新建轮次</div>
|
||||||
<div class="form-card" style="margin-bottom:28px;">
|
<div class="form-card" style="margin-bottom:28px;">
|
||||||
@@ -35,102 +34,87 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if not active_rounds %}
|
||||||
<div style="text-align:center;padding:60px;color:var(--sub);">
|
<div style="text-align:center;padding:60px;color:var(--sub);">
|
||||||
<p style="font-size:1.1rem;margin-bottom:8px;">暂无活跃轮次</p>
|
<p style="font-size:1.1rem;margin-bottom:8px;">暂无活跃轮次</p>
|
||||||
<p style="font-size:0.82rem;">创建一轮新的交易,开始跟踪仓位和锁仓状态</p>
|
<p style="font-size:0.82rem;">创建一轮新的交易,开始跟踪仓位和锁仓状态</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% else %}
|
|
||||||
{# ═══════════════ 活跃轮次信息 ═══════════════ #}
|
|
||||||
{% set locks = active_round.total_locks %}
|
|
||||||
<div class="stat-grid">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="label">合约</div>
|
|
||||||
<div class="value">{{ active_round.contract_code }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="label">每日手数</div>
|
|
||||||
<div class="value">{{ active_round.daily_hands }} 手/天</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="label">已开仓天数</div>
|
|
||||||
<div class="value">{{ active_round.positions|length }} 天</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card" style="{% if locks >= 3 %}border-color:var(--danger);background:var(--danger-bg);{% endif %}">
|
|
||||||
<div class="label">累计锁仓</div>
|
|
||||||
<div class="value" style="{% if locks >= 3 %}color:var(--danger-fg);{% endif %}">
|
|
||||||
{{ locks }} / 3
|
|
||||||
{% if locks >= 3 %}⚠ 熔断{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="label">开始日期</div>
|
|
||||||
<div class="value" style="font-size:1rem;">{{ active_round.started_at }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# ═══════════════ 止盈阈值提示 ═══════════════ #}
|
|
||||||
{% if active_round.positions|length > 0 %}
|
|
||||||
{% set latest = active_round.positions[-1] %}
|
|
||||||
<div style="background:var(--accent-light);border:1px solid var(--accent);border-radius:8px;padding:14px 18px;margin-bottom:24px;display:flex;align-items:center;gap:24px;flex-wrap:wrap;">
|
|
||||||
<div>
|
|
||||||
<span style="font-size:0.78rem;color:var(--sub);">止盈阈值</span>
|
|
||||||
<span style="font-weight:700;font-size:1.1rem;margin-left:8px;">{{ active_round.daily_hands }} × {{ latest.amp_threshold }} = {{ active_round.daily_hands * latest.amp_threshold }} 点</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span style="font-size:0.78rem;color:var(--sub);">当前振幅 A</span>
|
|
||||||
<span style="font-weight:700;font-size:1.1rem;margin-left:8px;">{{ latest.amp_threshold }} 点</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span style="font-size:0.78rem;color:var(--sub);">总手数</span>
|
|
||||||
<span style="font-weight:700;font-size:1.1rem;margin-left:8px;">{{ active_round.daily_hands * active_round.positions|length }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# ═══════════════ 添加仓位 ═══════════════ #}
|
{% for round in active_rounds %}
|
||||||
<div class="section-title">添加当日仓位</div>
|
{# ═══════════════ 轮次卡片 ═══════════════ #}
|
||||||
<div class="form-card" style="margin-bottom:28px;">
|
{% set locks = round.total_locks %}
|
||||||
<form method="post" action="/positions/add" id="addForm">
|
<div style="margin-bottom:32px;border:1px solid var(--border);border-radius:10px;overflow:hidden;">
|
||||||
<div style="display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap;">
|
|
||||||
<div class="form-group" style="margin-bottom:0;flex:1;min-width:120px;">
|
{# ── 轮次头部 ── #}
|
||||||
|
<div style="display:flex;align-items:center;gap:16px;padding:14px 20px;background:var(--th-bg);flex-wrap:wrap;">
|
||||||
|
<strong style="font-size:1rem;">{{ round.contract_code }}</strong>
|
||||||
|
<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.started_at }}</span>
|
||||||
|
<span style="font-size:0.82rem;font-weight:600;{% if locks >= 3 %}color:var(--danger-fg);{% endif %}">
|
||||||
|
锁仓 {{ locks }}/3
|
||||||
|
{% if locks >= 3 %} ⚠ 熔断{% endif %}
|
||||||
|
</span>
|
||||||
|
<span style="margin-left:auto;display:flex;gap:8px;">
|
||||||
|
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
|
||||||
|
<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="return confirm('确认 {{ round.contract_code }} 止盈清仓?')">止盈</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/positions/{{ round.id }}/close" style="display:inline;">
|
||||||
|
<input type="hidden" name="result" value="meltdown">
|
||||||
|
<button class="btn" style="background:var(--danger);color:#fff;font-size:0.78rem;padding:4px 14px;" onclick="return confirm('确认 {{ round.contract_code }} 熔断清仓?')">熔断</button>
|
||||||
|
</form>
|
||||||
|
<form method="post" action="/positions/{{ round.id }}/delete" style="display:inline;">
|
||||||
|
<button style="background:none;border:none;color:var(--na);cursor:pointer;font-size:0.78rem;" onclick="return confirm('确认删除 {{ round.contract_code }} 整轮数据?')">删除</button>
|
||||||
|
</form>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="padding:16px 20px;">
|
||||||
|
|
||||||
|
{# ── 止盈阈值 ── #}
|
||||||
|
{% if round.positions %}
|
||||||
|
{% set latest = round.positions[-1] %}
|
||||||
|
<div style="background:var(--accent-light);border:1px solid var(--accent);border-radius:6px;padding:10px 16px;margin-bottom:16px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;">
|
||||||
|
<span style="font-size:0.78rem;color:var(--sub);">止盈阈值</span>
|
||||||
|
<span style="font-weight:700;">{{ round.daily_hands }} × {{ latest.amp_threshold }} = {{ round.daily_hands * latest.amp_threshold }} 点</span>
|
||||||
|
<span style="color:var(--sub);">|</span>
|
||||||
|
<span style="font-size:0.78rem;color:var(--sub);">当前 A = {{ latest.amp_threshold }}</span>
|
||||||
|
<span style="color:var(--sub);">|</span>
|
||||||
|
<span style="font-size:0.78rem;color:var(--sub);">总手数 {{ round.daily_hands * round.positions|length }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# ── 添加仓位 ── #}
|
||||||
|
<div style="margin-bottom:16px;">
|
||||||
|
<form method="post" action="/positions/{{ round.id }}/add">
|
||||||
|
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
|
||||||
|
<div class="form-group" style="margin-bottom:0;flex:1;min-width:100px;">
|
||||||
<label>开仓价</label>
|
<label>开仓价</label>
|
||||||
<input type="number" name="open_price" id="openPrice" required placeholder="如 1300" style="font-size:1rem;">
|
<input type="number" name="open_price" required placeholder="如 1300" style="font-size:0.9rem;">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom:0;flex:1;min-width:120px;">
|
<div class="form-group" style="margin-bottom:0;flex:1;min-width:100px;">
|
||||||
<label>振幅阈值 A</label>
|
<label>振幅 A</label>
|
||||||
<input type="number" name="amp_threshold" id="ampThreshold" required placeholder="如 18" style="font-size:1rem;">
|
<input type="number" name="amp_threshold" required placeholder="如 18" style="font-size:0.9rem;">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom:0;flex:1;min-width:80px;">
|
<div class="form-group" style="margin-bottom:0;flex:1;min-width:70px;">
|
||||||
<label>手数</label>
|
<label>手数</label>
|
||||||
<input type="number" name="hands" value="1" min="1" max="10" required>
|
<input type="number" name="hands" value="{{ round.daily_hands }}" min="1" max="10" required style="font-size:0.9rem;">
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;flex-direction:column;align-items:center;min-width:100px;">
|
<button type="submit" class="btn btn-primary" style="font-size:0.82rem;padding:8px 18px;">+ 添加</button>
|
||||||
<span style="font-size:0.75rem;color:var(--sub);margin-bottom:4px;">锁仓价位</span>
|
|
||||||
<span id="lockPricePreview" style="font-size:1.3rem;font-weight:700;color:var(--danger-fg);">—</span>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="btn btn-primary">添加仓位</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# ═══════════════ 仓位列表 ═══════════════ #}
|
{# ── 仓位表格 ── #}
|
||||||
<div class="section-title">本轮仓位</div>
|
{% if round.positions %}
|
||||||
|
<div class="table-wrap" style="margin-bottom:0;">
|
||||||
{% if active_round.positions %}
|
<table style="font-size:0.84rem;">
|
||||||
<div class="table-wrap">
|
|
||||||
<table>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>日期</th>
|
<th>日期</th><th>开仓价</th><th>A</th><th>锁仓价</th><th>手数</th><th>已锁</th><th>状态</th><th>操作</th>
|
||||||
<th>开仓价</th>
|
|
||||||
<th>振幅 A</th>
|
|
||||||
<th>锁仓价位</th>
|
|
||||||
<th>手数</th>
|
|
||||||
<th>已锁</th>
|
|
||||||
<th>状态</th>
|
|
||||||
<th>操作</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% for p in active_round.positions|reverse %}
|
{% for p in round.positions|reverse %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{{ p.opened_at }}</strong></td>
|
<td><strong>{{ p.opened_at }}</strong></td>
|
||||||
<td>{{ p.open_price }}</td>
|
<td>{{ p.open_price }}</td>
|
||||||
@@ -148,66 +132,33 @@
|
|||||||
{% if p.locked_count >= p.hands %}
|
{% if p.locked_count >= p.hands %}
|
||||||
<span class="badge badge-down">已全锁</span>
|
<span class="badge badge-down">已全锁</span>
|
||||||
{% elif p.locked_count > 0 %}
|
{% elif p.locked_count > 0 %}
|
||||||
<span class="badge badge-warn">部分锁仓</span>
|
<span class="badge badge-warn">部分锁</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge badge-up">活跃</span>
|
<span class="badge badge-up">活跃</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div style="display:flex;gap:8px;justify-content:center;">
|
<div style="display:flex;gap:6px;justify-content:center;">
|
||||||
<form method="post" action="/positions/{{ p.id }}/lock" style="display:inline;">
|
<form method="post" action="/positions/{{ p.id }}/lock" style="display:inline;">
|
||||||
<button style="background:var(--warn-bg);color:var(--warn-fg);border:1px solid var(--warn);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;font-weight:600;"
|
<button style="background:var(--warn-bg);color:var(--warn-fg);border:1px solid var(--warn);padding:2px 8px;border-radius:4px;cursor:pointer;font-size:0.75rem;font-weight:600;"
|
||||||
{% if p.locked_count >= p.hands %}disabled style="opacity:0.4;cursor:default;"{% endif %}>
|
{% if p.locked_count >= p.hands %}disabled style="opacity:0.4;cursor:default;"{% endif %}>锁</button>
|
||||||
🔒 锁
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="/positions/{{ p.id }}/unlock" style="display:inline;">
|
<form method="post" action="/positions/{{ p.id }}/unlock" style="display:inline;">
|
||||||
<button style="background:var(--surface);color:var(--sub);border:1px solid var(--border);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;"
|
<button style="background:var(--surface);color:var(--sub);border:1px solid var(--border);padding:2px 8px;border-radius:4px;cursor:pointer;font-size:0.75rem;"
|
||||||
{% if p.locked_count == 0 %}disabled style="opacity:0.4;cursor:default;"{% endif %}>
|
{% if p.locked_count == 0 %}disabled style="opacity:0.4;cursor:default;"{% endif %}>撤</button>
|
||||||
撤销
|
|
||||||
</button>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div style="text-align:center;padding:24px;color:var(--sub);font-size:0.84rem;">暂无仓位,请添加。</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% endfor %}
|
||||||
<div style="text-align:center;padding:40px;color:var(--sub);">暂无仓位,请添加当日仓位。</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# ═══════════════ 结束轮次 ═══════════════ #}
|
|
||||||
<div style="margin-top:28px;padding:20px;background:var(--surface);border:1px solid var(--border);border-radius:10px;display:flex;align-items:center;gap:16px;">
|
|
||||||
<span style="font-size:0.88rem;font-weight:600;">结束本轮:</span>
|
|
||||||
<form method="post" action="/positions/close" style="display:inline;">
|
|
||||||
<input type="hidden" name="result" value="profit_taken">
|
|
||||||
<button type="submit" class="btn" style="background:var(--success);color:#fff;" onclick="return confirm('确认止盈清仓?')">🟢 止盈</button>
|
|
||||||
</form>
|
|
||||||
<form method="post" action="/positions/close" style="display:inline;">
|
|
||||||
<input type="hidden" name="result" value="meltdown">
|
|
||||||
<button type="submit" class="btn" style="background:var(--danger);color:#fff;" onclick="return confirm('确认熔断清仓?')">🔴 熔断</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
var openPrice = document.getElementById('openPrice');
|
|
||||||
var ampThreshold = document.getElementById('ampThreshold');
|
|
||||||
var preview = document.getElementById('lockPricePreview');
|
|
||||||
function updatePreview() {
|
|
||||||
var op = parseInt(openPrice.value) || 0;
|
|
||||||
var at = parseInt(ampThreshold.value) || 0;
|
|
||||||
if (op > 0 && at > 0) {
|
|
||||||
preview.textContent = (op + at) + ' 点';
|
|
||||||
} else {
|
|
||||||
preview.textContent = '—';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (openPrice && ampThreshold) {
|
|
||||||
openPrice.addEventListener('input', updatePreview);
|
|
||||||
ampThreshold.addEventListener('input', updatePreview);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block title %}交易记录{% endblock %}
|
||||||
|
{% block heading %}交易记录{% endblock %}
|
||||||
|
{% block breadcrumb %}开平仓记录{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% set view = request.query_params.get('view', '') %}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.tabs { display: flex; gap: 0; margin-bottom: 24px; border-bottom: 2px solid var(--border); }
|
||||||
|
.tab-btn {
|
||||||
|
padding: 10px 20px; border: none; background: none;
|
||||||
|
font-size: 0.88rem; font-weight: 500; color: var(--sub);
|
||||||
|
cursor: pointer; text-decoration: none; border-bottom: 2px solid transparent;
|
||||||
|
margin-bottom: -2px; transition: color .12s, border-color .12s;
|
||||||
|
}
|
||||||
|
.tab-btn:hover { color: var(--fg); }
|
||||||
|
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
|
||||||
|
.tab-panel { display: none; }
|
||||||
|
.tab-panel.active { display: block; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="tabs">
|
||||||
|
<a href="/trades/" class="tab-btn{% if view != 'closed' %} active{% endif %}">📋 持仓</a>
|
||||||
|
<a href="/trades/?view=closed" class="tab-btn{% if view == 'closed' %} active{% endif %}">📊 已平仓</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if view != 'closed' %}
|
||||||
|
<div class="tab-panel active">
|
||||||
|
|
||||||
|
{# ── 新建开仓 ── #}
|
||||||
|
<div class="section-title">新建开仓</div>
|
||||||
|
<div class="form-card" style="margin-bottom:28px;max-width:100%;">
|
||||||
|
<form method="post" action="/trades/open">
|
||||||
|
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
|
||||||
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:120px;">
|
||||||
|
<label>合约</label>
|
||||||
|
<select name="contract_code" required>
|
||||||
|
<option value="">--</option>
|
||||||
|
{% for c in contracts %}
|
||||||
|
<option value="{{ c }}">{{ c }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:70px;">
|
||||||
|
<label>方向</label>
|
||||||
|
<select name="direction" required>
|
||||||
|
{% for v, label in directions %}
|
||||||
|
<option value="{{ v }}">{{ label }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:0;flex:1;min-width:140px;">
|
||||||
|
<label>开仓日期</label>
|
||||||
|
<input type="date" name="open_date" value="{{ today }}" required style="font-size:0.85rem;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:80px;">
|
||||||
|
<label>开仓价</label>
|
||||||
|
<input type="number" step="any" name="open_price" required placeholder="1300" style="font-size:0.9rem;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:80px;">
|
||||||
|
<label>手续费</label>
|
||||||
|
<input type="number" step="any" name="open_fee" value="0" style="font-size:0.9rem;">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" style="font-size:0.82rem;padding:8px 18px;">开仓</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# ── 持仓列表 ── #}
|
||||||
|
<div class="section-title">持仓中 · {{ open_trades|length }} 笔</div>
|
||||||
|
|
||||||
|
{% if open_trades %}
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<tr><th>品种</th><th>合约</th><th>方向</th><th>开仓日期</th><th>开仓价</th><th>手续费</th><th>操作</th></tr>
|
||||||
|
{% for t in open_trades %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ t.product_code }}</td>
|
||||||
|
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
|
||||||
|
<td>
|
||||||
|
{% if t.direction == 'short' %}
|
||||||
|
<span class="badge badge-down">空</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge badge-up">多</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ t.open_date }}</td>
|
||||||
|
<td>{{ t.open_price }}</td>
|
||||||
|
<td>{{ t.open_fee or 0 }}</td>
|
||||||
|
<td>
|
||||||
|
<button style="background:var(--accent);color:#fff;border:none;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;"
|
||||||
|
onclick="showCloseForm({{ t.id }})">平仓</button>
|
||||||
|
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
|
||||||
|
<button style="background:var(--danger-bg);color:var(--danger-fg);border:1px solid var(--danger);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删除</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div style="text-align:center;padding:40px;color:var(--sub);">暂无持仓</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# ── 平仓弹窗 ── #}
|
||||||
|
<div id="closeFormOverlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:99;" onclick="hideCloseForm()"></div>
|
||||||
|
<div id="closeFormBox" 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:100;width:360px;max-width:90vw;">
|
||||||
|
<h3 style="margin-bottom:16px;">平仓</h3>
|
||||||
|
<form method="post" id="closeForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>平仓日期</label>
|
||||||
|
<input type="date" name="close_date" value="{{ today }}" required style="font-size:0.85rem;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>平仓价格</label>
|
||||||
|
<input type="number" step="any" name="close_price" required style="font-size:0.9rem;">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>手续费</label>
|
||||||
|
<input type="number" step="any" name="close_fee" value="0" style="font-size:0.9rem;">
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:10px;justify-content:flex-end;">
|
||||||
|
<button type="button" class="btn" style="background:var(--th-bg);color:var(--fg);" onclick="hideCloseForm()">取消</button>
|
||||||
|
<button type="submit" class="btn btn-primary">确认平仓</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="tab-panel active">
|
||||||
|
{# ═══════════════ 已平仓 ═══════════════ #}
|
||||||
|
|
||||||
|
{% if closed_trades %}
|
||||||
|
<div class="section-title">已平仓 · {{ closed_trades|length }} 笔</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<tr><th>品种</th><th>合约</th><th>方向</th><th>开仓</th><th>平仓</th><th>开仓价</th><th>平仓价</th><th>开仓费</th><th>平仓费</th><th>盈亏</th><th>操作</th></tr>
|
||||||
|
{% for t in closed_trades %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ t.product_code }}</td>
|
||||||
|
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
|
||||||
|
<td>
|
||||||
|
{% if t.direction == 'short' %}
|
||||||
|
<span class="badge badge-down">空</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge badge-up">多</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td style="font-size:0.8rem;">{{ t.open_date }}</td>
|
||||||
|
<td style="font-size:0.8rem;">{{ t.close_date }}</td>
|
||||||
|
<td>{{ t.open_price }}</td>
|
||||||
|
<td>{{ t.close_price }}</td>
|
||||||
|
<td>{{ t.open_fee or 0 }}</td>
|
||||||
|
<td>{{ t.close_fee or 0 }}</td>
|
||||||
|
<td>
|
||||||
|
{% set p = t.pnl %}
|
||||||
|
{% if p is not none %}
|
||||||
|
<span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% else %}color:var(--sub);{% endif %}">
|
||||||
|
{% if p > 0 %}+{% endif %}{{ p }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
|
||||||
|
<button style="background:var(--danger-bg);color:var(--danger-fg);border:1px solid var(--danger);padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删除</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% set ns = namespace(total_pnl=0, total_open_fee=0, total_close_fee=0) %}
|
||||||
|
{% for t in closed_trades %}
|
||||||
|
{% set ns.total_pnl = ns.total_pnl + (t.pnl or 0) %}
|
||||||
|
{% set ns.total_open_fee = ns.total_open_fee + (t.open_fee or 0) %}
|
||||||
|
{% set ns.total_close_fee = ns.total_close_fee + (t.close_fee or 0) %}
|
||||||
|
{% endfor %}
|
||||||
|
<div style="display:flex;gap:24px;margin-bottom:24px;padding:12px 18px;background:var(--surface);border:1px solid var(--border);border-radius:8px;font-size:0.88rem;flex-wrap:wrap;">
|
||||||
|
<span>盈亏合计 <b style="{% if ns.total_pnl > 0 %}color:var(--success-fg);{% elif ns.total_pnl < 0 %}color:var(--danger-fg);{% endif %}">{% if ns.total_pnl > 0 %}+{% endif %}{{ '%.2f'|format(ns.total_pnl) }}</b></span>
|
||||||
|
<span style="color:var(--sub);">|</span>
|
||||||
|
<span>开仓手续费 <b>{{ '%.2f'|format(ns.total_open_fee) }}</b></span>
|
||||||
|
<span style="color:var(--sub);">|</span>
|
||||||
|
<span>平仓手续费 <b>{{ '%.2f'|format(ns.total_close_fee) }}</b></span>
|
||||||
|
<span style="color:var(--sub);">|</span>
|
||||||
|
<span>手续费合计 <b>{{ '%.2f'|format(ns.total_open_fee + ns.total_close_fee) }}</b></span>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div style="text-align:center;padding:60px;color:var(--sub);">暂无已平仓记录</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if view != 'closed' %}
|
||||||
|
<script>
|
||||||
|
function showCloseForm(tradeId) {
|
||||||
|
document.getElementById('closeForm').action = '/trades/' + tradeId + '/close';
|
||||||
|
document.getElementById('closeFormOverlay').style.display = 'block';
|
||||||
|
document.getElementById('closeFormBox').style.display = 'block';
|
||||||
|
}
|
||||||
|
function hideCloseForm() {
|
||||||
|
document.getElementById('closeFormOverlay').style.display = 'none';
|
||||||
|
document.getElementById('closeFormBox').style.display = 'none';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user