#!/usr/bin/env python3
"""
Leverage Margin + Liquidation + RRR + Pivot Points + Pyramiding
+ Risk-% sizing + Tick rounding + Auto-OHLC + CSV log
"""

import argparse
import datetime
import requests
import sys
import os
import csv

# ---------- Core helpers ----------
def calculate_margin(position_size, leverage):
    if leverage <= 0:
        raise ValueError("Leverage must be greater than 0.")
    return position_size / leverage

def estimate_liquidation_price(entry_price, leverage, position_type):
    if position_type == 'long':
        return entry_price * (1 - (1 / leverage))
    elif position_type == 'short':
        return entry_price * (1 + (1 / leverage))
    else:
        raise ValueError("Position type must be 'long' or 'short'.")

def calculate_risk_to_reward(entry_price, stop_loss, target_price, position_type):
    if position_type == 'long':
        risk = entry_price - stop_loss
        reward = target_price - entry_price
    elif position_type == 'short':
        risk = stop_loss - entry_price
        reward = entry_price - target_price
    else:
        raise ValueError("Position type must be 'long' or 'short'.")
    if risk <= 0:
        raise ValueError("Invalid stop-loss configuration for chosen position type.")
    return reward / risk

def pivot_points(high, low, close):
    pp = (high + low + close) / 3
    r1 = 2 * pp - low
    r2 = pp + (high - low)
    r3 = r1 + (high - low)
    s1 = 2 * pp - high
    s2 = pp - (high - low)
    s3 = s1 - (high - low)
    return {"PP": pp, "R1": r1, "R2": r2, "R3": r3,
            "S1": s1, "S2": s2, "S3": s3}

def pyramid_entries(base_size, n_layers, scale_factor=0.5):
    sizes = [base_size]
    for i in range(1, n_layers):
        sizes.append(base_size * (scale_factor ** i))
    return sizes

# ---------- Binance daily candle ----------
def fetch_binance_daily(symbol="BTCUSDT"):
    """Return (high, low, close) for yesterday."""
    yesterday = datetime.date.today() - datetime.timedelta(days=1)
    start_ms  = int(yesterday.timestamp() * 1000)
    url = (f"https://api.binance.com/api/v3/klines"
           f"?symbol={symbol}&interval=1d&startTime={start_ms}&limit=1")
    r = requests.get(url, timeout=10).json()
    if not r:
        raise RuntimeError("Binance fetch failed")
    return float(r[0][2]), float(r[0][3]), float(r[0][4])

# ---------- CSV log ----------
CSV_FILE = "trade_log.csv"
def log_to_csv(row_dict):
    file_exists = os.path.isfile(CSV_FILE)
    with open(CSV_FILE, "a", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=list(row_dict.keys()))
        if not file_exists:
            writer.writeheader()
        writer.writerow(row_dict)

# ---------- Interactive mode ----------
def interactive_mode():
    print("📊 Leverage Margin + Liquidation + RRR + Pivot Points + Pyramiding")
    print("Leave blank for defaults where offered.\n")

    # Basic
    position_type = input("Position type (long/short): ").strip().lower()
    if position_type not in ['long', 'short']:
        raise ValueError("Position type must be 'long' or 'short'.")

    # Account sizing
    account_equity = float(input("Account equity (USDT): "))
    risk_pct       = float(input("Risk per trade (%)   : ")) / 100
    leverage       = float(input("Leverage (e.g., 10): "))
    entry_price    = float(input("Entry price           : "))
    stop_loss      = float(input("Stop-loss price       : "))
    target_price   = float(input("Target price (TP)     : "))
    tick_size      = float(input("Tick size (default 0.01): ") or 0.01)

    # Validate
    for n, v in {"Account equity": account_equity,
                 "Risk %": risk_pct*100,
                 "Leverage": leverage,
                 "Entry": entry_price,
                 "Stop": stop_loss,
                 "Target": target_price}.items():
        if v <= 0:
            raise ValueError(f"{n} must be positive.")

    # Risk-% sizing
    stop_pct = abs(entry_price - stop_loss) / entry_price
    position_size = account_equity * risk_pct / stop_pct
    print(f"→ Computed position size: {position_size:.2f} USDT")

    # Core metrics
    margin_required = calculate_margin(position_size, leverage)
    liq_price = estimate_liquidation_price(entry_price, leverage, position_type)
    liq_price = round(liq_price / tick_size) * tick_size
    rrr = calculate_risk_to_reward(entry_price, stop_loss, target_price, position_type)

    # Liquidation warning
    if (position_type == 'long' and stop_loss <= liq_price) or \
       (position_type == 'short' and stop_loss >= liq_price):
        print("⚠️  Warning: Stop-loss is beyond liquidation price!")

    # Pivot points
    use_auto = input("Fetch yesterday OHLC from Binance? (y/n): ").lower() == "y"
    symbol   = "BTCUSDT" if use_auto else None
    if use_auto:
        try:
            yh, yl, yc = fetch_binance_daily(symbol)
            print(f"Auto-fetched H={yh}, L={yl}, C={yc}")
        except Exception as e:
            print("Auto-fetch failed, switching to manual:", e)
            use_auto = False
    if not use_auto:
        yh = float(input("Yesterday's High: "))
        yl = float(input("Yesterday's Low : "))
        yc = float(input("Yesterday's Close: "))
    pivots = pivot_points(yh, yl, yc)
    for k in pivots:
        pivots[k] = round(pivots[k] / tick_size) * tick_size

    # Pyramiding
    use_pyramid = input("Use pyramiding? (y/n): ").lower() == "y"
    pyramid_layers = 0
    if use_pyramid:
        pyramid_layers = int(input("Extra layers [0]: ") or 0)
    pyramid_sizes = pyramid_entries(position_size, pyramid_layers + 1)
    total_pyramid_size = sum(pyramid_sizes)
    margin_pyramid = calculate_margin(total_pyramid_size, leverage)
    max_margin = account_equity * 0.8  # 80 % safety
    if margin_pyramid > max_margin:
        print(f"⚠️  Pyramid margin {margin_pyramid:.2f} > {max_margin:.2f} (80 % equity)!")

    # Report
    print("\n🔍 Results:")
    print(f"✅ Required margin (initial): {margin_required:.2f} USDT")
    print(f"💥 Liquidation price: {liq_price:.4f}")
    print(f"🎯 RRR: {rrr:.2f}:1")
    if use_pyramid:
        print("\n📈 Pyramid add-ons:")
        for idx, sz in enumerate(pyramid_sizes, 1):
            print(f"   #{idx}: {sz:.2f} USDT")
        print(f"   Total size: {total_pyramid_size:.2f} USDT")
        print(f"   Total margin: {margin_pyramid:.2f} USDT")
    print("\n📊 Pivot levels:")
    for k, v in pivots.items():
        print(f"   {k}: {v:.4f}")

    # CSV log
    if input("Log to CSV? (y/n): ").lower() == "y":
        log_to_csv({
            "datetime": datetime.datetime.now().isoformat(timespec="seconds"),
            "side": position_type,
            "size": position_size,
            "leverage": leverage,
            "entry": entry_price,
            "sl": stop_loss,
            "tp": target_price,
            "liq": liq_price,
            "rrr": round(rrr, 2)
        })
        print("Logged ✔")

# ---------- CLI mode ----------
def cli_mode():
    p = argparse.ArgumentParser(description="Leverage tool")
    p.add_argument("--side", choices=["long", "short"], required=True)
    p.add_argument("--account", type=float, required=True)
    p.add_argument("--risk", type=float, required=True)
    p.add_argument("--lev", type=float, required=True)
    p.add_argument("--entry", type=float, required=True)
    p.add_argument("--sl", type=float, required=True)
    p.add_argument("--tp", type=float, required=True)
    p.add_argument("--tick", type=float, default=0.01)
    p.add_argument("--yh", type=float)
    p.add_argument("--yl", type=float)
    p.add_argument("--yc", type=float)
    p.add_argument("--symbol", default="BTCUSDT")
    p.add_argument("--pyramid", type=int, default=0)
    p.add_argument("--csv", action="store_true")
    args = p.parse_args()

    # sizing
    stop_pct = abs(args.entry - args.sl) / args.entry
    position_size = args.account * (args.risk/100) / stop_pct
    margin_req = calculate_margin(position_size, args.lev)
    liq_price = round(estimate_liquidation_price(args.entry, args.lev, args.side) / args.tick) * args.tick
    rrr = calculate_risk_to_reward(args.entry, args.sl, args.tp, args.side)

    # pivots
    if args.yh is None or args.yl is None or args.yc is None:
        yh, yl, yc = fetch_binance_daily(args.symbol)
    else:
        yh, yl, yc = args.yh, args.yl, args.yc
    pivots = pivot_points(yh, yl, yc)
    for k in pivots:
        pivots[k] = round(pivots[k] / args.tick) * args.tick

    # pyramid
    sizes = pyramid_entries(position_size, args.pyramid + 1)
    total_sz = sum(sizes)
    margin_total = calculate_margin(total_sz, args.lev)

    # output
    print(f"size={position_size:.2f} margin={margin_req:.2f} liq={liq_price:.4f} rrr={rrr:.2f}")
    if args.pyramid:
        print(f"pyramid_total_size={total_sz:.2f} pyramid_total_margin={margin_total:.2f}")
    if args.csv:
        log_to_csv({
            "datetime": datetime.datetime.now().isoformat(timespec="seconds"),
            "side": args.side,
            "size": position_size,
            "leverage": args.lev,
            "entry": args.entry,
            "sl": args.sl,
            "tp": args.tp,
            "liq": liq_price,
            "rrr": round(rrr, 2)
        })

# ---------- Entry ----------
if __name__ == "__main__":
    if len(sys.argv) > 1:
        cli_mode()
    else:
        try:
            interactive_mode()
        except KeyboardInterrupt:
            print("\nAborted.")