"""
股票技术指标分析

用法: python stock_technical.py <stock_code> [--indicator <type>] [--period <days>]
  stock_code:  6位A股代码(如 600519) 或 5位港股代码(如 03888)
  --indicator: 指标类型 ma/macd/rsi/kdj/boll/all，默认all
  --period:    历史天数，默认120

AKShare接口:
  主: ak.stock_zh_a_daily(symbol) — 新浪日线行情（稳定可用，代码需带sh/sz前缀）
  备: ak.stock_zh_a_hist(symbol) — 东方财富日线行情（当前不稳定）
  港股主: ak.stock_hk_daily(symbol) — 新浪港股日线（快速）
  港股备: ak.stock_hk_hist(symbol) — 东方财富港股日线

本地计算: MA(5/10/20/60), MACD(12,26,9), RSI(6/12/24), KDJ(9,3,3), BOLL(20,2)

注意: 新浪接口代码需带市场前缀(如 sh600519)，频繁调用会被封IP
"""

import sys
import argparse
from datetime import datetime, timedelta

sys.path.insert(0, '.')
from _utils import normalize_code, get_market_prefix, output_json, output_error, check_akshare, safe_float, is_hk_code


def calc_ma(closes, periods=[5, 10, 20, 60]):
    """计算移动平均线"""
    result = {}
    for p in periods:
        if len(closes) >= p:
            result[f"ma{p}"] = round(sum(closes[-p:]) / p, 2)
        else:
            result[f"ma{p}"] = None

    # 判断排列
    vals = [result.get(f"ma{p}") for p in periods if result.get(f"ma{p}") is not None]
    if len(vals) >= 2:
        if all(vals[i] >= vals[i+1] for i in range(len(vals)-1)):
            result["signal"] = "多头排列"
        elif all(vals[i] <= vals[i+1] for i in range(len(vals)-1)):
            result["signal"] = "空头排列"
        else:
            result["signal"] = "交叉排列"
    else:
        result["signal"] = "数据不足"
    return result


def calc_macd(closes, fast=12, slow=26, signal=9):
    """计算MACD"""
    if len(closes) < slow + signal:
        return {"dif": None, "dea": None, "macd_hist": None, "signal": "数据不足"}

    # EMA计算
    def ema(data, period):
        k = 2 / (period + 1)
        result = data[0]
        for val in data[1:]:
            result = val * k + result * (1 - k)
        return result

    # 计算DIF序列
    dif_list = []
    for i in range(slow - 1, len(closes)):
        fast_ema = ema(closes[i-fast+1:i+1] if i >= fast-1 else closes[:i+1], fast)
        slow_ema = ema(closes[i-slow+1:i+1], slow)
        dif_list.append(fast_ema - slow_ema)

    if len(dif_list) < signal:
        return {"dif": None, "dea": None, "macd_hist": None, "signal": "数据不足"}

    dea = ema(dif_list, signal)
    dif = dif_list[-1]
    hist = 2 * (dif - dea)

    # 信号判断
    prev_hist = 2 * (dif_list[-2] - ema(dif_list[:-1], signal)) if len(dif_list) >= 2 else 0
    if hist > 0 and prev_hist <= 0:
        sig = "金叉"
    elif hist < 0 and prev_hist >= 0:
        sig = "死叉"
    elif hist > 0:
        sig = "多头"
    else:
        sig = "空头"

    return {
        "dif": round(dif, 3),
        "dea": round(dea, 3),
        "macd_hist": round(hist, 3),
        "signal": sig,
    }


def calc_rsi(closes, periods=[6, 12, 24]):
    """计算RSI"""
    result = {}
    for p in periods:
        if len(closes) < p + 1:
            result[f"rsi{p}"] = None
            continue

        gains = []
        losses = []
        for i in range(-p, 0):
            diff = closes[i] - closes[i-1]
            gains.append(max(diff, 0))
            losses.append(max(-diff, 0))

        avg_gain = sum(gains) / p
        avg_loss = sum(losses) / p

        if avg_loss == 0:
            rsi = 100
        else:
            rs = avg_gain / avg_loss
            rsi = 100 - (100 / (1 + rs))

        result[f"rsi{p}"] = round(rsi, 2)

    # 信号判断
    rsi6 = result.get("rsi6")
    if rsi6 is not None:
        if rsi6 > 80:
            result["signal"] = "超买"
        elif rsi6 > 60:
            result["signal"] = "偏强"
        elif rsi6 > 40:
            result["signal"] = "中性"
        elif rsi6 > 20:
            result["signal"] = "偏弱"
        else:
            result["signal"] = "超卖"
    else:
        result["signal"] = "数据不足"

    return result


def calc_kdj(highs, lows, closes, n=9, m1=3, m2=3):
    """计算KDJ"""
    if len(closes) < n:
        return {"k": None, "d": None, "j": None, "signal": "数据不足"}

    # 取最近n天
    recent_highs = highs[-n:]
    recent_lows = lows[-n:]
    close = closes[-1]

    hn = max(recent_highs)
    ln = min(recent_lows)

    if hn == ln:
        rsv = 50
    else:
        rsv = (close - ln) / (hn - ln) * 100

    # 简化计算（使用前一天的K/D值默认50）
    k = (2 / m1) * 50 + (1 / m1) * rsv
    d = (2 / m2) * 50 + (1 / m2) * k
    j = 3 * k - 2 * d

    # 信号判断
    if j > 100:
        sig = "超买"
    elif k > d and k < 80:
        sig = "偏强"
    elif k < d and k > 20:
        sig = "偏弱"
    elif j < 0:
        sig = "超卖"
    else:
        sig = "中性"

    return {
        "k": round(k, 2),
        "d": round(d, 2),
        "j": round(j, 2),
        "signal": sig,
    }


def calc_boll(closes, n=20, k=2):
    """计算布林带"""
    if len(closes) < n:
        return {"upper": None, "middle": None, "lower": None, "signal": "数据不足"}

    recent = closes[-n:]
    middle = sum(recent) / n
    std = (sum((x - middle) ** 2 for x in recent) / n) ** 0.5
    upper = middle + k * std
    lower = middle - k * std
    current = closes[-1]

    # 位置判断
    if current > upper:
        pos = "上轨上方"
        sig = "超买"
    elif current > middle:
        pos = "中轨上方"
        sig = "中性偏强"
    elif current > lower:
        pos = "中轨下方"
        sig = "中性偏弱"
    else:
        pos = "下轨下方"
        sig = "超卖"

    return {
        "upper": round(upper, 2),
        "middle": round(middle, 2),
        "lower": round(lower, 2),
        "current": round(current, 2),
        "current_position": pos,
        "signal": sig,
    }


def main():
    parser = argparse.ArgumentParser(description='股票技术指标分析')
    parser.add_argument('stock_code', help='股票代码')
    parser.add_argument('--indicator', default='all',
                        choices=['ma', 'macd', 'rsi', 'kdj', 'boll', 'all'],
                        help='指标类型(默认all)')
    parser.add_argument('--period', type=int, default=120, help='历史天数(默认120)')
    args = parser.parse_args()

    err = check_akshare()
    if err:
        output_error(err)
        return

    code = normalize_code(args.stock_code)

    try:
        import akshare as ak

        # 计算日期范围
        end_date = datetime.now().strftime('%Y%m%d')
        start_date = (datetime.now() - timedelta(days=args.period * 2)).strftime('%Y%m%d')

        df = None
        closes = []
        highs = []
        lows = []
        latest_date = ''

        # 港股：优先分钟线合成当日K线+新浪daily，失败回退东方财富hist
        if is_hk_code(code):
            # 1. 取新浪日线（T-1，稳定快速）
            daily_df = None
            try:
                daily_df = ak.stock_hk_daily(symbol=code, adjust="qfq")
                if not daily_df.empty:
                    daily_df = daily_df.tail(args.period * 2)
            except Exception:
                daily_df = None

            # 2. 尝试分钟线合成当日K线（盘中实时）
            intraday_appended = False
            try:
                min_df = ak.stock_hk_hist_min_em(symbol=code, period='1')
                if not min_df.empty:
                    today_str = str(min_df.iloc[-1].get('时间', ''))[:10]
                    today_data = min_df[min_df['时间'].astype(str).str.startswith(today_str)]
                    if not today_data.empty and daily_df is not None:
                        last_daily_date = str(daily_df.iloc[-1].get('date', ''))
                        if today_str > last_daily_date:
                            # 当日数据比日线更新，合成当日K线追加
                            today_close = safe_float(today_data.iloc[-1].get('收盘'))
                            today_high = max(safe_float(r.get('最高')) for _, r in today_data.iterrows() if safe_float(r.get('最高')) is not None)
                            today_low = min(safe_float(r.get('最低')) for _, r in today_data.iterrows() if safe_float(r.get('最低')) is not None)
                            today_open = safe_float(today_data.iloc[0].get('开盘'))
                            if today_close is not None:
                                daily_df = daily_df._append({
                                    'date': today_str, 'open': today_open,
                                    'high': today_high, 'low': today_low,
                                    'close': today_close, 'volume': 0, 'amount': 0,
                                }, ignore_index=True)
                                intraday_appended = True
            except Exception:
                pass

            if daily_df is not None and not daily_df.empty:
                closes = [safe_float(x) for x in daily_df['close'].tolist() if safe_float(x) is not None]
                highs = [safe_float(x) for x in daily_df['high'].tolist() if safe_float(x) is not None]
                lows = [safe_float(x) for x in daily_df['low'].tolist() if safe_float(x) is not None]
                latest_date = str(daily_df.iloc[-1].get('date', ''))
                df = daily_df  # 标记成功

            # 3. 兜底：东方财富港股日线
            if df is None or df.empty:
                try:
                    df = ak.stock_hk_hist(
                        symbol=code,
                        period="daily",
                        start_date=start_date,
                        end_date=end_date,
                        adjust="qfq"
                    )
                    if not df.empty:
                        closes = [safe_float(x) for x in df['收盘'].tolist() if safe_float(x) is not None]
                        highs = [safe_float(x) for x in df['最高'].tolist() if safe_float(x) is not None]
                        lows = [safe_float(x) for x in df['最低'].tolist() if safe_float(x) is not None]
                        latest_date = str(df.iloc[-1].get('日期', ''))
                except Exception:
                    df = None

            if df is None or df.empty:
                output_error(f"未找到港股 {code} 的历史数据，港股代码为5位数字（如 03888）")
                return

        else:
            # A股：优先新浪（稳定），失败回退东方财富
            try:
                prefix = get_market_prefix(code)
                sina_code = f"{prefix}{code}"
                df = ak.stock_zh_a_daily(
                    symbol=sina_code,
                    start_date=start_date,
                    end_date=end_date,
                    adjust="qfq"
                )
                if not df.empty:
                    # 新浪列名: date/open/high/low/close/volume/amount
                    closes = [safe_float(x) for x in df['close'].tolist() if safe_float(x) is not None]
                    highs = [safe_float(x) for x in df['high'].tolist() if safe_float(x) is not None]
                    lows = [safe_float(x) for x in df['low'].tolist() if safe_float(x) is not None]
                    latest_date = str(df.iloc[-1].get('date', ''))
            except Exception:
                df = None

            # 东方财富 fallback
            if df is None or df.empty:
                try:
                    df = ak.stock_zh_a_hist(
                        symbol=code,
                        period="daily",
                        start_date=start_date,
                        end_date=end_date,
                        adjust="qfq"
                    )
                    if not df.empty:
                        closes = [safe_float(x) for x in df['收盘'].tolist() if safe_float(x) is not None]
                        highs = [safe_float(x) for x in df['最高'].tolist() if safe_float(x) is not None]
                        lows = [safe_float(x) for x in df['最低'].tolist() if safe_float(x) is not None]
                        latest_date = str(df.iloc[-1].get('日期', ''))
                except Exception:
                    df = None

            if df is None or df.empty:
                output_error(f"未找到股票 {code} 的历史数据")
                return

        indicators = {}
        indicator = args.indicator

        if indicator in ('ma', 'all'):
            indicators["ma"] = calc_ma(closes)
        if indicator in ('macd', 'all'):
            indicators["macd"] = calc_macd(closes)
        if indicator in ('rsi', 'all'):
            indicators["rsi"] = calc_rsi(closes)
        if indicator in ('kdj', 'all'):
            indicators["kdj"] = calc_kdj(highs, lows, closes)
        if indicator in ('boll', 'all'):
            indicators["boll"] = calc_boll(closes)

        data = {
            "code": code,
            "latest_date": latest_date,
            "data_points": len(closes),
            "data_time": latest_date + (" (盘中实时)" if intraday_appended else " (收盘)"),
            "indicators": indicators,
        }

        # 时效性检查
        if latest_date:
            try:
                from datetime import date as date_cls
                today = date_cls.today()
                data_date = date_cls.fromisoformat(latest_date[:10])
                days_old = (today - data_date).days
                if days_old > 0:
                    data["data_staleness"] = f"最新K线为{days_old}天前({latest_date})"
                    if not intraday_appended:
                        data["data_staleness"] += "，日线数据为T-1（最近交易日收盘）"
                    if days_old > 3:
                        data["data_staleness"] += "，数据可能过时，请确认是否为非交易日"
            except (ValueError, TypeError):
                pass

        output_json(data)

    except Exception as e:
        output_error(f"获取技术指标失败: {str(e)}")


if __name__ == '__main__':
    main()
