単純移動平均を例にストラテジーを作る


概要

ストラテジーの作成方法を、移動平均を使った例で説明します。

インジケーターと共通の部分は説明を省略することもあります。

操作

トレーディングツール「新規登録>ストラテジー」からインジケーター作成画面を開いてください。

売買のサンプル

まず、売買のサンプルとしてリプレイ開始からバーが5回変わったタイミングで成り行き売買をするサンプルを作成します。

export default (): Strategy => {
    return {
        type: "s", //1
        name: "tutor.tradeSample",
        onInit() {
            let barChangeCount = 0;
            this.onChartChange = ({ index, opens, isBarChanged }) => {
                if (isBarChanged //2
                    && ++barChangeCount === 5) {
                    const open = opens.get(index);
                    const limit = open + 1000;
                    const stop = open - 1000;
                    this.entry({ orderType: 'market', direction: 'buy', lot: 1, limit, stop }) //3
                }
            }
        },
    }
}
               
1 typeプロパティに"s"を指定します
2 表示中のチャートのバーが変わったタイミングかどうかを判定しています。 (通常リプレイ時のみ必要。ストラテジーテスター使用時は常にtrue)
3 成行で買い注文を実行しています。 また、バージョン1.8.4.0時点で指値決済注文は新規成り行き注文時にのみ設定可能です。

このストラテジーを登録後にチャートに追加し、任意の日時に移動しリプレイを実行してください。 リプレイ開始からバーが5回変わった時に買い注文を実行し、指値・逆指値が刺されば決済をします

リプレイ速度が早すぎるとonChartChangeにindexが渡されない為売買が実行しないことがあります。 この問題を回避するにはストラテジーテスターを使用します。ストラテジーテスター

インジケーターの利用

次に、単純移動平均(builtIn.SMA)を使うストラテジーを作成します。(まだ売買は行いません)

このサンプルを実行する前に、tutor.SMAを登録してください 参照:単純移動平均を作る
tutor.SMAのコード
export default (): ItemIndicator<
    {
        period: number
        lineColor: string
        lineWidth: number
        lineDash: number[]
        targetOhlc: 'opens' | 'highs' | 'lows' | 'closes' 
    }, {
        smaBuffer: IndicatorBuffer
    }

> => {
    return {
        type: 'i',
        name: "tutor.SMA",
        onInit() {
            const { period, targetOhlc, ...smaParams } = this.params
            const smaBuffer = this.smaBuffer = this.addLineBuffer(smaParams)
            this.shortName = 'SMA'
            this.onChartChange = ({ index, ...rest }) => {
                const targetBuffer = rest[targetOhlc] 
                if (!targetBuffer) {
                    throw new Error(`${targetOhlc}にはopens,highs,lows,closesを指定してください`) 
                }
                targetBuffer.sma(smaBuffer, period, index)
            }
            this.getDisplayData = (index) => {
                return {
                    sma: smaBuffer.get(index)
                }
            }
        },
        params: {
            period: 25,
            lineColor: 'red',
            lineWidth: 2,
            lineDash: [3, 3],
            targetOhlc: 'closes'
        }
    }
}
               
export default (): Strategy => {
    return {
        type: "s",
        name: "tutor.displayIndicatorStrategySample",
        onInit() {
            const params = this.params;
            const mainContainerIndicator = this.chart.mainContainerIndicator //1
            //短期SMA
            const fastSMA = mainContainerIndicator.addItemIndicator("tutor.SMA", params.fast) //2
            //長期SMA
            const slowSMA = mainContainerIndicator.addItemIndicator("tutor.SMA", params.slow) //3
        },
        params:
        {
            fast: // 短期SMAのデフォルト値
            {
                period: 5,
                lineColor: "lime"
            },
            slow:  // 長期SMAのデフォルト値
            {
                period: 20,
                lineColor: "cyan"
            }
        }
    }
}
               
1

メインコンテナインジケーターへの参照はchartプロパティで取得できるchartオブジェクトのmainContainerIndicatorプロパティで取得できます。

全てのコンテナインジケーターの参照はchartプロパティで取得できるchartオブジェクトのconatainerIndicatorsプロパティで取得できます。
chart.mainContainerIndicatorはchart.containerIndicators[0]と同じです。
2

メインコンテナインジケーターに既存のインジケータ「tutor.SMA」を追加しています。

tutor.SMAのパラメーターは下記ですが、ここではperiodとlimeColorのみを設定しています。他の未設定の値はtutor.SMAのデフォルトの値が使用されます。

既存のインジケーターのデフォルトのパラメーターを取得するにはIndicator.getParameter静的メソッドを使用します。

const smaParams = Indicator.getParameter('tutor.SMA')
                               
3 ここでも2とはパラメーターを変えてtutor.SMAを呼び出しています。

この状態で登録し、チャートに追加すると2本のSMAがチャートに表示されます。

インジケーターを利用した売買

上記のコードで売買を行うには下記のように修正してください

export default (): Strategy => {
    return {
        type: "s",
        name: "tutor.smaCrossStrategy",
        onInit() {
            this.shortName = 'smaCross'
            const params = this.params;
            const mainContainerIndicator = this.chart.mainContainerIndicator //1
            //短期SMA
            const fastSMA = mainContainerIndicator.addItemIndicator("tutor.SMA", params.fast) //2
            //長期SMA
            const slowSMA = mainContainerIndicator.addItemIndicator("tutor.SMA", params.slow) //3
            //ポジション保有数 (今後ポジション情報を参照できるように修正予定ですが、現状参照できない為このようにストラテジーの中で管理してください)
            let buyPositionCount = 0; //1
            let sellPositionCount = 0;
            this.onChartChange = ({ index, times, opens, highs, lows, closes, spreads, isBarChanged }) => { // バーまたは価格が変わった時の処理
                if (!isBarChanged) return;
                const fastBuffer = fastSMA.smaBuffer //4
                const slowBuffer = slowSMA.smaBuffer
                if (fastBuffer.get(index - 2) <= slowBuffer.get(index - 2)
                    && fastBuffer.get(index - 1) > slowBuffer.get(index - 1) // ゴールデンクロス判定
                ) {
                    if (sellPositionCount > 0) { // 売りポジションを持っていれば
                        this.entry({ orderType: 'market', direction: 'buy', lot: 1, hedging: false })// 買を実行(反対売買で売りポジションを決済)
                        sellPositionCount = 0;
                    }
                    if (buyPositionCount == 0) { // 買いポジションを持っていなければ
                        this.entry({ orderType: 'market', direction: 'buy', lot: 1, hedging: false })//買を実行(新規)
                        buyPositionCount = 1;
                    }
                }
                if (fastBuffer.get(index - 2) >= slowBuffer.get(index - 2)
                    && fastBuffer.get(index - 1) < slowBuffer.get(index - 1)) { // デッドクロス判定
                    if (buyPositionCount > 0) {// 買いポジションを持っていれば
                        this.entry({ orderType: 'market', direction: 'sell', lot: 1, hedging: false })// 売りを実行(反対売買で買いポジションを決済)
                        buyPositionCount = 0
                    }
                    if (sellPositionCount == 0) { // 売りポジションを持っていなければ
                        this.entry({ orderType: 'market', direction: 'sell', lot: 1, hedging: false })// 売りを実行(新規)
                        sellPositionCount = 1;
                    }
                }
            }
        },
        params:
        {
            fast: // 短期SMAのデフォルト値
            {
                period: 5,
                lineColor: "lime"
            },
            slow:  // 長期SMAのデフォルト値
            {
                period: 20,
                lineColor: "cyan"
            }
        }
    }
}
               
1 2022年10月時点ではポジション情報の取得ができない為、ストラテジーの中で現在保有しているLot数を管理しています。
ポジション情報の参照は今後追加予定です。
2 onChartChangeで、onInit時に設定したインジケーターから目的のインジケーターバッファーを取得し、売買の判定をしています。

このストラテジーをチャートに追加すると、ゴールデンクロス、デッドクロスで売買を行います。

売買のタイミングをインジケーターで計算し、それを使って売買する

文字を表示するで2つの移動平均のクロスと売買のタイミングを表示するインジケーターを作成しました。 ストラテジーからそのインジケーターを読み込んで売買をさせることも可能です。
売買のタイミングは上記と同じですが、既存のインジケーターを参照していることが異なります。

このサンプルを実行する前に、tutor.SMA,tutor.SmaCrossSignalを登録してください 参照:単純移動平均を作る文字を表示する
tutor.SMA,tutor.SmaCrossSignalのコード
export default (): ItemIndicator<
    {
        period: number
        lineColor: string
        lineWidth: number
        lineDash: number[]
        targetOhlc: 'opens' | 'highs' | 'lows' | 'closes' 
    }, {
        smaBuffer: IndicatorBuffer
    }

> => {
    return {
        type: 'i',
        name: "tutor.SMA",
        onInit() {
            const { period, targetOhlc, ...smaParams } = this.params
            const smaBuffer = this.smaBuffer = this.addLineBuffer(smaParams)
            this.shortName = 'SMA'
            this.onChartChange = ({ index, ...rest }) => {
                const targetBuffer = rest[targetOhlc] 
                if (!targetBuffer) {
                    throw new Error(`${targetOhlc}にはopens,highs,lows,closesを指定してください`) 
                }
                targetBuffer.sma(smaBuffer, period, index)
            }
            this.getDisplayData = (index) => {
                return {
                    sma: smaBuffer.get(index)
                }
            }
        },
        params: {
            period: 25,
            lineColor: 'red',
            lineWidth: 2,
            lineDash: [3, 3],
            targetOhlc: 'closes'
        }
    }
}
               
export default (): ItemIndicator<{
    fast: {
        period: number,
        lineColor: string
    },
    slow: {
        period: number,
        lineColor: string
    }
}, {
    entryPointTextBuffer: TextBuffer 
}> => {
    return {
        type: "i",
        name: "tutor.SmaCrossSignal",
        onInit() {
            this.zIndex = 1; 
            const params = this.params;
            const origParams = IndicatorUtil.getParameter("tutor.SMA")
            const fastParams = { ...origParams, ...params.fast }
            const slowParams = { ...origParams, ...params.slow }
            const fastBuffer = this.addItemIndicator("tutor.SMA", { ...fastParams, ...{ zIndex: 0 } }).smaBuffer
            const slowBuffer = this.addItemIndicator("tutor.SMA", { ...slowParams, ...{ zIndex: 0 } }).smaBuffer
            const smaCrossSignalBuffer = this.addTextBuffer({
                arrow: false,
                backgroundColor: 'white',
                borderColor: 'black',
                borderWidth: 0,
                color: 'black',
                fontFamily: 'Font Awesome 6 Free',
                fontSize: { min: 20, max: 30 },
            })
            const entryPointTextBuffer = this.entryPointTextBuffer = this.addTextBuffer({ 
                arrow: true,
                backgroundColor: 'white',
                borderColor: 'black',
                color: 'red',
                fontSize: { min: 20, max: 30 },
                padding: 5
            })
            this.onChartChange = ({ index, opens, isBarChanged }) => {
                if (isBarChanged) {
                    if (fastBuffer.get(index - 2) <= slowBuffer.get(index - 2)
                        && fastBuffer.get(index - 1) > slowBuffer.get(index - 1)) {
                        smaCrossSignalBuffer.set(index - 1, fastBuffer.get(index - 1), {
                            position: 'bottom',
                            text: 0xf0aa 
                        })
                        entryPointTextBuffer.set(index, opens.get(index), {
                            position: 'left',
                            text: 'Buy'
                        })
                    }
                    if (fastBuffer.get(index - 2) >= slowBuffer.get(index - 2)
                        && fastBuffer.get(index - 1) < slowBuffer.get(index - 1)) {
                        smaCrossSignalBuffer.set(index - 1, fastBuffer.get(index - 1), {
                            position: 'top',
                            text: 0xf0ab 
                        })
                        entryPointTextBuffer.set(index, opens.get(index), {
                            position: 'left',
                            text: 'Sell'
                        })
                    }
                }
            }
        },
        params: {
            fast: {
                period: 10,
                lineColor: "lime"
            },
            slow: {
                period: 30,
                lineColor: "cyan"
            }
        }
    }
}
                   
export default (): Strategy => {
    return {
        type: "s",
        name: "tutor.smaCrossStrategy2",
        onInit() {
            this.shortName = "smaCross"
            const params = this.params;
            const mainContainerIndicator = this.chart.mainContainerIndicator;
            const smaCrossSignal = mainContainerIndicator.addItemIndicator("tutor.SmaCrossSignal", params)
            let buyPositionCount = 0;
            let sellPositionCount = 0;
            this.onChartChange = ({ index, isBarChanged }) => {
                if (!isBarChanged) return;
                const smaEntryPointTextBuffer = smaCrossSignal.entryPointTextBuffer
                const text = smaEntryPointTextBuffer.getText(index) //1
                if (text === 'Buy') {
                    if (sellPositionCount > 0) {
                        this.entry({ orderType: 'market', direction: 'buy', lot: 1, hedging: false })// 買いを実行(決済)
                        sellPositionCount = 0;
                    }
                    if (buyPositionCount === 0) {
                        this.entry({ orderType: 'market', direction: 'buy', lot: 1, hedging: false })// 買いを実行(新規)
                        buyPositionCount = 1;
                    }
                } else if (text === 'Sell') {
                    if (buyPositionCount > 0) {
                        this.entry({ orderType: 'market', direction: 'sell', lot: 1, hedging: false })// 売りを実行(決済)
                        buyPositionCount = 0
                    }
                    if (sellPositionCount === 0) {
                        this.entry({ orderType: 'market', direction: 'sell', lot: 1, hedging: false })// 売りを実行(新規)
                        sellPositionCount = 1;
                    }
                }
            }
        },
        params: {
            fast: // 短期SMAのデフォルト値
            {
                period: 5,
                lineColor: "lime"
            },
            slow:  // 長期SMAのデフォルト値
            {
                period: 20,
                lineColor: "cyan"
            }
        }
    }
}
               
1 tutor.SmaCrossSignalで設定したシグナルを元に売買を行っています。