交易记录优化:品种合约分离展示、盈亏取整、手续费统计
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -143,6 +143,7 @@ class Trade(Base):
|
|||||||
return None
|
return None
|
||||||
mul = 20 # glass futures point value
|
mul = 20 # glass futures point value
|
||||||
if self.direction == "long":
|
if self.direction == "long":
|
||||||
return (self.close_price - self.open_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
|
result = (self.close_price - self.open_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
|
||||||
else:
|
else:
|
||||||
return (self.open_price - self.close_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
|
result = (self.open_price - self.close_price) * mul - (self.open_fee or 0) - (self.close_fee or 0)
|
||||||
|
return round(result, 2)
|
||||||
@@ -3,7 +3,7 @@ from fastapi import APIRouter, Depends, Form, Request
|
|||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from app.database import get_db
|
from app.database import get_db
|
||||||
from app.models import Trade, Product, Contract
|
from app.models import Trade, Contract, Product
|
||||||
|
|
||||||
router = APIRouter(prefix="/trades", tags=["trades"])
|
router = APIRouter(prefix="/trades", tags=["trades"])
|
||||||
|
|
||||||
@@ -20,11 +20,6 @@ def get_active_contracts(db: Session) -> list[str]:
|
|||||||
return [c[0] for c in contracts]
|
return [c[0] for c in contracts]
|
||||||
|
|
||||||
|
|
||||||
def get_products(db: Session) -> list[str]:
|
|
||||||
products = db.query(Product.code).order_by(Product.code).all()
|
|
||||||
return [p[0] for p in products]
|
|
||||||
|
|
||||||
|
|
||||||
def today_str() -> str:
|
def today_str() -> str:
|
||||||
return date.today().isoformat()
|
return date.today().isoformat()
|
||||||
|
|
||||||
@@ -46,7 +41,6 @@ def trades_page(request: Request, db: Session = Depends(get_db)):
|
|||||||
request=request,
|
request=request,
|
||||||
active_nav="trades",
|
active_nav="trades",
|
||||||
contracts=get_active_contracts(db),
|
contracts=get_active_contracts(db),
|
||||||
products=get_products(db),
|
|
||||||
directions=DIRECTIONS,
|
directions=DIRECTIONS,
|
||||||
open_trades=open_trades,
|
open_trades=open_trades,
|
||||||
closed_trades=closed_trades,
|
closed_trades=closed_trades,
|
||||||
@@ -58,7 +52,6 @@ def trades_page(request: Request, db: Session = Depends(get_db)):
|
|||||||
@router.post("/open")
|
@router.post("/open")
|
||||||
def open_trade(
|
def open_trade(
|
||||||
request: Request,
|
request: Request,
|
||||||
product_code: str = Form(...),
|
|
||||||
contract_code: str = Form(...),
|
contract_code: str = Form(...),
|
||||||
direction: str = Form(...),
|
direction: str = Form(...),
|
||||||
open_date: str = Form(...),
|
open_date: str = Form(...),
|
||||||
@@ -66,9 +59,13 @@ def open_trade(
|
|||||||
open_fee: float = Form(0.0),
|
open_fee: float = Form(0.0),
|
||||||
db: Session = Depends(get_db),
|
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(
|
t = Trade(
|
||||||
product_code=product_code.upper(),
|
product_code=product_code,
|
||||||
contract_code=contract_code.upper(),
|
contract_code=code,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
open_date=date.fromisoformat(open_date),
|
open_date=date.fromisoformat(open_date),
|
||||||
open_price=open_price,
|
open_price=open_price,
|
||||||
|
|||||||
@@ -10,16 +10,7 @@
|
|||||||
<div class="form-card" style="margin-bottom:28px;max-width:100%;">
|
<div class="form-card" style="margin-bottom:28px;max-width:100%;">
|
||||||
<form method="post" action="/trades/open">
|
<form method="post" action="/trades/open">
|
||||||
<div style="display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
|
<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:100px;">
|
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:120px;">
|
||||||
<label>品种</label>
|
|
||||||
<select name="product_code" required>
|
|
||||||
<option value="">--</option>
|
|
||||||
{% for p in products %}
|
|
||||||
<option value="{{ p }}">{{ p }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group" style="margin-bottom:0;flex:0 0 auto;min-width:120px;">
|
|
||||||
<label>合约</label>
|
<label>合约</label>
|
||||||
<select name="contract_code" required>
|
<select name="contract_code" required>
|
||||||
<option value="">--</option>
|
<option value="">--</option>
|
||||||
@@ -63,7 +54,7 @@
|
|||||||
{% for t in open_trades %}
|
{% for t in open_trades %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ t.product_code }}</td>
|
<td>{{ t.product_code }}</td>
|
||||||
<td><strong>{{ t.contract_code }}</strong></td>
|
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
|
||||||
<td>
|
<td>
|
||||||
{% if t.direction == 'short' %}
|
{% if t.direction == 'short' %}
|
||||||
<span class="badge badge-down">空</span>
|
<span class="badge badge-down">空</span>
|
||||||
@@ -77,8 +68,8 @@
|
|||||||
<td>
|
<td>
|
||||||
<button style="background:var(--accent);color:#fff;border:none;padding:3px 10px;border-radius:4px;cursor:pointer;font-size:0.78rem;"
|
<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>
|
onclick="showCloseForm({{ t.id }})">平仓</button>
|
||||||
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;">
|
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
|
||||||
<button style="background:none;border:none;color:var(--na);cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删</button>
|
<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>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -122,7 +113,7 @@
|
|||||||
{% for t in closed_trades %}
|
{% for t in closed_trades %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ t.product_code }}</td>
|
<td>{{ t.product_code }}</td>
|
||||||
<td><strong>{{ t.contract_code }}</strong></td>
|
<td><strong>{{ t.contract_code.replace(t.product_code, '', 1) }}</strong></td>
|
||||||
<td>
|
<td>
|
||||||
{% if t.direction == 'short' %}
|
{% if t.direction == 'short' %}
|
||||||
<span class="badge badge-down">空</span>
|
<span class="badge badge-down">空</span>
|
||||||
@@ -143,14 +134,30 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;">
|
<form method="post" action="/trades/{{ t.id }}/delete" style="display:inline;margin-left:8px;">
|
||||||
<button style="background:none;border:none;color:var(--na);cursor:pointer;font-size:0.78rem;" onclick="return confirm('删除此记录?')">删</button>
|
<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>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user