#!/usr/bin/env python3
"""
🕯️ 烛龙 Sniper — 周线底狙击 Bot
策略：RSI>20 + 前8周跌超10% + 低于20周线 → 入场做多
持有8-12周，SL=3% TP=6% → 10x杠杆暴利
"""
import os, sys, time, hmac, hashlib, base64, json
import requests, pandas as pd, numpy as np

DRY_RUN = "--dry-run" in sys.argv
LEVERAGE = 10
SL_PCT, TP_PCT = 0.03, 0.06

OKX_KEY = os.getenv("OKX_API_KEY", "")
OKX_SECRET = os.getenv("OKX_SECRET_KEY", "")
OKX_PASS = os.getenv("OKX_PASSPHRASE", "")

def okx_req(method, path, body=""):
    ts = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime())
    sig = base64.b64encode(hmac.new(OKX_SECRET.encode(), (ts+method+path+body).encode(), hashlib.sha256).digest()).decode()
    h = {"OK-ACCESS-KEY": OKX_KEY, "OK-ACCESS-SIGN": sig, "OK-ACCESS-TIMESTAMP": ts,
         "OK-ACCESS-PASSPHRASE": OKX_PASS, "Content-Type": "application/json"}
    r = requests.request(method, "https://www.okx.com"+path, headers=h, data=body, timeout=10)
    return r.json()

def get_daily(limit=500):
    """拉取日线"""
    url = f"https://www.okx.com/api/v5/market/candles?instId=BTC-USDT-SWAP&bar=1D&limit={limit}"
    r = requests.get(url, timeout=10)
    data = r.json()["data"]
    df = pd.DataFrame(data, columns=["ts","o","h","l","c","vol","_","_","_"])
    for col in ["o","h","l","c","vol"]: df[col] = df[col].astype(float)
    df["ts"] = pd.to_datetime(df["ts"].astype(int), unit="ms")
    df = df.set_index("ts").iloc[::-1].sort_index()
    return df

def check_bottom(daily):
    """检查周线底部信号"""
    # 手动构建周线
    daily["week"] = daily.index.isocalendar().week.astype(str) + "_" + daily.index.year.astype(str)
    weekly = daily.groupby("week").agg({"o": "first", "h": "max", "l": "min", "c": "last", "vol": "sum"}).reset_index(drop=True)
    C = weekly["c"].values
    n = len(C)
    if n < 30:
        return None, "数据不足"
    
    # RSI(14)
    delta = pd.Series(C).diff()
    gain = delta.clip(lower=0).rolling(14).mean()
    loss = (-delta.clip(upper=0)).rolling(14).mean()
    rsi = (100 - 100/(1+gain/(loss+1e-9))).values
    
    # 20周均线
    sma20 = pd.Series(C).rolling(20).mean().values
    
    # 前8周跌幅
    ret_8w = C / np.roll(C, 8) - 1
    
    latest = n - 1
    price = C[latest]
    rsi_val = rsi[latest]
    ma20_val = sma20[latest]
    drop_8w = ret_8w[latest]
    
    signal = (rsi_val > 20) and (rsi_val < 35) and (drop_8w < -0.1) and (price < ma20_val)
    
    info = {
        "price": price,
        "rsi": round(rsi_val, 1),
        "ma20": round(ma20_val, 1),
        "drop_8w": f"{drop_8w*100:.1f}%",
        "signal": signal,
        "time": time.strftime("%Y-%m-%d %H:%M:%S")
    }
    
    if signal:
        return info, "🔥 底部信号触发!"
    else:
        return info, "无信号，继续等待"

def get_pos():
    r = okx_req("GET", "/api/v5/account/positions?instType=SWAP")
    return [p for p in r.get("data",[]) if float(p.get("pos",0)) != 0]

def get_bal():
    r = okx_req("GET", "/api/v5/account/balance")
    for d in r.get("data",[{}])[0].get("details",[]):
        if d["ccy"] == "USDT": return float(d["availEq"])
    return 0

def order():
    """开多，10x杠杆"""
    side = "buy"
    px = float(requests.get("https://www.okx.com/api/v5/market/ticker?instId=BTC-USDT-SWAP", timeout=10).json()["data"][0]["last"])
    
    # 计算仓位：全仓，10x杠杆
    bal = get_bal()
    sz = round(bal * LEVERAGE / px, 2)
    if sz < 0.01:
        sz = 0.01
    
    sl = round(px * (1 - SL_PCT), 1)
    tp = round(px * (1 + TP_PCT), 1)
    
    body = json.dumps({"instId":"BTC-USDT-SWAP","tdMode":"cross","side":side,"ordType":"market","sz":str(sz),
        "lev":str(LEVERAGE),
        "attachAlgoOrds":[{"slTriggerPx":str(sl),"slOrdPx":"-1","tpTriggerPx":str(tp),"tpOrdPx":"-1"}]})
    return okx_req("POST","/api/v5/trade/order",body), px, sl, tp, sz

LF = "/root/quant_pipeline/sniper_log.json"

def log(d):
    t = []
    if os.path.exists(LF):
        with open(LF) as f: t=json.load(f)
    t.append(d)
    with open(LF,"w") as f: json.dump(t,f,indent=2,default=str)

# ═══ 主逻辑 ═══
print(f"\n🕯️ 烛龙 Sniper | {'DRY RUN' if DRY_RUN else 'LIVE'}", flush=True)
print(f"策略: 周线底狙击 | 杠杆{LEVERAGE}x SL={SL_PCT*100:.0f}% TP={TP_PCT*100:.0f}%", flush=True)

# 先检查是否有持仓
pos = get_pos()
if pos:
    print(f"📌 已有持仓 {pos[0].get('pos','?')}张，跳过", flush=True)
    sys.exit(0)

# 拉数据检查信号
daily = get_daily()
info, msg = check_bottom(daily)

print(f"当前 BTC: ${info['price']:.0f}  RSI={info['rsi']}  20周线=${info['ma20']:.0f}  前8周跌幅{info['drop_8w']}", flush=True)
print(msg, flush=True)

if not info["signal"]:
    sys.exit(0)

# 检查余额
bal = get_bal()
print(f"余额: {bal:.2f}U", flush=True)

if bal < 1:
    print("余额不足", flush=True)
    sys.exit(1)

print(f"🎯 底部信号确认! 准备开多 {LEVERAGE}x...", flush=True)

if DRY_RUN:
    print(f"  DRY RUN @ ${info['price']:.0f}", flush=True)
    sys.exit(0)

result, px, sl, tp, sz = order()
if result.get("code") == "0":
    print(f"  ✅ LONG @ ${px:.0f} SL=${sl:.0f} TP=${tp:.0f} 仓位{sz}张 杠杆{LEVERAGE}x", flush=True)
    log({"time":time.strftime("%Y-%m-%d %H:%M:%S"),"action":"BUY","entry":px,"sl":sl,"tp":tp,"size":sz,"leverage":LEVERAGE,"balance":bal})
else:
    print(f"  ❌ {result.get('msg','?')}", flush=True)