""" 性价比检查模块 供 okx_position_advisor.py 调用 """ import os, sys sys.path.insert(0, os.path.dirname(__file__)) from config_loader import get as cfg def get_okx_fee_rate(inst_type='SWAP'): """ 从OKX API获取实际费率 返回: (maker_rate, taker_rate) 正数表示收费,负数表示返佣 """ import requests, hmac, hashlib, base64, time, os, re # 读取凭证 creds = {} with open(os.path.expanduser("~/.bashrc")) as f: for line in f: m = re.match(r'export\s+(OKX_\w+)=(.*)', line.strip()) if m: val = m.group(2).strip().strip('"').strip("'") creds[m.group(1)] = val api_key = creds.get('OKX_API_KEY', '') secret = creds.get('OKX_SECRET', '') passphrase = creds.get('OKX_PASSPHRASE', '') proxies = {"http": "http://127.0.0.1:7890", "https": "http://127.0.0.1:7890"} ts = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()) path = f"/api/v5/account/trade-fee?instType={inst_type}" msg = f"{ts}GET{path}" sig = hmac.new(secret.encode(), msg.encode(), hashlib.sha256).digest() sig_b64 = base64.b64encode(sig).decode() headers = { "OK-ACCESS-KEY": api_key, "OK-ACCESS-SIGN": sig_b64, "OK-ACCESS-TIMESTAMP": ts, "OK-ACCESS-PASSPHRASE": passphrase, } try: r = requests.get(f"https://www.okx.com{path}", headers=headers, proxies=proxies, timeout=15) data = r.json() if data['code'] == '0' and data['data']: maker = float(data['data'][0]['maker']) taker = float(data['data'][0]['taker']) return maker, taker except Exception as e: pass # 默认费率 (fallback) return 0.0002, 0.0005 def calc_cost_performance(entry_price, sl_price, tp_price, contracts, ct_val, leverage, fee_rate=None): """ 计算开仓性价比 参数: entry_price: 入场价 sl_price: 止损价 tp_price: 止盈价 contracts: 合约张数 ct_val: 合约面值 (如ETH=0.1) leverage: 杠杆倍数 fee_rate: 单边手续费率 (默认从OKX API获取) 返回: dict: { 'rr_ratio': 盈亏比, 'tp_distance': TP距离, 'sl_distance': SL距离, 'profit_amount': 盈利金额(USDT), 'loss_amount': 亏损金额(USDT), 'fee_cost': 手续费(USDT), 'fee_pct': 手续费占盈利百分比, 'rating': 'high'/'medium'/'low', 'rating_emoji': '✅'/'⚠️'/'❌', 'rating_text': '性价比高'/'性价比一般'/'性价比低', 'auto_execute': True/False, 'reason': 原因说明 } """ # 如果没有传入费率,从OKX API获取 if fee_rate is None: maker_rate, taker_rate = get_okx_fee_rate() # 用taker费率(市价单)- 可能是负数(返佣) fee_rate = taker_rate if fee_rate is None: fee_rate = cfg('cost_performance', 'fee_rate', 0.0005) # 计算距离 tp_distance = abs(tp_price - entry_price) sl_distance = abs(sl_price - entry_price) # 防止除零 if sl_distance == 0: return { 'rr_ratio': 0, 'tp_distance': tp_distance, 'sl_distance': sl_distance, 'profit_amount': 0, 'loss_amount': 0, 'fee_cost': 0, 'fee_pct': 100, 'rating': 'low', 'rating_emoji': '❌', 'rating_text': '性价比低', 'auto_execute': False, 'reason': '止损距离为0' } # 盈亏比 rr_ratio = tp_distance / sl_distance # 盈亏金额 position_size = contracts * ct_val profit_amount = tp_distance * position_size loss_amount = sl_distance * position_size # 手续费 (开+平, 含杠杆) # 注意:fee_rate可能是负数(返佣),此时fee_cost也是负数(即赚手续费) notional_value = entry_price * position_size fee_cost = notional_value * fee_rate * 2 # 手续费基于名义价值,不乘杠杆 # 手续费占盈利百分比(返佣时为负数,表示额外收益) if profit_amount > 0: fee_pct = (fee_cost / profit_amount * 100) else: fee_pct = 100 if fee_cost >= 0 else -100 # 性价比评级 # 注意:返佣时fee_pct为负数,表示额外收益,应该提高评级 reasons = [] # 计算净盈利(盈利 + 返佣 或 盈利 - 手续费) net_profit = profit_amount + fee_cost # fee_cost为负时是返佣,为正时是收费 rr_high = cfg('cost_performance', 'rr_high', 2.0) rr_medium = cfg('cost_performance', 'rr_medium', 1.5) fee_high = cfg('cost_performance', 'fee_high_pct', 10) fee_medium = cfg('cost_performance', 'fee_medium_pct', 5) min_profit = cfg('position_sizing', 'min_profit_usdt', 10) if rr_ratio >= rr_high and net_profit >= min_profit: # 盈亏比达标 且 净盈利达标 if fee_pct < 0: # 返佣 rating = 'high' rating_emoji = '✅' rating_text = '性价比高' auto_execute = True elif fee_pct < fee_medium: # 低费率 rating = 'high' rating_emoji = '✅' rating_text = '性价比高' auto_execute = True else: # 高费率 rating = 'medium' rating_emoji = '⚠️' rating_text = '性价比一般' auto_execute = False elif rr_ratio >= rr_medium and net_profit >= min_profit: rating = 'medium' rating_emoji = '⚠️' rating_text = '性价比一般' auto_execute = False else: rating = 'low' rating_emoji = '❌' rating_text = '性价比低' auto_execute = False # 具体原因 if rr_ratio < rr_medium: reasons.append(f'盈亏比{rr_ratio:.1f}:1<1.5:1') elif rr_ratio < rr_high: reasons.append(f'盈亏比{rr_ratio:.1f}:1偏低') if fee_pct > fee_high: reasons.append(f'手续费占比{fee_pct:.0f}%过高') elif fee_pct > fee_medium: reasons.append(f'手续费占比{fee_pct:.0f}%偏高') elif fee_pct < 0: reasons.append(f'返佣{abs(fee_pct):.0f}%') if net_profit < min_profit: reasons.append(f'净盈利{net_profit:.1f}USDT<5USDT') reason = '; '.join(reasons) if reasons else ('盈亏比≥2:1, 手续费合理, 盈利达标' if rating == 'high' else '') return { 'rr_ratio': round(rr_ratio, 2), 'tp_distance': round(tp_distance, 4), 'sl_distance': round(sl_distance, 4), 'profit_amount': round(profit_amount, 2), 'loss_amount': round(loss_amount, 2), 'fee_cost': round(fee_cost, 2), 'fee_pct': round(fee_pct, 2), 'net_profit': round(net_profit, 2), # 新增:净盈利 'rating': rating, 'rating_emoji': rating_emoji, 'rating_text': rating_text, 'auto_execute': auto_execute, 'reason': reason } def calc_min_contracts_for_profit(tp_distance, ct_val, min_profit=None): if min_profit is None: min_profit = cfg('position_sizing', 'min_profit_usdt', 10) """ 计算达到最小盈利所需的合约张数 参数: tp_distance: TP距离 ct_val: 合约面值 min_profit: 最小盈利额 (默认10USDT) 返回: int: 需要的合约张数 (向上取整) """ if tp_distance <= 0 or ct_val <= 0: return 0 # 盈利 = tp_distance * ct_val * contracts # contracts = min_profit / (tp_distance * ct_val) raw_contracts = min_profit / (tp_distance * ct_val) # 向上取整到lot_sz (这里先取整,外面再处理) import math return math.ceil(raw_contracts) # 测试 if __name__ == '__main__': # 测试案例1: 性价比高 check1 = calc_cost_performance( entry_price=1700, sl_price=1666, tp_price=1775, contracts=6, ct_val=0.1, leverage=25 ) print("测试1 - ETH做多 (性价比高):") print(f" 盈亏比: {check1['rr_ratio']}:1") print(f" 盈利: {check1['profit_amount']} USDT") print(f" 手续费: {check1['fee_cost']} USDT ({check1['fee_pct']}%)") print(f" 评级: {check1['rating_text']}") print(f" 自动开仓: {check1['auto_execute']}") print() # 测试案例2: 性价比低 (盈利<5USDT) check2 = calc_cost_performance( entry_price=67.21, sl_price=70.57, tp_price=63.85, contracts=1, ct_val=0.1, leverage=10 ) print("测试2 - HYPE做空 (盈利<5USDT):") print(f" 盈亏比: {check2['rr_ratio']}:1") print(f" 盈利: {check2['profit_amount']} USDT") print(f" 手续费: {check2['fee_cost']} USDT ({check2['fee_pct']}%)") print(f" 评级: {check2['rating_text']}") print(f" 原因: {check2['reason']}") print(f" 自动开仓: {check2['auto_execute']}") print() # 测试案例3: 计算最小张数 min_contracts = calc_min_contracts_for_profit( tp_distance=3.36, ct_val=0.1, min_profit=10 ) print(f"测试3 - HYPE最小张数: {min_contracts}张 (盈利={3.36*0.1*min_contracts:.1f}USDT)")