"""
台股當沖、漲停、零股、鉅額交易與爆量：7 個訊號單獨做、再疊在月營收動能選股上，共 14 個設計的三項驗證

三項驗證（「贏」＝日夏普高於同期 0050 含息買進持有）：
  1. 參數高原：每個設計 2 個主要參數各取 3 個值，9 組裡至少 7 組要贏。
  2. 前後兩段：回測期間對半切，兩段各自要贏。
  3. 交易成本：sim() 預設手續費 0.1425%（買賣各一次）與證交稅 0.3%（賣出）。
組合版另外要比底策略好：原參數日夏普高於同期底策略，而且 9 組至少 7 組高於底策略。

單獨版股票池：上市櫃普通股（四碼數字，含已下市，排除 ETF 與存託憑證），20 日平均成交金額至少 2,000 萬元。
訊號都用當天收盤後已知的資料，sim() 隔天收盤進場。
底策略：/strategies/monthly-revenue-momentum 原樣（站上 20／60／120 日均線、近 3 月平均營收高於近 12 月、
5 日平均漲跌幅最強 10 檔、每月 11 日換股、10% 停損），組合版只多一條濾網。
0050 含息：etl:adj_close 還原價買進持有，不經 sim、不含成本。日夏普＝日報酬平均 ÷ 標準差 × √252，不扣無風險利率。

執行：安裝 finlab 後直接跑，資料需要時會自動引導登入。
輸出：metrics.json、data.csv（14 個設計原參數、底策略與 0050 含息的每日淨值）。
"""
import datetime
import json
import math

import numpy as np
import pandas as pd
import finlab
from finlab import data
from finlab.backtest import sim
from finlab.dataframe import FinlabDataFrame

START = "2008-01-01"
BASE_START = "2015-01-01"     # 底策略原文的起點
DT_START = "2016-02-01"       # 當沖資料從這天起涵蓋全市場（之前只有約 200～420 檔可以當沖）
ODD_START = "2016-01-01"      # 盤後零股資料起點
BLOCK_START = "2015-01-01"    # 鉅額交易資料起點
MIN_TURNOVER_VALUE = 2e7


def plain(key):
    return pd.DataFrame(data.get(key))


# ---------------------------------------------------------------------------
# 1. 共同資料：股票池、0050、績效計算
# ---------------------------------------------------------------------------
adj_close = plain("etl:adj_close")
bench = adj_close["0050"].dropna()
stocks = [s for s in adj_close.columns if len(s) == 4 and s.isdigit() and not s.startswith(("0", "91"))]
days = adj_close.loc[START:].index
col_of = {s: i for i, s in enumerate(stocks)}


def on_grid(df):
    return df.reindex(index=days, columns=stocks)


turnover_value = plain("price:成交金額").reindex(columns=stocks).rolling(20).mean()
liquid = turnover_value.reindex(days) > MIN_TURNOVER_VALUE


def stats(curve):
    curve = curve.dropna()
    curve = curve / curve.iloc[0]
    ret = curve.pct_change().dropna()
    years = (curve.index[-1] - curve.index[0]).days / 365.25
    return {
        "start": str(curve.index[0].date()),
        "end": str(curve.index[-1].date()),
        "cagr": round(float(curve.iloc[-1] ** (1 / years) - 1), 6),
        "sharpe": round(float(ret.mean() / ret.std() * math.sqrt(252)), 6),
        "mdd": round(float((curve / curve.cummax() - 1).min()), 6),
        "vol": round(float(ret.std() * math.sqrt(252)), 6),
    }


def active(curve):
    """從淨值第一次變動的前一個交易日起算。"""
    curve = curve.dropna()
    moved = np.flatnonzero(curve.values != curve.values[0])
    return curve.iloc[max(moved[0] - 1, 0):] if len(moved) else curve


def versus_0050(curve):
    s = stats(active(curve))
    b = stats(bench.loc[s["start"]:s["end"]])
    return {"strategy": s, "bench": b, "win": s["sharpe"] > b["sharpe"]}


def halves(curve):
    curve = active(curve)
    mid = curve.index[0] + (curve.index[-1] - curve.index[0]) / 2
    return [versus_0050(curve.loc[lo:hi]) for lo, hi in ((curve.index[0], mid), (mid, curve.index[-1]))]


def trade_stats(report):
    trades = report.get_trades()
    curve = active(report.creturn)
    years = (curve.index[-1] - curve.index[0]).days / 365.25
    held_days = (pd.to_datetime(trades["exit_date"]).fillna(curve.index[-1]) - pd.to_datetime(trades["entry_date"])).dt.days
    holdings = (report.position.fillna(0) != 0).sum(axis=1)
    holdings = holdings.loc[curve.index[0]:]
    return {
        "trades": int(len(trades)),
        "trades_per_year": round(len(trades) / years, 1),
        "avg_hold_calendar_days": round(float(held_days.mean()), 1),
        "avg_holdings": round(float(holdings[holdings > 0].mean()), 1),
        "share_of_days_holding": round(float((holdings > 0).mean()), 4),
        "win_rate": round(float((trades["return"] > 0).mean()), 4),
        "mean_return": round(float(trades["return"].mean()), 6),
    }


def fees(cost):
    return {} if cost == "default" else {"fee_ratio": 0, "tax_ratio": 0}


def backtest(position, cost, **kw):
    position = position.reindex(index=days).fillna(False).astype(bool)
    position = position.loc[:, position.any()]
    return sim(position, upload=False, **fees(cost), **kw)


def hold(event, n):
    """事件當天收盤確認，sim 隔天收盤進場，持有 n 個交易日。"""
    return event.astype(float).rolling(n, min_periods=1).max().fillna(0) > 0


def recent(event, n):
    """近 n 個交易日（含當天）出現過事件。"""
    return hold(event, n)


# ---------------------------------------------------------------------------
# 2. 七個訊號的原始事件（不含流動性門檻；單獨版另外加流動性門檻）
# ---------------------------------------------------------------------------
close = on_grid(plain("price:收盤價"))
open_ = on_grid(plain("price:開盤價"))
high = on_grid(plain("price:最高價"))
low = on_grid(plain("price:最低價"))
volume = on_grid(plain("price:成交股數"))

# 1、2 當沖比率＝當沖成交股數 ÷ 成交股數；缺值是當天不能當沖，保留缺值
dt_ratio = on_grid(plain("intraday_trading:當日沖銷交易成交股數")) / volume.where(volume > 0)
dt_base = dt_ratio.rolling(60).mean().shift(1)


def dt_spike_event(k):
    return (dt_ratio >= k * dt_base) & (dt_base > 0)


# 3、4 漲停：收盤＝最高價，還原報酬 ≥ 9.5%（2015-06-01 前漲跌幅 7%，用 6.5%）；上市未滿 6 個交易日不算
adj = on_grid(adj_close)
adj_ret = adj.pct_change(fill_method=None)
limit_ret = pd.Series(np.where(days < pd.Timestamp("2015-06-01"), 0.065, 0.095), index=days)
limit_up = (close == high) & adj_ret.ge(limit_ret, axis=0) & adj.shift(5).notna()


def fresh_limit_up(fresh):
    """漲停收盤，而且前 fresh 個交易日沒有漲停收盤。"""
    return limit_up & ~recent(limit_up, fresh).shift(1, fill_value=False)


one_word = limit_up & (open_ == high) & (low == high)
total = one_word.cumsum()
streak = total - total.where(~one_word).ffill().fillna(0)   # 連續一字漲停天數


def one_word_open(k):
    """連續至少 k 天一字漲停之後，第一天不是一字漲停（當天有成交）。"""
    return ~one_word & (streak.shift(1) >= k) & close.notna()


# 5 盤後零股成交股數；資料期間內沒有成交的日子是 0
odd = plain("after_market_odd_lot_trade:成交股數").reindex(columns=stocks).fillna(0).reindex(days)
odd_base = odd.rolling(60).mean().shift(1)


def odd_surge(k):
    return (odd >= k * odd_base) & (odd_base > 0)


# 6 鉅額交易成交股數（逐筆＋配對、單一＋組合）；資料期間內沒有鉅額交易的日子是 0
block = plain("block_trade:成交股數").reindex(columns=stocks).fillna(0).reindex(days)
volume_base = volume.rolling(20).mean().shift(1)


def block_event(size):
    return (block > 0) & (block >= size * volume_base)


# 7 成交股數創 window 日新高（含當天）
def volume_high(window):
    return (volume >= volume.rolling(window, min_periods=window).max()) & (volume > 0)


# ---------------------------------------------------------------------------
# 3. 單獨版：每個函式接兩個參數與成本設定，回傳 sim 報告
# ---------------------------------------------------------------------------
def dt_low(window, n, cost):
    """當沖比率（window 日平均）最低的 n 檔，每月底換股。"""
    ratio = dt_ratio.rolling(window).mean().where(liquid).loc[DT_START:]
    return backtest(FinlabDataFrame(ratio).is_smallest(n), cost, resample="M")


def dt_spike(k, n, cost):
    return backtest(hold(dt_spike_event(k) & liquid, n).loc[DT_START:], cost)


def limit_chase(n, fresh, cost):
    return backtest(hold(fresh_limit_up(fresh) & liquid, n), cost)


def one_word_chase(k, n, cost):
    return backtest(hold(one_word_open(k) & liquid, n), cost)


def odd_chase(k, n, cost):
    return backtest(hold(odd_surge(k) & liquid, n).loc[ODD_START:], cost)


def block_chase(size, n, cost):
    return backtest(hold(block_event(size) & liquid, n).loc[BLOCK_START:], cost)


def volume_chase(window, n, cost):
    return backtest(hold(volume_high(window) & liquid, n), cost)


# ---------------------------------------------------------------------------
# 4. 組合版：底策略（月營收動能 10 檔）加一條濾網
# ---------------------------------------------------------------------------
mrm_close = data.get("price:收盤價")
rev = data.get("monthly_revenue:當月營收")
momentum = (mrm_close / mrm_close.shift() - 1).rolling(5).mean()
trend = (mrm_close > mrm_close.average(60)) & (mrm_close > mrm_close.average(20)) & (mrm_close > mrm_close.average(120))
base_cond = trend & (rev.average(3) > rev.average(12))


def mrm(start, cost, flag=None, keep=False):
    """flag：要排除的股票；keep=True 時改成只留 flag 裡的股票。flag 沒有資料的股票，排除時當成不排除、只留時當成不留。"""
    cond = base_cond
    if flag is not None:
        f = flag.astype(float).reindex(columns=cond.columns).reindex(cond.index, method="ffill").fillna(0) > 0
        cond = cond & (f if keep else ~f)
    position = momentum[cond].is_largest(10).loc[start:]
    return sim(position, resample="M", resample_offset="11D", stop_loss=0.1, upload=False, **fees(cost))


def dt_high_flag(window, top):
    ratio = dt_ratio.rolling(window).mean()
    return ratio.rank(axis=1, pct=True) > 1 - top


STRATEGIES = {
    "dt-low": dict(fn=dt_low, title="當沖比率最低", x=("當沖比率平均天數", [5, 20, 60]), y=("持股檔數", [10, 20, 30]), base=(20, 20)),
    "dt-spike": dict(fn=dt_spike, title="當沖比率驟升後追進", x=("驟升倍數", [1.5, 2, 3]), y=("持有天數", [5, 10, 20]), base=(2, 10)),
    "limit-chase": dict(fn=limit_chase, title="漲停隔天追進", x=("持有天數", [3, 5, 10]), y=("前幾天沒漲停", [10, 20, 60]), base=(5, 20)),
    "one-word-open": dict(fn=one_word_chase, title="一字漲停打開後追進", x=("連續一字天數", [1, 2, 3]), y=("持有天數", [3, 5, 10]), base=(2, 5)),
    "odd-lot-surge": dict(fn=odd_chase, title="盤後零股暴增後追進", x=("暴增倍數", [3, 5, 10]), y=("持有天數", [5, 10, 20]), base=(5, 10)),
    "block-trade": dict(fn=block_chase, title="鉅額交易後買進", x=("鉅額÷20 日均量", [0.25, 0.5, 1]), y=("持有天數", [10, 20, 40]), base=(0.5, 20)),
    "volume-high": dict(fn=volume_chase, title="爆量後買進", x=("創新高天數", [20, 50, 100]), y=("持有天數", [10, 20, 40]), base=(50, 20)),
}

# 組合版的濾網：flag(x, y) 回傳每天要排除（keep=True 時是只留）的股票
COMBOS = {
    "combo-dt-high": dict(flag=dt_high_flag, title="月營收動能＋排除高當沖", x=("當沖比率平均天數", [5, 20, 60]), y=("排除前幾成", [0.1, 0.2, 0.3]), base=(20, 0.2), start=DT_START),
    "combo-dt-spike": dict(flag=lambda k, n: recent(dt_spike_event(k), n), title="月營收動能＋排除當沖驟升", x=("驟升倍數", [1.5, 2, 3]), y=("排除近幾天", [5, 10, 20]), base=(2, 10), start=DT_START),
    "combo-limit": dict(flag=lambda fresh, n: recent(fresh_limit_up(fresh), n), title="月營收動能＋排除剛漲停", x=("前幾天沒漲停", [10, 20, 60]), y=("排除近幾天", [3, 5, 10]), base=(20, 5), start=BASE_START),
    "combo-one-word": dict(flag=lambda k, n: recent(one_word_open(k), n), title="月營收動能＋排除一字漲停打開", x=("連續一字天數", [1, 2, 3]), y=("排除近幾天", [5, 10, 20]), base=(2, 10), start=BASE_START),
    "combo-odd-lot": dict(flag=lambda k, n: recent(odd_surge(k), n), title="月營收動能＋排除零股暴增", x=("暴增倍數", [3, 5, 10]), y=("排除近幾天", [5, 10, 20]), base=(5, 10), start=ODD_START),
    "combo-block": dict(flag=lambda size, n: recent(block_event(size), n), title="月營收動能＋排除鉅額交易", x=("鉅額÷20 日均量", [0.25, 0.5, 1]), y=("排除近幾天", [10, 20, 40]), base=(0.5, 20), start=BLOCK_START),
    "combo-volume": dict(flag=lambda window, n: recent(volume_high(window), n), keep=True, title="月營收動能＋只留近期爆量", x=("創新高天數", [20, 50, 100]), y=("近幾天內", [10, 20, 40]), base=(50, 20), start=BASE_START),
}
for cfg in COMBOS.values():
    cfg["fn"] = lambda x, y, cost, cfg=cfg: mrm(cfg["start"], cost, cfg["flag"](x, y), cfg.get("keep", False))
STRATEGIES.update(COMBOS)

# ---------------------------------------------------------------------------
# 5. 三項驗證；組合版另外比底策略
# ---------------------------------------------------------------------------
base_reports = {start: mrm(start, "default") for start in (BASE_START, ODD_START, DT_START)}
base_curves = {start: report.creturn for start, report in base_reports.items()}


def versus_base(s, start):
    """s 是策略的 stats()：同一段期間底策略的統計，以及策略日夏普是否高於底策略。"""
    b = stats(base_curves[start].loc[s["start"]:s["end"]])
    return {"base": b, "beat_base": s["sharpe"] > b["sharpe"]}


results, curves, combo_trades = {}, {}, {}
for key, cfg in STRATEGIES.items():
    grid = []
    for xv in cfg["x"][1]:
        for yv in cfg["y"][1]:
            report = cfg["fn"](xv, yv, "default")
            cell = versus_0050(report.creturn)
            cell.update({"x": xv, "y": yv})
            if "start" in cfg:
                cell.update(versus_base(cell["strategy"], cfg["start"]))
            grid.append(cell)
            if (xv, yv) == cfg["base"]:
                base_report, base_cell = report, cell
    two_halves = halves(base_report.creturn)
    wins = sum(c["win"] for c in grid)
    results[key] = {
        "title": cfg["title"],
        "x": {"name": cfg["x"][0], "values": cfg["x"][1]},
        "y": {"name": cfg["y"][0], "values": cfg["y"][1]},
        "base": {"x": cfg["base"][0], "y": cfg["base"][1]},
        "grid": grid, "grid_wins": wins,
        "base_default_cost": base_cell, "base_zero_cost": versus_0050(cfg["fn"](*cfg["base"], "zero").creturn),
        "halves": two_halves,
        "trades": trade_stats(base_report),
        "pass": {"neighbors": wins >= 7, "halves": all(h["win"] for h in two_halves), "cost": base_cell["win"]},
    }
    if "start" in cfg:
        beats = sum(c["beat_base"] for c in grid)
        results[key]["grid_beat_base"] = beats
        results[key]["base_strategy_halves"] = [versus_base(h["strategy"], cfg["start"])["base"] for h in two_halves]
        results[key]["pass"]["beat_base"] = base_cell["beat_base"] and beats >= 7
        combo_trades[key] = base_report.get_trades()
    results[key]["pass"]["all"] = all(results[key]["pass"].values())
    curves[key] = active(base_report.creturn)
    print(key, "grid", wins, "/ 9", results[key]["pass"], round(base_cell["strategy"]["sharpe"], 2),
          "0050", round(base_cell["bench"]["sharpe"], 2), flush=True)

for start, curve in base_curves.items():
    curves[f"base-{start[:7]}"] = active(curve)

# ---------------------------------------------------------------------------
# 6. 描述統計
# ---------------------------------------------------------------------------
facts = {}
filled = adj.ffill()                                     # 停牌期間報酬算 0，復牌第一天補上整段漲跌；下市後停在最後價
filled_ret = filled.pct_change(fill_method=None)
market = filled_ret.where(liquid).mean(axis=1)           # 股票池等權平均日報酬
cum = np.vstack([np.zeros(len(stocks)), np.nan_to_num(filled_ret.sub(market, axis=0).values).cumsum(axis=0)])


def event_path(event, horizon=20):
    """事件當天收盤後才知道：從隔天收盤起算，之後 horizon 個交易日相對股票池等權的累積超額報酬。"""
    rows, cols = np.nonzero(event.fillna(False).values)
    keep = rows + 1 + horizon < len(days)
    rows, cols = rows[keep], cols[keep]
    paths = cum[rows[:, None] + 2 + np.arange(1, horizon + 1), cols[:, None]] - cum[rows + 2, cols][:, None]
    next_day = cum[rows + 2, cols] - cum[rows + 1, cols]   # 事件隔天（收盤才買得到）那一天的超額報酬
    return {"n": int(len(rows)), "cum_excess": [round(float(v), 6) for v in paths.mean(axis=0)],
            "median_end": round(float(np.median(paths[:, -1])), 6),
            "share_positive_end": round(float((paths[:, -1] > 0).mean()), 4),
            "next_day_excess": round(float(next_day.mean()), 6)}


def per_year(event):
    counts = event.fillna(False).sum(axis=1)
    return {str(y): int(v) for y, v in counts.groupby(counts.index.year).sum().items()}


events = {
    "dt-spike": (dt_spike_event(2) & liquid).loc[DT_START:],
    "limit-chase": fresh_limit_up(20) & liquid,
    "one-word-open": one_word_open(2) & liquid,
    "odd-lot-surge": (odd_surge(5) & liquid).loc[ODD_START:],
    "block-trade": (block_event(0.5) & liquid).loc[BLOCK_START:],
    "volume-high": volume_high(50) & liquid,
}
facts["event_paths"] = {k: event_path(v.reindex(days, fill_value=False)) for k, v in events.items()}
facts["events_per_year"] = {k: per_year(v) for k, v in events.items()}

# 當沖比率：全市場（股票池內）每天的中位數與當沖成交股數占比，年平均
pool_ratio = dt_ratio.where(liquid).loc[DT_START:]
facts["dt_ratio_by_year"] = {str(y): round(float(v), 4) for y, v in pool_ratio.median(axis=1).groupby(pool_ratio.index.year).mean().items()}
dt_total = plain("intraday_trading_stat:當日沖銷交易總成交股數").astype(float)     # 上市 sii、上櫃 otc 各一欄
dt_pct = plain("intraday_trading_stat:當日沖銷交易總成交股數占市場比重").astype(float)
market_dt = dt_total.sum(axis=1) / (dt_total / dt_pct).sum(axis=1)     # 上市櫃合計當沖股數 ÷ 合計成交股數（%）
facts["market_dt_share_by_year"] = {str(y): round(float(v), 2) for y, v in market_dt.groupby(market_dt.index.year).mean().items()}

# 盤後零股與盤中零股（2020-10-26 起）每年成交股數合計（股票池內外都算，億股）
for name, key in (("after_market", "after_market_odd_lot_trade:成交股數"), ("intraday", "intraday_odd_lot_trade:成交股數")):
    shares = plain(key).reindex(columns=stocks).sum(axis=1)
    facts.setdefault("odd_lot_shares_by_year_100m", {})[name] = {str(y): round(float(v) / 1e8, 2) for y, v in shares.groupby(shares.index.year).sum().items()}

# 鉅額交易的加權平均成交價與前一天收盤價的差距（普通股，所有鉅額交易日）
block_price = on_grid(plain("block_trade:加權平均成交價"))
premium = (block_price / close.shift(1) - 1).where(block > 0).stack().dropna()
facts["block_trade_premium"] = {"n": int(len(premium)), "median": round(float(premium.median()), 4),
                                "share_below_prev_close": round(float((premium < 0).mean()), 4),
                                "share_below_5pct": round(float((premium < -0.05).mean()), 4)}

# 當沖比率分五組（20 日平均、月底分組），隔天收盤起一個月相對股票池的超額報酬
month_end = days[np.append(days.to_period("M")[1:] != days.to_period("M")[:-1], True)]
month_end = month_end[month_end >= pd.Timestamp(DT_START)]
ratio20 = dt_ratio.rolling(20).mean().where(liquid)
next_day = dict(zip(days[:-1], days[1:]))
excess_month = []
for a, b in zip(month_end[:-1], month_end[1:]):
    r = filled.loc[next_day.get(b, b)] / filled.loc[next_day[a]] - 1     # 月底收盤後才知道當沖比率，從隔天收盤起算
    q = pd.qcut(ratio20.loc[a].dropna(), 5, labels=False) + 1
    r = r.reindex(q.index)
    excess_month.append((r - r.mean()).groupby(q).mean())
facts["dt_ratio_quintiles_next_month_excess"] = {str(int(q)): round(float(v), 6) for q, v in pd.concat(excess_month, axis=1).mean(axis=1).items()}


def trade_keys(trades):
    return list(zip(trades["stock_id"], trades["entry_sig_date"]))


# 組合版：底策略原參數的交易，進場訊號日有多少檔落在濾網裡；組合版換進來的交易（兩邊的回測第 5 節都跑過了）
for key, cfg in COMBOS.items():
    trades = base_reports[cfg["start"]].get_trades()
    flag = cfg["flag"](*cfg["base"])
    row = days.searchsorted(pd.to_datetime(trades["entry_sig_date"]), side="right") - 1   # 訊號日當天或之前最近的交易日
    hit = np.array([s in col_of and bool(flag.values[r, col_of[s]]) for r, s in zip(row, trades["stock_id"])])
    combo = combo_trades[key]
    new = ~pd.Series(trade_keys(combo)).isin(set(trade_keys(trades))).values
    facts.setdefault("combo_base_trades", {})[key] = {
        "trades": int(len(trades)), "flagged": int(hit.sum()),
        "flagged_mean_return": round(float(trades["return"][hit].mean()), 6) if hit.any() else None,
        "flagged_win_rate": round(float((trades["return"][hit] > 0).mean()), 4) if hit.any() else None,
        "other_mean_return": round(float(trades["return"][~hit].mean()), 6),
        "combo_new_trades": int(new.sum()),
        "combo_new_mean_return": round(float(combo["return"][new].mean()), 6) if new.any() else None,
    }

# 一字漲停打開：普通股全體（不設流動性門檻）的事件數與之後的超額報酬
facts["one_word_open_all"] = {}
for k in (1, 2, 3):
    event = one_word_open(k).loc[BASE_START:]
    facts["one_word_open_all"][str(k)] = {"per_year": per_year(event), "path": event_path(event.reindex(days, fill_value=False))}

# ---------------------------------------------------------------------------
# 7. 輸出
# ---------------------------------------------------------------------------
meta = {
    "run_date": str(datetime.date.today()),
    "finlab_version": finlab.__version__,
    "pandas_version": pd.__version__,
    "data_end": str(days[-1].date()),
    "start": START,
    "liquidity_filter_twd": MIN_TURNOVER_VALUE,
    "benchmark": "0050 含息買進持有（etl:adj_close，不含交易成本）",
    "sharpe": "日報酬平均 / 標準差 × √252，未扣無風險利率",
}
json.dump({"meta": meta, "strategies": results, "facts": facts}, open("metrics.json", "w"),
          ensure_ascii=False, indent=1, default=str)

table = pd.DataFrame(curves)
table = table / table.bfill().iloc[0]
table["0050含息"] = bench.reindex(table.index)
table["0050含息"] = table["0050含息"] / table["0050含息"].dropna().iloc[0]
table.round(6).to_csv("data.csv", index_label="date")
print("done", meta)
