feat: 导航开关与 CTP 柜台手续费
系统设置可开关五类导航;手续费默认从 CTP 查询同步,本地/AKShare 作离线兜底;补充 FEES.md。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+86
-29
@@ -1,4 +1,4 @@
|
||||
"""期货手续费:本地费率表 + 开平合计估算(模拟盘参考)。"""
|
||||
"""期货手续费:优先 CTP 柜台费率,本地/AKShare 为离线兜底。"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@@ -51,31 +51,79 @@ def get_fee_multiplier() -> float:
|
||||
return 2.0
|
||||
|
||||
|
||||
def get_fee_spec(ths_code: str) -> dict:
|
||||
def get_fee_source_mode() -> str:
|
||||
"""ctp=优先柜台同步费率;local=本地/AKShare 表。"""
|
||||
conn = _get_db()
|
||||
row = conn.execute(
|
||||
"SELECT value FROM settings WHERE key='fee_source_mode'"
|
||||
).fetchone()
|
||||
conn.close()
|
||||
mode = (row["value"] if row else "ctp") or "ctp"
|
||||
return mode if mode in ("ctp", "local") else "ctp"
|
||||
|
||||
|
||||
def _row_to_spec(row, mult: int) -> dict:
|
||||
return {
|
||||
"product": row["product"],
|
||||
"exchange": row["exchange"] or "",
|
||||
"mult": int(row["mult"] or mult),
|
||||
"open_fixed": float(row["open_fixed"] or 0),
|
||||
"open_ratio": float(row["open_ratio"] or 0),
|
||||
"close_yesterday_fixed": float(row["close_yesterday_fixed"] or 0),
|
||||
"close_yesterday_ratio": float(row["close_yesterday_ratio"] or 0),
|
||||
"close_today_fixed": float(row["close_today_fixed"] or 0),
|
||||
"close_today_ratio": float(row["close_today_ratio"] or 0),
|
||||
"source": row["source"] if "source" in row.keys() else "local",
|
||||
}
|
||||
|
||||
|
||||
def get_fee_spec(ths_code: str, *, trading_mode: str = "simulation") -> dict:
|
||||
product = product_from_code(ths_code)
|
||||
if not product:
|
||||
spec = get_contract_spec(ths_code)
|
||||
return {**DEFAULT_FEE, "mult": spec["mult"], "product": "", "exchange": ""}
|
||||
|
||||
conn = _get_db()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM fee_rates WHERE product=?", (product,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
return {**DEFAULT_FEE, "mult": spec["mult"], "product": "", "exchange": "", "source": "default"}
|
||||
|
||||
mult = get_contract_spec(ths_code)["mult"]
|
||||
if row:
|
||||
return {
|
||||
"product": product,
|
||||
"exchange": row["exchange"] or "",
|
||||
"mult": int(row["mult"] or mult),
|
||||
"open_fixed": float(row["open_fixed"] or 0),
|
||||
"open_ratio": float(row["open_ratio"] or 0),
|
||||
"close_yesterday_fixed": float(row["close_yesterday_fixed"] or 0),
|
||||
"close_yesterday_ratio": float(row["close_yesterday_ratio"] or 0),
|
||||
"close_today_fixed": float(row["close_today_fixed"] or 0),
|
||||
"close_today_ratio": float(row["close_today_ratio"] or 0),
|
||||
}
|
||||
source_mode = get_fee_source_mode()
|
||||
conn = _get_db()
|
||||
|
||||
if source_mode == "ctp":
|
||||
row = conn.execute(
|
||||
"SELECT * FROM fee_rates WHERE product=? AND source='ctp'",
|
||||
(product,),
|
||||
).fetchone()
|
||||
if not row:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM fee_rates WHERE product=? ORDER BY CASE source WHEN 'ctp' THEN 0 ELSE 1 END",
|
||||
(product,),
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if row:
|
||||
return _row_to_spec(row, mult)
|
||||
# 按需向 CTP 查询
|
||||
try:
|
||||
from ctp_fee_sync import sync_fee_for_symbol
|
||||
fields = sync_fee_for_symbol(trading_mode, ths_code)
|
||||
if fields:
|
||||
return {"product": product, **fields}
|
||||
except Exception:
|
||||
pass
|
||||
conn = _get_db()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM fee_rates WHERE product=?", (product,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if row:
|
||||
spec = _row_to_spec(row, mult)
|
||||
spec["source"] = spec.get("source") or "local_fallback"
|
||||
return spec
|
||||
else:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM fee_rates WHERE product=?", (product,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if row:
|
||||
return _row_to_spec(row, mult)
|
||||
|
||||
if product in _INDEX_PRODUCTS:
|
||||
return {
|
||||
@@ -95,6 +143,7 @@ def get_fee_spec(ths_code: str) -> dict:
|
||||
"exchange": "",
|
||||
"mult": mult,
|
||||
**DEFAULT_FEE,
|
||||
"source": "default",
|
||||
}
|
||||
|
||||
|
||||
@@ -126,10 +175,11 @@ def calc_round_trip_fee(
|
||||
lots: float,
|
||||
open_time: str = "",
|
||||
close_time: str = "",
|
||||
trading_mode: str = "simulation",
|
||||
) -> float:
|
||||
if not entry_price or not close_price:
|
||||
return 0.0
|
||||
spec = get_fee_spec(ths_code)
|
||||
spec = get_fee_spec(ths_code, trading_mode=trading_mode)
|
||||
mult = spec["mult"]
|
||||
lots = lots or 1.0
|
||||
|
||||
@@ -157,8 +207,9 @@ def calc_fee_breakdown(
|
||||
lots: float,
|
||||
open_time: str = "",
|
||||
close_time: str = "",
|
||||
trading_mode: str = "simulation",
|
||||
) -> dict:
|
||||
spec = get_fee_spec(ths_code)
|
||||
spec = get_fee_spec(ths_code, trading_mode=trading_mode)
|
||||
mult = spec["mult"]
|
||||
lots = lots or 1.0
|
||||
open_fee = calc_side_fee(
|
||||
@@ -184,6 +235,7 @@ def calc_fee_breakdown(
|
||||
"close_type": close_type,
|
||||
"total_fee": total,
|
||||
"same_day": same_day,
|
||||
"fee_source": spec.get("source", "local"),
|
||||
}
|
||||
|
||||
|
||||
@@ -204,8 +256,8 @@ def load_fee_rates_from_json(path: Optional[str] = None) -> int:
|
||||
(product, exchange, mult,
|
||||
open_fixed, open_ratio,
|
||||
close_yesterday_fixed, close_yesterday_ratio,
|
||||
close_today_fixed, close_today_ratio, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)
|
||||
close_today_fixed, close_today_ratio, updated_at, source)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(product) DO UPDATE SET
|
||||
exchange=excluded.exchange, mult=excluded.mult,
|
||||
open_fixed=excluded.open_fixed, open_ratio=excluded.open_ratio,
|
||||
@@ -213,7 +265,8 @@ def load_fee_rates_from_json(path: Optional[str] = None) -> int:
|
||||
close_yesterday_ratio=excluded.close_yesterday_ratio,
|
||||
close_today_fixed=excluded.close_today_fixed,
|
||||
close_today_ratio=excluded.close_today_ratio,
|
||||
updated_at=excluded.updated_at""",
|
||||
updated_at=excluded.updated_at,
|
||||
source=excluded.source""",
|
||||
(
|
||||
product.lower(),
|
||||
item.get("exchange", ""),
|
||||
@@ -225,6 +278,7 @@ def load_fee_rates_from_json(path: Optional[str] = None) -> int:
|
||||
float(item.get("close_today_fixed") or 0),
|
||||
float(item.get("close_today_ratio") or 0),
|
||||
now,
|
||||
item.get("source", "json"),
|
||||
),
|
||||
)
|
||||
count += 1
|
||||
@@ -246,13 +300,14 @@ def upsert_fee_rate(product: str, fields: dict) -> None:
|
||||
product = product.lower().strip()
|
||||
conn = _get_db()
|
||||
now = datetime.now().isoformat(timespec="seconds")
|
||||
source = fields.get("source", "manual")
|
||||
conn.execute(
|
||||
"""INSERT INTO fee_rates
|
||||
(product, exchange, mult,
|
||||
open_fixed, open_ratio,
|
||||
close_yesterday_fixed, close_yesterday_ratio,
|
||||
close_today_fixed, close_today_ratio, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)
|
||||
close_today_fixed, close_today_ratio, updated_at, source)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
ON CONFLICT(product) DO UPDATE SET
|
||||
exchange=excluded.exchange, mult=excluded.mult,
|
||||
open_fixed=excluded.open_fixed, open_ratio=excluded.open_ratio,
|
||||
@@ -260,7 +315,8 @@ def upsert_fee_rate(product: str, fields: dict) -> None:
|
||||
close_yesterday_ratio=excluded.close_yesterday_ratio,
|
||||
close_today_fixed=excluded.close_today_fixed,
|
||||
close_today_ratio=excluded.close_today_ratio,
|
||||
updated_at=excluded.updated_at""",
|
||||
updated_at=excluded.updated_at,
|
||||
source=excluded.source""",
|
||||
(
|
||||
product,
|
||||
fields.get("exchange", ""),
|
||||
@@ -272,6 +328,7 @@ def upsert_fee_rate(product: str, fields: dict) -> None:
|
||||
float(fields.get("close_today_fixed") or 0),
|
||||
float(fields.get("close_today_ratio") or 0),
|
||||
now,
|
||||
source,
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
Reference in New Issue
Block a user