Quantopian 日本語翻訳(Draft)

警告

このドキュメントは下書きです

重要

免責事項(Disclaimer)

東京 Quantopian ユーザーグループは、掲載している情報の正確性について万全を期しておりますが、その内容について保証するものではありません。 当該和訳は、英文を翻訳したものですので、和訳はあくまでも便宜的なものとして利用し、適宜、英文の原文を参照していただくようお願いします。 (英文の原文:https://www.quantopian.com/docs/index

東京 Quantopian ユーザーグループは、間違い、情報の欠落、あるいは、掲載されている情報の使用に起因して生じる結果に対して一切の責任を負わないものとします。掲載されている全ての情報は、その時点の情報が掲載されており、完全性、正確性、時間の経過、あるいは、情報の使用に起因して生じる結果について一切の責任を負わないものとします。また、あらゆる種類の保証、それが明示されているか示唆されているかにかかわらず、また業務遂行、商品性、あるいは特定の目的への適合性への保証、また、これらに限定されない保証も含め、いかなることも保証するものではありません。

Getting Started

重要

2020/06/07 更新。 本翻訳は、2020年5月以前に公開されていた 旧Tutorial1 Getting Started です。翻訳作業中に原作が大きく更新され、この翻訳は原作側には存在しません。しかしながら、記載されている内容やスクリプトは現在も使用可能ですので、日本語翻訳サイトには引き続き掲載します。

Quantopianへようこそ。この入門チュートリアルでは、Quantopianでのクオンツトレーディング戦略の研究と開発について説明します。 このチュートリアルでは、Quantopian APIの基本的な機能を多く取り上げており、Quantopianを初めて利用する方を対象にしています。 チュートリアルを始めるために必要なのは、基本的な Python のプログラミングスキルだけです。

取引アルゴリズムとは?

取引アルゴリズムとは、コンピューターでクオンツトレーディング戦略を実現し、取引対象の資産を売買するためのルールを実装したプログラムのことです。 一般に、取引アルゴリズムは、過去のデータについて数学的、統計的に分析して構築したモデルに基づいて作成され、取引の意思決定を行います。

何から始めればいいですか?

取引アルゴリズムを作成するためには、まず、戦略のベースとなる経済的または統計的な関係を見つけなくてはなりません。QuantopianのResearch環境では、そのために必要な過去のデータセットが提供されており、それを利用して分析を行うことができます。

Research は Jupyter Notebook 環境で提供されており、Pythonのコードを 'セル' と呼ばれる場所で実行することができます。

例えば、以下のコードは、Apple Inc. (AAPL)の毎日の終値と20日と50日移動平均線をプロットしています。

# Research 環境用関数
from quantopian.research import prices, symbols

# Pandas library: https://pandas.pydata.org/
import pandas as pd

# AAPL の過去の価格データを取得する
aapl_close = prices(
    assets=symbols('AAPL'),
    start='2013-01-01',
    end='2016-01-01',
)

# AAPL の価格データより20日と50日の移動平均を算出する
aapl_sma20 = aapl_close.rolling(20).mean()
aapl_sma50 = aapl_close.rolling(50).mean()

# 結果を結合して pandas の DataFrameに入れ、描画する
pd.DataFrame({
    'AAPL': aapl_close,
    'SMA20': aapl_sma20,
    'SMA50': aapl_sma50
}).plot(
    title='AAPL Close Price / SMA Crossover'
);

上記のコードは、Research環境で新しいノートブックを開いて、セルにコピーアンドペーストすることで利用できます。そして、このコードのあるセルを選択してShift+Enterキーを押せばセルを実行でき、次のような出力がなされます。

_images/notebook_5_0.png

では早速、Research環境を使ってQuantopianのデータセットを触ってみましょう。次のレッスンでは、取引戦略を定義し、過去のデータに基づいてリターンを効果的に予測できるかどうかを検証してみます。さらに、その結果をもとに、インタラクティブ開発環境(IDE)で取引アルゴリズムを開発し、テストを行ってみましょう。

レッスン2から4は、Research環境で行います。

Research環境を利用するときには、新しいノートブックを作成 して下さい。

データを探す

Researchは、価格、出来高、およびリターンを照会するためのユーティリティ関数を提供します。 データは、2002年から今日現在までの8000株以上の米国株式データです。 関数は、資産(または資産のリスト)、開始日、終了日を引数として受け取り、日付をindexに持つ、pandasの Series (もしくは DataFrame)を返します。

ここで、期間を指定して、AAPLのリターンを returns 関数を使って照会してみましょう。

# Research環境用関数
from quantopian.research import returns, symbols

# 時間範囲を指定
period_start = '2014-01-01'
period_end = '2014-12-31'

# 上記の時間範囲で、AAPLのリターンデータを照会する
aapl_returns = returns(
    assets=symbols('AAPL'),
    start=period_start,
    end=period_end,
)

# 最初の10行のみ表示
aapl_returns.head(10)
2014-01-02 00:00:00+00:00   -0.014137
2014-01-03 00:00:00+00:00   -0.022027
2014-01-06 00:00:00+00:00    0.005376
2014-01-07 00:00:00+00:00   -0.007200
2014-01-08 00:00:00+00:00    0.006406
2014-01-09 00:00:00+00:00   -0.012861
2014-01-10 00:00:00+00:00   -0.006674
2014-01-13 00:00:00+00:00    0.005043
2014-01-14 00:00:00+00:00    0.020123
2014-01-15 00:00:00+00:00    0.020079
Freq: C, Name: Equity(24 [AAPL]), dtype: float64

さまざまなデータ

Quantopianには、価格や出来高のデータだけでなく、企業のファンダメンタルズやセンチメント分析、マクロ経済指標など様々なデータセットが用意されています。データセットは、全部で50以上用意されており、詳細については、Quantopianの Data Reference で確認できます。

このチュートリアルでは、センチメントデータを使って株式を選び、取引を評価するところまで扱います。 今回は、センチメントデータとして、PsychSignalの StockTwits Trader Mood データセットを使います。このPsychSignalのデータセットは、Stocktwitsという株式専門SNSに投稿されたメッセージの総体的なセンチメントに基づいて、日次で銘柄ごとにブル(強気)とベア(弱気)のスコアを割り当てたものです。

ではまず、stocktwits データセットのメッセージの総量とセンチメントスコア(ブルからベアを引いたもの)の列を見ていきましょう。 データを照会するには、QuantopianのPipeline APIを使います。 Pipeline APIは、今後Research環境でデータを照会したり分析したりする時に何度も使うことになります。 詳しくは次のレッスンや、Pipeline専用のチュートリアル で学ぶことができます。 今のところ知っておく必要があるのは、以下のコードがdata pipelineを使用して stocktwits を照会してデータを返し、AAPLの結果をプロットしているということだけです。

# Pipeline imports
from quantopian.research import run_pipeline
from quantopian.pipeline import Pipeline
from quantopian.pipeline.factors import Returns
from quantopian.pipeline.data.psychsignal import stocktwits

# Pipeline 定義
def make_pipeline():

    returns = Returns(window_length=2)
    sentiment = stocktwits.bull_minus_bear.latest
    msg_volume = stocktwits.total_scanned_messages.latest

    return Pipeline(
        columns={
            'daily_returns': returns,
            'sentiment': sentiment,
            'msg_volume': msg_volume,
        },
    )

# Pipeline 実行
data_output = run_pipeline(
    make_pipeline(),
    start_date=period_start,
    end_date=period_end
)

# AAPLのデータだけを取得
aapl_output = data_output.xs(
    symbols('AAPL'),
    level=1
)

# 描画
aapl_output.plot(subplots=True);
_images/notebook_5_1.png

データセットの中味を検討するときには、データセットと株価の動きを見比べて、何かパターンがないか探して見てください。そうして見つけたパターンが、取引ストラテジーの基礎になるかもしれません。 上記の例では、株価の日々のリターン(収益)のスパイク(急激な変化)と stocktwits のメッセージ総量のスパイクが、いくつか同じタイミングで起きていることが見て取れますし、リターンのスパイクとAAPLのセンチメントスコアの方向がマッチしている様子もいくつか確認できます。 こうして見ると、充分に使えそうな面白いデータセットのようですので、さらにしっかりと統計的に分析して、うまく行くかどうか検証してみましょう。

次のレッスンでは、Pipeline APIについて詳しく説明します。

Pipeline API

警告

このTutorialで使用している PsychSignal Trader Mood データは2020年5月で更新終了しました。ご注意下さい。データに関する詳しい情報は PsychSignal Trader Mood (DEPRECATED) を参照して下さい。

Pipeline APIは、横断的に資産データ分析を行うための強力なツールです。これにより、複数のデータに対して一連の演算を行い、一度に大量の資産を分析することができます。 一般的なPipeline API の用途として、以下のようなものがあります。

  • フィルタリングルールに基づいた資産の選択

  • スコアリング関数に基づく資産のランク付け

  • ポートフォリオの配分の計算

まず、Pipelineクラスをインポートして、空のpipelineを返す関数を作成します。 関数のなかでpipelineを使う形にすれば、複雑な処理をすっきりと扱うことができます。そうしておけば、pipelineを使った仕組みをResearch環境からIDEに移す時にも、関数ごとまとめて扱えてるので便利です。

# Pipeline class
from quantopian.pipeline import Pipeline

def make_pipeline():
    # 空の Pipeline を作成し返す。
    return Pipeline()

pipelineからデータ出力を取り出すためには、まず、データセットに収録されているデータ項目と、そのデータに対して行いたい演算を指定します。具体的には、日々の終値を取り出す場合、 USEquityPricing データセットにある close (終値) を用いて、下記のように記述します。

# Pipeline class と USEquityPricing dataset を import
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import USEquityPricing

def make_pipeline():
    # 日々の最終価格を取得
    close_price = USEquityPricing.close.latest

    # 上記のデータを Pipeline に入れて返す
    return Pipeline(
        columns={
            'close_price': close_price,
        }
    )

Pipeline APIには、計算機能が予め多数用意されており、移動平均など、データのなかで一定期間を切り出して処理をする演算機能なども利用できます。例えば、Psychsignalの stocktwits データセットで提供されている bull_minus_bear データについて、3日移動平均を計算して出力するコードは以下のように定義できます。

# Pipeline と データセットをインポート
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import USEquityPricing
from quantopian.pipeline.data.psychsignal import stocktwits

# 移動平均を計算する関数をインポート
from quantopian.pipeline.factors import SimpleMovingAverage


def make_pipeline():
    # 日々の最終価格を取得
    close_price = USEquityPricing.close.latest

    # bull_minus_bearスコアの3日移動平均を演算
    sentiment_score = SimpleMovingAverage(
        inputs=[stocktwits.bull_minus_bear],
        window_length=3,
    )

    # pipelineに、最終価格と、センチメントスコアを入れて、返す
    return Pipeline(
        columns={
            'close_price': close_price,
            'sentiment_score': sentiment_score,
        }
    )

評価対象となる資産を選ぶ

戦略を開発するうえで、どのような取引対象資産を選ぶかは重要です。 つまり、取引対象として考えられる銘柄からなる資産セットを予め用意し、そのなかから銘柄を選んで取引することを考えます。この資産セットのことを、トレーディング・ユニバース(trading universe)と呼びます。

トレーディング・ユニバースは、できるだけ多くの資産が含まれているのが望ましいですが、一方で、不必要な資産については排除しておく必要もあります。例えば、流動性の低い銘柄や、取引困難な銘柄などは外しておきたいところです。そこで便利なのが、予めそのようなことを考慮して用意されている QTradableStocksUS ユニバースです。 早速、pipelineのスクリーニングパラメータを使って、 QTradableStocksUS を私達のトレーディング・ユニバースとして設定しましょう。

# Pipeline と データセットをインポート
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import USEquityPricing
from quantopian.pipeline.data.psychsignal import stocktwits

# 移動平均を計算する関数をインポート
from quantopian.pipeline.factors import SimpleMovingAverage

# 組み込みトレーディング・ユニバースをインポート
from quantopian.pipeline.filters import QTradableStocksUS


def make_pipeline():
    # トレーディング・ユニバースへの参照を作成
    base_universe = QTradableStocksUS()

    # 日々の最終価格を取得
    close_price = USEquityPricing.close.latest

    # bull_minus_bearスコアの3日移動平均を演算
    sentiment_score = SimpleMovingAverage(
        inputs=[stocktwits.bull_minus_bear],
        window_length=3,
    )

    # pipelineに、最終価格と、センチメントスコア、スクリーニングとして、トレーディング・ユニバースを入れて返す
    return Pipeline(
        columns={
            'close_price': close_price,
            'sentiment_score': sentiment_score,
        },
        screen=base_universe
    )

これでpipelineの定義は完了しました。次に、 run_pipeline を使い、期間を指定してpipelineを実行してみましょう。結果はpandasのDataFrameで出力され、そのインデックスが日付と資産名、列は pipelineで定義したカラムとなります。

# run_pipelineをインポート
from quantopian.research import run_pipeline

# start_date と end_dateを指定してmmake_pipeline関数を実行して pipeline を実行。
pipeline_output = run_pipeline(
    make_pipeline(),
    start_date='2013-01-01',
    end_date='2013-12-31'
)

# 最初の10行を表示
pipeline_output.tail(10)
close_price sentiment_score
2013-12-31 00:00:00+00:00 Equity(43721 [SCTY]) 57.32 -0.176667
Equity(43919 [LMCA]) 146.22 0.000000
Equity(43981 [NCLH]) 35.25 -0.700000
Equity(44053 [TPH]) 19.33 0.333333
Equity(44060 [ZTS]) 32.68 0.000000
Equity(44089 [BCC]) 29.66 1.000000
Equity(44102 [XONE]) 60.50 0.396667
Equity(44158 [XOOM]) 27.31 -0.160000
Equity(44249 [APAM]) 64.53 0.000000
Equity(44270 [SSNI]) 21.05 0.423333

次のレッスンでは、アルゴリズムが取引銘柄を選ぶ戦略を構築し、ファクター分析ツールを使って、過去のデータに対する戦略の予測力の評価をします。

戦略定義

Quantopianのデータへのアクセスと操作の方法を学んだところで、株式のロングショート戦略を行うデータ取得の仕組みをpipelineで構築してみましょう。 株式のロングショート戦略とは、一般に、対象銘柄の価格の動き方を比較し、今後最も上昇すると思われる銘柄(ロング ) と、下落すると思われる銘柄(ショート )を組み合わせて利益を狙う戦略のことを言います。

株式のロングショート戦略では、値上がりした銘柄の価格変化と値下がりした銘柄の価格変化の差(スプレッド)が利益になります。つまり、この戦略は、株式の将来的な値動きの大きさについて、うまく順位付けができるということを前提にしています。このチュートリアルでは、簡単な順位付け方法を使ってみます。

今回の戦略: センチメントスコアの3日移動平均を取得し、銘柄別のセンチメントの状態の高さ、あるいは低さが、今後の値動きの高低に影響するものとして売買の判断をします。

戦略分析

上記の戦略は、 SimpleMovingAverage 関数と、 stocktwitsbull_minus_bear データを使って定義出来ます。 先述したpipelineのレッスンと同じように書くことができます。

# Pipeline インポート
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.psychsignal import stocktwits
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS


# Pipeline 定義
def  make_pipeline():

    base_universe = QTradableStocksUS()

    sentiment_score = SimpleMovingAverage(
        inputs=[stocktwits.bull_minus_bear],
        window_length=3,
    )

    return Pipeline(
        columns={
            'sentiment_score': sentiment_score,
        },
        screen=base_universe
    )

まずここでは、説明を簡単にするために、 sentiment_score を使って順位した各銘柄のなかで、上位および下位のそれぞれ350銘柄だけについて分析します。 具体的には、pipelineフィルターというものを利用し、 sentiment_score の出力を、 topbottom メソッドを使って上位と下位だけ取得するフィルターを作ります。そして、その結果を | オペレータ でつないで和集合を作れば、上位と下位の銘柄を集めたものをまとめられます。次に、そのなかから、私達のトレーディングユニバースにない銘柄を除くために、フィルターとユニバースを & オペレータでつなぎます。

# Pipeline インポート
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.psychsignal import stocktwits
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS

# Pipeline 定義
def  make_pipeline():

    base_universe = QTradableStocksUS()

    sentiment_score = SimpleMovingAverage(
        inputs=[stocktwits.bull_minus_bear],
        window_length=3,
    )

    # センチメントスコアに基づいて上位下位350銘柄のみを取得するフィルターを作成
    top_bottom_scores = (
        sentiment_score.top(350) | sentiment_score.bottom(350)
    )

    return Pipeline(
        columns={
            'sentiment_score': sentiment_score,
        },
        # 定義したフィルターとトレーディングユニバースのどちらにも入っている銘柄のみにスクリーニングする
        screen=(
            base_universe
            & top_bottom_scores
        )
    )

それでは、3年間のpipelineを実行して、このあとの分析で使う情報を取り出してみましょう。これには1分ほどかかります。

# run_pipeline インポート
from quantopian.research import run_pipeline

# 評価する期間を指定
period_start = '2013-01-01'
period_end = '2016-01-01'

# 指定期間で pipeline 実行
pipeline_output = run_pipeline(
    make_pipeline(),
    start_date=period_start,
    end_date=period_end
)

この先の分析には、各銘柄のセンチメントデータに加えて、同じ期間の価格のデータも必要です。pipelineが出力するDataFrameのindexは銘柄のリストになっていますので、そのリストを prices に渡せば価格データを得ることが出来ます。

# prices 関数をインポート
from quantopian.research import prices

# pipeline が出力した dataframe の index から銘柄リストを取得し、 unique 関数を使って、重複しないリストを取得します。
asset_list = pipeline_output.index.levels[1].unique()

# 銘柄リストに入っている銘柄全てに対して、指定期間の価格を取得します。
asset_prices = prices(
    asset_list,
    start=period_start,
    end=period_end
)

次に、Quantopianが作ったオープンソースの分析ツールである、 Alphalens を使って、私達の戦略の品質を検証してみましょう。 まず、 get_clean_factor_and_forward_returns 関数を使って、ファクターデータと価格データを組み合わせます。この関数は、ファクターデータを順位付けて分類し、数日間にわたり銘柄を保有したら、収益がいくらになるかを(複数の評価基準日に対して)計算します。ここでは、ファクターデータを上位と下位の半分ずつにわけ、評価基準日から1日、5日、10日後の収益結果をみます。

# Alphalens インポート
import alphalens as al

# センチメントスコアに基づいて、quantileに指定された分位数にわける
factor_data = al.utils.get_clean_factor_and_forward_returns(
    factor=pipeline_output['sentiment_score'],
    prices=asset_prices,
    quantiles=2,
    periods=(1,5,10),
)

# 上から5行を表示
factor_data.head(5)
1D 5D 11D factor factor_quantile
date asset
2013-01-02 00:00:00+00:00 Equity(52 [ABM]) 0.004430 0.004430 0.004430 2.560000 2
Equity(114 [ADBE]) -0.015389 0.008086 -0.012259 -1.896667 1
Equity(166 [AES]) -0.006368 -0.008104 -0.005403 -2.630000 1
Equity(209 [AM]) 0.001801 -0.022995 -0.038365 2.370000 2
Equity(337 [AMAT]) -0.002525 -0.014339 0.007575 2.370000 2

これらの出力結果を、 Alphalensに渡せば、分析や描画を行なうことができます。 ではまず、指定した全期間における、平均の収益を四分位ごとに見てみましょう。 私達の戦略はロングショート戦略なので、ショートする下位の四分位の収益がネガティブ、ロングする上位の四分位の収益がポジティブであればうまく行くということになります。

# ファクターの四分位別に、平均を算出
mean_return_by_q, std_err_by_q = al.performance.mean_return_by_quantile(factor_data)

# 四分位と保有ごとに、平均を描画
al.plotting.plot_quantile_returns_bar(
    mean_return_by_q.apply(
        al.utils.rate_of_return,
        axis=0,
        args=('1D',)
    )
);
_images/notebook_14_0.png

次に、5日間保有した場合の累積収益を見てみましょう。ただし今回は、ロングとショートのポートフォリオにファクターでウェイトをかけます。

import pandas as pd
# ファクターでウェイト付けしたロングショートのポートフォリオを収益を算出
ls_factor_returns = al.performance.factor_returns(factor_data)

# 5日間保有した場合の累積収益を描画
al.plotting.plot_cumulative_returns(ls_factor_returns['5D'], '5D', freq=pd.tseries.offsets.BDay());
_images/notebook_16_0.png

このチャートを見ると、大きなドローダウンの期間がありますね。しかも、この分析では、取引コストやマーケットインパクトをまだ考慮に入れていません。ですので、これはあまり有望な戦略とは言えないようです。より良い戦略にするためには、さらに深い分析をAlphalensで行い、色んなアイデアで試行錯誤していく必要があるでしょう。 ですが、これはチュートリアルですので、この戦略のままで進めて行きたいと思います。

さて、ここまでのところで、取引戦略を実装し、その性能の検証をしてみました。次のチュートリアルレッスンでは、バックテストの機能を使って、この株式ロングショート戦略のパフォーマンスの検証を行います。バックテストではIDEでAlgorithm APIを使っていきます。

前回のレッスンでは、Researchの機能を使って、ポートフォリオに組み入れる銘柄を選択し、その資産のアルファスコアを計算するデータパイプラインを作成しました。 残りのレッスンでは、 Quantopianの Interactive Development Environment (IDE) を使って、取引アルゴリズムを作成します。IDEでも、データパイプラインを使い、アルファスコアを算出して、ポートフォリオを構築していきます。そして、過去データを用いてシミュレーションを行い、より現実的な条件下でのアルゴリズムのパフォーマンスを分析します。このようなヒストリカルシミュレーションは一般的に、"バックテスト"として知られています。

Algorithm API と 主な関数

次のステップでは、 QuantopianのAlgorithm APIを使用しながら、取引アルゴリズムの基本構造を構築しましょう。 Algorithm APIは、注文のスケジュールや実行を簡単に行う事ができる機能や、アルゴリズムのパラメータを初期化したり管理したりする機能を提供しています。 ここでは、アルゴリズムで使用する主要な関数をいくつか紹介します。

initialize(context)

アルゴリズムの実行を開始するときに 、 initialize という関数は真っ先に一度だけ呼ばれます。その時必ず引数として context を渡さなくてはいけません。IDEで用いるパラメータの初期化や、一回限りで行なう初期化や設定のロジックは、すべてこの initialize で行う必要があります。

context は Pythonの dictionary を拡張したもので、シミュレーションの過程において、その状況を格納するために使われ、アルゴリズムのどの時点においても参照することができるようにしてあります。このため、IDEでの関数呼び出しで共有させたい変数は、グローバル変数を用いるのではなく context の中に属性として保存しておきましょう。 context に保存する属性はドット( 例: context.some_attribute )でアクセスして、その内容の設定や参照をすることができます。

before_trading_start(context, data)

before_trading_start という関数は、シミュレーションの中で、毎日マーケットが開く前に1回だけ呼び出され、引数として contextdata を必要とします。 contextinitialize で使った辞書と同じものです。 data は複数のAPI関数を格納しているオブジェクトです。それらの関数を使って、任意の資産の現在または過去の価格や数量のデータを調べることができます。 before_trading_start 関数は、 pipeline の出力結果を取得する場所でもあります。また、その結果として得られたデータをポートフォリオ構築に使用するための前処理も行います。これらについては次のレッスンで説明します。

schedule_function(func, day_rule, time_rule)

Quantopianのアルゴリズムは ニューヨーク証券取引所の取引カレンダー に沿って、午前9時30分から午後4時の間株式の取引を行います。 schedule_function を使うと、指定した日時にユーザーがカスタマイズした関数を実行することができます。 例えば、毎週最初の営業日のマーケットオープン時にポートフォリオのリバランスを行う関数は、以下のようにスケジュールすることができます。

schedule_function(
    rebalance, ## ユーザーがカスタマイズした関数名
    date_rule=date_rules.week_start(),
    time_rule=time_rules.market_open()
)

スケジューリング関数は initialize 関数の中で呼び出す必要があり、决められたスケジュールに従って呼び出される関数(通常ユーザーが作成するもの)と呼び出すタイミングを引数として取ります。この呼び出される関数は、必ず contextdata を引数に取るように定義されている必要があります。

利用可能な date_rulestime_rules を使ってどのようにスケジューリングすることができるかは documentation を参照してください。

次に、取引アルゴリズムの基本構造を作ってみましょう。 下記のアルゴリズムは、シミュレーションで経過した日数を記録し、日付と時間に応じて異なるメッセージをログに出力しているだけです。 この後のレッスンで、 データパイプライン と、取引ロジックをこの基本構造に追加していきます。 "Clone" ボタンをクリックすると、IDEにコピーが作成され、アルゴリズムを実行することが出来ます。

重要

Lesson1の冒頭で説明したとおり、原作側がTutorial1を大幅に改定した為、このスクリプトをクローンすることは出来ません。よって、 Quantopianにログイン後、Research > Algorithm と進み、 New Algorithm ボタンをおしてIDEを開きスクリプトをコピー&ペーストしてお使い下さい。

IDEに入ったら、"Build Algorithm" (左上) または "Run Full Backtest" (右上)をクリックしてバックテストを実行します。

# Algorithm API をインポート
import quantopian.algorithm as algo


def initialize(context):
    # アルゴリズムパラメータを初期化
    context.day_count = 0
    context.daily_message = "Day {}."
    context.weekly_message = "Time to place some trades!"

    # rebalance 関数をスケジューリング
    algo.schedule_function(
        rebalance,
        date_rule=algo.date_rules.week_start(),
        time_rule=algo.time_rules.market_open()
    )


def before_trading_start(context, data):
    # 毎日、トレード時間が始まる前に必ず実行する
    context.day_count += 1
    log.info(context.daily_message, context.day_count)


def rebalance(context, data):
    # リバランスのロジックを実行する
    log.info(context.weekly_message)

取引アルゴリズムの基本的な構造ができたので、前回のレッスンで作成したデータパイプラインをアルゴリズムに追加してみましょう。

アルゴリズムにおけるデータ処理

次のステップでは、Researchで構築したデータパイプラインをアルゴリズムに統合します。 Researchとの重要な違いは、バックテストの間、シミュレーションの進行に合わせてpipelineが必ず毎日実行されることです。 したがって、 start_dateend_date を記述する必要はありません。

アルゴリズムの中でデータパイプラインを使うためには、まずアルゴリズムの initialize 関数の中に データパイプライン への参照を追加します。 これは attach_pipeline メソッドを使って行います。これには2つの引数があり、一つめは、 Pipeline オブジェクトへの参照(これは、 make_pipeline を使って作成します。)、もう一つは、その参照に名前を付けるための任意の 文字列 です。

# Algorithm API をインポート
import quantopian.algorithm as algo


def initialize(context):
    # アルゴリズムを pipeline に 取り付ける
    algo.attach_pipeline(
        make_pipeline(),
        'data_pipe'
    )

    # rebalance 関数をスケジュールする
    algo.schedule_function(
        rebalance,
        date_rule=algo.date_rules.week_start(),
        time_rule=algo.time_rules.market_open()
    )


def before_trading_start(context, data):
    pass


def rebalance(context, data):
    pass

冒頭で述べたように、パイプラインは、バックテスト期間内の毎日、マーケットが開く前にデータを処理し、出力を生成します。この出力は、 before_trading_start 関数のなかで、 pipeline_output 関数を使って取得することができます。なお、その出力は、 pandasのDataFrame型のデータになっています。 それでは、毎日生成されるパイプラインの出力のうち、最初の10行だけ、 rebalance 関数のなかでログに書き出してみましょう。

# Algorithm APIをインポート
import quantopian.algorithm as algo


def initialize(context):
    # algorithm に pipeline を取り付ける
    algo.attach_pipeline(
        make_pipeline(),
        'data_pipe'
    )

    # rebalance 関数をスケジュールする
    algo.schedule_function(
        rebalance,
        date_rule=algo.date_rules.week_start(),
        time_rule=algo.time_rules.market_open()
    )


def before_trading_start(context, data):
    # pipeline の出力結果を取得。
    # それを、context.pipeline_data 変数へ格納する。
    context.pipeline_data = algo.pipeline_output(
        'data_pipe'
    )


def rebalance(context, data):
    # pipeline の出力結果の最初の10行だけをログに出力
    log.info(context.pipeline_data.head(10))

では、Researchで作った make_pipeline 関数をアルゴリズムに追加してみましょう。 このアルゴリズムでは、あらかじめ用意した取引対象銘柄に入っている銘柄のうち、センチメントスコアが付与されている全ての銘柄を(銘柄数の制限なしで)対象とします。 そのため、 sentiment_scorenotnull メソッドを使って、センチメントスコアが付与されている銘柄を取り出し、それを & 演算子で取引対象銘柄と組み合わせて、取引すべき銘柄を絞り込みます。

# Algorithm API インポート
import quantopian.algorithm as algo

# Pipeline インポート
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.psychsignal import stocktwits
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS


def initialize(context):
    # algorithm に pipeline を取り付ける
    algo.attach_pipeline(
        make_pipeline(),
        'data_pipe'
    )

    # rebalance 関数をスケジュールする
    algo.schedule_function(
        rebalance,
        date_rule=algo.date_rules.week_start(),
        time_rule=algo.time_rules.market_open()
    )


def before_trading_start(context, data):
    # pipeline の出力結果を取得。
    # それを、context.pipeline_data 変数へ格納する。
    context.pipeline_data = algo.pipeline_output('data_pipe')


def rebalance(context, data):
    # pipeline の出力結果の最初の10行だけをログに出力
    log.info(context.pipeline_data.head(10))


# Pipeline definition
def make_pipeline():

    base_universe = QTradableStocksUS()

    sentiment_score = SimpleMovingAverage(
        inputs=[stocktwits.bull_minus_bear],
        window_length=3,
    )

    return Pipeline(
        columns={
            'sentiment_score': sentiment_score,
        },
        screen=(
            base_universe
            & sentiment_score.notnull()
        )
    )

ここまでのところで、バックテスト期間中の毎日、取引の対象となる銘柄候補を選び出し、それぞれについて、ポートフォリオ内の資産配分を決定するために使うアルファスコアを得られるようになりました。 次のレッスンでは、データパイプラインによって得られたアルファスコアに基づいて、最適なポートフォリオを構築する方法を学びます。

ポートフォリオマネジメント

前回のレッスンでは、取引アルゴリズムにdata pipelineを組み込みました。

次は、一般に、ポートフォリオ最適化 と呼ばれている取り組みを行います。アルゴリズムが対象資産を売買し、ポートフォリオを組み上げていくときには、制約条件やルールに従って、リターンを最大にすることを目指しますが、ここでは、pipeline によって生成したアルファスコアを活用して、リターンを最大化するために最適と見込まれるポートフォリオを作っていきます。

QuantopianのOptimize APIを使うと、ユーザーの望む目的関数と制約条件に基づいて、 pipelineからの出力を変換して利用することができます。さらに、 order_optimal_portfolio を使うことで、ポートフォリオをターゲットポートフォリオに近づけていくための注文を行なうことができます。

最初のステップは、 目的関数を定義することです。ここでは MaximizeAlpha を使用し、アルファスコアに基づいて、資本(資金)を各対象資産に配分することを考えます。

# Optimize API module インポート
import quantopian.optimize as opt


def rebalance(context, data):
  # pipeline 出力から alpha を取り出す
  alpha = context.pipeline_data.sentiment_score

  if not alpha.empty:
      # MaximizeAlpha objective を作成
      objective = opt.MaximizeAlpha(alpha)

次に、ターゲットポートフォリオが満たすべき制約のリストを指定します。 まず、いくつかの閾値を initialize 関数で定義し、それを context 変数に格納してみましょう。

# 制約パラメータ
context.max_leverage = 1.0
context.max_pos_size = 0.015
context.max_turnover = 0.95

上で定義した閾値を使って rebalance 関数で制約を指定しましょう。

# Optimize API module インポート
import quantopian.optimize as opt


def rebalance(context, data):
  # pipeline 出力から alpha を取り出す
  alpha = context.pipeline_data.sentiment_score

  if not alpha.empty:
      # MaximizeAlpha objective を作成
      objective = opt.MaximizeAlpha(alpha)

      # ポジションサイズの制約
      constrain_pos_size = opt.PositionConcentration.with_equal_bounds(
          -context.max_pos_size,
          context.max_pos_size
      )

      # ポートフォリオレバレッジの制約
      max_leverage = opt.MaxGrossExposure(context.max_leverage)

      # ロング(買い持ち)とショート(売り持ち)のサイズをだいたい同じに合わせる
      dollar_neutral = opt.DollarNeutral()

      # ポートフォリオの出来高の制約
      max_turnover = opt.MaxTurnover(context.max_turnover)

最後に、 目的関数と制約のリストを order_optimal_portfolio 関数に渡してターゲットポートフォリオを評価し、現在のポートフォリオを最適な状態にするために必要な注文を出します。

# Algorithm API インポート
import quantopian.algorithm as algo

# Optimize API インポート
import quantopian.optimize as opt


def rebalance(context, data):
  # pipeline 出力から alpha を取り出す
  alpha = context.pipeline_data.sentiment_score

  if not alpha.empty:
      # MaximizeAlpha objective を作成
      objective = opt.MaximizeAlpha(alpha)

      # ポジションサイズの制約
      constrain_pos_size = opt.PositionConcentration.with_equal_bounds(
          -context.max_pos_size,
          context.max_pos_size
      )

      # ポートフォリオレバレッジの制約
      max_leverage = opt.MaxGrossExposure(context.max_leverage)

      # ロング(買い持ち)とショート(売り持ち)のサイズをだいたい同じに合わせる
      dollar_neutral = opt.DollarNeutral()

      # ポートフォリオの出来高の制約
      max_turnover = opt.MaxTurnover(context.max_turnover)

      # 目的関数と制約リストを使ってポートフォリオをリバランスする
      algo.order_optimal_portfolio(
          objective=objective,
          constraints=[
              constrain_pos_size,
              max_leverage,
              dollar_neutral,
              max_turnover,
          ]
      )

リスクマネジメント

ターゲットポートフォリオに制約条件を設定して、ポートフォリオを最適化していくのとあわせ、ここで、ポートフォリオのパフォーマンスに悪い影響を与えやすいリスク要因を避けるような制約条件も設定しておきたいと思います。 例えば、 stocktwits のセンチメントデータは一時的な性質のものなので、センチメントスコアの急上昇を捉えて投資をするようなアルゴリズムを組むと、逆に急降下のリスクにさらされてしまう可能性があります。 Quantopianの Risk Model を使用すれば、一般的なリスク要因に対するポートフォリオのエクスポージャーを管理できます。Risk Model では、資産にまつわる16種類のリスク要因に対して評価ができ、11のセクターリスク要因と5つのスタイルリスク要因(短期的な反転を含む)に対応しています。

このリスク評価の情報は risk_loading_pipeline 関数を使えば簡単に取得できます。この関数は、Risk Modelに定義された各リスク要因の結果をコラムに持つ、 data pipeline を返します。

risk data pipeline は、data pipelineと同じやり方で、識別名を付けてアルゴリズムに登録します。そうすれば before_trading_start 関数でrisk data pipeline の出力を取得し、それを context に保存することができるようになります。

# Algorithm API インポート
import quantopian.algorithm as algo

# Risk API method インポート
from quantopian.pipeline.experimental import risk_loading_pipeline

def initialize(context):
    # 制約パラメータ
    context.max_leverage = 1.0
    context.max_pos_size = 0.015
    context.max_turnover = 0.95

    # data pipelines を取り付ける
    algo.attach_pipeline(
        make_pipeline(),
        'data_pipe'
    )
    algo.attach_pipeline(
        risk_loading_pipeline(),
        'risk_pipe'
    )

    # rebalance 関数をスケジュール
    algo.schedule_function(
        rebalance,
        algo.date_rules.week_start(),
        algo.time_rules.market_open(),
    )


def before_trading_start(context, data):
    # pipeline出力を取得し、contextに格納する。
    context.pipeline_data = algo.pipeline_output(
      'data_pipe'
    )

    context.risk_factor_betas = algo.pipeline_output(
      'risk_pipe'
    )

次に、ポートフォリオ最適化ロジックに RiskModelExposure 制約を追加します。 この制約はリスクモデルによって生成されたデータを受け取り、リスクモデルに含まれるひとつひとつの要因に対して、ターゲットポートフォリオに対するエクスポージャーの制限を設定します。

# ターゲットポートフォリオのリスクエクスポージャーを制限する。
# デフォルト値は、セクターエクスポージャーの最大値は0.2、スタイルエクスポージャーの最大値は0.4
factor_risk_constraints = opt.experimental.RiskModelExposure(
    context.risk_factor_betas,
    version=opt.Newest
)

下記のコードが、私たちの戦略とポートフォリオ構築ロジックを記述したアルゴリズムです。このコードでバックテストすることができます。アルゴリズムをclone した後、IDEの右上にある「Run Full Backtest」をクリックして、完全なバックテストを実行してみましょう。

注釈

Quantopianにログイン後、本翻訳の原作ページ https://www.quantopian.com/tutorials/getting-started#lesson7 で、 Clone ボタンを押してコードをクローンして下さい。

# Algorithm API インポート
import quantopian.algorithm as algo

# Optimize API インポート
import quantopian.optimize as opt

# Pipeline  インポート
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.psychsignal import stocktwits
from quantopian.pipeline.factors import SimpleMovingAverage

# built-in universe と Risk API method インポート
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.experimental import risk_loading_pipeline


def initialize(context):
    # 制約パラメータ
    context.max_leverage = 1.0
    context.max_pos_size = 0.015
    context.max_turnover = 0.95

    # data pipelines を取り付ける
    algo.attach_pipeline(
        make_pipeline(),
        'data_pipe'
    )
    algo.attach_pipeline(
        risk_loading_pipeline(),
        'risk_pipe'
    )

    # rebalance 関数をスケジュール
    algo.schedule_function(
        rebalance,
        algo.date_rules.week_start(),
        algo.time_rules.market_open(),
    )


def before_trading_start(context, data):
    # pipeline出力を取得し、contextに格納する。
    context.pipeline_data = algo.pipeline_output('data_pipe')

    context.risk_factor_betas = algo.pipeline_output('risk_pipe')


# Pipeline definition
def make_pipeline():

    sentiment_score = SimpleMovingAverage(
        inputs=[stocktwits.bull_minus_bear],
        window_length=3,
        mask=QTradableStocksUS()
    )

    return Pipeline(
        columns={
            'sentiment_score': sentiment_score,
        },
        screen=sentiment_score.notnull()
    )


def rebalance(context, data):
    # pipeline 出力から alpha を取り出す
    alpha = context.pipeline_data.sentiment_score

    if not alpha.empty:
        # MaximizeAlpha objective 作成
        objective = opt.MaximizeAlpha(alpha)

        # ポジションサイズ制約
        constrain_pos_size = opt.PositionConcentration.with_equal_bounds(
            -context.max_pos_size,
            context.max_pos_size
        )

        # ターゲットポートフォリオレバレッジ制約
        max_leverage = opt.MaxGrossExposure(context.max_leverage)

        # ロング(買い持ち)とショート(売り持ち)のサイズをだいたい同じに合わせる
        dollar_neutral = opt.DollarNeutral()

        # ポートフォリオの出来高の制約
        max_turnover = opt.MaxTurnover(context.max_turnover)

        # ターゲットポートフォリオのリスクエクスポージャーを制限する。
        # デフォルト値は、セクターエクスポージャーの最大値は0.2
        # スタイルエクスポージャーの最大値は0.4
        factor_risk_constraints = opt.experimental.RiskModelExposure(
            context.risk_factor_betas,
            version=opt.Newest
        )

        # 目的関数と制約リストを使ってポートフォリオをリバランスする
        algo.order_optimal_portfolio(
            objective=objective,
            constraints=[
                constrain_pos_size,
                max_leverage,
                dollar_neutral,
                max_turnover,
                factor_risk_constraints,
            ]
        )

次のレッスンでは、バックテストの結果をより詳しく分析する方法を学びます。

バックテスト で分析する

バックテストの実行が終了したら、"Notebook"タブをクリックします。

_images/getting_started1_l8_screenshot1.png

そうすると、Research notebookが開き、セルの1つに以下のようなコードが自動で挿入されます。

bt = get_backtest('5a4e4faec73c4e44f218170a')
bt.create_full_tear_sheet()

このセルを実行(Shift+Enter)すると、backtestで生成されたデータがresearch notebookに読み込まれ、それを使ってPyfolioのtear sheetが作成されます。

注釈

notebookに表示される英数字の文字列は、上記のものとは異なります。 この文字列は、Quantopian内でのバックテストを特定するための識別子です。 full backtestを行った時の結果ページのURLにも、この文字列が使われています。full backtestとは、Algorithm IDE 機能の1つでストラテジーのパフォーマンスやリスク要因などを詳細に分析するツールです。詳しくは、 ドキュメント を参照してください。

Pyfolio は、Quantopianが開発したポートフォリオとリスク分析を行うオープンソースのツールです。

このツールには、アルゴリズムの動作やリスクエクスポージャーを時間の経過とともによりよく理解するために設計された、多くの可視化ツールが用意されています。 例えば以下のプロットは、私達のポートフォリオがマーケットの影響をどのくらい受けているか、一定期間単位で時系列に表示したものです。 私たちが株式ロング・ショート取引アルゴリズムを構築しようと思った理由の一つは、市場との相関関係を低く維持することでした。 したがって、このプロットでは、backtestingの期間中ずっと、相関係数が概ねゼロ付近になっているのが望ましい姿ということになります。

_images/getting_started1_l9_screenshot2.png

tear sheetのもう1つの面白い点は、パフォーマンスがどの特性から生み出されたものかを表示するところです。 以下のプロットは、リターンのうちどれだけが私達の戦略に起因するか、そしてそのうちどれだけが一般的なリスク要因に起因するかを説明するために、Quantopian の Risk Model を使っています。

_images/getting_started1_l9_screenshot3.png

上のプロットから、ポートフォリオのトータルリターンのほとんどが特定のリターンから来ていることがわかります。 これは、アルゴリズムのパフォーマンスが一般的なリスク要因から来ていないことを示唆しています。 それは私達のアルゴリズムにとって良いことです。

これでQuantopianの入門チュートリアルが終了です。おめでとうございます。 プラットフォームのAPIに慣れてきたところで、ご自身の戦略を研究・開発して、 contest に応募してみましょう。

アイデアが必要な場合は、 Lecture Series を試して下さい。 金融工学(クオンツ)について色々と学ぶことができます。 また、 community で他のメンバーが共有しているアイデアを見るのもよいでしょう。

Pipeline

Quantopianパイプラインのチュートリアルへようこそ。このチュートリアルでは pipeline API を紹介します。 もしあなたがQuantopianに不慣れであれば Getting Started Tutorial から始め、少なくともPythonの動作知識を学習しておくことをお薦めします。 このチュートリアルはいくつかのレッスンに分かれており、それぞれのレッスンで異なるPipeline APIの機能に触れていきます。 レッスン2から11はResearch環境で動作します。レッスン12はIDE環境で動作します。

なぜ Pipeline なのか?

多くの取引アルゴリズムは、以下のような構造を持っています:

  1. 既知の(大きな)データセット内に存在する資産に対し、一定区間データに基づくN個のスカラ値を計算する。

  2. (1)の計算結果に基づき、取引可能な資産の集合を絞り込む。

  3. (2)で絞り込みした資産の集合に対し、望ましい投資比率を計算する。

  4. 現在のポートフォリオの投資比率が、(3)で計算した望ましい投資比率となるように発注する。

これらを頑健に実行するためには、いくつかの技術的困難が存在します。そこには、以下のようなものが含まれます。

  • 大規模データセットに対する効率的な問い合わせ

  • 大規模データセットに対する計算効率

  • データ修正作業(株式分割や配当金)

  • 資産の上場廃止作業

パイプラインは、さまざまなデータセットのコレクションに対する計算処理を表現するための統一されたAPIを提供することにより、こうした技術的困難を解決します。

Research と IDE

理想的なアルゴリズムデザインワークフローには、調査(Research)段階と、実装(Implementaion)段階が内在します。 Reserch環境内では、notebook を通じてデータに触れたり、様々なアイデアを素早く試したりすることができます。 アルゴリズムは、バックテストが可能なIDEの中で実装されます。

パイプラインAPIの特徴のひとつは、パイプラインの構築はResearchとIDEの両方で同じであることです。 2つの環境内でパイプラインを使用する際の唯一の違いは、その実行方法です。 この特徴は、パイプラインをReserch環境でデザインし、それをIDE内のアルゴリズムに単純にコピーアンドペーストできることを可能にしています。 このワークフローは、後ほどのレッスンで詳細に議論する予定ですが、チュートリアル全体を通して見ていくことになるでしょう。

計算処理

パイプラインで表現される計算処理には、「ファクター(Factors)」「フィルタ(Filters)」「クラシファイア(Classifires)」の3種類あります。

抽象的にいうと、ファクター、フィルタ、クラシファイアで表されるすべての関数は、とある資産のある一時点における何らかの値を出力します。 ファクター、フィルタ、クラシファイアは、出力する値の種類によって区別されます。

ファクター

ファクターとは、とある資産のある一時点から数値を出力する関数のことです。

ファクターの単純な例は、とある証券の直近値段です。証券の銘柄名と特定時点を与えられることによって、その直近の値段が数値として返ってきます。 また別の例として、とある証券の10日間の平均出来高が挙げられます。 ファクターは数値を証券に割り当てるために最も一般的に利用され、いろいろな方法で用いられます。 ファクターは、以下のような処理において使われます。

  • ターゲットとなる比率の計算

  • アルファシグナルの生成

  • より複雑なファクターの作成

  • フィルタの作成

フィルタ

フィルタとは、とある資産のある一時点からブール型の値を出力する関数のことです。

フィルタの一例は、とある証券の値段が10ドル以下かどうかを表す関数です。証券の銘柄名と特定時点を与えられることによって、TrueまたはFalseによって評価されます。 フィルタは特定の目的に対し、資産の集合が該当するか否かを表現するために最も一般的に利用されます。

クラシファイア

クラシファイアとは、とある資産のある一時点から分類型の値を出力する関数のことです。

より明確には、クラシファイアは数値で表現することのできない、string型、あるいはint型(例えば数値でラベリングされた業種コードなど)を返します。 クラシファイアはファクター計算上の複雑な変換を行ううえで、資産をグループ化するために最も一般的に利用されます。 クラシファイアの一例は、とある資産の現在取引可能な取引所を返す関数です。

データセット

パイプライン処理は、四本値や出来高財務データ 、そして コンセンサス予想データ といった 多様なデータ を用いて実行することが可能です。 後ほどのレッスンで、こうしたデータセットを見ていくことにします。

典型的なパイプラインには通常、複数の計算処理とデータセットを含んでいます。このチュートリアルでは、10日間平均値段と30日値段の間で大きな値動きがあった流動性の高い証券を選別するパイプラインを構築していきます。

レッスン2から11は、research環境で実行します。researchを利用可能な状態にするためにはJupyter notebookを新規作成するか、"Get Notebook"をクリックすることでこのレッスンのnotebookバージョンを複製することができます。 もしあなたががまだresearch環境に不慣れであれば Introduction to Research レクチャーから始めるか、 documentation に目を通しておくことをお薦めします。

パイプラインを作成する

いくつかのimport文を追加するところから始めましょう。まず始めに、 Pipeline クラスをインポートします。

from quantopian.pipeline import Pipeline

新しいセルの中で、パイプラインを作成するための関数を定義します。 関数の中にパイプラインの作成を内包することによって、後ほど紹介する、より複雑なパイプラインを構築するための土台が出来上がります。 ここで、以下の関数は単純に空のパイプラインを返します。

def make_pipeline():
    return Pipeline()

新しいセルで make_pipeline() を実行することにより、パイプラインをインスタンス化しましょう:

my_pipe = make_pipeline()

パイプラインを実行する

空のパイプラインを参照する my_pipe を作成しました。これを実行し、どのようになっているかを見てみましょう。 ただし、パイプラインを実行する前に run_pipeline をインポートする必要があります。 これは research 環境だけで使える関数で、指定した期間を通してパイプラインを実行することを可能にします。

from quantopian.research import run_pipeline

では run_pipeline を使いパイプラインを1日(2015-05-05)だけ実行してその中身を表示してみましょう。 なお、第2引数と第3引数はそれぞれシミュレーションの「開始日付」と「終了日付」です。

result = run_pipeline(my_pipe, '2015-05-05', '2015-05-05')

run_pipeline を実行すると、日付と証券でインデックスされた pandas DataFrame が返ってきます。 空のパイプラインがどのようになっているか見てみましょう。

result
2015-05-05 00:00:00+00:00 Equity(2 [AA])
Equity(21 [AAME])
Equity(24 [AAPL])
Equity(25 [AA_PR])
Equity(31 [ABAX])
Equity(39 [DDC])
Equity(41 [ARCB])
Equity(52 [ABM])
Equity(53 [ABMD])
Equity(62 [ABT])
Equity(64 [ABX])
Equity(66 [AB])
Equity(67 [ADSK])
Equity(69 [ACAT])
Equity(70 [VBF])
Equity(76 [TAP])
Equity(84 [ACET])
Equity(86 [ACG])
Equity(88 [ACI])
Equity(100 [IEP])
Equity(106 [ACU])
Equity(110 [ACXM])
Equity(112 [ACY])
Equity(114 [ADBE])
Equity(117 [AEY])
Equity(122 [ADI])
Equity(128 [ADM])
Equity(134 [SXCL])
Equity(149 [ADX])
Equity(153 [AE])
...
Equity(48961 [NYMT_O])
Equity(48962 [CSAL])
Equity(48963 [PAK])
Equity(48969 [NSA])
Equity(48971 [BSM])
Equity(48972 [EVA])
Equity(48981 [APIC])
Equity(48989 [UK])
Equity(48990 [ACWF])
Equity(48991 [ISCF])
Equity(48992 [INTF])
Equity(48993 [JETS])
Equity(48994 [ACTX])
Equity(48995 [LRGF])
Equity(48996 [SMLF])
Equity(48997 [VKTX])
Equity(48998 [OPGN])
Equity(48999 [AAPC])
Equity(49000 [BPMC])
Equity(49001 [CLCD])
Equity(49004 [TNP_PRD])
Equity(49005 [ARWA_U])
Equity(49006 [BVXV])
Equity(49007 [BVXV_W])
Equity(49008 [OPGN_W])
Equity(49009 [PRKU])
Equity(49010 [TBRA])
Equity(49131 [OESX])
Equity(49259 [ITUS])
Equity(49523 [TLGT])

8236 rows × 0 columns

空のパイプラインは列データを持たないDataFrameを出力しています。今回の場合、パイプラインは2015年5月5日に対して8000超(上の表では途中中断) の証券からなるインデックスを持っていますが、列データを何も持っていません。

以降のレッスンでは、パイプラインの出力に対してどのように列を追加していくのか、どのようにフィルタをかけて証券を絞り込んでいくのかをみていきます。

ファクター

ファクターは、ある資産とある一時点から数値型を返す関数です。

\[F(asset, timestamp) \rightarrow float\]

パイプラインでは ファクター は最も一般的な用語であり、数値として返される計算結果を表します。 ファクターには入力値として列データと計算区間が必要です。

パイプラインにおける最も単純なファクターは、ビルトイン・ファクター と呼ばれるものです。 ビルトイン・ファクターは、一般的によく使われる計算を実行するために予め用意されています。 最初の例として、資産ごとに10日間の区間を動かしながら終値の平均値段を計算するファクターを作成しましょう。 指定した計算区間(10日間)を対象に入力データ(終値)の平均値を計算する SipleMovingAverate というビルトイン・ファクターを使います。 これを使うためには SimpleMovingAverage ビルトイン・ファクターと、 USEquityPricing dataset をインポートします。

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline

# 前回のレッスンに加えて、USEquityPricing datasetをインポート
from quantopian.pipeline.data.builtin import USEquityPricing

# 前回のレッスンに加えて、SimpleMovingAverageビルトイン・ファクターをインポート
from quantopian.pipeline.factors import SimpleMovingAverage

ファクターの作成

前のレッスンで作成した make_pipeline 関数に戻って、 SimpleMovingAverage ファクターをインスタンス化します。 SimpleMovingAverage の作成には、SimpleMovingAverage にinput( BoundColumn 型のリスト)と、 window_length(移動平均の計算が受け取るデータ日数を表す整数値)という2つの引数を渡してコンストラクタを呼び出します。 ( BoundColumn については後ほど詳細に触れます。今の段階では、 BoundColumn がファクターを作成する上で必要な必要なものであるということだけ知っていれば十分です。)

以下の一行で、証券の10日間移動平均を計算する ファクター が出来上がります。

mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)

ここで重要なのは、ファクターの作成段階では、実際には計算を実行していないという点です。ファクターの作成は、関数を定義するのに似ています。 計算を実行するためには、ファクターをパイプラインに追加して、実行する必要があります。

ファクターをパイプラインに追加する

元となる空のパイプラインを、移動平均を計算するファクターにアップデートさせましょう。 まず始めに、ファクターのインスタンス化を make_pipeline 内に移動させます。 次に、columns (列名に対してファクター、フィルタ、あるいはクラシファイアを紐づける辞書型)引数を通じて、パイプラインにファクターの計算を指示します。 アップデートされた make_pipline 関数は、このような感じになるはずです。

def make_pipeline():

    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)

    return Pipeline(
        columns={
            '10_day_mean_close': mean_close_10
        }
    )

中身がどのようになっているかを確認するため、パイプラインを実行して結果を表示させます。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result
10_day_mean_close
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 13.559500
Equity(21 [AAME]) 3.962500
Equity(24 [AAPL]) 129.025700
Equity(25 [AA_PR]) 88.362500
Equity(31 [ABAX]) 61.920900
Equity(39 [DDC]) 19.287072
Equity(41 [ARCB]) 37.880000
Equity(52 [ABM]) 32.083400
Equity(53 [ABMD]) 66.795000
Equity(62 [ABT]) 47.466000
Equity(64 [ABX]) 12.919000
Equity(66 [AB]) 31.547000
Equity(67 [ADSK]) 60.212000
Equity(69 [ACAT]) 36.331000
Equity(70 [VBF]) 18.767000
Equity(76 [TAP]) 74.632000
Equity(84 [ACET]) 19.873000
Equity(86 [ACG]) 7.810000
Equity(88 [ACI]) 0.996100
Equity(100 [IEP]) 91.821200
Equity(106 [ACU]) 18.641000
Equity(110 [ACXM]) 18.045500
Equity(112 [ACY]) 11.571000
Equity(114 [ADBE]) 76.072000
Equity(117 [AEY]) 2.423400
Equity(122 [ADI]) 63.205900
Equity(128 [ADM]) 48.788500
Equity(134 [SXCL]) NaN
Equity(149 [ADX]) 14.150500
Equity(153 [AE]) 54.099000
... ...
Equity(48961 [NYMT_O]) NaN
Equity(48962 [CSAL]) 29.992000
Equity(48963 [PAK]) 15.531875
Equity(48969 [NSA]) 13.045000
Equity(48971 [BSM]) 17.995000
Equity(48972 [EVA]) 21.413250
Equity(48981 [APIC]) 14.814000
Equity(48989 [UK]) 24.946667
Equity(48990 [ACWF]) 25.250000
Equity(48991 [ISCF]) 24.985000
Equity(48992 [INTF]) 25.030000
Equity(48993 [JETS]) 24.579333
Equity(48994 [ACTX]) 15.097333
Equity(48995 [LRGF]) 24.890000
Equity(48996 [SMLF]) 29.456667
Equity(48997 [VKTX]) 9.115000
Equity(48998 [OPGN]) NaN
Equity(48999 [AAPC]) 10.144000
Equity(49000 [BPMC]) 20.810000
Equity(49001 [CLCD]) NaN
Equity(49004 [TNP_PRD]) 24.750000
Equity(49005 [ARWA_U]) NaN
Equity(49006 [BVXV]) NaN
Equity(49007 [BVXV_W]) NaN
Equity(49008 [OPGN_W]) NaN
Equity(49009 [PRKU]) NaN
Equity(49010 [TBRA]) NaN
Equity(49131 [OESX]) NaN
Equity(49259 [ITUS]) NaN
Equity(49523 [TLGT]) NaN

8236 rows × 1 columns

これでパイプラインの出力に、8000超の全銘柄(画面上は途中まで)に対して計算された10日間終値移動平均の列が追加されました。 各行は、該当する証券と該当する日付における計算結果に対応しています。 この DataFrame は、 マルチインデックス (第1レベルは計算を行った日付を表す日時、第2レベルは証券に対応する Equity オブジェクト) を持っています。 例えば1行目(2015-05-05 00:00:00+00:00, Equity(2 [AA]))には、2015年5月5日のAA (訳者注:AAはアルコア社(アルミニウム、アルミニウム製品およびアルミナの世界的なメーカー)を表す証券コード) の mean_close_10 ファクターの計算結果が格納されます。

もし1日よりも長い期間パイプラインを実行すれば、その結果はこのようになります。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-07')
result
10_day_mean_close
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 13.559500
Equity(21 [AAME]) 3.962500
Equity(24 [AAPL]) 129.025700
Equity(25 [AA_PR]) 88.362500
Equity(31 [ABAX]) 61.920900
Equity(39 [DDC]) 19.287072
Equity(41 [ARCB]) 37.880000
Equity(52 [ABM]) 32.083400
Equity(53 [ABMD]) 66.795000
Equity(62 [ABT]) 47.466000
Equity(64 [ABX]) 12.919000
Equity(66 [AB]) 31.547000
Equity(67 [ADSK]) 60.212000
Equity(69 [ACAT]) 36.331000
Equity(70 [VBF]) 18.767000
Equity(76 [TAP]) 74.632000
Equity(84 [ACET]) 19.873000
Equity(86 [ACG]) 7.810000
Equity(88 [ACI]) 0.996100
Equity(100 [IEP]) 91.821200
Equity(106 [ACU]) 18.641000
Equity(110 [ACXM]) 18.045500
Equity(112 [ACY]) 11.571000
Equity(114 [ADBE]) 76.072000
Equity(117 [AEY]) 2.423400
Equity(122 [ADI]) 63.205900
Equity(128 [ADM]) 48.788500
Equity(134 [SXCL]) NaN
Equity(149 [ADX]) 14.150500
Equity(153 [AE]) 54.099000
... ... ...
2015-05-07 00:00:00+00:00 Equity(48981 [APIC]) 14.646000
Equity(48989 [UK]) 24.878000
Equity(48990 [ACWF]) 25.036667
Equity(48991 [ISCF]) 24.875000
Equity(48992 [INTF]) 24.813000
Equity(48993 [JETS]) 24.343600
Equity(48994 [ACTX]) 15.020400
Equity(48995 [LRGF]) 24.788000
Equity(48996 [SMLF]) 29.370000
Equity(48997 [VKTX]) 9.232500
Equity(48998 [OPGN]) 4.950000
Equity(48999 [AAPC]) 10.167000
Equity(49000 [BPMC]) 20.906667
Equity(49001 [CLCD]) 8.010000
Equity(49004 [TNP_PRD]) 24.633333
Equity(49005 [ARWA_U]) 10.010000
Equity(49006 [BVXV]) NaN
Equity(49007 [BVXV_W]) NaN
Equity(49008 [OPGN_W]) 0.817500
Equity(49009 [PRKU]) NaN
Equity(49010 [TBRA]) NaN
Equity(49015 [ADAP]) NaN
Equity(49016 [COLL]) NaN
Equity(49017 [GLSS]) NaN
Equity(49018 [HTGM]) NaN
Equity(49019 [LRET]) NaN
Equity(49020 [MVIR]) NaN
Equity(49131 [OESX]) NaN
Equity(49259 [ITUS]) NaN
Equity(49523 [TLGT]) NaN

24705 rows × 1 columns

備考:Pipeline.add メソッドを用いることでも同様に Pipeline インスタンスに対してファクターを追加できます。 add を使う場合はこのような感じになります:>>> my_pipe = Pipeline() >>> f1 = SomeFactor(…) >>> my_pipe.add(f1, ‘f1’)

Latest

最もよく使われるビルトイン Factor は、 Latest です。 Latest ファクターは、与えられたデータ列中で最も直近の値を取得します。 このファクターは非常によく使われるので、他のファクターとは異なる方法でインスタンス化されます。 データ列から直近の値を取得するには、 .latest アトリビュートから取得するのが最良の方法です。 例として make_pipeline をアップデートして直近終値を取得するファクターを作成してパイプラインに追加してみましょう。

def make_pipeline():

    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
    latest_close = USEquityPricing.close.latest

    return Pipeline(
        columns={
            '10_day_mean_close': mean_close_10,
            'latest_close_price': latest_close
        }
    )

ここで再びパイプラインを作成し実行すると、出力されたdataframeには2つの列ができます。 一方は10日間終値移動平均の列で、もう一方は直近の終値の列になっています。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result.head(5)
10_day_mean_close latest_close_price
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 13.5595 14.015
Equity(21 [AAME]) 3.9625 NaN
Equity(24 [AAPL]) 129.0257 128.699
Equity(25 [AA_PR]) 88.3625 NaN
Equity(31 [ABAX]) 61.9209 55.030

.latestファクター 以外のものを返すことがあります(訳者注:latest_close_priceに数値ではないNaNを含む)。 これ以外の起こり得る返り値の型については後ほどみていきます。

デフォルト入力

いくつかのファクターは、変更すべきでないデフォルト入力があります。たとえば VWAP ビルトイン・ファクター は常に USEquityPricing.closeUSEquityPricing.volume から計算されます。ファクターが常に同じ BoundColumns から計算される場合、 input 引数を明示せずにコンストラクタを呼び出せます。

from quantopian.pipeline.factors import VWAP
vwap = VWAP(window_length=10)

次のレッスンでは、ファクターの結合を見ていきます。

ファクターの結合

ファクターは組込み数学演算子(+、-、*など)のいずれかを使い、別のファクターやスカラ値と結合できます。 これにより、複数のファクターを結合させた複雑な式を簡単にかけます。 例えば2つの異なるファクターの平均を取るファクターの作成はシンプルに:

>>> f1 = SomeFactor(...)
>>> f2 = SomeOtherFactor(...)
>>> average = (f1 + f2) / 2.0

と書けます。 このレッスンでは、10日間平均ファクターと30日間平均ファクターを組み合わせた relative_difference ファクターを計算するパイプラインを作成します。 いつもと同じく、import文から始めます:

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage

今回は、10日間移動平均と30日間移動平均の2つのファクターが必要です:

mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)

では、mean_close_30 ファクターと mean_close_10 ファクターを使って相対変化( percent_difference )を計算するファクターを作成します。

percent_difference = (mean_close_10 - mean_close_30) / mean_close_30

この例を見ると、 percent_difference はより単純なファクターの組み合わせによって構成されていますが、依然として Factor です。 percent_difference をパイプラインの列として追加できます。

それでは percent_difference を列に持つ(終値平均を計算するファクターは列に加えません)パイプラインを作成する make_pipeline を定義しましょう:

def make_pipeline():

    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
    mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)

    percent_difference = (mean_close_10 - mean_close_30) / mean_close_30

    return Pipeline(
        columns={
            'percent_difference': percent_difference
        }
    )

新しい出力がどのようになっているか確認します:

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result
percent_difference
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 0.017975
Equity(21 [AAME]) -0.002325
Equity(24 [AAPL]) 0.016905
Equity(25 [AA_PR]) 0.021544
Equity(31 [ABAX]) -0.019639
Equity(39 [DDC]) 0.074730
Equity(41 [ARCB]) 0.007067
Equity(52 [ABM]) 0.003340
Equity(53 [ABMD]) -0.024682
Equity(62 [ABT]) 0.014385
Equity(64 [ABX]) 0.046963
Equity(66 [AB]) 0.013488
Equity(67 [ADSK]) -0.003921
Equity(69 [ACAT]) -0.007079
Equity(70 [VBF]) 0.005507
Equity(76 [TAP]) -0.008759
Equity(84 [ACET]) -0.056139
Equity(86 [ACG]) 0.010096
Equity(88 [ACI]) -0.022089
Equity(100 [IEP]) 0.011293
Equity(106 [ACU]) 0.003306
Equity(110 [ACXM]) -0.029551
Equity(112 [ACY]) -0.057763
Equity(114 [ADBE]) 0.009499
Equity(117 [AEY]) 0.012543
Equity(122 [ADI]) 0.009271
Equity(128 [ADM]) 0.015760
Equity(134 [SXCL]) NaN
Equity(149 [ADX]) 0.007232
Equity(153 [AE]) -0.112999
... ...
Equity(48961 [NYMT_O]) NaN
Equity(48962 [CSAL]) 0.000000
Equity(48963 [PAK]) 0.000000
Equity(48969 [NSA]) 0.000000
Equity(48971 [BSM]) 0.000000
Equity(48972 [EVA]) 0.000000
Equity(48981 [APIC]) 0.000000
Equity(48989 [UK]) 0.000000
Equity(48990 [ACWF]) 0.000000
Equity(48991 [ISCF]) 0.000000
Equity(48992 [INTF]) 0.000000
Equity(48993 [JETS]) 0.000000
Equity(48994 [ACTX]) 0.000000
Equity(48995 [LRGF]) 0.000000
Equity(48996 [SMLF]) 0.000000
Equity(48997 [VKTX]) 0.000000
Equity(48998 [OPGN]) NaN
Equity(48999 [AAPC]) 0.000000
Equity(49000 [BPMC]) 0.000000
Equity(49001 [CLCD]) NaN
Equity(49004 [TNP_PRD]) 0.000000
Equity(49005 [ARWA_U]) NaN
Equity(49006 [BVXV]) NaN
Equity(49007 [BVXV_W]) NaN
Equity(49008 [OPGN_W]) NaN
Equity(49009 [PRKU]) NaN
Equity(49010 [TBRA]) NaN
Equity(49131 [OESX]) NaN
Equity(49259 [ITUS]) NaN
Equity(49523 [TLGT]) NaN

8235 rows × 1 columns

次のレッスンでは、フィルタについて学習します。

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage

フィルタ

フィルタとは、とある資産のある一時点からブール型の値を出力する関数です:

\[F(asset, timestamp) \rightarrow boolean\]

フィルタ は 計算過程やパイプラインの最終出力に含まれる証券の絞り込みに使われます。 フィルタ 作成には2つの一般的な方法があります。比較演算子と ファクター / クラシファイア メソッドを使う方法です。

比較演算子

ファクタークラシファイア に用いられる比較演算子から フィルタ が作られます。 ここまで クラシファイア について触れていませんが、とにかく ファクター を使った例で進めていきます。 以下の例では、直近の終値が20ドルを超える場合に True を返すフィルタを作成します。

last_close_price = USEquityPricing.close.latest
close_price_filter = last_close_price > 20

またこの例では、10日間平均が30日間平均を下回るる場合に True を返すフィルタを作成しています。

mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)
mean_crossover_filter = mean_close_10 < mean_close_30

それぞれの証券は日付ごとに True または False の値を持っていることに留意してください。

ファクター / クラシファイア メソッド

ファクタークラシファイア クラスが フィルタ を作成する方法は多様です。 相変わらず クラシファイア について触れていませんが ファクター を使った例で進めていきます(このあと クラシファイア メソッドを見ていきます)。 Factor.top(n) メソッドは、与えられた ファクター における上位 n 件の証券に True を返す フィルタ を作成します。 以下の例はすべての利用可能な証券のうち、日付ごとに終値が上位200位以内に含まれる銘柄に対し True を返すフィルタを作成します。

last_close_price = USEquityPricing.close.latest
top_close_price_filter = last_close_price.top(200)

フィルタ を返す ファクター メソッドの一覧は、ここ を参照してください。

フィルタ を返す クラシファイア メソッドの一覧は、ここ を参照してください。

売買代金(Dollar Volume)フィルタ

最初の例として、証券の30日間の平均売買代金が10,000,000ドルより大きい場合に True を返すフィルタを作成します。 まず、 30日間の平均売買代金を計算する AverageDollarVolume ファクターを作成します。 import文を使って組込みの AverageDollarVolume ファクターを利用可能にします。

from quantopian.pipeline.factors import AverageDollarVolume

次に、AverageDollarVolumeファクターをインスタンス化します。

dollar_volume = AverageDollarVolume(window_length=30)

AverageDollarVolume はデフォルトで USEquityPricing.closeUSEquityPricing.volume を利用しているため inputs 引数を指定する必要はありません。 さてこれで売買代金ファクターが用意できたので、booleanを使ったフィルタを作成できます。 以下の行で、dollar_volume が10,000,000よりも大きい証券に対して True を返すフィルタを作成します:

high_dollar_volume = (dollar_volume > 10000000)

フィルタの中がどのようになっているか確認するため、前のレッスンで作成したパイプラインにフィルタを追加します。

def make_pipeline():

    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
    mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)

    percent_difference = (mean_close_10 - mean_close_30) / mean_close_30

    dollar_volume = AverageDollarVolume(window_length=30)
    high_dollar_volume = (dollar_volume > 10000000)

    return Pipeline(
        columns={
            'percent_difference': percent_difference,
            'high_dollar_volume': high_dollar_volume
        }
    )

パイプラインを作成・実行すると、各証券に対してフィルタの結果を表すboolean値が入った high_dollar_volume 列が作られます。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result
high_dollar_volume percent_difference
2015-05-05 00:00:00+00:00 Equity(2 [AA]) True 0.017975
Equity(21 [AAME]) False -0.002325
Equity(24 [AAPL]) True 0.016905
Equity(25 [AA_PR]) False 0.021544
Equity(31 [ABAX]) False -0.019639
Equity(39 [DDC]) False 0.074730
Equity(41 [ARCB]) False 0.007067
Equity(52 [ABM]) False 0.003340
Equity(53 [ABMD]) True -0.024682
Equity(62 [ABT]) True 0.014385
Equity(64 [ABX]) True 0.046963
Equity(66 [AB]) False 0.013488
Equity(67 [ADSK]) True -0.003921
Equity(69 [ACAT]) False -0.007079
Equity(70 [VBF]) False 0.005507
Equity(76 [TAP]) True -0.008759
Equity(84 [ACET]) False -0.056139
Equity(86 [ACG]) False 0.010096
Equity(88 [ACI]) False -0.022089
Equity(100 [IEP]) False 0.011293
Equity(106 [ACU]) False 0.003306
Equity(110 [ACXM]) False -0.029551
Equity(112 [ACY]) False -0.057763
Equity(114 [ADBE]) True 0.009499
Equity(117 [AEY]) False 0.012543
Equity(122 [ADI]) True 0.009271
Equity(128 [ADM]) True 0.015760
Equity(134 [SXCL]) False NaN
Equity(149 [ADX]) False 0.007232
Equity(153 [AE]) False -0.112999
... ... ...
Equity(48961 [NYMT_O]) False NaN
Equity(48962 [CSAL]) True 0.000000
Equity(48963 [PAK]) False 0.000000
Equity(48969 [NSA]) True 0.000000
Equity(48971 [BSM]) True 0.000000
Equity(48972 [EVA]) True 0.000000
Equity(48981 [APIC]) False 0.000000
Equity(48989 [UK]) False 0.000000
Equity(48990 [ACWF]) False 0.000000
Equity(48991 [ISCF]) False 0.000000
Equity(48992 [INTF]) False 0.000000
Equity(48993 [JETS]) False 0.000000
Equity(48994 [ACTX]) False 0.000000
Equity(48995 [LRGF]) False 0.000000
Equity(48996 [SMLF]) False 0.000000
Equity(48997 [VKTX]) False 0.000000
Equity(48998 [OPGN]) False NaN
Equity(48999 [AAPC]) False 0.000000
Equity(49000 [BPMC]) False 0.000000
Equity(49001 [CLCD]) False NaN
Equity(49004 [TNP_PRD]) False 0.000000
Equity(49005 [ARWA_U]) False NaN
Equity(49006 [BVXV]) False NaN
Equity(49007 [BVXV_W]) False NaN
Equity(49008 [OPGN_W]) False NaN
Equity(49009 [PRKU]) False NaN
Equity(49010 [TBRA]) False NaN
Equity(49131 [OESX]) False NaN
Equity(49259 [ITUS]) False NaN
Equity(49523 [TLGT]) False NaN

8236 rows × 2 columns

スクリーニング

通常、パイプラインはQuantopianのデータベースに存在するすべての資産を対象に、日付ごとに計算結果を出力します。 しかしながら、特定の基準を満たした証券の部分集合だけが必要なケースが頻繁に起こります(例えば、日々の取引が活発で注文が即座に成立するような銘柄のみが必要となることがあります)。 パイプラインの中で screen キーワードを使うと、パイプラインの実行でフィルタが False を返した銘柄をふるい落とすことができます。

出力結果を30日間の平均売買代金が10,000,000ドルよりも大きい証券だけに絞り込むには、screen の引数として high_dollar_volume をあてはめるだけです。 make_pipeline はこのような感じになります:

def make_pipeline():

    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
    mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)

    percent_difference = (mean_close_10 - mean_close_30) / mean_close_30

    dollar_volume = AverageDollarVolume(window_length=30)
    high_dollar_volume = dollar_volume > 10000000

    return Pipeline(
        columns={
            'percent_difference': percent_difference
        },
        screen=high_dollar_volume
    )

実行すると、パイプラインの出力にはそれぞれの日付において high_dollar_volume フィルタを通過した証券のみが含まれています。 例えばこのパイプラインを2015年5月5日に対して実行した出力結果は、約2,100銘柄程度となります。

(翻訳者注:以下のソースコードはprint文がpython2の文法であるため、python3では通常エラーとなる)

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
print 'Number of securities that passed the filter: %d' % len(result)
result
Number of securities that passed the filter: 2110
percent_difference
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 0.017975
Equity(24 [AAPL]) 0.016905
Equity(53 [ABMD]) -0.024682
Equity(62 [ABT]) 0.014385
Equity(64 [ABX]) 0.046963
Equity(67 [ADSK]) -0.003921
Equity(76 [TAP]) -0.008759
Equity(114 [ADBE]) 0.009499
Equity(122 [ADI]) 0.009271
Equity(128 [ADM]) 0.015760
Equity(154 [AEM]) 0.026035
Equity(161 [AEP]) 0.010405
Equity(166 [AES]) 0.022158
Equity(168 [AET]) 0.005853
Equity(185 [AFL]) -0.002239
Equity(197 [AGCO]) 0.032124
Equity(216 [HES]) 0.036528
Equity(239 [AIG]) 0.012322
Equity(253 [AIR]) -0.012412
Equity(266 [AJG]) 0.012267
Equity(270 [AKRX]) -0.024963
Equity(273 [ALU]) -0.021750
Equity(300 [ALK]) 0.015147
Equity(301 [ALKS]) -0.033228
Equity(328 [ALTR]) 0.012284
Equity(337 [AMAT]) -0.050162
Equity(351 [AMD]) -0.101477
Equity(353 [AME]) -0.003008
Equity(357 [TWX]) 0.000365
Equity(368 [AMGN]) 0.008860
... ...
Equity(48126 [HABT]) 0.063080
Equity(48129 [UBS]) 0.025888
Equity(48169 [KLXI]) 0.021062
Equity(48215 [QSR]) 0.037460
Equity(48220 [LC]) -0.035048
Equity(48317 [JUNO]) -0.103370
Equity(48384 [QRVO]) -0.050578
Equity(48465 [SWNC]) 0.061669
Equity(48486 [BOX]) -0.003837
Equity(48531 [VSTO]) 0.017196
Equity(48543 [SHAK]) 0.175877
Equity(48544 [HIFR]) 0.027339
Equity(48547 [ONCE]) -0.112191
Equity(48575 [XHR]) -0.008521
Equity(48629 [INOV]) -0.068366
Equity(48662 [JPM_PRF]) 0.002978
Equity(48672 [TOTL]) 0.000991
Equity(48730 [AGN_PRA]) -0.008843
Equity(48821 [CJES]) 0.099492
Equity(48823 [SEDG]) 0.056643
Equity(48863 [GDDY]) -0.003563
Equity(48892 [IGT]) 0.005591
Equity(48925 [ADRO]) -0.076840
Equity(48933 [PRTY]) -0.001741
Equity(48934 [ETSY]) -0.030142
Equity(48943 [VIRT]) -0.009077
Equity(48962 [CSAL]) 0.000000
Equity(48969 [NSA]) 0.000000
Equity(48971 [BSM]) 0.000000
Equity(48972 [EVA]) 0.000000

2110 rows × 1 columns

フィルタの反転

~ 演算子はフィルタの反転に使われ、 True 値を False 値に置き換えます(逆もしかり)。 例えば、売買代金の少ない証券にフィルタをかける場合は以下のように書きます:

low_dollar_volume = ~high_dollar_volume

この場合、過去30日間の平均売買代金が10,000,000ドル以下の銘柄に対して True が返ります。

次のレッスンでは、フィルタの結合について見ていきます。

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume

フィルタの結合

ファクタと同じく、フィルタも結合できます。 フィルタの結合には & (and)や | (or)演算子を使います。 例えば直近の終値が20ドルよりも高く、かつ、平均売買代金が全銘柄の上位10パーセントという条件でスクリーニングを実施したいとしましょう。 まず売買代金フィルタを AverageDollarVolume ファクター と percentile_between を使って作成します:

dollar_volume = AverageDollarVolume(window_length=30)
high_dollar_volume = dollar_volume.percentile_between(90, 100)

(備考) percentile_between は、 フィルタ を返す ファクター メソッドです。

次に latest_close ファクターを作成し、終値が20ドルよりも高い証券に絞り込むフィルタを定義します:

latest_close = USEquityPricing.close.latest
above_20 = latest_close > 20

そして high_dollar_volume フィルタと above_20 フィルタを & 演算子を使って結合します:

tradeable_filter = high_dollar_volume & above_20

このフィルタは high_dollar_volumeabove_20 の双方が True となる証券に対して True と判定します。 それら以外は False となります。 類似の演算が | (or) 演算子を使って実行可能です。 このフィルタをパイプライン内でスクリーニングに利用したい場合、 screen 引数として tradeable_filter を指定するだけです。

def make_pipeline():

    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
    mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30)

    percent_difference = (mean_close_10 - mean_close_30) / mean_close_30

    dollar_volume = AverageDollarVolume(window_length=30)
    high_dollar_volume = dollar_volume.percentile_between(90, 100)

    latest_close = USEquityPricing.close.latest
    above_20 = latest_close > 20

    tradeable_filter = high_dollar_volume & above_20

    return Pipeline(
        columns={
            'percent_difference': percent_difference
        },
        screen=tradeable_filter
    )

実行すると、パイプラインは約700銘柄を出力します。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
print 'Number of securities that passed the filter: %d' % len(result)
result

課題

print文を python3用に書き換えるか検討

Number of securities that passed the filter: 737
percent_difference
2015-05-05 00:00:00+00:00 Equity(24 [AAPL]) 0.016905
Equity(62 [ABT]) 0.014385
Equity(67 [ADSK]) -0.003921
Equity(76 [TAP]) -0.008759
Equity(114 [ADBE]) 0.009499
Equity(122 [ADI]) 0.009271
Equity(128 [ADM]) 0.015760
Equity(154 [AEM]) 0.026035
Equity(161 [AEP]) 0.010405
Equity(168 [AET]) 0.005853
Equity(185 [AFL]) -0.002239
Equity(216 [HES]) 0.036528
Equity(239 [AIG]) 0.012322
Equity(270 [AKRX]) -0.024963
Equity(300 [ALK]) 0.015147
Equity(301 [ALKS]) -0.033228
Equity(328 [ALTR]) 0.012284
Equity(357 [TWX]) 0.000365
Equity(368 [AMGN]) 0.008860
Equity(438 [AON]) 0.002327
Equity(448 [APA]) 0.035926
Equity(455 [APC]) 0.049153
Equity(460 [APD]) -0.006999
Equity(624 [ATW]) 0.014957
Equity(630 [ADP]) -0.002134
Equity(679 [AXP]) -0.011809
Equity(693 [AZO]) 0.002395
Equity(698 [BA]) -0.016685
Equity(734 [BAX]) 0.009414
Equity(739 [BBBY]) -0.027796
... ...
Equity(45269 [EVHC]) -0.004877
Equity(45451 [FEYE]) 0.042108
Equity(45558 [BURL]) -0.053654
Equity(45570 [JNUG]) 0.053977
Equity(45618 [AR]) 0.091085
Equity(45769 [WUBA]) 0.234141
Equity(45804 [ASHR]) 0.082573
Equity(45815 [TWTR]) -0.077268
Equity(45971 [AAL]) 0.008087
Equity(45978 [ATHM]) 0.063568
Equity(45993 [HLT]) -0.000895
Equity(46015 [ALLY]) 0.009605
Equity(46308 [ASPX]) 0.054145
Equity(46631 [GOOG]) 0.004730
Equity(46693 [GRUB]) -0.016904
Equity(46979 [JD]) 0.058362
Equity(47169 [KITE]) -0.049366
Equity(47208 [GPRO]) 0.061078
Equity(47230 [NSAM]) -0.037879
Equity(47430 [MBLY]) 0.050288
Equity(47740 [BABA]) -0.008354
Equity(47777 [CFG]) 0.025703
Equity(47779 [CYBR]) 0.101844
Equity(48065 [AXTA]) 0.034600
Equity(48317 [JUNO]) -0.103370
Equity(48384 [QRVO]) -0.050578
Equity(48892 [IGT]) 0.005591
Equity(48934 [ETSY]) -0.030142
Equity(48962 [CSAL]) 0.000000
Equity(48972 [EVA]) 0.000000

737 rows × 1 columns

次のレッスンでは、ファクターとフィルタのマスキングについて見ていきます。

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume

マスキング

パイプラインを処理するときに特定の資産を除外したいことがあります。資産が除外できると便利な事例が2つあります:

  1. 計算コストが高い評価式を計算したいが、特定の資産の結果だけ確認できれば良い場合。そのような計算コストが高い評価式の例として、回帰係数 ( RollingLinearRegressionOfReturns) を計算する ファクター が挙げられます。

  2. 資産どうしの比較を行いたいが、計算は全資産のうちの一部分だけを対象にして実行したい場合。たとえば ファクター メソッドのひとつである top を使って益回り(earnings yield)の高い上位200銘柄を計算したいが、一定の流動性制約を満たさない資産は除外したいと考える場合が挙げられます。

このようなユースケースに対応するため、すべての ファクター と多くの ファクター メソッドは、mask引数を受け取ることができます。 ただし、その引数は計算時にどのような資産を対象とすべきかを示す Filter になってなければなりません。

ファクターに対するマスキング

売買代金が10,000,000ドルよりも大きい銘柄のみを対象とし、値段変化率が高いまたは低い銘柄を出力するパイプラインを考えてみましょう。 これを実現するためには make_pipeline 関数を再編集して最初に high_dollar_volume フィルタを作成します。 そして high_dollar_volume フィルタを SimpleMovingAveragemask 引数として渡すことで、移動平均を計算するファクターの mask とすることができます。

# 売買代金を計算するファクター
dollar_volume = AverageDollarVolume(window_length=30)

# 高売買代金フィルタ
high_dollar_volume = (dollar_volume > 10000000)

# 終値移動平均を計算するファクター
mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10, mask=high_dollar_volume)
mean_close_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30, mask=high_dollar_volume)

# 変化率を計算するファクター
percent_difference = (mean_close_10 - mean_close_30) / mean_close_30

high_dollar_volume フィルタによって SimpleMovingAverage をマスキングすることで、終値平均値段のファクターの計算対象は マスキング無しで約8,000銘柄となるのに対し、約2,000銘柄に絞り込まれます。 mean_close_10mean_close_30 を組み合わせた percent_defference ファクターの計算は同じ約2,000銘柄を対象に行われます。

フィルタに対するマスキング

マスキングは topbottompercentile_between といったフィルタを返すメソッドに対しても適用可能です。 マスキングは結合処理の早い段階でフィルタを適用したいときに最も役立ちます。 例えば売買代金トップ10%の中で始値が最も高い50銘柄を取り出したいとします。 さらに、それらの銘柄のうち終値が第90分位から第100分位の間に含まれる銘柄を取り出したいとします。 これらは以下のようにして実現できます:

# 売買代金を計算するファクター
dollar_volume = AverageDollarVolume(window_length=30)

# 高売買代金フィルタ
high_dollar_volume = dollar_volume.percentile_between(90,100)

# 始値上位フィルタ (高売買代金フィルタでマスキング)
top_open_price = USEquityPricing.open.latest.top(50, mask=high_dollar_volume)

# 終値分位上位フィルタ (高売買代金フィルタと始値上位50銘柄フィルタでマスキング)
high_close_price = USEquityPricing.close.latest.percentile_between(90, 100, mask=top_open_price)

これを make_pipeline に追加し、high_close_price でスクリーニングをかけた空のパイプラインを出力します。

def make_pipeline():

    # 売買代金を計算するファクター
    dollar_volume = AverageDollarVolume(window_length=30)

    # 高売買代金フィルタ
    high_dollar_volume = dollar_volume.percentile_between(90,100)

    # 始値上位フィルタ (高売買代金フィルタでマスキング)
    top_open_price = USEquityPricing.open.latest.top(50, mask=high_dollar_volume)

    # 終値分位上位フィルタ (高売買代金フィルタと始値上位50銘柄フィルタでマスキング)
    high_close_price = USEquityPricing.close.latest.percentile_between(90, 100, mask=top_open_price)

    return Pipeline(
        screen=high_close_price
    )

2015年5月15日付のパイプライン実行結果は、5銘柄の出力となります。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
print 'Number of securities that passed the filter: %d' % len(result)

課題

python2コードの修正どうするか

Number of securities that passed the filter: 5

上記で行ったようにレイヤにマスクを適用することは、 「資産の絞り込み作業」 と捉えることができます。 次のレッスンでは、クラシファイアについて見ていきます。

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume

クラシファイア

クラシファイアとは、とある資産のある一時点から 文字列整数 ラベルといった 分類型の値を出力 する関数のことです。

\[F(asset, timestamp) \rightarrow category\]

たとえばクラシファイアは当該証券の取引所IDを表す文字列を出力します。 このクラシファイアを作成するには、 Fundamentals.exchange_id をインポートして、インスタンスの latest アトリビュートを参照します。

from quantopian.pipeline.data import Fundamentals

# Fudamentals.exchange_id はstring型なので、.latestはクラシファイアを返します。
exchange = Fundamentals.exchange_id.latest

以前のレッスンでは latest アトリビュートは ファクター のインスタンスを返していましたが、今回のケースでは string を返しているため、 latest アトリビュートは クラシファイア を作成していることがわかります。

同様に、ある証券に対して直近のモーニングスター社による業種コードを返すコードは クラシファイア と言えます。 このケースでは返り値は int となりますが、この整数の値は数値を表すものではなく区分を表すものなので、このコードもまたクラシファイアを作成しています。

直近の業種コードは、組込の Sector クラシファイアを使うことで取得できます。

from quantopian.pipeline.classifiers.fundamentals import Sector
morningstar_sector = Sector()

Sector を用いるのと、 Fundamentals.morningstar_sector_code.latest は同じ結果となります。

クラシファイアからフィルタを作る

クラシファイアは isnulleqstartwith などのメソッドを使ってフィルタを作成することにも使われます。 フィルタを作ることができる クラシファイア メソッドの一覧は ここ にあります。

たとえばニューヨーク証券取引所で取引されている証券を選択するフィルタが必要な場合、 exchange クラシファイアに eq メソッドを使うことで実現できます。

nyse_filter = exchange.eq('NYS')

このフィルタは直近の exchange_idNYS であるものに対して True を返します。

分位数

クラシファイアは、さまざまな ファクタ メソッドからも作り出すことができます。 その最たる例が quantiles(分位数) メソッドです。このメソッドは引数として分割数(bin)を受け取ります。 quantile メソッドは、全ての非数値型でないファクター出力に対して、0から(bin - 1)までの数値でラベル付けを行うことで、 クラシファイア を返します。 非数値( NaN )には、-1がラベル付けされます。 四分位数(quartiles) (quantiles(4) と等価) や、 五分位数(quintiles) (quantiles(5) と等価)、 十分位数(deciles) (quantiles(10) と等価) といった別名メソッドを使うこともできます。

たとえばファクター出力に対して10分割(deciles)を行い、その第10分位に含まれる銘柄を選択するフィルタはこのようになります:

dollar_volume_decile = AverageDollarVolume(window_length=10).deciles()
top_decile = (dollar_volume_decile.eq(9))

パイプラインに作成したクラシファイアをセットしてその結果を確認すると以下のようになります:

def make_pipeline():
    exchange = Fundamentals.exchange_id.latest
    nyse_filter = exchange.eq('NYS')

    morningstar_sector = Sector()

    dollar_volume_decile = AverageDollarVolume(window_length=10).deciles()
    top_decile = (dollar_volume_decile.eq(9))

    return Pipeline(
        columns={
            'exchange': exchange,
            'sector_code': morningstar_sector,
            'dollar_volume_decile': dollar_volume_decile
        },
        screen=(nyse_filter & top_decile)
    )
result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
print 'Number of securities that passed the filter: %d' % len(result)
result.head(5)
Number of securities that passed the filter: 513
dollar_volume_decile exchange sector_code
2015-05-05 00:00:00+00:00 Equity(2 [ARNC]) 9 NYS 101
Equity(62 [ABT]) 9 NYS 206
Equity(64 [ABX]) 9 NYS 101
Equity(76 [TAP]) 9 NYS 205
Equity(128 [ADM]) 9 NYS 205

クラシファイアはファクター出力に対する複雑なグループ化処理を表現することにも役立ちます。 demean や、 groupby といった集計処理は このチュートリアルの範囲を超えます。より一歩進んだクラシファイアの使い方については、今後のチュートリアルで取り上げることになるでしょう。

次のレッスンでは、パイプラインの中で使うことができるほかのデータセットを見ていきます。

パイプラインを作成する際、計算に必要な入力データを特定する方法が必要となります。 パイプライン計算に必要な入力データは、 DatasetBoundColumns を使うことで特定できます。

データセット(Dataset)と連結列(BoundColumns)

DataSets はパイプラインAPIに対して計算に必要な入力データを、どこからどのように取り出すのかを知らせる単純なオブジェクトのコレクションです。 ここまでに Dataset の一例としてすでに USEquityPricing を紹介しました。

BoundColumn とは、DataSet と密接に連結したデータ列を表します。BoundColumn のインスタンスは、DataSets のアトリビュートにアクスすることで 動的に作られます。パイプライン計算に対する入力データは、 BoundColumn 型でなければなりません。 BoundColumn の一例としてすでに USEquityPrincing.close を紹介しました。

DataSetsBoundColumn は、実際のデータを保持していない、ということを理解することが大切です。計算処理を記述してパイプラインに追加した場合、 実際の計算処理はパイプラインが実行されるまでは実際には計算を実行していないことを思い出してください。

DataSetsBoundColumn は同じ方法で解釈することができます。これらは単純に計算の入力データを定義するために使われているのです。 そしてデータはパイプラインが実行された後に配置されます。

dtypes

パイプライン計算を定義する場合、使用できる関数や操作を知るために、入力データのデータ型を知っている必要があります。 BoundColumndtype は、パイプラインが実行されたときのデータ型を教えてくれます。 たとえば、 USEquityPricingfloat 型の dtype を持っているのでファクターは USequityPricing.close を使って算術計算を実行できます。

BoundColumndtype は、計算処理のタイプも決定できます。 たとえば latest 計算では、 dtype の型によって計算処理がファクター( float )なのか、フィルタ( bool )なのか、はたまたクラシファイア( string または int) なのかが決まります。

価格データ(Pricing Data)

米国株の株価は、 USEquityPricing データセットに保存されています。 USEquityPricing は5つの列を持っています。

  • USEquityPricing.open(始値)

  • USEquityPriging.high(高値)

  • USEquityPricing.low(安値)

  • USEquityPricing.close(終値)

  • USEquityPricing.volume(出来高)

それぞれの列は floatdtype を持っています。

財務データ(Fundamental Data)

Quantopianでは モーニングスター が提供する多くの財務データを利用できます。財務データは Fundamental データセットにおいて BoundColumn として存在しており、900種類以上が利用可能です。詳しくは Quantopian Fundamental Reference を参照してください。

パートナーデータ(Partner Data)

Quantopianでは`USEquityPricing`やモーニングスター財務データのほかにも多くのデータセットを利用できます。 コンセンサス予想データ、ニュースセンチメントなどがこれらに含まれます。ほとんどのデータセットは quantopian.pipline.data 以下に、プロバイダ名で名前空間が定義されています。

USEquityPricing 同様、そのほかのデータセットもパイプライン計算に利用される列( BoundColumn )を持ちます。 詳しい情報は、データを使う際のパイプラインの例とともに、 Data Reference に記載されています。 dtype は多岐にわたります。

BoundColumns は次のレッスンで見ていくカスタムファクターで一般的に使われます。

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume

カスタムファクター

このレッスンでファクターについて最初に触れたとき、ビルトインファクターを見ていきました。 しかしながら多くの場合、実施したい計算処理はビルトインファクターとして準備されていません。 パイプラインAPIの最も強力な機能のひとつは、カスタムファクターを自身で作成できる機能です。 実施したい計算処理が組み込みに存在しなければ、自分でカスタムファクターを作れます。

概念的には、カスタムファクターはビルトインファクターと同じです。 カスタムファクターは、 inputswindow_length 、および mask をコンストラクタ引数として受け取ることができ、 Factor オブジェクトを計算対象日ごとに返します。

ビルトインとして用意されていない複雑な計算例「標準偏差」を例にとってみます。 計算区間をずらしながら 標準偏差 を計算するには quantopian.pipeline.CustomFactor を からサブクラスを作成してcomputeメソッドを実装します。このメソッドのシグネチャ(訳注:メソッドの入力と出力の定義)は:

def compute(self, today, asset_ids, out, *inputs):
    ...
  • *inputs はM行N列の numpy arrays です。 ここでMは window_length 、Nは銘柄数( mask を使わない場合は通常8,000程度)です。 *inputs は計算区間のデータとなります。 ファクターの入力リストに渡される BoundColumn ごとに1つのM行N列の配列が存在することに注意してください。各配列のデータ型は対応する BoundColumndtype となります

  • out は長さNの空の配列です。 out は計算日ごとのカスタムファクターの計算結果となります。計算ジョブは出力をoutに書き出します。

  • asset_ids は長さNの整数 配列 となります。 *inputs の列データに対応する銘柄コードが含まれます

  • todaypandas タイムスタンプ型 となります。 compute が呼び出されたときの日付を表します

当然 *inputsout が最も一般的に使われます。

パイプラインに追加された CustomFactor のインスタンスはcomputeメソッドを毎日呼び出します。 たとえば過去5日間の終値による移動標準偏差を計算するカスタムファクターを定義してみます。 まずはじめに、 CustomFactornumpy を import文に追加します。

from quantopian.pipeline import CustomFactor
import numpy

次に numpy.nanstd を使い、計算期間に対して標準偏差を計算する カスタムファクターを定義します。

class StdDev(CustomFactor):
    def compute(self, today, asset_ids, out, values):
        # NaN を無視して、列ごとの標準偏差を計算する
        out[:] = numpy.nanstd(values, axis=0)

最後にカスタムファクターを make_pipeline() の中でインスタンス化します:

def make_pipeline():
    std_dev = StdDev(inputs=[USEquityPricing.close], window_length=5)

    return Pipeline(
        columns={
            'std_dev': std_dev
        }
    )

パイプラインを実行すると、 以下のようなデータとともに StdDev.compute() が毎日呼び出されます:

  • values: 1つのM行N列の numpy array です。 ここでMは5(計算区間)で、Nは約8,000(計算対象日に存在する銘柄の数)です

  • out : 長さNの空の配列です。この例では、 compute メソッドの実行で、5日間の終値による移動標準偏差を out に書き込みます

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result
/usr/local/lib/python2.7/dist-packages/numpy/lib/nanfunctions.py:1057: RuntimeWarning: Degrees of freedom <= 0 for slice.
  warnings.warn("Degrees of freedom <= 0 for slice.", RuntimeWarning)
std_dev
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 0.293428
Equity(21 [AAME]) 0.004714
Equity(24 [AAPL]) 1.737677
Equity(25 [AA_PR]) 0.275000
Equity(31 [ABAX]) 4.402971
Equity(39 [DDC]) 0.138939
Equity(41 [ARCB]) 0.826109
Equity(52 [ABM]) 0.093680
Equity(53 [ABMD]) 1.293058
Equity(62 [ABT]) 0.406546
Equity(64 [ABX]) 0.178034
Equity(66 [AB]) 0.510427
Equity(67 [ADSK]) 1.405754
Equity(69 [ACAT]) 0.561413
Equity(70 [VBF]) 0.054626
Equity(76 [TAP]) 0.411757
Equity(84 [ACET]) 0.320624
Equity(86 [ACG]) 0.012806
Equity(88 [ACI]) 0.026447
Equity(100 [IEP]) 0.444189
Equity(106 [ACU]) 0.060531
Equity(110 [ACXM]) 0.485444
Equity(112 [ACY]) 0.207107
Equity(114 [ADBE]) 0.280385
Equity(117 [AEY]) 0.022471
Equity(122 [ADI]) 0.549778
Equity(128 [ADM]) 0.605495
Equity(134 [SXCL]) NaN
Equity(149 [ADX]) 0.072153
Equity(153 [AE]) 3.676240
... ...
Equity(48961 [NYMT_O]) NaN
Equity(48962 [CSAL]) 0.285755
Equity(48963 [PAK]) 0.034871
Equity(48969 [NSA]) 0.144305
Equity(48971 [BSM]) 0.245000
Equity(48972 [EVA]) 0.207175
Equity(48981 [APIC]) 0.364560
Equity(48989 [UK]) 0.148399
Equity(48990 [ACWF]) 0.000000
Equity(48991 [ISCF]) 0.035000
Equity(48992 [INTF]) 0.000000
Equity(48993 [JETS]) 0.294937
Equity(48994 [ACTX]) 0.091365
Equity(48995 [LRGF]) 0.172047
Equity(48996 [SMLF]) 0.245130
Equity(48997 [VKTX]) 0.065000
Equity(48998 [OPGN]) NaN
Equity(48999 [AAPC]) 0.000000
Equity(49000 [BPMC]) 0.000000
Equity(49001 [CLCD]) NaN
Equity(49004 [TNP_PRD]) 0.000000
Equity(49005 [ARWA_U]) NaN
Equity(49006 [BVXV]) NaN
Equity(49007 [BVXV_W]) NaN
Equity(49008 [OPGN_W]) NaN
Equity(49009 [PRKU]) NaN
Equity(49010 [TBRA]) NaN
Equity(49131 [OESX]) NaN
Equity(49259 [ITUS]) NaN
Equity(49523 [TLGT]) NaN

8236 rows × 1 columns

デフォルト入力

カスタムファクターの作成時、デフォルトの inputwindow_length を指定できます。 たとえば計算期間に対して numpy.nanmean を使って2つのデータ列の差の平均を計算する TenDayMeanDifference カスタムファクターを定義してみます。 デフォルト input として [USEquityPricing.close, USEquityPricing.open] window_lengthを、デフォルト window_length として 10をセットします:

class TenDayMeanDifference(CustomFactor):
    # Default inputs.
    inputs = [USEquityPricing.close, USEquityPricing.open]
    window_length = 10
    def compute(self, today, asset_ids, out, close, open):
         # NaN を無視して、列ごとの差の平均を計算する
        out[:] = numpy.nanmean(close - open, axis=0)

この例では closeopen はそれぞれ、10行8000列の二次元 numpy arrays. です。 もし TenDayMeanDifference に対して何も引数を与えずに呼び出せば、ここでセットしたデフォルト入力が使われます。

# 10日間の始値と終値の差の平均を計算する。
close_open_diff = TenDayMeanDifference()

デフォルト入力はコンストラクタ呼び出しの際に引数を与えることにより上書きできます。

# 10日間の高値と安値の差の平均を計算する。
high_low_diff = TenDayMeanDifference(inputs=[USEquityPricing.high, USEquityPricing.low])

より複雑な例

モメンタム カスタムファクターを作成し、それをフィルタの作成に使ってみます。 フィルタをパイプラインの screen として用います。

まず始めに、直近の終値を n 日前の終値で割った Momentum ファクターを定義します。ここで nwindow_length となります。

class Momentum(CustomFactor):
    # デフォルト入力
    inputs = [USEquityPricing.close]

    # モメンタムの計算
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]

ここで、 Momentum ファクターのインスタンスを2つ(10日間モメンタムと20日間モメンタム)作成します。 同時に 10日間、20日間ともに正のモメンタムを持つ銘柄に対して``True`` を返す positive_momentum フィルターを作成します。

ten_day_momentum = Momentum(window_length=10)
twenty_day_momentum = Momentum(window_length=20)

positive_momentum = ((ten_day_momentum > 1) & (twenty_day_momentum > 1))

次に、2つのモメンタムファクターと positive_momentum フィルタを make_pipeline に追加します。 また、positive_momentum をパイプラインの screen 引数に対して渡します。

def make_pipeline():

    ten_day_momentum = Momentum(window_length=10)
    twenty_day_momentum = Momentum(window_length=20)

    positive_momentum = ((ten_day_momentum > 1) & (twenty_day_momentum > 1))

    std_dev = StdDev(inputs=[USEquityPricing.close], window_length=5)

    return Pipeline(
        columns={
            'std_dev': std_dev,
            'ten_day_momentum': ten_day_momentum,
            'twenty_day_momentum': twenty_day_momentum
        },
        screen=positive_momentum
    )

このパイプライン出力は、2つのモメンタムがともに正である銘柄の、標準偏差、10日間モメンタム、20日間モメンタムを出力します。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result
std_dev ten_day_momentum twenty_day_momentum
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 0.293428 1.036612 1.042783
Equity(24 [AAPL]) 1.737677 1.014256 1.021380
Equity(39 [DDC]) 0.138939 1.062261 1.167319
Equity(52 [ABM]) 0.093680 1.009212 1.015075
Equity(64 [ABX]) 0.178034 1.025721 1.065587
Equity(66 [AB]) 0.510427 1.036137 1.067545
Equity(100 [IEP]) 0.444189 1.008820 1.011385
Equity(114 [ADBE]) 0.280385 1.016618 1.002909
Equity(117 [AEY]) 0.022471 1.004167 1.025532
Equity(128 [ADM]) 0.605495 1.049625 1.044832
Equity(149 [ADX]) 0.072153 1.004607 1.016129
Equity(154 [AEM]) 0.634920 1.032690 1.065071
Equity(161 [AEP]) 0.458938 1.024926 1.017563
Equity(166 [AES]) 0.164973 1.031037 1.045946
Equity(168 [AET]) 1.166938 1.007566 1.022472
Equity(192 [ATAX]) 0.024819 1.009025 1.018215
Equity(197 [AGCO]) 0.646594 1.066522 1.098572
Equity(239 [AIG]) 0.710307 1.027189 1.058588
Equity(253 [AIR]) 0.156844 1.007474 1.003818
Equity(266 [AJG]) 0.397769 1.000839 1.018799
Equity(312 [ALOT]) 0.182893 1.031780 1.021352
Equity(328 [ALTR]) 2.286573 1.041397 1.088996
Equity(353 [AME]) 0.362513 1.023622 1.004902
Equity(357 [TWX]) 0.502816 1.022013 1.006976
Equity(366 [AVD]) 0.842249 1.114111 1.093162
Equity(438 [AON]) 0.881295 1.020732 1.018739
Equity(448 [APA]) 0.678899 1.002193 1.051258
Equity(451 [APB]) 0.081240 1.026542 1.105042
Equity(455 [APC]) 0.152394 1.012312 1.097284
Equity(474 [APOG]) 0.610410 1.030843 1.206232
... ... ... ...
Equity(48504 [ERUS]) 0.052688 1.030893 1.052812
Equity(48531 [VSTO]) 0.513443 1.029164 1.028110
Equity(48532 [ENTL]) 0.163756 1.043708 1.152246
Equity(48535 [ANH_PRC]) 0.072388 1.010656 1.010656
Equity(48543 [SHAK]) 2.705316 1.262727 1.498020
Equity(48591 [SPYB]) 0.221848 1.001279 1.005801
Equity(48602 [ITEK]) 0.177042 1.213693 1.133721
Equity(48623 [TCCB]) 0.056148 1.003641 1.006349
Equity(48641 [GDJJ]) 0.530298 1.041176 1.111809
Equity(48644 [GDXX]) 0.401079 1.042319 1.120948
Equity(48680 [RODM]) 0.080455 1.005037 1.018853
Equity(48688 [QVM]) 0.152245 1.009996 1.021845
Equity(48701 [AMT_PRB]) 0.546691 1.010356 1.023537
Equity(48706 [GBSN_U]) 0.442285 1.214035 1.272059
Equity(48730 [AGN_PRA]) 9.614542 1.000948 1.001694
Equity(48746 [SUM]) 0.457585 1.024112 1.131837
Equity(48747 [AFTY]) 0.193080 1.032030 1.146784
Equity(48754 [IBDJ]) 0.048949 1.000161 1.000561
Equity(48768 [SDEM]) 0.102439 1.068141 1.103535
Equity(48783 [CHEK_W]) 0.222528 1.466667 1.157895
Equity(48785 [NCOM]) 0.166885 1.018349 1.020221
Equity(48792 [AFSI_PRD]) 0.062426 1.001572 1.008307
Equity(48804 [TANH]) 0.620471 1.179510 1.381538
Equity(48809 [AIC]) 0.027276 1.000399 1.008857
Equity(48821 [CJES]) 0.851751 1.220506 1.335895
Equity(48822 [CLLS]) 0.230596 1.014299 1.023526
Equity(48823 [SEDG]) 1.228733 1.207086 1.234685
Equity(48853 [SGDJ]) 0.381209 1.026782 1.060795
Equity(48863 [GDDY]) 0.453669 1.046755 1.029738
Equity(48875 [HBHC_L]) 0.025687 1.001746 1.005010

2773 rows × 3 columns

カスタムファクターはパイプライン内で独自の計算処理を定義することを可能にします。 パートナーデータセット や複数のデータ列を用いた計算を実行するうえでベストの方法になることが多いです。 カスタムファクターに関する完全なドキュメントは ここ を参照してください。

次のレッスンでは、これまでに学んだすべての内容を使い、アルゴリズムのためのパイプラインを作成します。

from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage, AverageDollarVolume

全部のせ

ここまでパイプラインAPIの基本コンポーネントについて見てきたので、アルゴリズムで使いたくなるパイプラインの構築をしてみましょう。

まず始めに、パイプライン出力の銘柄数を絞り込むためのフィルタの作成を行います。 今回は、以下の条件を全て満たす銘柄のみを抽出するフィルタを作成します。

  • プライマリ株式であること(訳注:ここ によれば、会社がIPOを行い、現在も活発に取引が行われている最初の株式のことを表す)

  • 普通株式(common stock)であること

  • 預託証券 (ADR/GDR)ではないこと

  • 相対取引 (OTC)されていないこと

  • 発行日前取引 ではないこと(訳注:発行日前取引

  • 会社名に limited partnership (LP)が含まれないこと

  • 企業情報にLPであることが記載されていないこと

  • ETF ではないこと (モーニングスターの財務データを利用する)

基準の選択理由

プライマリ株式や普通株式を選択することで、1企業に対して1銘柄のみを選択できます。 通常、プライマリ株式は企業の代表的な証券であるため、パイプラインではこれらを選択することにします(訳注:株式には普通株のほかに優先株や劣後株といった複数の種類が存在するため、代表的な株式のみをパイプラインの出力対象としていると考えられます)。

ADRやGDRは異なる取引所で売買される株式に対する米国株式市場における預託証書の取引です。 これらには為替変動による固有のリスクが発生することがしばしば起こるのでパイプラインから除外することにします。

OTCやWI、そしてLP株式はほとんどのブローカで取り扱っていないためパイプラインから除外となります。

課題

以下の一文はTutorialには存在するが、Notebookには存在しない。掲載すべきか?

証券を比較したりランク付けする場合、ETFには財務データがないため、通常の株式とETFとの比較はほぼ無意味です。 ETFの価値は保有する多数の証券から生まれます。りんごとオレンジを比較することがないよう、ETFはパイプラインから除外します。

パイプライン作成

それぞれの基準毎にフィルタを作成し組み合わせることで、 tradable_stocks フィルタを作成します。 まず、モーニングスターの DataSetIsPrimaryShare ビルトインフィルタをインポートします。

from quantopian.pipeline.data import Fundamentals
from quantopian.pipeline.filters.fundamentals import IsPrimaryShare

次に、フィルタを定義します。

# プライマリ株式に対するフィルタ。IsPrimaryShareはビルトインフィルタです。
primary_share = IsPrimaryShare()

# 普通株式に対するフィルタ(普通株と対照的な株式には「優先株」がある)
# 'ST00000001' は普通株を意味する
common_stock = Fundamentals.security_type.latest.eq('ST00000001')

# ADR ではないものに対するフィルタ。~ 演算子は、フィルタを反転させる
not_depositary = ~Fundamentals.is_depositary_receipt.latest

# OTC ではないものに対するフィルタ。
not_otc = ~Fundamentals.exchange_id.latest.startswith('OTC')

# WI ではないものに対するフィルタ。
not_wi = ~Fundamentals.symbol.latest.endswith('.WI')

# 名前にLPを含まないものに対するフィルタ。.matches は正規表現を使って検索します。
not_lp_name = ~Fundamentals.standard_name.latest.matches('.* L[. ]?P.?$')

# モーニングスターの財務データ中の limited_partnarship フィールドがNullであるものに対するフィルタ。
not_lp_balance_sheet = Fundamentals.limited_partnership.latest.isnull()

# 直近のモーニングスターの時価総額フィールドがNullでない場合はETFではない。
have_market_cap = Fundamentals.market_cap.latest.notnull()

# 以上全てのフィルタに適合する銘柄に対するフィルタ
tradeable_stocks = (
    primary_share
    & common_stock
    & not_depositary
    & not_otc
    & not_wi
    & not_lp_name
    & not_lp_balance_sheet
    & have_market_cap
)

フィルタを定義する際、これまでのレッスンで取り扱っていない notnullstartswithendswidh 、 matches といった Classifier メソッドを使っていることに注意してください。 これらのメソッドに関するドキュメントは ここ を参照してください。

次に、20日間の平均売買代金の上位30%に対するフィルタを作成します。ここではこのファクターを base_universe と命名します。

base_universe = AverageDollarVolume(window_length=20, mask=tradeable_stocks).percentile_between(70, 100)

ビルトインユニバース

いまここで、売買代金にもとづいて`売買可能な`銘柄を選択する基本ユニバースを構築しましたが、Quantopianでは同様のことを実現するビルトインフィルタを 用意しています。その中でも最良で最新のフィルタが、 QTradableStocksUS です。

QTradableStocksUSは日々の銘柄ユニバースを選択するビルトインフィルタです。 このフィルタは3種類のフィルタを通して選択基準を維持する、サイズ制約のない可能な限り流動性の高いユニバースを構築します。 QTradableStocksUS は以前の Q500USQ1500US フィルタとは異なりサイズ上限がありません。

QTradableStocksUSに関するより詳細な銘柄選定基準は、ここ を参照してください。

パイプラインを簡略化するため、ここまでに書いた base_universeQTradableStocksUS ビルトインファクターに置き換えます。 まず、import文が必要です。

from quantopian.pipeline.filters import QTradableStocksUS

次にbase_universeに対して、 QTradableStocksUS をセットします。

base_universe = QTradableStocksUS()

これで銘柄の絞り込みを行う base_universe が用意できました。次は対象銘柄に適用するファクターの構築に目を向けます。 今回は、平均回帰戦略(mean reversion strategy)のためのパイプラインを作成します。 この戦略は、10日間と30日感の移動平均値段(終値)を使います。 2つの移動平均の変動率が最も小さい75銘柄に対して同じ金額だけ株式を保有する一方、 変動率が最も大きい75銘柄に対して同じ金額を空売りするアルゴリズムとします。

これを実現するため、 base_universe フィルタをパイプラインのマスクとして適用し、2つの移動平均ファクタを作成します。 そして2つのファクターを組み合わせて、変動率を計算するファクターを作成します。

# 10日間の終値移動平均
mean_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10, mask=base_universe)

# 30日間の終値移動平均
mean_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30, mask=base_universe)

percent_difference = (mean_10 - mean_30) / mean_30

次に percent_difference を使い、上位75銘柄と下位75銘柄を選択するフィルタをそれぞれ作成します。

# 空売りする銘柄を選択するフィルタを作成
shorts = percent_difference.top(75)

# 購入する銘柄を選択するフィルタを作成
longs = percent_difference.bottom(75)

shortslongs を結合して、パイプラインのスクリーニングに使うフィルタを作成します。

securities_to_trade = (shorts | longs)

コードの前の方にあるフィルタはこの最終フィルタ( securities_to_trade )を構築するためのマスクとして使用してきたので、 securities_to_trade をスクリーンとして用いるとパイプライン出力される銘柄はこのレッスンの冒頭で見てきた基準 (プライマリ株、非ETFなど)を満たします。同様に売買代金も高いものとなります。

(訳注:base_universeはの選択基準は途中でQTradableStockUSに置き換えているため、実際にはQTradableStockUSの選択基準を満たしています)

最後に、パイプラインをインスタンス化します。 同じ金額だけ購入あるいは空売りをするアルゴリズムとしているので、パイプライン出力する情報は取引を行う銘柄(パイプラインのインデックスとして出力されます)と、 それを購入するのか空売りするのかという情報だけです。longsshorts フィルタをパイプラインに追加し、screenとして securities_to_trade をセットします。

def make_pipeline():

    # ベースとなるユニバース
    base_universe = QTradableStocksUS()

    # 10日間の終値移動平均
    mean_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10, mask=base_universe)

    # 30日間の終値移動平均
    mean_30 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=30, mask=base_universe)

    # 変化率ファクタ
    percent_difference = (mean_10 - mean_30) / mean_30

    # 空売り銘柄を選択するフィルタ
    shorts = percent_difference.top(75)

    # 購入銘柄を選択するフィルタ
    longs = percent_difference.bottom(75)

    # 売買を行う銘柄を選択するフィルタ
    securities_to_trade = (shorts | longs)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=securities_to_trade
    )

パイプラインの実行結果は、2つの列データを持つDataFrameが返ってきます。 これらの列には銘柄に対する購入するか空売りするかを示すboolean値が日付ごとに格納されます。

result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result.head()
longs shorts
2015-05-05 00:00:00+00:00 Equity(39 [DDC]) False True
Equity(351 [AMD]) True False
Equity(371 [TVTY]) True False
Equity(474 [APOG]) False True
Equity(523 [AAN]) False True

次のレッスンでは、このパイプラインをアルゴリズムに追加します。

IDEへの移行

ここまでのレッスンはResearch環境でパイプラインを作成して実行してきました。 いよいよIDE環境へ移行します。まず始まる前に、Pipeline をインポートのインポートと 空のパイプライン出力を返す make_pipeline を作成したスケルトンアルゴリズムを作ります。

(訳注:このレッスンのコードはResearch環境では動作しません。IDE環境(アルゴリズム)環境で動作します)

import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline

def initialize(context):
    my_pipe = make_pipeline()
    algo.attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    return Pipeline()

パイプラインへのアタッチ

Research環境では make_pipline はパイプラインオブジェクトのインスタンス化を行い、 run_pipeline は実行期間指定の指定とパイプライン実行を行ってきました。 アルゴリズムの中でこれを安全に実行することはできないため、どうにかしてアルゴリズムの シミュレーション(訳注:IDE環境でアルゴリズムのバックテストを行うこと)でパイプラインを実行させる必要があります。 シミュレーションでパイプラインを実行させるためには、 attach_pipeline を使ってパイプラインをアタッチします。

attatch_pipeline 関数は2つの引数を必要とします。 Pipeline オブジェクトへの参照、および文字列による任意のパイプライン名です。 attatch_pipeline をインポートして、スケルトンアルゴリズムに作成した空のパイプラインをアタッチします。

import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline

def initialize(context):
    my_pipe = make_pipeline()
    algo.attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    return Pipeline()

パイプラインにアタッチできたので、パイプラインは毎日一度実行されるようになりました。もし2016年6月6日(月)から2016年6月10日(金)まで アルゴリズムのバックテストやライブトレードを行った場合、パイプラインは毎日1回(合計5回)実行されます。 アタッチしたパイプラインは、実行日ごとに新しいDataFrameを出力します。しかしながら、現在のシミュレーション対象日はパイプライン計算の日付 として暗に示されるため出力されたDataFrameは日付によるインデクスを持ちません。

パイプライン出力

日々のパイプライン出力結果は、 before_trading_start の中の pipeline_output で取り出すことができます。 pipeline_output は引数としてアタッチしたパイプライン名を必要とし、シミュレーション対象日におけるシミュレーション結果である Dataframeを返します。pipeline_output をインポートし、スケルトンアルゴリズムを修正して日々のパイプライン出力を context に ストアできるようにします。

import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline

def initialize(context):
    my_pipe = make_pipeline()
    algo.attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    return Pipeline()

def before_trading_start(context, data):
    # パイプライン出力のDataFrameをcontextにストアする
    context.output = algo.pipeline_output('my_pipeline')

これでスケルトンアルゴリズムは約8000行と0個の列を持つ空のDataframeを毎日出力するようになりました。 出力結果はこのようになります(Research環境とは異なり、 MultiIndex にならないことに注目してください)。

Equity(2 [AA])
Equity(21 [AAME])
Equity(24 [AAPL])
Equity(25 [AA_PR])
Equity(31 [ABAX])

Research環境で作成したパイプラインの使用法

以前のレッスン で作成したパイプラインのアルゴリズムに移行するには、 Research環境で使った必要なimport文と、 make_pipeline 関数を、アルゴリズムにコピーするだけです。 以下のパイプラインを実行すると150行と2つの列( longsshorts )をもつDataframeをシミュレーション対象日ごとに

context にストアします。

import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS

def initialize(context):
    my_pipe = make_pipeline()
    algo.attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """
    パイプラインを作成する
    """

    # ベースとなるユニバースとして、QTradableStockUSをセット
    base_universe = QTradableStocksUS()

    # 10日間の終値移動平均
    mean_10 = SimpleMovingAverage(
        inputs=[USEquityPricing.close],
        window_length=10,
        mask=base_universe
    )

    # 30日間の終値移動平均
    mean_30 = SimpleMovingAverage(
        inputs=[USEquityPricing.close],
        window_length=30,
        mask=base_universe
    )

    percent_difference = (mean_10 - mean_30) / mean_30

    # 空売り銘柄を選択するフィルタ
    shorts = percent_difference.top(75)

    # 購入銘柄を選択するフィルタ
    longs = percent_difference.bottom(75)

    # 全取引銘柄を選択するフィルタ
    securities_to_trade = (shorts | longs)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(securities_to_trade),
    )

def before_trading_start(context, data):
    # パイプライン出力のDataFrameをcontextにストアする
    context.output = algo.pipeline_output('my_pipeline')

シミュレーション対象日ごとのパイプライン出力は以下のような感じになります:

longs shorts
Equity(39 [DDC]) False True
Equity(351 [AMD]) True False
Equity(371 [TVTY]) True False
Equity(474 [APOG]) False True
Equity(523 [AAN]) False True

購入または空売りを行う銘柄に対する取引量の計算と、発注を行うための関数をいくつか作成し、パイプライン出力で指定します。 Getting Started Tutorial で学んだ基本知識を使い、 取引量の計算と発注を行う関数を実装します。

def compute_target_weights(context, data):
    """
    取引量を計算します。
    """
    # 目標取引量を格納する空のディクショナリの初期化
    # 銘柄と目標取引量をマッピングします。
    weights = {}

    # longs または shorts のリストに銘柄が存在する場合、
    # 全ての銘柄が等しくなるよう取引量を決定します。
    if context.longs and context.shorts:
        long_weight = 0.5 / len(context.longs)
        short_weight = -0.5 / len(context.shorts)
    else:
        return weights

    # longs または shorts のリストの中に保有銘柄が含まれていない場合、
    # ポジションを解消(訳注:保有量をゼロにする)します。
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    パイプライン出力を取得します。
    """

    # シミュレーション日ごとにパイプライン出力を取得します。
    pipe_results = algo.pipeline_output('my_pipeline')

    # `longs`  列の値がTrueの場合は購入対象となります。
    # 取引可能かどうかを併せてチェックします。
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # `shorts`  列の値がTrueの場合は空売り対象となります。
    # 取引可能かどうかを併せてチェックします。
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)

def my_rebalance(context, data):
    """
    週1度リバランスを実行します。
    """

    # リバランスの際の目標取引量を計算します。
    target_weights = compute_target_weights(context, data)

    # 計算できたらポートフォリオのリバランスを実行します。
    if target_weights:
        algo.order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )

最後にここまでの成果をひとまとめにします。リバランスの回数は週1回とします。

(訳注:Quantopianのオンラインレッスン上では、Cloneボタンをクリックすることで以下のアルゴリズムを自分のIDE環境にコピーできます)

from quantopian.algorithm import order_optimal_portfolio
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS
import quantopian.optimize as opt

def initialize(context):
    # リバランス関数を週の始めの取引開始時に実行します。
    schedule_function(
        my_rebalance,
        date_rules.week_start(),
        time_rules.market_open()
    )

    # パイプラインを作成しアルゴリズムにアタッチします。
    my_pipe = make_pipeline()
    attach_pipeline(my_pipe, 'my_pipeline')

def make_pipeline():
    """
    パイプラインを作成します。
    """

    # ベースとなるユニバースとしてQTradableStocksUSをセットします。
    base_universe = QTradableStocksUS()

    # 10日間の終値移動平均を計算します。
    mean_10 = SimpleMovingAverage(
        inputs=[USEquityPricing.close],
        window_length=10,
        mask=base_universe
    )

    # 30日間の終値移動平均を計算します。
    mean_30 = SimpleMovingAverage(
        inputs=[USEquityPricing.close],
        window_length=30,
        mask=base_universe
    )

    percent_difference = (mean_10 - mean_30) / mean_30

    # 空売り銘柄を選択するフィルタ
    shorts = percent_difference.top(75)

    # 購入銘柄を選択するフィルタ
    longs = percent_difference.bottom(75)

    # 全取引銘柄を選択するフィルタ
    securities_to_trade = (shorts | longs)

    return Pipeline(
        columns={
            'longs': longs,
            'shorts': shorts
        },
        screen=(securities_to_trade),
    )

def compute_target_weights(context, data):
    """
    取引量を計算します。
    """

    # 目標取引量を格納する空のディクショナリの初期化
    # 銘柄と目標取引量をマッピングします。
    weights = {}

    # longs または shorts のリストに銘柄が存在する場合、
    # 全ての銘柄が等しくなるよう取引量を決定します。
    if context.longs and context.shorts:
        long_weight = 0.5 / len(context.longs)
        short_weight = -0.5 / len(context.shorts)
    else:
        return weights

    # longs または shorts のリストの中に保有銘柄が含まれていない場合、
    # ポジションを解消(訳注:保有量をゼロにする)します。
    for security in context.portfolio.positions:
        if security not in context.longs and security not in context.shorts and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight

    for security in context.shorts:
        weights[security] = short_weight

    return weights

def before_trading_start(context, data):
    """
    パイプライン出力を取得します。
    """

    # シミュレーション日ごとにパイプライン出力を取得します。
    pipe_results = pipeline_output('my_pipeline')

    # `longs`  列の値がTrueの場合は購入対象となります。
    # 取引可能かどうかを併せてチェックします。
    context.longs = []
    for sec in pipe_results[pipe_results['longs']].index.tolist():
        if data.can_trade(sec):
            context.longs.append(sec)

    # `shorts`  列の値がTrueの場合は空売り対象となります。
    # 取引可能かどうかを併せてチェックします。
    context.shorts = []
    for sec in pipe_results[pipe_results['shorts']].index.tolist():
        if data.can_trade(sec):
            context.shorts.append(sec)

def my_rebalance(context, data):
    """
    週1度リバランスを実行します。
    """

    # リバランスの際の目標取引量を計算します。
    target_weights = compute_target_weights(context, data)

    # 計算できたらポートフォリオのリバランスを実行します。
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )

パイプラインをバックテストで実行する場合、全体の計算スピードを向上させるためにバッチ実行で行われる点に注意してください。 そのためパフォーマンスチャートは周期的に止まって見えます。

パイプラインチュートリアルは以上です。 ぜひResearch環境でパイプラインを自分自身でデザインし、アルゴリズムで実行してみてください。

Lectures

appendix

用語集

株式を ロング します。

ロング

銘柄を買うこと

ショート

銘柄を売ること

ターンオーバー

売買回転率