- OKX交易自动化 (okx-auto-position, okx-crypto, okx-exchange) - 交易信号处理 (signal-confirmation-templates, trading-signal-aggregator) - 量化因子挖掘 (quant-factor-mining) - 长桥集成 (longbridge-cli, longbridge-python-sdk) - 六合彩分析 (lottery-hk) - 股息投资 (dividend-investing, dividend-scanner) - 日内交易 (intraday-trading) - 同花顺 (tonghuashun)
281 lines
9.3 KiB
Python
281 lines
9.3 KiB
Python
"""
|
|
性价比检查模块
|
|
供 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)")
|