C#

【C#】cAlgo開発のためのLINQ講座 その1

最初に

たまには純粋なプログラムの話でもしてみようかと思います。今回はcAlgoでの開発にも役立つLINQという仕組みについて簡単に説明します。

基本的にcTraderのcAlgoで何か作りたい初心者向けですが、どこかから迷い込んだC#初心者の方もわかるように書いてるつもりです。

「LINQって聞いたことあるけど、よくわかんなくて使ってない」とか「なんか特殊な書き方するアレでしょ?やだ怖い。」とか思ってる方はぜひ読んでみてください。

C#使っててLINQ使ってないなんてもったいないです。この記事の内容だけでも世界が広がります。

 

LINQってなんだ

LINQとは統合言語クエリとか呼ばれるもので、簡単に言うと配列やListみたいなデータの塊から効率よく目的のデータを取り出す仕組みのことです。

こういうとデータベースからデータを取り出すSQLを思い浮かべる人もいると思いますが、まさにそれと似ています。

LINQ自体がSQLと直接関係あるわけではありませんが、「統合言語クエリ」という名前の通り、SQLデータベースやXMLなどのデータもプログラム内のデータと同じように扱えるよう用意された仕組みです。

実際、LINQはSQLチックな書き方もできるようになってます。この記述をクエリ構文と呼びます。(コードはMSDNより引用。意味わかんなくても大丈夫。)

    IEnumerable numQuery1 =
        from num in numbers
        where num % 2 == 0
        orderby num
        select num;

これは一見特殊な記法見えますよね。いつものC#っぽい書き方で書くとこうなります。上と全く同じ意味です。こっちはメソッド構文と呼びます。

    IEnumerable numQuery1 = 
        numbers
        .Where(num => num % 2 == 0)
        .OrderBy(num => num)
        .Select(num => num);

クエリ構文に合わせて改行してますが、1行で書いてもかまいません。(ちなみに別にクエリ構文を1行で書いても構いません。普通改行すると思いますけど。)

メソッドにメソッドをつなげて書いてるのを不思議に思う方もいるかもしれませんが、これは単にメソッドの返り値からまた別のメソッドを呼んでるだけです。こういうのをメソッドチェーンって呼んだりもします。

SQLに慣れてる方はクエリ構文のが読みやすいのかもしれませんが、ここではC#プログラマーにわかりやすいようメソッド構文の記述で進めていきます。慣れてきたら好きな方使ってください。

なお、メソッド構文を見ればわかるように、別に特殊なものでもなんでもなく、配列やListみたいなデータの塊を表すクラスに共通して使えるデータ取り出し用のメソッドが用意されてるってだけの話です。

 

どういうものに対して使えるのか

これらのデータ取り出しメソッドはIEnumearbleインターフェイスを実装していれば利用可能になってます。つまりIEnumearbleを実装しているオブジェクトではLINQが使えるということです。

具体的には配列やList、Dictionaryなどのコレクション型、cAlgo内でいうならばBars、DataSeries、TimeSeries、Positions、Historyなど何かのコレクションの形になってるものであればだいたい使えます。

もちろん自作クラスでもIEnumerableを実装してあげれば使えるようになります。LINQのメソッドはIEnumerableのメンバメソッドと考えて問題ありません。(実際は拡張メソッド)

基本的にメソッドはIEnumerableの実装型を返すので、そこからつなげて新たなメソッドを呼ぶような「メソッドチェーン」での記述ができるようになってます。

 

どうやって使うのか

まずはファイル冒頭にこの記述が必要です。

using System.Linq;

cTraderのcBot作成時はテンプレートで最初から記述されてます。なぜかIndicator作成時は入りませんので自分で書き加えてください

これだけであとは配列などのメンバメソッドとして普通に使うことができます。

 

LINQの主要メソッドと具体例

では実際にどんなメソッドでどんなことができるのか具体例を見ながら学んでいきましょう。

ここではcAlgoで一番よく使うであろうAlgoクラスのプロパティBarsを例にとって説明します。チャート上に表示されてるすべてのローソク足の情報を持ってるのがBarsです。

cAlgoとか知らんという方も、こんな株価チャートとかで使われるローソク足の価格情報をまとめたものがBarsと思ってもらえればわかると思います。

念のため中身を確認しておきます。まずBar構造体はローソク足1本を表します。だいたいこんな感じのを想像してください。

struct Bar {
    public double Open { get; set; } //始値
    public double High { get; set; } //高値
    public double Low { get; set; } //安値
    public double Close { get; set; } //終値
    // --- 以下略 ---
}

そしてBarsはこのBar構造体の配列だと思ってもらえればいいです。(厳密にはcAlgoのBarsはBarsインターフェイスを持つ別のオブジェクトですが、LINQを使うだけなら関係ないので)

さて、それではここから「直近50本の確定したローソク足のうち陽線の値幅平均を求める」というコードを書いてみましょう。最初はわかりやすいように一つずつ処理していきます。

 

Skip

まず最初に直近のローソク足だけ取り出すことを考えます。Barsは古い順にローソク足が並んでいるため、最初のいくつかを飛ばして残りの要素を最後まで取得するSkipというメソッドを使います。

//Bars全体数から51引いた数を飛ばして残りを取得=最後の51本を取得
var recentBars = Bars.Skip(Bars.Count - 51); 

これでrecentBarsは直近51本のローソク足を表します。なんで1本余計に取得したかというと、最後の一本は未確定の現在のローソク足を示してるからです。

ちなみにSkipの引数にBars.Count以上の数を渡しても例外は出ず、空のオブジェクトが返るだけになります。エラー処理は例外に頼らないよう気を付けましょう。

 

Take

続いてこの51本のBarの集まりから最後の1本 (未確定ローソク足) を除いた50本を取得します。これには最初のいくつかの要素を取得するTakeというメソッドを使います。

var fiftyBars = recentBars.Take(50);

これも最大数以上の数を指定しても、そのまま返るだけで例外は出ません。

なお、TakeとSkipにはクエリ構文はありません。

 

Where

fiftyBarsの50本は陽線陰線混ざってますので、ここから陽線だけ取り出します。指定した条件に合うものだけを取り出すWhereを使います。

var positiveBars = fiftyBars.Where( bar=>bar.Open < bar.Close);

条件を指定する引数はAction型のためラムダ式で指定するのが一般的です。返り値はbool型である必要があります。要素のbarのうちOpenがCloseより小さいもの(=陽線)だけを取り出す、という意味ですね。

 

Select

今度はこれらローソク足から陽線の値幅(=高値ー安値)部分だけを取り出します。ここではそれぞれの要素に特定の処理をしてその結果を取り出すSelectを使います。

var nehabas = positiveBars.Select (bar => bar.High - bar.Low) ;

引数はこれもAction型のラムダ式です。Selectの返り値はIEnumerable<ラムダ式の返り値型>となります。この例では値幅を取り出すのでIEnumerable<double>型ですが、例えばOpenTimeを取り出したらIEnumerable<DateTime>型になります。

 

Average

最後に平均を求めます。そのまんまですが平均値を求めるAverageメソッドを使います。

var average = nehabas.Average();

ここでの返り値はIEnumerableではなくてdouble型になり、ここで処理終了です。なお似たようなメソッドに合計値を求めるSumもあります。

 

いっぺんに書ける

これらをメソッドチェーンで書くとこうなります。こんな風に使うことの方が多いです。繰り返しますが改行はしてもしなくてもいいです。

var average = Bars.Skip(Bars.Count - 51)
                    .Take(50)
                    .Where(bar => bar.Open < bar.Close)
                    .Select(bar => bar.High - bar.Low)
                    .Average();

LINQの機能はまだまだたくさんありますが、ここで紹介したものが使えるだけでかなりBarsの取り回しがきくようになると思います。

-C#

© 2021 cTrader's Life Powered by AFFINGER5