#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
籌碼策略實測：投信買超 × 月營收動能(本文所有回測數字的可追溯來源)
================================================================
這支腳本示範一件事:在 finlab 裡,股價、月營收、三大法人籌碼
全部用同一個 `data.get()` 取得,不必爬蟲、不必對齊欄位,
所以「把多種台股資料組成一個策略」只是幾行 rank 運算。

執行環境:conda activate finlab(finlab 2.0.x)。
基準 0050 一律用 etl:adj_close(已調除權息,含息報酬才公平)。

對照設計:
  - 對照組(baseline):天真地「投信連續 5 日買超就買」 → 實測會虧錢。
  - 優化組(strategy):投信買超強度 + 月營收 YoY + 月營收創高,
    用 rank 複合排序選 15 檔,月頻換股、對齊財報公布日(resample_offset='14D')。

下載:https://finlab.finance/blog/python-get-taiwan-stock-data/strategy.py
"""
import os
# 用你自己的 finlab token:os.environ['FINLAB_API_TOKEN'] = 'YOUR_TOKEN'
import warnings; warnings.filterwarnings("ignore")
import numpy as np, pandas as pd
from finlab import data
from finlab.backtest import sim

# ===== 一行 data.get 取得多種資料(展示資料廣度)=====
close   = data.get('price:收盤價')          # 日收盤價,2748 檔
adj     = data.get('etl:adj_close')         # 已調除權息,基準用
vol     = data.get('price:成交股數')         # 成交量,做流動性過濾
trust   = data.get('institutional_investors_trading_summary:投信買賣超股數')
foreign = data.get('institutional_investors_trading_summary:外陸資買賣超股數(不含外資自營商)')
rev     = data.get('monthly_revenue:當月營收')
rev_yoy = data.get('monthly_revenue:去年同月增減(%)')

START = '2013-01-01'   # 籌碼資料自 2012-05 起,留半年暖機

# ===== 對齊到日頻 =====
rev_d     = rev.reindex(close.index, method='ffill')
rev_yoy_d = rev_yoy.reindex(close.index, method='ffill')
liquidity = vol.average(20) > 500 * 1000      # 20 日均量 > 500 張

# ===== 基準:0050 含息 buy & hold =====
e = adj['0050'].dropna(); e = e[e.index >= START]
yrs = (e.index[-1] - e.index[0]).days / 365.25
cagr0050 = (e.iloc[-1] / e.iloc[0]) ** (1 / yrs) - 1
print(f"0050(含息)CAGR = {cagr0050:.2%}  期間 {e.index[0].date()}~{e.index[-1].date()}")

# ===== 對照組(baseline):天真地追投信連買 =====
trust_streak = (trust > 0).rolling(5).sum() >= 5          # 投信連續 5 日買超
trust_sum5   = trust.rolling(5).sum()
pos_base = trust_sum5[trust_streak & liquidity].is_largest(20)
report_base = sim(pos_base.loc[START:], resample='W', upload=False, fee_ratio=1.425/1000/3)
sb = report_base.get_stats()
print(f"[對照組] 投信連買 5 日 → CAGR={sb['cagr']:.2%}  MDD={sb['max_drawdown']:.2%}  "
      f"Sharpe={sb.get('daily_sharpe'):.2f}")

# ===== 優化組(strategy):籌碼強度 × 月營收動能 rank 複合 =====
# 投信買超強度 = 20 日投信淨買超 ÷ 20 日成交量(占比,排除大型股偏誤)
trust_intensity = (trust.rolling(20).sum() / vol.rolling(20).sum()).reindex(close.index, method='ffill')
rev_ma3  = rev.average(3)
rev_high = (rev_ma3 == rev_ma3.rolling(12).max()).reindex(close.index, method='ffill')   # 月營收 3 月均創 12 月新高

score = trust_intensity.rank(axis=1, pct=True) + rev_yoy_d.rank(axis=1, pct=True)
cond  = liquidity & rev_high & (rev_yoy_d > 0)
position = score[cond].is_largest(15)

report = sim(
    position.loc[START:],
    resample='M',                 # 月頻換股
    resample_offset='14D',        # 對齊月營收/財報公布日,避免前視偏差、捕捉 PEAD
    upload=False,
    fee_ratio=1.425/1000/3,
)
s = report.get_stats()
print(f"[優化組] 籌碼強度×營收動能 → CAGR={s['cagr']:.2%}  MDD={s['max_drawdown']:.2%}  "
      f"monthly_sharpe={s.get('monthly_sharpe'):.2f}  monthly_sortino={s.get('monthly_sortino'):.2f}  "
      f"daily_sortino={s.get('daily_sortino'):.2f}  win={s.get('win_ratio'):.2%}")

# report.to_html('report_strategy.html')   # 互動式報告
