# -*- coding: utf-8 -*-
"""投信外資買超持續三因子策略（Claude Code × finlab 教學範例）

文章：https://finlab.finance/blog/claude-code-finlab-quant-trading

兩個版本都在這支腳本裡：
  1. naive 版：近 20 日投信買賣超股數合計排名前 20，等權，每月再平衡
  2. 改進版：投信買超強度 + 買超持續性 + 外資同步 三因子排名，
     流動性過濾 + 個股 60 日均線之上，前 20 檔分數平方加權，每月再平衡

回測設定：
  - 視窗：2018-01-01 起（執行當日資料為準）
  - 交易成本：finlab sim() 台股預設（手續費 0.1425%、賣出證交稅 0.3%）
  - 流動性：近 20 日平均成交金額 > 1,000 萬

投資警語：本程式僅供教學與研究用途，過去績效不代表未來表現，不構成投資建議。
"""

import finlab
from finlab import data
from finlab.backtest import sim

# 首次取資料時 finlab 會自動引導登入
finlab.login()

START = "2018-01-01"

# 1) 載入資料
close = data.get("price:收盤價")
volume = data.get("price:成交股數")
trust_net = data.get("institutional_investors_trading_summary:投信買賣超股數")
foreign_net = data.get("institutional_investors_trading_summary:外陸資買賣超股數(不含外資自營商)")

# 法人買賣超只在有公布的交易日有值，沒公布視為 0
trust_net = trust_net.reindex(close.index).fillna(0)
foreign_net = foreign_net.reindex(close.index).fillna(0)

# 2) 流動性過濾與趨勢條件
amount20 = (close * volume).rolling(20).mean()
liquid = (amount20 > 10_000_000) & close.notna()
ma60 = close.rolling(60).mean()

# 3) 三個籌碼因子
vol60_sum = volume.rolling(60).sum()

# 投信買超強度：近 60 日投信買賣超合計佔同期成交量比（消除規模差異）
trust_intensity = trust_net.rolling(60).sum() / vol60_sum

# 投信買超持續性：近 60 日投信站在買方的天數比例
trust_persistence = (trust_net > 0).rolling(60).mean()

# 外資同步：近 60 日外資買賣超合計佔同期成交量比
foreign_intensity = foreign_net.rolling(60).sum() / vol60_sum


def rank_pct(factor):
    """流動性池內做全市場百分位排名"""
    return factor.where(liquid).rank(axis=1, pct=True)


def top_weight(score, mask, n, power=2.0):
    """取分數前 n 名，依分數的 power 次方加權（power=0 代表等權）"""
    top = score.where(mask).rank(axis=1, ascending=False) <= n
    if power == 0:
        weight = top.astype(float)
    else:
        weight = score.where(top, 0).clip(lower=0) ** power
    return weight.div(weight.sum(axis=1), axis=0).fillna(0)


# 4) naive 版：只看近 20 日投信買賣超股數排行
naive_score = rank_pct(trust_net.rolling(20).sum())
naive_position = top_weight(naive_score, liquid, 20, power=0)

# 5) 改進版：三因子排名相加，加上個股趨勢過濾，分數平方加權
score = rank_pct(trust_intensity) + rank_pct(trust_persistence) + rank_pct(foreign_intensity)
position = top_weight(score, liquid & (close > ma60), 20, power=2.0)

# 6) 回測（先跑 naive 再跑改進版，各自輸出報告）
naive_report = sim(naive_position[naive_position.index >= START], resample="M", name="naive 投信買超排行")
naive_report.display()

report = sim(position[position.index >= START], resample="M", name="投信外資買超持續三因子")
report.display()
