77 lines
2.4 KiB
Python
77 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import matplotlib.pyplot as plt
|
|
import pandas as pd
|
|
import yfinance as yf
|
|
|
|
_DARK_BG = "#0f172a"
|
|
_AXES_BG = "#1e293b"
|
|
_GRID_COLOR = "#334155"
|
|
_TEXT_COLOR = "#cbd5e1"
|
|
_TICK_COLOR = "#94a3b8"
|
|
|
|
|
|
def apply_dark_style(fig: plt.Figure, *axes) -> None:
|
|
fig.patch.set_facecolor(_DARK_BG)
|
|
for ax in axes:
|
|
ax.set_facecolor(_AXES_BG)
|
|
ax.tick_params(colors=_TICK_COLOR)
|
|
ax.xaxis.label.set_color(_TEXT_COLOR)
|
|
ax.yaxis.label.set_color(_TEXT_COLOR)
|
|
ax.title.set_color(_TEXT_COLOR)
|
|
for s in ax.spines.values():
|
|
s.set_color(_GRID_COLOR)
|
|
ax.grid(True, color=_GRID_COLOR, alpha=0.45)
|
|
lg = ax.get_legend()
|
|
if lg is not None:
|
|
lg.get_frame().set_facecolor(_AXES_BG)
|
|
lg.get_frame().set_edgecolor(_GRID_COLOR)
|
|
for t in lg.get_texts():
|
|
t.set_color(_TEXT_COLOR)
|
|
|
|
|
|
def close_series(ticker: str, start: str, end: str) -> pd.Series:
|
|
df = yf.download(ticker, start=start, end=end, auto_adjust=True, progress=False)
|
|
if df.empty:
|
|
raise RuntimeError(f"未获取到 {ticker} 数据")
|
|
close = df["Close"]
|
|
if isinstance(close, pd.DataFrame):
|
|
close = close.iloc[:, 0]
|
|
s = close.dropna()
|
|
s.name = ticker
|
|
return s
|
|
|
|
|
|
def annual_to_daily(rate: float, periods: int = 252) -> float:
|
|
return (1.0 + rate) ** (1.0 / periods)
|
|
|
|
|
|
def rsi(series: pd.Series, period: int = 14) -> pd.Series:
|
|
d = series.diff()
|
|
up = d.clip(lower=0).ewm(alpha=1.0 / period, adjust=False).mean()
|
|
dn = (-d.clip(upper=0)).ewm(alpha=1.0 / period, adjust=False).mean()
|
|
rs = up / dn.replace(0, math.nan)
|
|
return 100.0 - (100.0 / (1.0 + rs))
|
|
|
|
|
|
def norm_cdf(x: float) -> float:
|
|
return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))
|
|
|
|
|
|
def bs_price(spot: float, strike: float, t_years: float, rate: float, sigma: float, kind: str) -> float:
|
|
if t_years <= 0:
|
|
if kind == "put":
|
|
return max(strike - spot, 0.0)
|
|
return max(spot - strike, 0.0)
|
|
sigma = max(sigma, 1e-6)
|
|
if spot <= 0 or strike <= 0:
|
|
return 0.0
|
|
d1 = (math.log(spot / strike) + (rate + 0.5 * sigma * sigma) * t_years) / (sigma * math.sqrt(t_years))
|
|
d2 = d1 - sigma * math.sqrt(t_years)
|
|
if kind == "put":
|
|
return strike * math.exp(-rate * t_years) * norm_cdf(-d2) - spot * norm_cdf(-d1)
|
|
return spot * norm_cdf(d1) - strike * math.exp(-rate * t_years) * norm_cdf(d2)
|
|
|