最終プロジェクトでは、移動平均、入力、条件、自作関数、注文、表示を1つのファイルにまとめます。すべての判断を、データと記述したルールまでさかのぼって確認できるプログラムが目標です。

コードを書く前に説明する

2本のEMAのクロスを調べ、3本目の長期SMAで、終値が平均より上にあるかも確認します。

ロングの条件は、短期EMAが長期EMAを上抜けし、終値がトレンドフィルターのSMAより上にあることです。長さは短期 < 長期 < トレンドの順にします。満たさなければ、エントリー注文を出しません。

EMAの下抜けで成行決済します。エントリーには、tick単位のストップと利益目標も付けます。固定数量1、手数料0.1%、スリッページ2 tickを使います。

以上が課題のルールです。特定の市場で利益が出るとは判断しません。確認には、本の固定したデモデータを使います。

プログラム全体

final_strategy.pyを作ります。

"""
@pyne edge
"""
from pynecore.lib import close, color, input, location, plot, plotshape, script, shape, strategy, ta


def lengths_are_ordered(fast: int, slow: int, trend: int) -> bool:
    return fast < slow and slow < trend


@script.strategy(
    "Filtered Crossing Strategy",
    overlay=True,
    initial_capital=10000,
    default_qty_type=strategy.fixed,
    default_qty_value=1,
    pyramiding=1,
    commission_type=strategy.commission.percent,
    commission_value=0.1,
    slippage=2,
)
def main(
    fast_length: int = input.int(9, title="Fast EMA length", minval=1, maxval=200),
    slow_length: int = input.int(21, title="Slow EMA length", minval=1, maxval=200),
    trend_length: int = input.int(50, title="Trend SMA length", minval=1, maxval=200),
    use_trend_filter: bool = input.bool(True, title="Use trend filter"),
    stop_ticks: int = input.int(100, title="Stop distance (ticks)", minval=1),
    target_ticks: int = input.int(200, title="Target distance (ticks)", minval=1),
):
    fast_average = ta.ema(close, fast_length)
    slow_average = ta.ema(close, slow_length)
    trend_average = ta.sma(close, trend_length)
    crosses_up = ta.crossover(fast_average, slow_average)
    crosses_down = ta.crossunder(fast_average, slow_average)
    valid_lengths = lengths_are_ordered(fast_length, slow_length, trend_length)
    trend_passes = not use_trend_filter or close > trend_average
    entry_signal = valid_lengths and crosses_up and trend_passes
    if entry_signal:
        strategy.entry("Long", strategy.long)
    if crosses_down:
        strategy.close("Long")
    strategy.exit("Bracket", "Long", loss=stop_ticks, profit=target_ticks)
    plot(fast_average, "Fast EMA", color=color.blue, linewidth=2)
    plot(slow_average, "Slow EMA", color=color.orange, linewidth=2)
    plot(trend_average, "Trend SMA", color=color.gray, linewidth=2)
    plotshape(entry_signal, "Entry signal", style=shape.triangleup,
              location=location.belowbar, color=color.green)

保存してデモデータで実行します。3本の平均と、エントリー条件を表す緑の三角が現れます。実際のエントリーと決済は、ストラテジーの取引マーカーが示します。

final_strategy.pyのチャートに短期と長期のEMA、トレンド用SMA、エントリーシグナルと約定した取引が表示されている。
最終プロジェクトは、3本の平均、切り替え可能なフィルター、エントリーに対応する決済ルールを使います。

すべての足で計算するもの

3本の平均と2つのクロスは、条件の前に実行します。フィルターでエントリーしない場合も計算するため、全ローソク足にわたって計算状態が進みます。

lengths_are_orderedは3つの数を調べて論理値を返す自作関数で、ローソク足間の独自の状態はありません。valid_lengthsは、現在の結果を後の式で使うために保持します。

trend_passesは、フィルターが無効か、終値がSMAより上なら真です。input.boolで切り替え可能な論理入力を作ります。フィルター無効時はnot use_trend_filterが真なので、2つ目の条件を調べる必要がありません。

フィルターを無効にしても、平均は計算して描画します。切り替えるのは、線の表示ではなくエントリー判定です。

長さの順序が正しくない場合

各入力の範囲は、それぞれ1から200という制限だけです。短期30、長期10とも指定できてしまい、課題の順序に反します。

そのため、確認用の関数で関係を調べます。順序が違っても線は描画しますが、entry_signalは真になりません。課題で定めた長さの関係を外れたまま、エントリーしないようにしています。

デバッガーでvalid_lengthsを直接確認できます。保存済み入力が不適切なら、実行全体で偽のままです。

シグナルが出ても取引になるとは限らない

緑の三角はエントリー条件を示します。ストラテジーはシミュレーターに注文を渡します。約定には、次の足があること、十分な資金、許容数量など、さらに条件がある場合があります。

pyramiding=1のため、すでにロングを保有していると、追加のロングは許可されない場合があります。シグナルの数と取引数を同じだと思わないでください。

先頭では、トレンド用の50本SMAがまだありません。フィルター有効時は、その平均より価格が上という条件を真にできず、エントリー条件も満たしません。

動作を確認する

まず既定値を使い、Propertiesで数量、費用、約定方法を確認します。以前保存した上書き設定が、ソースの設定を変更していることがあります。

緑のシグナルを1つ選びます。デバッガーかチャートの値で、現在と前の足の2つのEMAを調べます。クロス、SMAフィルター、長さの関係を確認してください。3つとも真である必要があります。

次にTradesで対応するエントリーを探し、どの足で、いくらで約定したか確認します。決済は、クロス、ストップ、目標のどれで説明できるかを調べます。

予想できるケースを試す

動作確認には、結果を前もって予想できる変更を使います。

変更確認すること
短期と長期を同じにする長さの確認により新しいエントリー印がない
フィルターを無効にするクロスに加えてSMAより上の価格は不要になる
ストップと目標を広げる同じ値動きでも決済の足が変わり得る
数量を変える価格シグナルは同じでも損益金額が変わり得る

1回に1つだけ変えます。先に予想を書き、実際の結果を記録してください。合わなければ、デバッガーで最初に違う判断を探します。

結果を比較する

フィルターの有効・無効を比べるときは、データ、数量、費用、長さを同じにします。Performanceの資産曲線とTradesを合わせて見てください。取引の少なさや最後の未決済ポジションも、集計に影響します。

実際の過去データを使う場合、設定に使う期間と、別に確認する期間を、結果を見る前に決めておきます。確認期間を見てからパラメーターを調整すると、その期間も設定に使ったことになります。

入力データの先頭には、十分なウォームアップ用の足が必要です。ある期間だけを切り出して実行すると、移動平均とポジション状態を作り直します。そのため、同じ期間でも、長いデータの途中で実行した結果と、最初の部分が異なることがあります。

自分で試す

Use crossings exitスイッチを加えてください。有効なら下抜けによる決済を残し、無効ならストップと目標だけで決済します。下抜け自体は両方で計算します。

解答

main()のパラメーターに加えます。

use_crossing_exit: bool = input.bool(True, title="Use crossings exit"),

決済の分岐です。

if use_crossing_exit and crosses_down:
    strategy.close("Long")

crosses_down = ta.crossunder(...)は条件の前に残します。strategy.exitは、スイッチの状態にかかわらず残します。

クロス決済を無効にすれば、EMAの下抜け後もポジションが残る場合があります。次のエントリーシグナルが出ても、既存ポジションがあるため、新しい取引は開かない場合があります。

自分の版を作る

別名でコピーし、加えたいルールを1つ文章にします。どのデータからどの足で判断し、どう手計算で確認し、どの入力で変えるかを説明してください。

最初は新ルールを独立した変数で計算します。エントリーや決済の条件に使う前に、描画するか、デバッガーで観察します。全体の損益を説明しようとする前に、計算そのものの誤りを探せます。

ソース、保存した設定、使ったデータがそろって実験を表します。残しておけば、実行を再現し、確認できます。