User edition: prune docs, license gate, obfuscate core lib.

Keep deploy/basic docs only; integrate sq.bz121.com license client; encrypt strategy/trade/key_monitor/options/hedge_plan for release.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-17 16:30:58 +08:00
parent 53863559f4
commit 16ef0f31e3
154 changed files with 1998 additions and 27914 deletions
+12 -1
View File
@@ -1 +1,12 @@
"""Shared library package."""
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNq7lppkmWqWkpJoYWmSbGqeaJJiYJRSYWCZKGZjYpASYJmfymZhZuabJq2fFcPkHsQqwhkZIBbp5OAoGhoezRocI63tbbHf0NqaNTwq22aOubBZqoGRmBVQ/4QUoP5y5kwDwRhxVwsGlmCfKPvCIKF4wXgni3vLOlvu9V1a9vhCU3PDsQbLUDFZQZ+wbL6oSA4HLqdcQe9Qb1vO2LAcAbeIQAa7NKt4Ll7R9DQpYZ+3mRwc8WGCIaHsCakmJs5pJpamuUkAloE41Q=='
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
+12 -16
View File
@@ -1,16 +1,12 @@
"""开仓后挂 TP/SL 失败时的补偿平仓(避免裸仓)."""
from __future__ import annotations
from typing import Callable
def log_compensating_close_error(prefix: str, exc: BaseException) -> None:
print(f"[{prefix}] {exc}", flush=True)
def run_compensating_close(close_fn: Callable[[], None], *, log_prefix: str = "compensating_close") -> None:
"""执行补偿平仓;二次失败只打日志,不掩盖原始异常."""
try:
close_fn()
except Exception as e:
log_compensating_close_error(log_prefix, e)
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNpVk21oE2cAx/PWNC81u5jmUre2VJBuUNnS+FxyzyW2aUhzNS946TVJr0mau9xdNC8mDOkqFUbaik5XqTrmC5uf/DZh3WAwY5gVYYPBGLWl6lzKtrqBdKNCGXv5IOwJ6Id9/vP8/8//9/yfB3IWyk5JEkgIRMIlAMHukE7YIY+bgF1iYEXWkkqgk9so0ZklWJ7Jpfr1nsJBXmZ6xsqM43OB9CgpRf45TO9t3cce/0wsAEWfCEeOD5nsEmic/e303F9rf26d+aLe7pI7HCeG1y+vr5z6+MzO3NonPzeuXqg+OV1dfFh9fNO8sXlu9dfa+Q/OVedvYIcXAMrvIp+V1Qkjh7VYo/EJrNNZ9/RGJ3XcaEyz7PD7lKNMZFkImv13jmi8E9vl6fjuaM9IVDniZ0zGbYdP6Q3TxlsH+ziDUkW96CejfpQdCjzyfx1AgL+L+mNSCxwG/xCsOCLddPzrGaNahJqzH/XPySFP33fy4GuU1O5C+f3qFG1j9pB13h/gDF7MZcuh83gW8bsu0hTy/7okkfaZJBToGE7LUYU+bCz0KHezkaQlf//vU9X335v79MuNx2v3L5wlE6GwFY9TSaNBF44HPF6/JgBTSa3kDymU0aOskTvEGM3j5oxBPZCMJTHm6K50a7eva3N/zKQqJjJ6fyqJqyNt42q3aixOawujloDBR6D8oCgDsF8OOAk5K07HjHpzOkGrhiZfYSl20hiKYfzywHhGXTxEKzRM1x+l1l2JYNTDch/1vuQ7UMl3+mfiScVgaArtQwVf8JMQPzfiV3nJL4X6d2e7yNsgS/ACI4kOnvTDjpMEiOQIeDiPk3tcHblNigSAKCM9Yn+VGiMdMkNEckG3T7LCMDD3In6rW5d+unvr/MUrs7XFxqMHK4s3Mivztfnvr1YvrS+sX2/Uv127s7O68+HDH5c6N354unr72vzS0srvzxv3vpp99GRjdiFyslvcLk0FW9PeiM4wERiUAOzMYW+gfeNvo33XwKgFovfHCXR/G4X2tf0WjbXltJy+XRMPxmwpTlvUptK2tBsKMk2i/bpdedElvSmXfqG+CbyD+s+EnV6etENLcz+4AxIyNtD0zyN/swfxQfpUU59u6vuauuf/uuupeKSSNmmLxa1yMcdieNjGut2W5j6f9VsjVi6lDh5A+QE3yg/1oP9pc6P/GQSI/3AJ7ddbQP71YzouoxiKMUkR8beVET+7CJ3HnJUDvfx/96pBjA=='
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -136
View File
@@ -1,136 +1,12 @@
"""实盘人工下单:止盈止损模式(价格 / 百分比 / 固定盈亏比)."""
from __future__ import annotations
from typing import Any, Optional, Tuple
MANUAL_FIXED_RR_DEFAULT = 1.5
SLTP_MODE_PRICE = "price"
SLTP_MODE_PCT = "pct"
SLTP_MODE_FIXED_RR = "fixed_rr"
OPEN_SLTP_MODES = frozenset({SLTP_MODE_PRICE, SLTP_MODE_PCT, SLTP_MODE_FIXED_RR})
ENTRUST_SLTP_MODES = frozenset({SLTP_MODE_PRICE, SLTP_MODE_PCT})
def normalize_open_sltp_mode(raw: Optional[str]) -> str:
mode = (raw or SLTP_MODE_FIXED_RR).strip().lower()
if mode in OPEN_SLTP_MODES:
return mode
return SLTP_MODE_PRICE
def normalize_entrust_sltp_mode(raw: Optional[str]) -> str:
mode = (raw or SLTP_MODE_PRICE).strip().lower()
if mode in ENTRUST_SLTP_MODES:
return mode
return SLTP_MODE_PRICE
def parse_fixed_rr(raw: Any, *, default: float = MANUAL_FIXED_RR_DEFAULT) -> float:
try:
v = float(raw)
if v > 0:
return v
except (TypeError, ValueError):
pass
return float(default)
def calc_tp_from_fixed_rr(
direction: str,
entry_price: float,
stop_loss: float,
rr_ratio: float,
) -> float:
entry = float(entry_price)
sl = float(stop_loss)
rr = float(rr_ratio)
if entry <= 0 or sl <= 0 or rr <= 0:
raise ValueError("固定盈亏比参数无效")
side = (direction or "long").strip().lower()
if side == "short":
risk = sl - entry
if risk <= 0:
raise ValueError("止损方向不合法:做空时止损须高于入场价")
return entry - risk * rr
risk = entry - sl
if risk <= 0:
raise ValueError("止损方向不合法:做多时止损须低于入场价")
return entry + risk * rr
def _resolve_pct_sltp(direction: str, live_price: float, data: dict[str, Any]) -> Tuple[float, float]:
sl_pct = float(data.get("sl_pct") or 0)
tp_pct = float(data.get("tp_pct") or 0)
if sl_pct <= 0 or tp_pct <= 0:
raise ValueError("百分比止盈止损须为正数")
sl_ratio = sl_pct / 100.0
tp_ratio = tp_pct / 100.0
entry = float(live_price)
if (direction or "long").strip().lower() == "short":
stop_loss = entry * (1 + sl_ratio)
take_profit = entry * (1 - tp_ratio)
else:
stop_loss = entry * (1 - sl_ratio)
take_profit = entry * (1 + tp_ratio)
return stop_loss, take_profit
def _resolve_price_sltp(
data: dict[str, Any],
*,
fallback_sl: Optional[float] = None,
fallback_tp: Optional[float] = None,
require_tp: bool = True,
) -> Tuple[float, float]:
stop_loss = float(data.get("sl") or data.get("stop_loss") or 0)
take_profit = float(data.get("tp") or data.get("take_profit") or data.get("tgt") or 0)
if stop_loss <= 0 and fallback_sl is not None:
stop_loss = float(fallback_sl)
if take_profit <= 0 and fallback_tp is not None:
take_profit = float(fallback_tp)
if stop_loss <= 0:
raise ValueError("止损价格须大于 0" if require_tp else "请填写止损价格")
if require_tp and take_profit <= 0:
raise ValueError("止盈止损价格须大于 0" if fallback_tp is None else "请填写止盈价格,或保留原计划止盈")
return stop_loss, take_profit
def resolve_open_sltp_prices(
direction: str,
live_price: float,
sltp_mode: Optional[str],
data: dict[str, Any],
) -> Tuple[float, float]:
"""新开仓 /add_order:支持 price,pct,fixed_rr."""
mode = normalize_open_sltp_mode(sltp_mode)
if mode == SLTP_MODE_PCT:
return _resolve_pct_sltp(direction, live_price, data)
if mode == SLTP_MODE_FIXED_RR:
stop_loss, _ = _resolve_price_sltp(data, require_tp=False)
rr = parse_fixed_rr(data.get("fixed_rr"))
take_profit = calc_tp_from_fixed_rr(direction, live_price, stop_loss, rr)
return stop_loss, take_profit
return _resolve_price_sltp(data, require_tp=True)
def resolve_entrust_sltp_prices(
direction: str,
live_price: float,
sltp_mode: Optional[str],
data: dict[str, Any],
*,
fallback_sl: Optional[float] = None,
fallback_tp: Optional[float] = None,
) -> Tuple[float, float]:
"""持仓委托弹窗:仅 price / pct,不校验盈亏比."""
mode = normalize_entrust_sltp_mode(sltp_mode)
if mode == SLTP_MODE_PCT:
return _resolve_pct_sltp(direction, live_price, data)
return _resolve_price_sltp(
data,
fallback_sl=fallback_sl,
fallback_tp=fallback_tp,
require_tp=True,
)
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -94
View File
@@ -1,94 +1,12 @@
"""永续估算盈亏:固定 taker 手续费(默认单边 0.05%,开+平双边).
浮盈亏仍读交易所;本模块只服务「盈利金额 / 止盈盈利 / 推送 / 记账 pnl_amount」等估算口径.
"""
from __future__ import annotations
import math
import os
from typing import Optional
def _finite(v) -> Optional[float]:
try:
f = float(v)
return f if math.isfinite(f) else None
except (TypeError, ValueError):
return None
def taker_fee_rate() -> float:
"""单边 taker 费率,默认 0.0005(=0.05%)."""
raw = os.getenv("PERP_TAKER_FEE_RATE", "0.0005")
rate = _finite(raw)
if rate is None or rate < 0:
return 0.0005
return rate
def notional_usdt(price, qty, contract_size: float = 1.0) -> Optional[float]:
"""名义价值 U = 价格 × 张数 × 合约面值."""
p = _finite(price)
q = _finite(qty)
cs = _finite(contract_size)
if p is None or q is None or p <= 0 or q <= 0:
return None
if cs is None or cs <= 0:
cs = 1.0
return abs(q) * p * cs
def estimate_roundtrip_fee_usdt(
entry_price,
exit_price,
qty=None,
contract_size: float = 1.0,
*,
open_notional: float | None = None,
rate: float | None = None,
) -> float:
"""开+平双边手续费(各单边 rate).
优先用 价×张×面值;若无张数则用 open_notional 估开仓名义,
平仓名义按 exit/entry 缩放.
"""
fee_rate = taker_fee_rate() if rate is None else float(rate)
if fee_rate <= 0:
return 0.0
entry = _finite(entry_price)
exit_p = _finite(exit_price)
open_n = notional_usdt(entry, qty, contract_size) if qty is not None else None
if open_n is None:
open_n = _finite(open_notional)
if open_n is None or open_n <= 0:
return 0.0
if entry is not None and entry > 0 and exit_p is not None and exit_p > 0:
close_n = open_n * (exit_p / entry)
else:
close_n = open_n
return round(open_n * fee_rate + close_n * fee_rate, 8)
def net_pnl_after_fee(
gross_pnl,
entry_price,
exit_price,
qty=None,
contract_size: float = 1.0,
*,
open_notional: float | None = None,
rate: float | None = None,
) -> Optional[float]:
"""毛利扣双边手续费后的净盈亏;gross 无效则返回 None."""
gross = _finite(gross_pnl)
if gross is None:
return None
fee = estimate_roundtrip_fee_usdt(
entry_price,
exit_price,
qty,
contract_size,
open_notional=open_notional,
rate=rate,
)
return round(gross - fee, 4)
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
+12 -52
View File
@@ -1,52 +1,12 @@
"""Flask 实例接入 trade policy(三所 app.py 共用)."""
from __future__ import annotations
from typing import Callable, Tuple
from lib.trade.trade_policy_lib import (
TradePolicy,
assert_direction_allowed,
assert_symbol_allowed,
assert_trade_policy_open,
trade_policy_to_dict,
)
def trade_policy_template_context(policy: TradePolicy) -> dict:
return trade_policy_to_dict(policy)
def default_symbol_for_policy(policy: TradePolicy, raw_default: str) -> str:
d = (raw_default or "BTC/USDT").strip() or "BTC/USDT"
if policy.symbol_restrict_enabled and policy.symbol_whitelist:
from lib.trade.trade_policy_lib import symbol_base_coin
base = symbol_base_coin(d)
if base not in policy.symbol_whitelist:
return f"{policy.symbol_whitelist[0]}/USDT"
return d
def check_symbol_policy(
policy: TradePolicy,
symbol: str,
normalize_symbol_fn: Callable[[str], str],
) -> Tuple[bool, str]:
return assert_symbol_allowed(
policy, symbol, normalize_symbol_fn=normalize_symbol_fn
)
def check_direction_policy(policy: TradePolicy, direction: str) -> Tuple[bool, str]:
return assert_direction_allowed(policy, direction)
def check_open_policy(
policy: TradePolicy,
symbol: str,
direction: str,
normalize_symbol_fn: Callable[[str], str],
) -> Tuple[bool, str]:
return assert_trade_policy_open(
policy, symbol, direction, normalize_symbol_fn=normalize_symbol_fn
)
# obfuscated module — do not edit
import base64, marshal, zlib
_B = 'eNpNVNtv03YYrXtJ3DQmt+ayQbSWlQJFAyexEydx0iRN3CZOwG6bOImTNqkvaRLSTIN2HaWgXcTjJLSHiUnTJDZpL3tgjwhBJ21ve0AbYtK0IQbVbmzAv7DPhVV5PvrO75zvnO/3k7ISVPyyXKeChEQG6oSKe+VNPFhzMAQuc8GuoqMQAlWMIcm/Qi7UOLXiGaZbEWNtzeOROYqvH5hGPVHJiEv0AbqzWuq2qGFfy1tTuFOLHc73dZ0ikam+5pD/Nh6kFi5yG4uTv9GtwNAWd3jx3I1It6WbRE72bf9OSxP6U3LdnUoNyhR+GRucXTa/+8mXP/zy98N/Pnr30XeuHNPPz8eThoqoj7se/Pzvj7dvJPPpFGuNpe49un/lq6vu3K+gX3nFd8fHCyZDLJ/ERIHJ1nY760MoVhKLuudeMm6vCLnIEy/pzHN5QsxXdAd3twWnczCTa+srqUHBUqoaGnweM8zwi3fGi4xDz1qaTrexXCjSiyImWvOFO2N8xtrHxLpmpsj3RdKGMqZ3NQpJnfh0PebkC0gjVsCMQq5rFlXeiOR28NERjC/nLDjo2/HHkGyKNe3gXEkfKxM9+1+B/fsV3HuUwilrhyBwM5Ei/5BaymfK1FyQjUA+c6Qz5JWpa0FbG/Crq0FSGR8ISk7ExiqcbilhWh3Px03Z5XTk/esf3H/44cPPH1957969bx5ERDZnixejJcOSaBYS6qiDZ86iVbFpMReRBcpsK1V5OlXEqrp0Z2gupmLGwqsx97OJuHOg2td8oZ+OzxuTwzyDNBYKJgufTbaJIOmogf6ziu0Y9Oc5bctbuwVkic9ilmeBGTNTDvT4U3v9nQD9LjLtR6RrysfyqFqh0vV0YIR02k/L8142ifg5HxpY9LjqlJJpuKRbVLqR9rkJkzepZOypusnPebeJ6ySiftFQZJfUDrrVrVxzw3PO7Wmf79hk4M+Ru0ccBxf1JTQ8UMrwdKGIll8j9/C52jMP6ONS0+uZQ6JuBGuwvDmeQgVLVzCU5vODyNrEjLsklocjp7FlM4LxmdjNUyJaYJOxoUreadf6E04WSkYkFipjOtsJ8H/Ia4uCP8dxPFjfIbnE27CfHVUcTCGOLvi3hCF/5zTk92RsqG8+n0JjKnPIJBrK4f4DGTVpLC315zbg/thosK5MkLRCUZHGKrUeqCm2MW2/HmsOna+ij18XDjhL+Qp2utJ66U9f+HZsDPqZ11eEaWkmZ6Nco+BPvrvuz9rLyPSAUCiesSftDLXggfsCftiPTIRIZYWGfI7WKWq2A/nElKzXFcyGh+SbAUn+NLyVldjLkB/0z6f1L7DXv8QYzDNTMhGcbUO+yRbky4YBv3VUVxT0C92MCy2IlZf72YJ5axT0B7wNUrobuwDv3zrLVROIkKsYjOVGz/+j9aMB+GGFIGfWYH+WlZSSUf6EfJk1wN1yggD9ZskZBf3MJOhn2zBv88P8LAX3/2RCz+XLhtA+v2ovoVh/3AL8SYlQcOiTGfwLOPiH+9nrZw3e98L7hwnFEz8P79uVOSKrzIXTqo76yxuQbUe0fP02GvTl6IRP839c839B839J8x+CeVtIyz+g5X9EKCLCfMtgrYy8vJ8zoH9WgnxDfk9N+p6xwPxWVmW7mv6Ipp8G/Ymo5q/xwh+t+Tuv4VP7+JiGN3vwi/v4Gxp+HHC2BfkmNsFfVu7hP/b/fA++2oOf3McvafgG4M5N2I/jMuyHWQX9zDug3zK+q4ba3LK+rT7vRtSBGbPFVlXblsDe/8a450qCzhnQ9EU0fW/u83s0flLjb2r8HY3/LY3/osa/rfGngD9jgXyYEOTDhiEfJwX9yPghv6dNFF0uWBbz+hrMz2j66iukPKluSl482pim29E28R+aw+hr'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
File diff suppressed because one or more lines are too long
+12 -61
View File
@@ -1,61 +1,12 @@
"""交易结果展示与入库时的语义归一化."""
_WIN_EPS = 1e-9
def normalize_display_result(result):
"""展示用:外部平仓一律视为手动平仓."""
res = (result or "").strip()
if res == "外部平仓" or res.startswith("外部平仓"):
return "手动平仓"
return res
def is_winning_pnl(pnl_amount) -> bool:
"""胜率统计:盈亏为正即计为盈利单."""
try:
return float(pnl_amount or 0) > _WIN_EPS
except (TypeError, ValueError):
return False
def sql_effective_pnl_expr() -> str:
"""与 to_effective_trade_dict / hub_trades_lib 一致的盈亏 SQL 表达式."""
return "COALESCE(reviewed_pnl_amount, exchange_realized_pnl, pnl_amount, 0)"
def count_winning_trades(trades) -> int:
return sum(1 for r in trades or [] if is_winning_pnl(r.get("effective_pnl_amount")))
MISS_TRADE_RESULT = "错过"
def is_miss_trade_result(result) -> bool:
return (result or "").strip() == MISS_TRADE_RESULT
def filter_trade_records_excluding_miss(records):
"""列表/统计:不展示,不计入「错过」类交易记录."""
return [
r
for r in (records or [])
if not is_miss_trade_result(r.get("effective_result") or r.get("result"))
]
def normalize_result_with_pnl(result, pnl_amount):
"""
非手动平仓且实际盈利时,不应记为「止损」.
程序触发的止损类平仓若盈亏为正,归类为「移动止盈」.
"""
res = normalize_display_result(result)
if res == "手动平仓":
return res
if res == "止损":
try:
if float(pnl_amount or 0) > 0:
return "移动止盈"
except (TypeError, ValueError):
pass
return res
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())
+12 -115
View File
@@ -1,115 +1,12 @@
"""按交易日聚合实例 trade_records 盈亏,供统计分析页日历 API 使用."""
from __future__ import annotations
import json
from datetime import datetime, timedelta
from typing import Any, Callable
def build_trade_stats_calendar(
pnls: list[tuple],
year: int,
month: int,
segment_key: str,
row_matches_fn: Callable[[Any, str], bool],
*,
reset_hour: int = 8,
) -> dict[str, Any]:
"""pnls: _load_completed_trade_pnls 返回值 (pnl, close_dt, trading_day, row)."""
y = int(year)
m = int(month)
if m < 1 or m > 12:
raise ValueError("month 无效")
first = f"{y:04d}-{m:02d}-01"
if m == 12:
next_first = datetime(y + 1, 1, 1)
else:
next_first = datetime(y, m + 1, 1)
last = (next_first - timedelta(days=1)).strftime("%Y-%m-%d")
seg = (segment_key or "all").strip() or "all"
days: dict[str, dict[str, Any]] = {}
for pnl, _close_dt, td, row in pnls:
if not td or td < first or td > last:
continue
if not row_matches_fn(row, seg):
continue
bucket = days.setdefault(
td,
{
"trading_day": td,
"open_count": 0,
"pnl_total": 0.0,
"turnover_total": 0.0,
"commission_total": 0.0,
"has_sick": False,
"sick_count": 0,
},
)
bucket["open_count"] += 1
bucket["pnl_total"] += float(pnl or 0)
try:
bucket["turnover_total"] += float(row["exchange_turnover_usdt"] or 0)
except (TypeError, ValueError, KeyError):
pass
try:
bucket["commission_total"] += float(row["exchange_commission_usdt"] or 0)
except (TypeError, ValueError, KeyError):
pass
for d in days.values():
d["pnl_total"] = round(float(d["pnl_total"]), 4)
d["turnover_total"] = round(float(d["turnover_total"]), 4)
d["commission_total"] = round(float(d["commission_total"]), 4)
month_pnl = sum(float(d["pnl_total"]) for d in days.values())
month_count = sum(int(d["open_count"]) for d in days.values())
return {
"year": y,
"month": m,
"date_from": first,
"date_to": last,
"segment": seg,
"reset_hour": int(reset_hour),
"days": days,
"month_pnl_total": round(month_pnl, 4),
"month_open_count": month_count,
}
def build_initial_stats_calendar(
pnls: list[tuple],
now_dt: datetime,
row_matches_fn: Callable[[Any, str], bool],
*,
reset_hour: int = 8,
segment_key: str = "all",
) -> dict[str, Any]:
"""统计页首屏内嵌日历(当前自然月,默认品类)."""
return build_trade_stats_calendar(
pnls,
now_dt.year,
now_dt.month,
segment_key,
row_matches_fn,
reset_hour=reset_hour,
)
def build_stats_calendar_bootstrap(
pnls: list[tuple],
now_dt: datetime,
row_matches_fn: Callable[[Any, str], bool],
*,
reset_hour: int = 8,
segment_key: str = "all",
) -> tuple[dict[str, Any] | None, str | None]:
"""返回 (payload, json_str);失败时 (None, None),供模板安全内嵌."""
try:
payload = build_initial_stats_calendar(
pnls,
now_dt,
row_matches_fn,
reset_hour=reset_hour,
segment_key=segment_key,
)
return payload, json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
except Exception:
return None, None
# obfuscated module — do not edit
import base64, marshal, zlib
_B = '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'
_K = '5eb9e6dda894c57a4e02d809ae440d49'
def _d(b, k):
raw = zlib.decompress(base64.b64decode(b))
key = k.encode("utf-8") if isinstance(k, str) else k
out = bytearray(len(raw))
for i, c in enumerate(raw):
out[i] = c ^ key[i % len(key)]
return bytes(out)
exec(marshal.loads(_d(_B, _K)), globals())