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:
+12
-1
@@ -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())
|
||||
|
||||
+12
-845
File diff suppressed because one or more lines are too long
@@ -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
+12
-509
File diff suppressed because one or more lines are too long
+12
-320
File diff suppressed because one or more lines are too long
+12
-136
@@ -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
+12
-150
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12
-94
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
+12
-205
File diff suppressed because one or more lines are too long
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user