Skip to content

필사 모드: AI金融 & クオンツトレーディング: FinBERT、強化学習、バックテストまで

日本語
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

1. 金融データの収集と前処理

クオンツトレーディングの出発点はデータです。OHLCV(始値・高値・安値・終値・出来高)に加え、オーダーブックのスナップショット、ティックデータ、ニュースや衛星画像といった代替データまで活用範囲が広がっています。

yfinanceで株価データをダウンロード

複数銘柄の日足OHLCVデータを取得

tickers = ["AAPL", "MSFT", "GOOGL", "NVDA"]

df = yf.download(tickers, start="2020-01-01", end="2026-01-01", auto_adjust=True)

MultiIndex → 銘柄別 DataFrame に変換

close = df["Close"]

volume = df["Volume"]

欠損値処理: 前値補完後、先頭のNaN行を削除

close = close.ffill().dropna()

print(close.tail())

ccxtで暗号資産オーダーブックを取得

exchange = ccxt.binance()

symbol = "BTC/USDT"

orderbook = exchange.fetch_order_book(symbol, limit=20)

bids = orderbook["bids"][:5] # 上位5件 [価格, 数量] の買い注文

asks = orderbook["asks"][:5] # 上位5件 [価格, 数量] の売り注文

mid_price = (bids[0][0] + asks[0][0]) / 2

spread_bps = (asks[0][0] - bids[0][0]) / mid_price * 10000

print(f"Mid: {mid_price:.2f}, Spread: {spread_bps:.2f} bps")

代替データ: ニュースヘッドラインの収集

ニュースやSNSデータは、価格データでは捉えられない**自然言語アルファ**を提供します。

from datetime import datetime, timedelta

API_KEY = "YOUR_NEWSAPI_KEY"

yesterday = (datetime.today() - timedelta(days=1)).strftime("%Y-%m-%d")

url = (

f"https://newsapi.org/v2/everything"

f"?q=NVIDIA+earnings&from={yesterday}&sortBy=publishedAt"

f"&language=en&apiKey={API_KEY}"

)

resp = requests.get(url).json()

headlines = [art["title"] for art in resp.get("articles", [])]

print(headlines[:5])

2. テクニカル分析の自動化

TA-Libとpandas-taを使えば、数百種類のテクニカル指標をPython一行で計算できます。

RSI / MACDの計算

df = yf.download("AAPL", start="2023-01-01", end="2026-01-01", auto_adjust=True)

close = df["Close"].squeeze().values.astype(float)

RSI (14日)

rsi = talib.RSI(close, timeperiod=14)

MACD

macd, signal, hist = talib.MACD(close, fastperiod=12, slowperiod=26, signalperiod=9)

pandas-ta の使い方(TA-Lib 不要)

df_ta = df["Close"].squeeze().to_frame("close")

df_ta.ta.rsi(length=14, append=True)

df_ta.ta.macd(fast=12, slow=26, signal=9, append=True)

print(df_ta.tail())

ローソク足パターン認識

open_p = df["Open"].squeeze().values.astype(float)

high_p = df["High"].squeeze().values.astype(float)

low_p = df["Low"].squeeze().values.astype(float)

close_p = df["Close"].squeeze().values.astype(float)

hammer = talib.CDLHAMMER(open_p, high_p, low_p, close_p)

engulfing = talib.CDLENGULFING(open_p, high_p, low_p, close_p)

morning_star = talib.CDLMORNINGSTAR(open_p, high_p, low_p, close_p)

100(強気)/ -100(弱気)/ 0(パターンなし)

print("ハンマー検出数:", (hammer != 0).sum())

3. MLトレーディング戦略: XGBoostアルファファクター

フィーチャーエンジニアリング

df = yf.download("SPY", start="2018-01-01", end="2026-01-01", auto_adjust=True)

df.columns = df.columns.droplevel(1) if df.columns.nlevels > 1 else df.columns

df.columns = [c.lower() for c in df.columns]

リターン特徴量

df["ret_1d"] = df["close"].pct_change(1)

df["ret_5d"] = df["close"].pct_change(5)

df["ret_20d"] = df["close"].pct_change(20)

ボラティリティ特徴量

df["vol_20d"] = df["ret_1d"].rolling(20).std()

テクニカル指標特徴量

df.ta.rsi(length=14, append=True)

df.ta.macd(fast=12, slow=26, signal=9, append=True)

df.ta.bbands(length=20, append=True)

出来高特徴量

df["vol_ratio"] = df["volume"] / df["volume"].rolling(20).mean()

ターゲット: 5日先リターンの符号 (1: 上昇, 0: 下落)

df["target"] = (df["close"].pct_change(5).shift(-5) > 0).astype(int)

df.dropna(inplace=True)

print(df.shape)

ウォークフォワード検証でXGBoostを学習

from xgboost import XGBClassifier

from sklearn.metrics import accuracy_score, roc_auc_score

warnings.filterwarnings("ignore")

feature_cols = [

"ret_1d", "ret_5d", "ret_20d", "vol_20d",

"RSI_14", "MACD_12_26_9", "MACDs_12_26_9",

"BBL_20_2.0", "BBM_20_2.0", "BBU_20_2.0",

"vol_ratio"

]

target_col = "target"

results = []

train_years = 2

test_months = 3

dates = df.index

start_year = dates[0].year + train_years

for year in range(start_year, 2026):

for q in range(1, 5):

train_end = pd.Timestamp(f"{year}-{(q-1)*3+1:02d}-01") if q > 1 else pd.Timestamp(f"{year}-01-01")

test_start = train_end

test_end = test_start + pd.DateOffset(months=test_months)

train_df = df[df.index < test_start].tail(504)

test_df = df[(df.index >= test_start) & (df.index < test_end)]

if len(train_df) < 100 or len(test_df) < 10:

continue

X_train, y_train = train_df[feature_cols], train_df[target_col]

X_test, y_test = test_df[feature_cols], test_df[target_col]

model = XGBClassifier(

n_estimators=200, max_depth=4,

learning_rate=0.05, subsample=0.8,

eval_metric="logloss", random_state=42

)

model.fit(X_train, y_train)

preds = model.predict(X_test)

proba = model.predict_proba(X_test)[:, 1]

acc = accuracy_score(y_test, preds)

auc = roc_auc_score(y_test, proba)

results.append({"period": str(test_start.date()), "acc": acc, "auc": auc})

result_df = pd.DataFrame(results)

print(result_df.tail(8))

print(f"\n平均AUC: {result_df['auc'].mean():.4f}")

4. 金融向け深層学習: LSTMとTemporal Fusion Transformer

LSTM株価予測

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()

scaled = scaler.fit_transform(df[["close"]].values)

SEQ_LEN = 60

def make_sequences(data, seq_len):

X, y = [], []

for i in range(len(data) - seq_len):

X.append(data[i:i+seq_len])

y.append(data[i+seq_len])

return np.array(X), np.array(y)

X, y = make_sequences(scaled, SEQ_LEN)

split = int(len(X) * 0.8)

X_train, X_test = X[:split], X[split:]

y_train, y_test = y[:split], y[split:]

X_train_t = torch.tensor(X_train, dtype=torch.float32)

y_train_t = torch.tensor(y_train, dtype=torch.float32)

class LSTMModel(nn.Module):

def __init__(self, input_size=1, hidden_size=64, num_layers=2):

super().__init__()

self.lstm = nn.LSTM(input_size, hidden_size, num_layers,

batch_first=True, dropout=0.2)

self.fc = nn.Linear(hidden_size, 1)

def forward(self, x):

out, _ = self.lstm(x)

return self.fc(out[:, -1, :])

model = LSTMModel()

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

criterion = nn.MSELoss()

for epoch in range(30):

model.train()

pred = model(X_train_t)

loss = criterion(pred, y_train_t)

optimizer.zero_grad()

loss.backward()

optimizer.step()

if (epoch + 1) % 10 == 0:

print(f"Epoch {epoch+1}, Loss: {loss.item():.6f}")

FinRL強化学習トレーディングエージェント

FinRLはOpenAI Gymベースの環境で株式トレーディングのRLエージェントを学習させるフレームワークです。

pip install finrl

from finrl.meta.env_stock_trading.env_stocktrading import StockTradingEnv

from finrl.agents.stablebaselines3.models import DRLAgent

FinRL形式に前処理済みの金融データ

必須カラム: date, tic, open, high, low, close, volume + テクニカル指標

processed_df = pd.read_csv("processed_stock_data.csv")

env_kwargs = {

"hmax": 100, # 最大保有株数

"initial_amount": 100000, # 初期資金(ドル)

"buy_cost_pct": 0.001, # 売買手数料 0.1%

"sell_cost_pct": 0.001,

"reward_scaling": 1e-4,

"state_space": 181,

"action_space": 30,

"tech_indicator_list": ["macd", "rsi_30", "cci_30", "dx_30"],

}

train_env = StockTradingEnv(df=processed_df, **env_kwargs)

agent = DRLAgent(env=train_env)

model_ppo = agent.get_model("ppo")

trained_ppo = agent.train_model(

model=model_ppo,

tb_log_name="ppo_stock",

total_timesteps=50000

)

5. LLM for Finance: FinBERT感情分析

FinBERTは金融ニュースや決算発表に特化したBERTの事前学習モデルで、Positive / Negative / Neutralの3クラス分類を行います。

from transformers import BertTokenizer, BertForSequenceClassification

model_name = "ProsusAI/finbert"

tokenizer = BertTokenizer.from_pretrained(model_name)

model = BertForSequenceClassification.from_pretrained(model_name)

model.eval()

def finbert_sentiment(texts):

inputs = tokenizer(texts, padding=True, truncation=True,

max_length=512, return_tensors="pt")

with torch.no_grad():

logits = model(**inputs).logits

probs = F.softmax(logits, dim=-1).numpy()

labels = ["positive", "negative", "neutral"]

return [

{"text": t, "label": labels[p.argmax()], "score": float(p.max())}

for t, p in zip(texts, probs)

]

headlines = [

"NVIDIA beats Q4 earnings estimates by 15%, raises guidance",

"Fed signals higher-for-longer rates amid sticky inflation",

"Apple reports record services revenue despite iPhone slowdown",

]

results = finbert_sentiment(headlines)

for r in results:

print(f"[{r['label'].upper():8s}] {r['score']:.3f} | {r['text']}")

決算発表の数値/ガイダンスとテキストトーンの分離分析

決算発表には(1) EPS・売上などの定量数値、(2) 将来ガイダンス数値、(3) 経営陣発言のテキストトーンという3種類の情報が含まれます。LLMはトーンの把握が得意ですが、数値が含まれる文章は誤判定することがあります。数値パース(regex/構造化NLP)とテキスト感情分析を分離してアンサンブルすると、より強い自然言語アルファを得られます。

6. リスク管理: VaR、CVaR、ケリー基準

VaR / CVaRの計算

def calculate_var_cvar(returns, confidence=0.95):

"""

ヒストリカルVaR/CVaRの計算

returns: 日次リターン配列

"""

sorted_returns = np.sort(returns)

index = int((1 - confidence) * len(sorted_returns))

var = -sorted_returns[index]

cvar = -sorted_returns[:index].mean()

return var, cvar

daily_returns = df["close"].pct_change().dropna().values

var_95, cvar_95 = calculate_var_cvar(daily_returns, 0.95)

var_99, cvar_99 = calculate_var_cvar(daily_returns, 0.99)

print(f"VaR 95%: {var_95:.4f} ({var_95*100:.2f}%)")

print(f"CVaR 95%: {cvar_95:.4f} ({cvar_95*100:.2f}%)")

print(f"VaR 99%: {var_99:.4f} ({var_99*100:.2f}%)")

print(f"CVaR 99%: {cvar_99:.4f} ({cvar_99*100:.2f}%)")

主要リスク指標の比較表

| 指標 | 計算式 | 特徴 | 限界 |

| ---------------- | -------------------- | -------------------------------- | ------------------------ |

| シャープレシオ | (Rp - Rf) / sigma_p | 標準化されたリスク調整後リターン | 上昇変動性にもペナルティ |

| ソルティノレシオ | (Rp - Rf) / sigma_d | 下方変動性のみにペナルティ | 分母の計算が直感的でない |

| 最大ドローダウン | 最高値からの最大損失 | 極端な損失を捕捉 | 回復期間を反映しない |

| VaR 95% | 下位5%損失分位 | 規制標準 | テールリスクを過小評価 |

| CVaR 95% | VaR超過損失の期待値 | テールリスクを反映 | 分布仮定に感応 |

| カルマーレシオ | CAGR / MDD | 下落対比の成長性 | 短期分析には不向き |

ケリー基準によるポジションサイジング

def kelly_fraction(win_rate, win_loss_ratio):

"""

f* = W - (1 - W) / R

W: 勝率, R: 平均損益比

"""

return win_rate - (1 - win_rate) / win_loss_ratio

例: 勝率55%、損益比1.5

f_full = kelly_fraction(0.55, 1.5)

f_half = f_full * 0.5 # fractional Kelly(変動性を低減)

print(f"フルケリー: {f_full:.2%}")

print(f"ハーフケリー: {f_half:.2%}")

> fractional Kelly(通常は0.25〜0.5倍)を使う理由: 勝率・損益比の推定誤差が大きく、フルケリーは破産確率を過小評価するため、安全マージンを設けます。

7. バックテスト: Vectorbtで戦略を検証

Vectorbtで移動平均クロスオーバーをバックテスト

price = yf.download("SPY", start="2018-01-01", end="2026-01-01",

auto_adjust=True)["Close"].squeeze()

fast_ma = vbt.MA.run(price, 20)

slow_ma = vbt.MA.run(price, 60)

entries = fast_ma.ma_crossed_above(slow_ma)

exits = fast_ma.ma_crossed_below(slow_ma)

portfolio = vbt.Portfolio.from_signals(

price,

entries,

exits,

init_cash=100_000,

fees=0.001, # 手数料 0.1%

slippage=0.001, # スリッページ 0.1%

freq="D",

)

stats = portfolio.stats()

print(stats[["Total Return [%]", "Sharpe Ratio", "Max Drawdown [%]",

"Win Rate [%]", "Profit Factor"]])

バックテストのバイアスチェックリスト

バックテストで好成績が出たときに必ず確認すべき項目:

| バイアスの種類 | 原因 | 対策 |

| ------------------------ | ---------------------------------- | ---------------------------------------------- |

| ルックアヘッドバイアス | 未来データで現在のシグナルを計算 | shift(-1)を確認し特徴量生成タイミングを見直す |

| サバイバーシップバイアス | 上場廃止銘柄がユニバースから除外 | ポイントインタイムの全銘柄データを使用 |

| 最適化バイアス | インサンプルパラメータの過学習 | ウォークフォワード検証、アウトオブサンプル保留 |

| マーケットインパクト無視 | 大口注文の価格影響を無視 | スリッページモデル、出来高制限付きサイジング |

| 取引コストの過小評価 | 実際のスプレッド・手数料を含まない | 現実的な手数料とスリッページを設定 |

クイズ

**答え**: 金融時系列には時間的依存性があり、ランダムに分割するとフォールド間で未来データが訓練に混入するルックアヘッドバイアスが発生します。

**解説**: ウォークフォワード検証は常に過去データで学習し未来データでテストするため、時系列の順序を保持します。k分割では一部のフォールドにテスト期より後の観測値が訓練データとして入り込み、モデルが「未来を知っている」ように見えて性能が水増しされます。金融時系列は自己相関やレジームシフトがあるため、時間順序を保った検証が不可欠です。

**答え**: シャープレシオは上昇・下降の変動性を同等にペナルティとして扱うため、大きな上昇リターンによる変動性の高い戦略を不当に低く評価します。

**解説**: ソルティノレシオは分母を下方標準偏差に置き換え、投資家が本当に嫌う損失方向の変動性のみにペナルティを与えます。オプション売り、モメンタム戦略など非対称なリターン分布を持つ戦略や、右裾が厚い戦略の評価にソルティノレシオがより適しています。

**答え**: シグナルの生成時点では存在しなかった未来のデータ(当日の終値、翌四半期の決算など)がフィーチャーやラベルの計算に含まれ、モデルが実際よりはるかに高い予測精度を示します。

**解説**: 具体的な原因として、当日終値で計算したRSIを同日始値のエントリーシグナルに使う、shift(-n)を適用し忘れる、ローリング統計にmin_periodsの設定ミスがある、指数移動平均が未来の情報を逆方向に漏洩させるなどが挙げられます。

**答え**: ケリー公式 f\* = W - (1 - W) / R は、期待対数リターンを最大化し、長期的な幾何平均成長率を最大にする賭け比率を導出します。

**解説**: E[log(資産)]を最大化するため、理論上は他のいかなる固定比率戦略より長期的に資産を最速で増やせます。しかし勝率・損益比の推定誤差が大きいと、過剰なレバレッジで大きなドローダウンが発生します。fractional Kelly(f\*の25〜50%)は漸近成長率を若干犠牲にしながら分散とドローダウンを大幅に削減し、実運用でより現実的です。

**答え**: 経営陣が数値は良好に発表しながら翌四半期のガイダンスを保守的に示したり、逆に悪い実績をポジティブな表現で包むケースが頻繁にあり、混合分析ではシグナルが希薄化します。

**解説**: 決算発表には(1) EPS・売上などの実績数値、(2) 将来ガイダンス数値、(3) 経営陣コメントのテキストトーンという3種類の情報が含まれます。FinBERTなどのLLMはテキストのトーンを得意としますが、「売上が8%未達」のように数値が埋め込まれた文は文脈によって誤分類することがあります。数値の抽出(regex/構造化NLP)とテキスト感情分析を分離してアンサンブルすることで、より精度の高い自然言語アルファシグナルを得られます。

현재 단락 (1/254)

クオンツトレーディングの出発点はデータです。OHLCV(始値・高値・安値・終値・出来高)に加え、オーダーブックのスナップショット、ティックデータ、ニュースや衛星画像といった代替データまで活用範囲が広が...

작성 글자: 0원문 글자: 10,926작성 단락: 0/254