σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyロードマップ
Ctrl K
AO
+1.2k 人の学習者
ログイン参加する ↗
ロードマップ
AO
+1.2k 人の学習者
ログイン
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python ロードマップ›モジュール 10 · 高性能Pythonエンジニアリング›トピック 10.13·~50 分上級

時系列データベース

ロードマップ上の位置モジュール 10/11 · トピック 13/13
10.13 時系列データベース
—ラボスコア— / 100合計XP—連続日数—

時計付きで届くデータ

ティック、気配、リスク指標、サーバーのメトリクスは同じ形をしています。タイムスタンプ、いくつかのラベル、いくつかの測定値、そしてそれらの終わりのない流れです。時系列データベース(InfluxDB、TimescaleDB、QuestDB、kdb+)は、まさにこのために作られています。書き込みはほとんど常に最新時刻への追記なので高速で、データは時間順のチャンクに保存されるため「直近1時間」は少量にしか触れず、古い詳細データはより粗いデータに要約して最後には削除できます。InfluxDB のデータモデルは典型的です。メジャーメント(テーブルに相当)、タグ(銘柄や市場のようなインデックス付きラベル)、フィールド(測定値)、タイムスタンプで構成されます。

# One point in InfluxDB line protocol: measurement,tags fields timestamp
point = "trades,symbol=AAPL,venue=XNAS price=184.25,qty=100i 1700000000000000000"
head, fields, ts = point.split(" ")
measurement, *tag_pairs = head.split(",")
tags = dict(pair.split("=") for pair in tag_pairs)
print(measurement, tags)                             # trades {'symbol': 'AAPL', 'venue': 'XNAS'}
print(fields, int(ts))                               # price=184.25,qty=100i 1700000000000000000

ダウンサンプリング:ティックからバーへ

すべてのティックを永遠に保存するのは高価で、ほとんど必要ありません。ダウンサンプリングは点を固定の時間バケットにまとめ、各バケットの要約を残します。価格の標準的な要約が OHLCV で、始値(バケット内の最初の価格)、高値、安値、終値(最後の価格)、出来高です。時刻 ts のティックは ts - ts % bucket で始まるバケットに属します。データベースはこれを継続的に行い、結果をはるかに小さい別テーブル(連続集計)に書きます。チャートやほとんどのバックテストが読むのはこちらで、生のティックは短期間しか保持しません。

# Rolling ticks (ts, price, size) into 60-second OHLCV bars
def ohlcv_bars(ticks, bucket):
    bars = []
    for ts, price, size in ticks:
        start = ts - ts % bucket
        if bars and bars[-1]["start"] == start:
            bar = bars[-1]
            bar["high"], bar["low"] = max(bar["high"], price), min(bar["low"], price)
            bar["close"] = price
            bar["volume"] += size
        else:
            bars.append({"start": start, "open": price, "high": price, "low": price, "close": price, "volume": size})
    return bars

ticks = [(0, 100.0, 10), (30, 101.0, 5), (61, 99.5, 20), (65, 100.5, 1)]
for bar in ohlcv_bars(ticks, 60):
    print(bar)
# {'start': 0, 'open': 100.0, 'high': 101.0, 'low': 100.0, 'close': 101.0, 'volume': 15}
# {'start': 60, 'open': 99.5, 'high': 100.5, 'low': 99.5, 'close': 100.5, 'volume': 21}

as-of 結合と保持期間

時系列が同じ瞬間に更新されることはまれなので、等しいタイムスタンプで結合しても、ほとんど何も見つかりません。実際に知りたいのは「この約定が起きたとき、気配はいくらだったか」で、約定ごとにその時刻以前で最も新しい気配を取ります。この as-of 結合(pandas.merge_asof、kdb+ や DuckDB の ASOF JOIN)は過去方向だけを見なければなりません。約定より後の気配を使うと、未来が分析に漏れ込みます。気配は時刻順に並んでいるので、二分探索(bisect_right)で対数時間で正しいものが見つかります。最後に保持ポリシーは、一定より古い点を捨てて保存量に上限を設けます。たとえばティックは30日、分足は1年、日足は永久に保持します。

# For each trade, the latest quote at or before it, found by binary search
from bisect import bisect_right

def asof_join(trades, quotes):
    times = [quote[0] for quote in quotes]
    joined = []
    for ts, price in trades:
        position = bisect_right(times, ts)
        joined.append((ts, price, *quotes[position - 1][1:]) if position else (ts, price, None, None))
    return joined

quotes = [(10, 99.9, 100.1), (40, 100.5, 100.7)]
print(asof_join([(5, 100.0), (10, 100.0), (50, 101.0)], quotes))
# [(5, 100.0, None, None), (10, 100.0, 99.9, 100.1), (50, 101.0, 100.5, 100.7)]
# Retention: keep only the points younger than the limit
def apply_retention(points, now, ttl):
    return [(ts, value) for ts, value in points if ts > now - ttl]

points = [(10, 1.0), (40, 2.0), (50, 3.0), (90, 4.0)]
print(apply_retention(points, 100, 60))              # [(50, 3.0), (90, 4.0)]   the point at 40 is exactly 60 s old: dropped
print(apply_retention(points, 100, 1000))            # everything is younger than the limit
タグのカーディナリティに注意するタグはインデックスされるため、タグ値の異なる組み合わせごとに別の系列ができます。銘柄や市場なら問題ありません(数百の値)。しかし注文 ID やタイムスタンプをタグに入れると何百万もの系列ができ、データベースが立ち行かなくなることがあります。点ごとに変わる識別子はタグではなくフィールドに入れます。また、タイムスタンプはシステム全体で UTC かつ単一の精度(秒、ミリ秒、ナノ秒のいずれか)で保存してください。混ぜると、結合が何にも一致しないまま黙って失敗します。
  • 時系列のポイントはメジャーメント、インデックス付きタグ、測定フィールド、タイムスタンプでできている。書き込みは追記で、データは時間順に保存される
  • ティックを OHLCV バー(ts - ts % bucket)にダウンサンプリングし、バーを読む。生のティックは短期間だけ保持する
  • 過去方向だけを見る as-of 結合(二分探索)で系列を結び、保持ポリシーを適用し、タグのカーディナリティと時間の単位を管理する

次は Python ライブラリのパッケージングです。あなたのコードを他の人がインストールできる形にします。

ラボ:採点される内容

  • ohlcv_bars でティックをバーにまとめる25点
  • asof_join で各約定時点の気配を求める25点
  • line_protocol でポイントを書き出す25点
  • apply_retention で期限切れのデータを捨てる25点
エクスプローラー · lab-129-1
▾ lab-129-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-129-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-129-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける