RustとCでTA-Lib(テクニカル指標を計算するライブラリ)を使ってみる。

2018/05/27追記

C/C++に死を」に書いてあるようにC言語はエグいので、おすすめ通りにRust言語で書き直しました

併せてC言語版も出力が一致するように書き換えました。

入力データーはネットストックハイスピードからCSVをダウンロードするか
以下のようにquandlからダウンロードしたファイルを使ってください。

$ curl "https://www.quandl.com/api/v3/datasets/NIKKEI/INDEX.csv?start_date=2013-01-04" | sed '1p;1s/$/,Volume/;1s/Date,/Date,Time,/;1!s/,/,00:00:00,/;1!s/$/,0.00/'> nikkei.csv

 

以前こんな

MariaDB(MySQL)から取り出した株価からテクニカル指標を計算してチャートをJupyter NotebookとExcel両方で描いてみた。


テクニカル指標を計算するコードを自作した。

でも実装と検証が面倒なので実績のあるテクニカル指標ライブラリTA-Libを導入します。

始めに

以下のプログラムはこの環境で動くことを確認しています。

$ uname -srvm
Linux 4.4.0-127-generic #153-Ubuntu SMP Sat May 19 10:58:46 UTC 2018 x86_64
$ gcc -v 2>&1 | cut -c-80
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) 

テクニカル指標ライブラリTA-Lib

この説明はサイトからの引用です

Multi-Platform Tools for Market Analysis …
TA-Lib is widely used by trading software developers requiring to perform technical analysis of financial market data.

  • Includes 200 indicators such as ADX, MACD, RSI, Stochastic, Bollinger Bands etc… (more info)
  • Candlestick pattern recognition
  • Open-source API for C/C++, Java, Perl, Python and 100% Managed .NET

Free Open-Source Library
TA-Lib is available under a BSD License allowing it to be integrated in your own open-source or commercial application. (more info)

Commercial Application
TA-Lib is also available as an easy to install Excel Add-Ins. Try it for free!
TA-Lib : Technical Analysis Library

ここに載ってあるとおり、TA-Lib APIは

  • C/C++
  • Java
  • Perl
  • Python
  • 100% Managed .Net

の中から選べます。
Pythonの優秀な記事はネットにたくさんあるし、

自分はC言語がチョットワカルのでこの中からC言語を選択します。

今回はPythonとかHaskellでは無いです。
あしからず御了承ください。

TA-Libライブラリのダウンロード

TA-LibサイトのDownloads リンクから

”Unix/Linux Source Automake” 版のta-lib-0.4.0-src.tar.gz をダウンロードしてきて適当な場所で展開する。

TA-Libライブラリのビルド&インストール

2.3.2 All Unix Flavors Shared Librariesに書かれているとおり

~/ta-lib$ ./configure
~/ta-lib$ make
~/ta-lib$ sudo make install
~/ta-lib$ sudo ldconfig
~/ta-lib$ ldconfig -p | grep ta_lib
        libta_lib.so.0 (libc6,x86-64) => /usr/local/lib/libta_lib.so.0
        libta_lib.so (libc6,x86-64) => /usr/local/lib/libta_lib.so

終わったらライブラリの居場所を確認する。

~/ta-lib$ find /usr/local/include/ /usr/local/lib/ -name "*ta_lib*"
/usr/local/include/ta-lib/ta_libc.h
/usr/local/lib/libta_lib.a
/usr/local/lib/libta_lib.so.0.0.0
/usr/local/lib/libta_lib.la
/usr/local/lib/libta_lib.so.0
/usr/local/lib/libta_lib.so

ヘッダファイル、ライブラリが /usr/local/ 以下にインストールされていることが確認できた。

データを用意する

ライブラリのテストプログラムのために某ハイスピードで日経平均株価と東証8306の日足を代表的なテクニカル指標を付けてCSVでダウンロードしておく。

このCSVはタイトル行に2行、3行目以降新旧の順番で並んだ4102行のデータで、これのテクニカル指標をTA-Libで計算するプログラムを作ります。

おおざっぱに設計

  • fscanfでCSVの取り扱いは厳しかったりするので、入力はタブ区切りのTSVにする
  • 各行はDate Time Open High Low Close Volumeの7列にする
  • 入力時系列は旧新の順で(3行目以降、行の昇降順を反転する)

こう決めたので、事前にCSVを変換するスクリプトを作ります。

CSVを変換するスクリプト

taFromHiSpeedCSV

#! /usr/bin/env bash
#

if [ "$#" -eq "1" ]
then
    inputfile="$1"
    outputfileprefix="$(basename -s csv $1)ta"
    programfile="$(pwd)/mta"
    echo "Input: $inputfile"
    # build program
    gcc makeTechnicalWithTA-lib.c -o "$programfile" -Wall -O2 -std=c11 -static -lta_lib -lm || exit 1

    # 1,2行目のタイトル行を残して逆順にして
    # Date,Time,Open,High,Low,Close,Volume,OI, ... の並びから
    # Date,Time,Open,High,Low,Close,Volume を取り出して
    # テクニカル指標を計算して
    # 結果を分割してファイルに出力する
    sed -n -e 's/,/\t/g;1,2p;1,2d;1!G;h;$p' "$inputfile" | cut -f1-7 | $programfile - | csplit -sz --prefix=$outputfileprefix --suffix-format=%02d.csv - /Date/ {*}
else
    echo "Usage: $0 \"*.csv\""
fi
  • 区切り文字の,をタブに置換して
  • 1,2行目をそのまま出力
  • それ以降の行を反転する

この作業をsedで、次にcutで7列を取り出して、これから作るプログラムに渡す部分がここ。

sed -n -e 's/,/\t/g;1,2p;1,2d;1!G;h;$p' "$inputfile" | cut -f1-7

スクリプトに書いているように、毎回ビルドするがこれは一瞬で終わるので

$ time gcc makeTechnicalWithTA-lib.c -o mta -Wall -O2 -std=c11 -static -lta_lib -lm

real    0m0.276s
user    0m0.244s
sys     0m0.024s

実行時に毎回ビルドしてもLL系の言語には処理速度で負けないと思っている。

テクニカル指標を計算する

こう使います。

$ ./taFromHiSpeedCSV ../ni225.csv 
Input: ../ni225.csv
made by "/home/a/tractor/mta" program with "TA-lib 0.4.0 (Mar  5 2018 02:08:45)"
Input from stdin
0(TA_SUCCESS): No error
$ ls -l
-rw-rw-r-- 1 aki aki 512625 5月 25 18:58 ni225.ta00.csv
-rw-rw-r-- 1 aki aki 405999 5月 25 18:58 ni225.ta01.csv
-rw-rw-r-- 1 aki aki 631554 5月 25 18:58 ni225.ta02.csv
-rw-rw-r-- 1 aki aki 307575 5月 25 18:58 ni225.ta03.csv
-rw-rw-r-- 1 aki aki 405999 5月 25 18:58 ni225.ta04.csv
$ head -n 70 ni225.ta00.csv 
                DateTime,    Close,  SMA(12),  EMA(12),  WMA(12), DEMA(12), TEMA(12),TRIMA(12), KAMA(12), MAMA(12),   T3(12)
2001-06-25T00:00:00+0900, 12896.47,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-06-26T00:00:00+0900, 12978.82,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-06-27T00:00:00+0900, 12828.98,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-06-28T00:00:00+0900, 12679.88,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-06-29T00:00:00+0900, 12969.05,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-07-02T00:00:00+0900, 12751.18,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-07-03T00:00:00+0900, 12817.41,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-07-04T00:00:00+0900, 12629.02,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-07-05T00:00:00+0900, 12607.30,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-07-06T00:00:00+0900, 12306.08,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-07-09T00:00:00+0900, 12239.68,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A,      N/A
2001-07-10T00:00:00+0900, 12300.41, 12667.02,      N/A, 12550.45,      N/A,      N/A, 12704.52,      N/A,      N/A,      N/A
2001-07-11T00:00:00+0900, 12005.11, 12592.74,      N/A, 12448.61,      N/A,      N/A, 12632.69, 12264.49,      N/A,      N/A
2001-07-12T00:00:00+0900, 12407.95, 12545.17,      N/A, 12420.18,      N/A,      N/A, 12557.48, 12271.45,      N/A,      N/A
2001-07-13T00:00:00+0900, 12355.15, 12505.69,      N/A, 12390.95,      N/A,      N/A, 12484.59, 12274.78,      N/A,      N/A
2001-07-16T00:00:00+0900, 12343.37, 12477.64,      N/A, 12365.98,      N/A,      N/A, 12417.88, 12276.70,      N/A,      N/A
2001-07-17T00:00:00+0900, 12128.57, 12407.60,      N/A, 12312.28,      N/A,      N/A, 12357.41, 12260.48,      N/A,      N/A
2001-07-18T00:00:00+0900, 11892.58, 12336.05,      N/A, 12233.04,      N/A,      N/A, 12304.60, 12219.43,      N/A,      N/A
2001-07-19T00:00:00+0900, 11908.39, 12260.30,      N/A, 12167.25,      N/A,      N/A, 12260.23, 12179.66,      N/A,      N/A
2001-07-23T00:00:00+0900, 11609.63, 12175.35,      N/A, 12067.14,      N/A,      N/A, 12216.18, 12099.58,      N/A,      N/A
2001-07-24T00:00:00+0900, 11883.25, 12115.01,      N/A, 12022.20,      N/A,      N/A, 12166.16, 12084.95,      N/A,      N/A
2001-07-25T00:00:00+0900, 11891.61, 12080.48,      N/A, 11987.83,      N/A,      N/A, 12111.39, 12077.72,      N/A,      N/A
2001-07-26T00:00:00+0900, 11858.56, 12048.72,      N/A, 11953.69,      N/A,      N/A, 12049.31, 12070.22,      N/A,      N/A
2001-07-27T00:00:00+0900, 11798.08, 12006.85,      N/A, 11915.13,      N/A,      N/A, 11987.61, 12056.63,      N/A,      N/A
2001-07-30T00:00:00+0900, 11579.27, 11971.37,      N/A, 11849.35,      N/A,      N/A, 11927.80, 12036.60,      N/A,      N/A
2001-07-31T00:00:00+0900, 11860.77, 11925.77,      N/A, 11832.34,      N/A,      N/A, 11876.26, 12024.93,      N/A,      N/A
2001-08-01T00:00:00+0900, 11959.33, 11892.78,      N/A, 11837.50,      N/A,      N/A, 11845.55, 12022.29,      N/A,      N/A
2001-08-02T00:00:00+0900, 12399.20, 11897.44,      N/A, 11915.41,      N/A,      N/A, 11838.15, 12024.69,      N/A,      N/A
2001-08-03T00:00:00+0900, 12241.97, 11906.89,      N/A, 11968.42,      N/A,      N/A, 11850.64, 12026.73,      N/A,      N/A
2001-08-06T00:00:00+0900, 12243.90, 11936.16,      N/A, 12020.26,      N/A,      N/A, 11880.18, 12033.50,      N/A,      N/A
2001-08-07T00:00:00+0900, 12319.46, 11970.42,      N/A, 12079.23,      N/A,      N/A, 11929.58, 12043.99,      N/A,      N/A
2001-08-08T00:00:00+0900, 12163.67, 12016.59, 12121.90, 12108.96,      N/A,      N/A, 11994.04, 12051.44,      N/A,      N/A
2001-08-09T00:00:00+0900, 11754.56, 12005.87, 12065.39, 12068.65,      N/A,      N/A, 12047.64, 12048.20, 12151.68,      N/A
2001-08-10T00:00:00+0900, 11735.06, 11992.82, 12014.57, 12026.99,      N/A,      N/A, 12083.61, 12044.21, 12130.85,      N/A
2001-08-13T00:00:00+0900, 11477.56, 11961.07, 11931.95, 11947.72,      N/A,      N/A, 12089.31, 12027.84, 12098.19,      N/A
2001-08-14T00:00:00+0900, 11917.95, 11971.06, 11929.80, 11941.08,      N/A,      N/A, 12078.11, 12026.90, 12089.18,      N/A
2001-08-15T00:00:00+0900, 11755.40, 11985.74, 11902.97, 11907.91,      N/A,      N/A, 12042.86, 12023.79, 11922.29,      N/A
2001-08-16T00:00:00+0900, 11515.02, 11956.92, 11843.28, 11835.49,      N/A,      N/A, 11974.55, 12012.47, 11901.92,      N/A
2001-08-17T00:00:00+0900, 11445.54, 11914.11, 11782.09, 11756.81,      N/A,      N/A, 11891.67, 11991.60, 11879.11,      N/A
2001-08-20T00:00:00+0900, 11257.94, 11819.00, 11701.45, 11655.87,      N/A,      N/A, 11802.30, 11885.55, 11848.05,      N/A
2001-08-21T00:00:00+0900, 11280.38, 11738.87, 11636.67, 11573.00,      N/A,      N/A, 11724.06, 11812.25, 11819.66,      N/A
2001-08-22T00:00:00+0900, 11396.43, 11668.25, 11599.71, 11520.32,      N/A,      N/A, 11651.59, 11774.53, 11798.50,      N/A
2001-08-23T00:00:00+0900, 11126.92, 11568.87, 11526.98, 11437.04,      N/A,      N/A, 11571.93, 11685.85, 11462.71,      N/A
2001-08-24T00:00:00+0900, 11166.31, 11485.76, 11471.49, 11375.10,      N/A,      N/A, 11497.39, 11628.12, 11447.89,      N/A
2001-08-27T00:00:00+0900, 11275.01, 11445.79, 11441.26, 11342.68,      N/A,      N/A, 11434.23, 11611.97, 11439.25,      N/A
2001-08-28T00:00:00+0900, 11189.40, 11400.32, 11402.51, 11303.24,      N/A,      N/A, 11376.80, 11589.84, 11426.75,      N/A
2001-08-29T00:00:00+0900, 10979.76, 11358.84, 11337.47, 11238.53,      N/A,      N/A, 11323.57, 11560.82, 11404.40,      N/A
2001-08-30T00:00:00+0900, 10938.45, 11277.21, 11276.09, 11173.86,      N/A,      N/A, 11264.13, 11437.92, 11171.43,      N/A
2001-08-31T00:00:00+0900, 10713.51, 11190.39, 11189.54, 11087.14,      N/A,      N/A, 11207.27, 11289.28, 11148.53,      N/A
2001-09-03T00:00:00+0900, 10409.68, 11098.28, 11069.56, 10967.03,      N/A,      N/A, 11147.35, 11102.31, 11111.59,      N/A
2001-09-04T00:00:00+0900, 10772.59, 11042.20, 11023.87, 10916.92,      N/A,      N/A, 11083.78, 11078.27, 10942.09,      N/A
2001-09-05T00:00:00+0900, 10598.79, 10987.27, 10958.47, 10848.70,      N/A,      N/A, 11010.20, 11044.03, 10919.57,      N/A
2001-09-06T00:00:00+0900, 10650.33, 10934.77, 10911.07, 10796.87,      N/A,      N/A, 10930.42, 11018.35, 10906.11,      N/A
2001-09-07T00:00:00+0900, 10516.79, 10861.46, 10850.41, 10732.56,      N/A,      N/A, 10847.75, 10964.15, 10791.04,      N/A
2001-09-10T00:00:00+0900, 10195.69, 10783.86, 10749.68, 10630.14,      N/A,      N/A, 10763.65, 10876.77, 10761.27,      N/A
2001-09-11T00:00:00+0900, 10292.95, 10711.08, 10679.42, 10554.61,      N/A,      N/A, 10686.62, 10819.47, 10737.86,      N/A
2001-09-12T00:00:00+0900,  9610.10, 10572.34, 10514.91, 10385.23,      N/A,      N/A, 10599.93, 10588.06, 10279.42,      N/A
2001-09-13T00:00:00+0900,  9613.09, 10440.98, 10376.17, 10237.66,      N/A,      N/A, 10501.72, 10408.81,  9946.25,      N/A
2001-09-14T00:00:00+0900, 10008.89, 10360.07, 10319.66, 10171.18,      N/A,      N/A, 10402.31, 10378.80,  9977.57,      N/A
2001-09-17T00:00:00+0900,  9504.41, 10240.57, 10194.24, 10039.54,      N/A,      N/A, 10286.64, 10283.70,  9953.91,      N/A
2001-09-18T00:00:00+0900,  9679.87, 10154.43, 10115.10,  9953.28,      N/A,      N/A, 10168.73, 10243.29,  9940.21,      N/A
2001-09-19T00:00:00+0900,  9939.60, 10115.26, 10088.10,  9920.23,      N/A,      N/A, 10054.73, 10236.08,  9939.91,      N/A
2001-09-20T00:00:00+0900,  9785.16, 10032.97, 10041.50,  9869.44,  9627.30,      N/A,  9947.68, 10204.21,  9932.17,      N/A
2001-09-21T00:00:00+0900,  9554.99,  9945.99,  9966.65,  9795.91,  9552.85,      N/A,  9866.92, 10155.69,  9913.31,      N/A
2001-09-25T00:00:00+0900,  9693.96,  9866.29,  9924.70,  9757.13,  9539.06,      N/A,  9802.14, 10126.72,  9803.63,      N/A
2001-09-26T00:00:00+0900,  9641.70,  9793.37,  9881.16,  9722.58,  9518.01,      N/A,  9755.90, 10098.96,  9795.54,      N/A
2001-09-27T00:00:00+0900,  9696.53,  9751.77,  9852.75,  9707.68,  9521.44,      N/A,  9734.15, 10086.80,  9746.03,  9707.24
2001-09-28T00:00:00+0900,  9774.68,  9708.58,  9840.74,  9711.21,  9550.24,      N/A,  9720.77, 10076.82,  9747.47,  9653.37
2001-10-01T00:00:00+0900,  9972.28,  9738.76,  9860.98,  9751.78,  9632.29,      N/A,  9720.25, 10074.08,  9859.87,  9613.60

計算結果のMACDと入力ファイルのMACDとが初期値の影響を脱した頃から一致することを確認した。

ここでEMA(12)がSMA(12)より20本遅れるのは
TA_SetUnstablePeriod( TA_FUNC_UNST_EMA, 20 )

この関数の影響なので意図した物です。

C言語ソース

makeTechnicalWithTA-lib.c

//
// TA-libでテクニカル指標を計算するプログラム
// https://ak1211.com
// Copyright (c) 2018 Akihiro Yamamoto
//
// This software is released under the MIT License.
// http://opensource.org/licenses/mit-license.php
//
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdalign.h>
#include <math.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <libgen.h>
#include <linux/limits.h>
#include <assert.h>

#include <ta-lib/ta_libc.h>

typedef int errno_t;

enum {
    DATE_TIME_COLUMN_WIDTH = 24,    // 日付時刻列の幅
    COLUMN_WIDTH_MIN = 9,           // 列の最小幅
    COLUMN_WIDTH_MAX = 128,         // 列の最大幅(多めに)
    ONE_TIME_ROWS = 128,            // 漸進処理1回の行数
};

int max( int a, int b )
{
    return (a > b) ? a : b;
}

//
// double型 v は 0.0 か?
//
bool double_equal_zero( double v )
{
    return ( fabs(v) <= DBL_EPSILON );
}

// 行または列見出し型
typedef char rc_caption_t[COLUMN_WIDTH_MAX+1];
typedef rc_caption_t row_caption_t; // 行見出し型
typedef rc_caption_t col_caption_t; // 列見出し型

// パラメータ型
typedef double param_t;

// 列型
typedef struct {
    col_caption_t col_capt; // 列見出し
    param_t param[3];       // パラメータ(0値は不要を表す)
    int begIdx;             // 入力値のインデックスに対して、
                            // 出力値の始まるインデックス(これ未満はN/A)
    int nbElement;          // 値の数
    double vs[];            // 値
} column_t;

//
// column_t 型の新規割り当て関数
// 返値は使用後にfreeすること
column_t* new_column_t( col_caption_t incol_capt,
                        double inp1,
                        double inp2,
                        double inp3,
                        size_t length )
{
    column_t* p = calloc( 1, sizeof(column_t)+sizeof(double)*length );
    if( p == NULL ) {
        return NULL;
    }

    assert( sizeof(p->param)/sizeof(p->param[0]) == 3 );
    p->param[0] = inp1;
    p->param[1] = inp2;
    p->param[2] = inp3;
    //
    strncpy( p->col_capt, incol_capt, COLUMN_WIDTH_MAX );
    if( !double_equal_zero(p->param[0]) ) {
        // パラメータを書く
        strncat( p->col_capt, "(", COLUMN_WIDTH_MAX );
        for( int i=0; i<sizeof(p->param)/sizeof(p->param[0]); i++ ) {
            // 0を見つけると打ち切る
            if( double_equal_zero(p->param[i]) ) {break;}
            //
            col_caption_t work;
            if( double_equal_zero( p->param[i] - floor(p->param[i]) ) ) {
                // 整数部のみ
                snprintf( work, COLUMN_WIDTH_MAX, "%d,", (int)floor(p->param[i]) );
            } else {
                // 浮動小数
                snprintf( work, COLUMN_WIDTH_MAX, "%.2f,", p->param[i] );
            }
            strncat( p->col_capt, work, COLUMN_WIDTH_MAX );
        }
        // 最終の','を')'に上書きする
        p->col_capt[ strlen(p->col_capt) - 1 ] = ')';
    }
    return p;
}

//
// 文字列を指定の幅で出力する
//
void printString( int colwidth, char* s ) {
    char fmt[COLUMN_WIDTH_MAX+1];
    snprintf( fmt, COLUMN_WIDTH_MAX, ",%%%ds", colwidth );
    printf( fmt, s );
}

//
// double型を指定の幅で出力する
//
void printDouble( int colwidth, double v ) {
    char fmt[COLUMN_WIDTH_MAX+1];
    snprintf( fmt, COLUMN_WIDTH_MAX, ",%%%d.2f", colwidth );
    printf( fmt, double_equal_zero(v) ? 0.00 : v );
}

//
// 行内に列を注入する
//
void inject_column( int idx, const column_t* column )
{
    int colwidth = max( strlen(column->col_capt), COLUMN_WIDTH_MIN );

    if( idx < column->begIdx ) {
        printString( colwidth, "N/A" );
    } else if( idx - column->begIdx < column->nbElement ) {
        printDouble( colwidth, column->vs[idx - column->begIdx] );
    } else {
        printString( colwidth, "N/A" );
    }
}

//
// 計算結果を出力する
//
void disp(  const row_caption_t caption[],
            int startIdx, int endIdx, const double inprice[],
            int columnCount, ... )
{
    va_list ap;
    va_start( ap, columnCount );

    column_t** this_arg = calloc( columnCount, sizeof(column_t*) );
    if( this_arg ) {
        char fmt[COLUMN_WIDTH_MAX+1];
        snprintf( fmt, COLUMN_WIDTH_MAX, "%%%ds", DATE_TIME_COLUMN_WIDTH );
        // タイトル行の出力
        printf( fmt, "DateTime" );
        printString( COLUMN_WIDTH_MIN, "Close" );
         for( int i=0; i<columnCount; i++ ) {
            this_arg[i] = va_arg( ap, column_t* );
            printString( COLUMN_WIDTH_MIN, this_arg[i]->col_capt );
        }
        printf("\n");

        // データ行の出力
        for( int i=startIdx; i<=endIdx; i++ ) {
            printf( fmt, caption[i] );
            printDouble( COLUMN_WIDTH_MIN, inprice[i] );
            for( int k=0; k<columnCount; k++ ) {
                inject_column( i, this_arg[k] );
            }
            printf("\n");
        }
        free( this_arg );
    }
    va_end( ap );
}

//
// TSVから列を分解して取り出す
//
size_t read_part(   FILE* fp,
                    row_caption_t* caption,
                    double* open,
                    double* high,
                    double* low,
                    double* close,
                    double* volume,
                    size_t length )
{
    size_t count=0;
    for( ; count<length; count++ ) {
        row_caption_t date;
        char time[256];
        double o, h, l, c, v;
        char buff[256];
        if( fgets( buff, sizeof(buff), fp) == NULL ) {break;}
        if( sscanf( buff,
                    "%s%s" "%lf%lf%lf%lf%lf" "\n",
                    date, time,
                    &o, &h, &l, &c, &v )
            == EOF)
        {
            break;
        }
        if(errno) {goto fail;}
        // DateTimeを作る
        snprintf( caption[count], COLUMN_WIDTH_MAX, "%sT%s+0900", date, time );
        open[count] = o;
        high[count] = h;
        low[count]  = l;
        close[count]= c;
        volume[count]= v;
    }
    return count;
fail:
    perror("read_part");
    return 0UL;
}

//
// 入力TSVファイルから列を分解して取り出す
//
errno_t read_file(  FILE *fp,
                    row_caption_t** outcaption,
                    double** outopen,
                    double** outhigh,
                    double** outlow,
                    double** outclose,
                    double** outvolume,
                    size_t* outlength )
{
    row_caption_t* capt = NULL;
    double* open = NULL;
    double* high = NULL;
    double* low = NULL;
    double* close = NULL;
    double* volume = NULL;

    assert( fp != NULL );
    // 1,2行目はタイトル行なので読み飛ばす
    if (1) {
        // テキストモードの入力を仮定している
        for (int c=0; c!=EOF && c!='\n'; c=fgetc(fp) ){}
        for (int c=0; c!=EOF && c!='\n'; c=fgetc(fp) ){}
        if(errno) {goto fail;}
    }

    size_t rowcount = 0;
    for( int batchcount=0; !feof(fp); batchcount++ ) {
        const size_t nmemb = (1+batchcount) * ONE_TIME_ROWS;
        void* p = NULL;
        // 処理ごとに領域の拡張を行なう
        if((p=realloc(capt, sizeof(row_caption_t)*nmemb)) == NULL){goto fail;}else{capt=p;}
        if((p=realloc(open, sizeof(double)*nmemb)) == NULL){goto fail;}else{open=p;}
        if((p=realloc(high, sizeof(double)*nmemb)) == NULL){goto fail;}else{high=p;}
        if((p=realloc(low, sizeof(double)*nmemb)) == NULL){goto fail;}else{low=p;}
        if((p=realloc(close, sizeof(double)*nmemb)) == NULL){goto fail;}else{close=p;}
        if((p=realloc(volume, sizeof(double)*nmemb)) == NULL){goto fail;}else{volume=p;}
        //
        rowcount += read_part(  fp,
                                &capt[ batchcount*ONE_TIME_ROWS ],
                                &open[ batchcount*ONE_TIME_ROWS ],
                                &high[ batchcount*ONE_TIME_ROWS ],
                                &low[ batchcount*ONE_TIME_ROWS ],
                                &close[ batchcount*ONE_TIME_ROWS ],
                                &volume[ batchcount*ONE_TIME_ROWS ],
                                ONE_TIME_ROWS );
        if(errno) {goto fail;}
    }

    // 成功結果を返す
    *outcaption = capt;
    *outopen = open;
    *outhigh= high;
    *outlow = low;
    *outclose = close;
    *outvolume = volume;
    *outlength = rowcount;
    return EXIT_SUCCESS;
fail:
    perror("read_file");
    // 失敗したので、この関数で用意した領域を解放する
    free(capt);
    free(open);
    free(high);
    free(low);
    free(close);
    free(volume);
    return EXIT_FAILURE;
}

//
// テクニカル指標の計算と出力
//
TA_RetCode calculate(   row_caption_t* caption,
                        double* open,
                        double* high,
                        double* low,
                        double* close,
                        double* volume,
                        size_t length )
{
    struct columns {
        column_t* sma;
        column_t* ema;
        column_t* wma;
        column_t* dema;
        column_t* tema;
        column_t* trima;
        column_t* kama;
        column_t* mama;
        column_t* t3;
        //
        column_t* ema12;
        column_t* ema26;
        column_t* macd;
        column_t* signal;
        column_t* hist;
        //
        column_t* bbup;
        column_t* bbmid;
        column_t* bblow;
        column_t* rsi;
        column_t* roc;
        column_t* mom;
        column_t* sar;
        column_t* willr;
        //
        column_t* adx;
        column_t* adxr;
        column_t* posDI;
        column_t* negDI;
        //
        column_t* ad;
        column_t* adosc;
        column_t* obv;
    } __attribute__((__packed__)) v = {NULL};   // NULL初期化する
    column_t** columns_pp = (column_t**)&v;     // 上の構造体をポインタの配列で
                                                // アクセスするためのダブルポインタ

    TA_RetCode rc = TA_SUCCESS;
    //
    do {
        if((v.sma   = new_column_t( "SMA", 12,0,0, length )) == NULL) {break;}
        if((v.ema   = new_column_t( "EMA", 12,0,0, length )) == NULL) {break;}
        if((v.wma   = new_column_t( "WMA", 12,0,0, length )) == NULL) {break;}
        if((v.dema  = new_column_t( "DEMA", 12,0,0, length )) == NULL) {break;}
        if((v.tema  = new_column_t( "TEMA", 12,0,0, length )) == NULL) {break;}
        if((v.trima = new_column_t( "TRIMA", 12,0,0, length )) == NULL) {break;}
        if((v.kama  = new_column_t( "KAMA", 12,0,0, length )) == NULL) {break;}
        if((v.mama  = new_column_t( "MAMA", 12,0,0, length )) == NULL) {break;}
        if((v.t3    = new_column_t( "T3", 12,0,0, length )) == NULL) {break;}
        //
        if((v.ema12 = new_column_t( "EMA", 12,0,0, length )) == NULL) {break;}
        if((v.ema26 = new_column_t( "EMA", 26,0,0, length )) == NULL) {break;}
        if((v.macd  = new_column_t( "MACD", 12,26,9, length )) == NULL) {break;}
        if((v.signal= new_column_t( "Signal", 12,26,9, length )) == NULL) {break;}
        if((v.hist  = new_column_t( "Hist", 12,26,9, length )) == NULL) {break;}
        //
        if((v.bbup  = new_column_t( "BBUP", 25,1.5,1.5, length )) == NULL) {break;}
        if((v.bbmid = new_column_t( "BBMID", 25,1.5,1.5, length )) == NULL) {break;}
        if((v.bblow = new_column_t( "BBLOW", 25,1.5,1.5, length )) == NULL) {break;}
        if((v.rsi   = new_column_t( "RSI", 14,0,0, length )) == NULL) {break;}
        if((v.roc   = new_column_t( "ROC", 14,0,0, length )) == NULL) {break;}
        if((v.mom   = new_column_t( "MOM", 14,0,0, length )) == NULL) {break;}
        if((v.sar   = new_column_t( "SAR", 0.04,0.2,0, length )) == NULL) {break;}
        if((v.willr = new_column_t( "Williams%R", 14,0,0, length )) == NULL) {break;}
        //
        if((v.adx   = new_column_t( "ADX",  9,0,0, length )) == NULL) {break;}
        if((v.adxr  = new_column_t( "ADXR", 9,0,0, length )) == NULL) {break;}
        if((v.posDI = new_column_t( "+DI", 14,0,0, length )) == NULL) {break;}
        if((v.negDI = new_column_t( "-DI", 14,0,0, length )) == NULL) {break;}
        //
        if((v.ad    = new_column_t( "Chaikin A/D Line", 0,0,0, length )) == NULL) {break;}
        if((v.adosc = new_column_t( "Chaikin A/D Oscillator", 3,10,0, length )) == NULL) {break;}
        if((v.obv   = new_column_t( "On Balance Volume", 0,0,0, length )) == NULL) {break;}

        rc = TA_Initialize();
        if(rc) {break;}

        // EMAが安定するまでの数
        rc = TA_SetUnstablePeriod( TA_FUNC_UNST_EMA, 20 );
        if(rc) {break;}

        //
        // SMA - Simple Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.sma->param[0],
                    TA_MAType_SMA,
                    &v.sma->begIdx, &v.sma->nbElement, v.sma->vs );
        if(rc) {break;}

        //
        // EMA - Exponential Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.ema->param[0],
                    TA_MAType_EMA,
                    &v.ema->begIdx, &v.ema->nbElement, v.ema->vs );
        if(rc) {break;}

        //
        // WMA - Weighted Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.wma->param[0],
                    TA_MAType_WMA,
                    &v.wma->begIdx, &v.wma->nbElement, v.wma->vs );
        if(rc) {break;}

        //
        // DEMA - Double Exponential Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.dema->param[0],
                    TA_MAType_DEMA,
                    &v.dema->begIdx, &v.dema->nbElement, v.dema->vs );
        if(rc) {break;}

        //
        // TEMA - Triple Exponential Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.tema->param[0],
                    TA_MAType_TEMA,
                    &v.tema->begIdx, &v.tema->nbElement, v.tema->vs );
        if(rc) {break;}

        //
        // TRIMA - Triangular Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.trima->param[0],
                    TA_MAType_TRIMA,
                    &v.trima->begIdx, &v.trima->nbElement, v.trima->vs );
        if(rc) {break;}

        //
        // KAMA - Kaufman Adaptive Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.kama->param[0],
                    TA_MAType_KAMA,
                    &v.kama->begIdx, &v.kama->nbElement, v.kama->vs );
        if(rc) {break;}

        //
        // MAMA - MESA Adaptive Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.mama->param[0],
                    TA_MAType_MAMA,
                    &v.mama->begIdx, &v.mama->nbElement, v.mama->vs );
        if(rc) {break;}

        //
        // T3 - Triple Exponential Moving Average (T3)
        //
        rc = TA_MA( 0, (length-1), close,
                    v.t3->param[0],
                    TA_MAType_T3,
                    &v.t3->begIdx, &v.t3->nbElement, v.t3->vs );
        if(rc) {break;}

        //
        // EMA - Exponential Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.ema12->param[0],
                    TA_MAType_EMA,
                    &v.ema12->begIdx, &v.ema12->nbElement, v.ema12->vs );
        if(rc) {break;}

        //
        // EMA - Exponential Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.ema26->param[0],
                    TA_MAType_EMA,
                    &v.ema26->begIdx, &v.ema26->nbElement, v.ema26->vs );
        if(rc) {break;}

        //
        // MACD - Moving Average Convergence/Divergence
        //
        // この3値は必ず一致するように設定すること
        assert( 2*v.macd->param[0] - v.hist->param[0] - v.signal->param[0] == 0 );
        assert( 2*v.macd->param[1] - v.hist->param[1] - v.signal->param[1] == 0 );
        assert( 2*v.macd->param[2] - v.hist->param[2] - v.signal->param[2] == 0 );
        //
        rc = TA_MACD( 0, (length-1), close,
                    v.macd->param[0], v.macd->param[1], v.macd->param[2],
                    &v.macd->begIdx, &v.macd->nbElement,
                    v.macd->vs,
                    v.signal->vs,
                    v.hist->vs );
        v.hist->begIdx      = v.signal->begIdx      = v.macd->begIdx;
        v.hist->nbElement   = v.signal->nbElement   = v.macd->nbElement;
        if(rc) {break;}

        //
        // BBANDS - Bollinger Bands
        //
        // この3値は必ず一致するように設定すること
        assert( 2*v.bbup->param[0] - v.bbmid->param[0] - v.bblow->param[0] == 0 );
        assert( 2*v.bbup->param[1] - v.bbmid->param[1] - v.bblow->param[1] == 0 );
        assert( 2*v.bbup->param[2] - v.bbmid->param[2] - v.bblow->param[2] == 0 );
        //
        rc = TA_BBANDS( 0, (length-1), close,
                    v.bbup->param[0],
                    v.bbup->param[1],
                    v.bbup->param[2],
                    TA_MAType_SMA,      // Simple MA
                    &v.bblow->begIdx, &v.bblow->nbElement,
                    v.bbup->vs,
                    v.bbmid->vs,
                    v.bblow->vs );
        v.bbup->begIdx      = v.bbmid->begIdx       = v.bblow->begIdx;
        v.bbup->nbElement   = v.bbmid->nbElement    = v.bblow->nbElement;
        if(rc) {break;}

        //
        // RSI - Relative Strength Index
        //
        rc = TA_RSI( 0, (length-1), close,
                    v.rsi->param[0],
                    &v.rsi->begIdx, &v.rsi->nbElement, v.rsi->vs );
        if(rc) {break;}

        //
        // ROC - Rate of change : ((price/prevPrice)-1)*100
        //
        rc = TA_ROC( 0, (length-1), close,
                    v.roc->param[0],
                    &v.roc->begIdx, &v.roc->nbElement, v.roc->vs );
        if(rc) {break;}

        //
        // MOM - Momentum
        //
        rc = TA_MOM( 0, (length-1), close,
                    v.mom->param[0],
                    &v.mom->begIdx, &v.mom->nbElement, v.mom->vs );
        if(rc) {break;}

        //
        // SAR - Parabolic SAR
        //
        rc = TA_SAR( 0, (length-1), high, low,
                    v.sar->param[0], v.sar->param[1],
                    &v.sar->begIdx, &v.sar->nbElement, v.sar->vs );
        if(rc) {break;}

        //
        // WILLR - Williams %R
        //
        rc = TA_WILLR( 0, (length-1), high, low, close,
                    v.willr->param[0],
                    &v.willr->begIdx, &v.willr->nbElement, v.willr->vs );
        if(rc) {break;}

        //
        // ADX - Average Directional Movement Index
        //
        rc = TA_ADX( 0, (length-1), high, low, close,
                    v.adx->param[0],
                    &v.adx->begIdx, &v.adx->nbElement, v.adx->vs );
        if(rc) {break;}

        //
        // ADXR - Average Directional Movement Index Rating
        //
        rc = TA_ADXR( 0, (length-1), high, low, close,
                    v.adxr->param[0],
                    &v.adxr->begIdx, &v.adxr->nbElement, v.adxr->vs );
        if(rc) {break;}

        //
        // PLUS_DI - Plus Directional Indicator
        //
        rc = TA_PLUS_DI( 0, (length-1), high, low, close,
                    v.posDI->param[0],
                    &v.posDI->begIdx, &v.posDI->nbElement, v.posDI->vs );
        if(rc) {break;}

        //
        // MINUS_DI - Minus Directional Indicator
        //
        rc = TA_MINUS_DI( 0, (length-1), high, low, close,
                    v.negDI->param[0],
                    &v.negDI->begIdx, &v.negDI->nbElement, v.negDI->vs );
        if(rc) {break;}

        //
        // AD - Chaikin A/D Line
        //
        rc = TA_AD( 0, (length-1), high, low, close, volume,
                    &v.ad->begIdx, &v.ad->nbElement, v.ad->vs );
        if(rc) {break;}

        //
        // ADOSC - Chaikin A/D Oscillator
        //
        rc = TA_ADOSC( 0, (length-1), high, low, close, volume,
                    v.adosc->param[0],
                    v.adosc->param[1],
                    &v.adosc->begIdx, &v.adosc->nbElement, v.adosc->vs );
        if(rc) {break;}

        //
        // OBV - On Balance Volume
        //
        rc = TA_OBV( 0, (length-1), close, volume,
                    &v.obv->begIdx, &v.obv->nbElement, v.obv->vs );
        if(rc) {break;}

        //
        // 出力
        //
        // number: 00
        disp( caption, 0, (length-1), close,
            9,  v.sma, v.ema, v.wma, v.dema, v.tema, v.trima, v.kama, v.mama, v.t3 );
        // number: 01
        disp( caption, 0, (length-1), close,
            5, v.ema12, v.ema26, v.macd, v.signal, v.hist );
        // number: 02
        disp( caption, 0, (length-1), close,
            8,  v.bbup, v.bbmid, v.bblow, v.rsi, v.roc, v.mom, v.sar, v.willr );
        // number: 03
        disp( caption, 0, (length-1), close,
            4,  v.adx, v.adxr, v.posDI, v.negDI );
        // number: 04
        disp( caption, 0, (length-1), close,
            3,  v.ad, v.adosc, v.obv );
    } while(0);

    TA_Shutdown();

    // new_column_t された全領域の解放
    for( int i=0; i<sizeof(struct columns)/sizeof(column_t*); i++ ) {
        free( columns_pp[i] );
    }
    return rc;
}

//
// エントリポイント
//
int main ( int argc, char** argv )
{
    char* inputFileName = NULL;
    char* thisProgramName = NULL;
    row_caption_t* caption = NULL;
    double* open = NULL;
    double* high = NULL;
    double* low = NULL;
    double* close = NULL;
    double* volume = NULL;
    size_t length;

    //
    // 作業開始前にグローバル変数の errno を初期化する
    //
    errno = EXIT_SUCCESS;

    do {
        if( (thisProgramName = calloc( PATH_MAX+1, sizeof(char) )) == NULL ) {break;}
        if( realpath( argv[0], thisProgramName ) == NULL ) {break;}
        if( argc < 2 ) {
            fprintf( stderr, "Usage: %s [file or '-' standard input].\n\n", thisProgramName );
            break;
        }
        fprintf( stderr, "made by \"%s\" program with \"TA-lib %s\"\n", thisProgramName, TA_GetVersionString() );
        //
        if( (inputFileName = calloc( PATH_MAX+1, sizeof(char) )) == NULL ) {break;}
        FILE* fp = NULL;
        enum {FileInput, StdInput} source = FileInput;
        if( strcmp( argv[1], "-" ) == 0) {
            source = StdInput;
            strcpy( inputFileName, "" );
            fprintf( stderr, "Input from stdin\n" );
            fp = stdin;
        } else {
            source = FileInput;
            if( realpath( argv[1], inputFileName ) == NULL ) {break;}
            fprintf( stderr, "Input from file is \"%s\"\n", inputFileName );
            // テキストモードで開く
            if( (fp = fopen( inputFileName, "rt" )) == NULL ) {break;}
        }

        // 入力
        if( read_file( fp, &caption, &open, &high, &low, &close, &volume, &length ) != EXIT_SUCCESS ) {break;}
        if( source == FileInput && fp ) {fclose(fp);}

        // テクニカル分析
        TA_RetCode rc = calculate( caption, open, high, low, close, volume, length );

        // 結果の報告
        TA_RetCodeInfo info;
        TA_SetRetCodeInfo( rc, &info );
        fprintf( stderr, "%d(%s): %s\n", rc, info.enumStr, info.infoStr );
    } while(0);

    free(caption);
    free(open);
    free(high);
    free(low);
    free(close);
    free(volume);
    free(inputFileName);
    free(thisProgramName);
    if(errno) {perror( "main" );}
    return 0;
}

C言語はチョットワカルのでチョット解説

解説

標準ヘッダーの解説は他を当たって欲しい。

この環境にある /usr/local/include/ta-lib/ta_libc.h ヘッダをインクルードするとTA-Libが使えるようになる。

#include <ta-lib/ta_libc.h>

開始

実行はmain()からはじまる。

int main( int argc, char** argv )
{
    char* inputFileName = NULL;
    char* thisProgramName = NULL;
    row_caption_t* caption = NULL;
    double* open = NULL;
    double* high = NULL;
    double* low = NULL;
    double* close = NULL;
    double* volume = NULL;
    size_t length;

変数は使用前に初期化しましょう。

    do {
        if( (thisProgramName = calloc( PATH_MAX+1, sizeof(char) )) == NULL ) {break;}
        if( realpath( argv[0], thisProgramName ) == NULL ) {break;}
        .....
    } while(0);

自分は do { ... } while(0);
とかよく使う。
「ループしてないやん、何をしとんのや」と思った?

これは do{ブロック}を作って、失敗を検出したときにbreak;で脱出する目的。

地の文からインデントを持ち上げて、その上を走り、途中で失敗したら下にたたき落とすという事。
(オフサイドルール採用のPython,Haskellと違い、Cはインデント関係ないけれども、そういう概念でよろしく)

これ、使っている人をあまりみないな。
PHP マニュアルにも載っている正当な使い方と思うんだけどな。

    do {
        if( (thisProgramName = calloc( PATH_MAX+1, sizeof(char) )) == NULL ) {break;}
        if( realpath( argv[0], thisProgramName ) == NULL ) {break;}
        if( argc < 2 ) {
            fprintf( stderr, "Usage: %s [file or '-' standard input].\n\n", thisProgramName );
            break;
        }
        fprintf( stderr, "made by \"%s\" program with \"TA-lib %s\"\n", thisProgramName, TA_GetVersionString() );
        //
        if( (inputFileName = calloc( PATH_MAX+1, sizeof(char) )) == NULL ) {break;}
        FILE* fp = NULL;
        enum {FileInput, StdInput} source = FileInput;
        if( strcmp( argv[1], "-" ) == 0) {
            source = StdInput;
            strcpy( inputFileName, "" );
            fprintf( stderr, "Input from stdin\n" );
            fp = stdin;
        } else {
            source = FileInput;
            if( realpath( argv[1], inputFileName ) == NULL ) {break;}
            fprintf( stderr, "Input from file is \"%s\"\n", inputFileName );
            // テキストモードで開く
            if( (fp = fopen( inputFileName, "rt" )) == NULL ) {break;}
        }

OSが渡してきたコマンドラインパラメータの解釈ね
argv[0]は自分自身のコマンド名、この後は引数
詳細は検索して。
引数に入力ファイル名をよこせと書いている(ただし”-“を指定したら標準入力から)

        // 入力
        if( read_file( fp, &caption, &open, &high, &low, &close, &volume, &length ) != EXIT_SUCCESS ) {break;}
        if( source == FileInput && fp ) {fclose(fp);}

        // テクニカル分析
        TA_RetCode rc = calculate( caption, open, high, low, close, volume, length );

        // 結果の報告
        TA_RetCodeInfo info;
        TA_SetRetCodeInfo( rc, &info );
        fprintf( stderr, "%d(%s): %s\n", rc, info.enumStr, info.infoStr );
    } while(0);

本体。
ファイルを読んで、計算、出力する。
これがしたかった。

    free(caption);
    free(open);
    free(high);
    free(low);
    free(close);
    free(volume);
    free(inputFileName);
    free(thisProgramName);
    if(errno) {perror( "main" );}
    return 0;
}

OSに任せずに、丁寧に後始末しましょう。
free(NULL);は何もしないから問題ない。

ファイル読み込み

errno_t read_file(  FILE *fp,
                    row_caption_t** outcaption,
                    double** outopen,
                    double** outhigh,
                    double** outlow,
                    double** outclose,
                    double** outvolume,
                    size_t* outlength )
{
    row_caption_t* capt = NULL;
    double* open = NULL;
    double* high = NULL;
    double* low = NULL;
    double* close = NULL;
    double* volume = NULL;

キた。初心者の鬼門。
ダブルポインタ。
これは、

  • 変数を返却したいなら、ポインタ。
  • ポインタを返却したいなら、ダブルポインタ。
  • ダブルポインタを返却したいなら、トリプルポインタ。
  • トリプルポインタを返却したいなら、設計ミス。

という雰囲気を味わってください。

    assert( fp != NULL );

ここでファイルポインタ fp はNULLではない表明。

    size_t rowcount = 0;
    for( int batchcount=0; !feof(fp); batchcount++ ) {
        const size_t nmemb = (1+batchcount) * ONE_TIME_ROWS;
        void* p = NULL;
        // 処理ごとに領域の拡張を行なう
        if((p=realloc(capt, sizeof(row_caption_t)*nmemb)) == NULL){goto fail;}else{capt=p;}
        if((p=realloc(open, sizeof(double)*nmemb)) == NULL){goto fail;}else{open=p;}
        if((p=realloc(high, sizeof(double)*nmemb)) == NULL){goto fail;}else{high=p;}
        if((p=realloc(low, sizeof(double)*nmemb)) == NULL){goto fail;}else{low=p;}
        if((p=realloc(close, sizeof(double)*nmemb)) == NULL){goto fail;}else{close=p;}
        if((p=realloc(volume, sizeof(double)*nmemb)) == NULL){goto fail;}else{volume=p;}
        //
        rowcount += read_part(  fp,
                                &capt[ batchcount*ONE_TIME_ROWS ],
                                &open[ batchcount*ONE_TIME_ROWS ],
                                &high[ batchcount*ONE_TIME_ROWS ],
                                &low[ batchcount*ONE_TIME_ROWS ],
                                &close[ batchcount*ONE_TIME_ROWS ],
                                &volume[ batchcount*ONE_TIME_ROWS ],
                                ONE_TIME_ROWS );
        if(errno) {goto fail;}
    }

標準入力を取り扱うので、ファイルサイズがこの時点でわからないので、
漸進的に各列用のメモリを確保しながら読み込んでいきます。

    ONE_TIME_ROWS = 128,    // 漸進処理1回の行数 

こう定義しているから128行毎(128の意味はない、100でも1000でもいい)にforループを回ります。

ループ毎に動的メモリ割り当てが必要になるので、ヒープ領域のメモリ確保関数malloc(https://ja.wikipedia.org/wiki/Malloc)の仲間であるreallocを使ってメモリ領域の伸張をしていきます。

reallocですから、メモリの確保に失敗することも、メモリ領域が移動していることもあります。

メモリの確保に失敗する問題

    size_t rowcount = 0;
    for( int batchcount=0; !feof(fp); batchcount++ ) {
        const size_t nmemb = (1+batchcount) * ONE_TIME_ROWS;
        void* p = NULL;
        // 処理ごとに領域の拡張を行なう
        if((p=realloc(capt, sizeof(row_caption_t)*nmemb)) == NULL){goto fail;}else{capt=p;}
        if((p=realloc(open, sizeof(double)*nmemb)) == NULL){goto fail;}else{open=p;}
        if((p=realloc(high, sizeof(double)*nmemb)) == NULL){goto fail;}else{high=p;}
        if((p=realloc(low, sizeof(double)*nmemb)) == NULL){goto fail;}else{low=p;}
        if((p=realloc(close, sizeof(double)*nmemb)) == NULL){goto fail;}else{close=p;}
        if((p=realloc(volume, sizeof(double)*nmemb)) == NULL){goto fail;}else{volume=p;}
        //

reallocの返値をifで確認して

  • メモリ確保失敗なら(elseの左側)gotoで脱出
  • メモリ確保成功なら(elseの右側)確保できたので新領域へ書き換える。

ここで

  • 左 = Left = 逃げた
  • 右 = Right = 正しい

となっているのはわざとです。

for() {
    if(realloc() != NULL) {
        if(realloc() != NULL) {
            if(realloc() != NULL) {
                if(realloc() != NULL) {
                    if(realloc() != NULL) {
                        if(realloc() != NULL) {
                        }
                    }
                }
            }
        }
    }
}

gotoは嫌われ者だけど、実際こんなの見たくないでしょ。

メモリ領域が移動する問題

そもそもこの関数の外に読み込み途中のポインタを持ち出していないので問題なし。

成功しても、失敗しても終わりが来る

    // 成功結果を返す
    *outcaption = capt;
    *outopen = open;
    *outhigh= high;
    *outlow = low;
    *outclose = close;
    *outvolume = volume;
    *outlength = rowcount;
    return EXIT_SUCCESS;
fail:
    perror("read_file");
    // 失敗したので、この関数で用意した領域を解放する
    free(capt);
    free(open);
    free(high);
    free(low);
    free(close);
    free(volume);
    return EXIT_FAILURE;
}

成功したら、呼び出し元へ内容を入れたポインタを返却する。
失敗したらreallocで確保したメモリを解放して、呼び出し元のポインタは触らない。

漸進的読み込み処理

読み込み処理1回毎の処理はこの関数、飛ばします。

//
// TSVから列を分解して取り出す
//
size_t read_part(   FILE* fp,
                    row_caption_t* caption,
                    double* open,
                    double* high,
                    double* low,
                    double* close,
                    double* volume,
                    size_t length )
{
}

テクニカル指標を計算する

長いので端折ります。

//
// テクニカル指標の計算と出力
//
TA_RetCode calculate(   row_caption_t* caption,
                        double* open,
                        double* high,
                        double* low,
                        double* close,
                        double* volume,
                        size_t length )
{
    struct columns {
        column_t* sma;
        column_t* ema;
    } __attribute__((__packed__)) v = {NULL};   // NULL初期化する
    column_t** columns_pp = (column_t**)&v;     // 上の構造体をポインタの配列で
                                                // アクセスするためのダブルポインタ

    TA_RetCode rc = TA_SUCCESS;
    //
    do {
        if((v.sma   = new_column_t( "SMA", 12,0,0, length )) == NULL) {break;}
        if((v.ema   = new_column_t( "EMA", 12,0,0, length )) == NULL) {break;}

        rc = TA_Initialize();
        if(rc) {break;}

        // EMAが安定するまでの数
        rc = TA_SetUnstablePeriod( TA_FUNC_UNST_EMA, 20 );
        if(rc) {break;}

        //
        // SMA - Simple Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.sma->param[0],
                    TA_MAType_SMA,
                    &v.sma->begIdx, &v.sma->nbElement, v.sma->vs );
        if(rc) {break;}

        //
        // EMA - Exponential Moving Average
        //
        rc = TA_MA( 0, (length-1), close,
                    v.ema->param[0],
                    TA_MAType_EMA,
                    &v.ema->begIdx, &v.ema->nbElement, v.ema->vs );
        if(rc) {break;}


        //
        // 出力
        //
        // number: 00
        disp( caption, 0, (length-1), close,
            9,  v.sma, v.ema, v.wma, v.dema, v.tema, v.trima, v.kama, v.mama, v.t3 );
        // number: 01
        disp( caption, 0, (length-1), close,
            5, v.ema12, v.ema26, v.macd, v.signal, v.hist );
        // number: 02
        disp( caption, 0, (length-1), close,
            8,  v.bbup, v.bbmid, v.bblow, v.rsi, v.roc, v.mom, v.sar, v.willr );
        // number: 03
        disp( caption, 0, (length-1), close,
            4,  v.adx, v.adxr, v.posDI, v.negDI );
        // number: 04
        disp( caption, 0, (length-1), close,
            3,  v.ad, v.adosc, v.obv );
    } while(0);

    TA_Shutdown();

    // new_column_t された全領域の解放
    for( int i=0; i<sizeof(struct columns)/sizeof(column_t*); i++ ) {
        free( columns_pp[i] );
    }
    return rc;
}

上で説明した do { ... } while(0);
で失敗即脱出とTA-Libの計算関数呼び出しを組み合わせているだけ。
disp()
関数の呼出を区切りにスクリプトでcsplitでCSVファイルを分割している。

終わりに

C言語チョットワカルからC言語を使っているけれども、この言語はどうしても動的メモリ割り当てとかポインタとかダブルポインタとかを使う事になるから、

絶対速度勝負ならとにかく、普通はPythonでええねんな。


ここからRust言語版(2018/05/27追記)

C/C++に死を」ですすめられているのでRust言語で同じ物を書き直しました。

C,Haskellの知識で書いているのでRust言語はチョットよくわからないが、C言語版,Rust言語版共に同じ入力に対して同じ出力が得られているのでこれで良しとする。

Cargoのプロジェクトはここに

TA-LibのRustラッパーはこれを採用しました

以下のプログラムはこの環境で動くことを確認しています。

$ uname -srvm
Linux 4.4.0-127-generic #153-Ubuntu SMP Sat May 19 10:58:46 UTC 2018 x86_64
$ cargo --version
cargo 1.26.0 (0e7c5a931 2018-04-06)
$ rustc --version
rustc 1.26.0 (a77568041 2018-05-07)
$ clang --version
clang version 3.8.0-2ubuntu4 (tags/RELEASE_380/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

CSVを変換するスクリプト

taFromHiSpeedCSV

#! /usr/bin/env bash
#

if [ "$#" -eq "1" ]
then
    inputfile="$1"
    outputfileprefix="$(basename -s csv $1)ta"
    programfile="mta-rust"
    echo "Input: $inputfile"
    # build program
    cargo install --force

    # 1,2行目のタイトル行を残して逆順にして
    # Date,Time,Open,High,Low,Close,Volume,OI, ... の並びから
    # Date,Time,Open,High,Low,Close,Volume を取り出して
    # テクニカル指標を計算して
    # 結果を分割してファイルに出力する
    sed -n -e 's/,/\t/g;1,2p;1,2d;1!G;h;$p' "$inputfile" | cut -f1-7 | $programfile - | csplit -sz --prefix=$outputfileprefix --suffix-format=%02d.csv - /Date/ {*}
else
    echo "Usage: $0 \"*.csv\""
fi

テクニカル指標を計算する

こう使います。

~/tractor/rust_ta_test/ > ./taFromHiSpeedCSV ~/ni225.csv 
Input: /home/aki/ni225.csv
  Installing mta-rust v0.1.0 (file:///home/aki/tractor/rust_ta_test)
   Compiling mta-rust v0.1.0 (file:///home/aki/tractor/rust_ta_test)
    Finished release [optimized] target(s) in 4.6 secs
   Replacing /home/aki/.cargo/bin/mta-rust
made by "mta-rust" program, with "TA-lib 0.4.0 (Mar  5 2018 02:08:45)"
~/tractor/rust_ta_test/ > ls -l
合計 2272
-rw-rw-r-- 1 aki aki  27118  5月 24 17:34 Cargo.lock
-rw-rw-r-- 1 aki aki    143  5月 24 23:55 Cargo.toml
-rw-rw-r-- 1 aki aki 512625  5月 25 19:44 ni225.ta00.csv
-rw-rw-r-- 1 aki aki 405999  5月 25 19:44 ni225.ta01.csv
-rw-rw-r-- 1 aki aki 631554  5月 25 19:44 ni225.ta02.csv
-rw-rw-r-- 1 aki aki 307575  5月 25 19:44 ni225.ta03.csv
-rw-rw-r-- 1 aki aki 405999  5月 25 19:44 ni225.ta04.csv
drwxrwxr-x 2 aki aki   4096  5月 25 18:37 src
-rwxrwxr-x 1 aki aki    711  5月 24 23:36 taFromHiSpeedCSV
drwxrwxr-x 3 aki aki   4096  5月 24 23:56 target
~/tractor/rust_ta_test/ > ls *.csv | xargs -n 1 -I {} diff -s ./{} ../{}
ファイル ./ni225.ta00.csv と ../ni225.ta00.csv は同一です
ファイル ./ni225.ta01.csv と ../ni225.ta01.csv は同一です
ファイル ./ni225.ta02.csv と ../ni225.ta02.csv は同一です
ファイル ./ni225.ta03.csv と ../ni225.ta03.csv は同一です
ファイル ./ni225.ta04.csv と ../ni225.ta04.csv は同一です 

Rust言語ソース

rust_ta_test/src/main.rs

///
/// TA-libでテクニカル指標を計算するプログラム
/// https://ak1211.com
/// Copyright (c) 2018 Akihiro Yamamoto
///
/// This software is released under the MIT License.
/// http://opensource.org/licenses/mit-license.php
///
extern crate ta_lib_wrapper;
use std::cmp;
use std::env;
use std::ffi;
use std::fmt;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::mem;
use std::str::FromStr;
use ta_lib_wrapper::{TA_FuncUnstId, TA_GetVersionString, TA_Initialize, TA_Integer, TA_MAType,
                     TA_Real, TA_RetCode, TA_RetCodeInfo, TA_SetRetCodeInfo, TA_SetUnstablePeriod,
                     TA_Shutdown, TA_AD, TA_ADOSC, TA_ADX, TA_ADXR, TA_BBANDS, TA_MA, TA_MACD,
                     TA_MINUS_DI, TA_MOM, TA_OBV, TA_PLUS_DI, TA_ROC, TA_RSI, TA_SAR, TA_WILLR};

// 日付時刻列の幅
const DATE_TIME_COLUMN_WIDTH: usize = 24;
// 列の最小幅
const COLUMN_WIDTH_MIN: usize = 9;

///
/// 日本時間
///
#[derive(Clone)]
struct AsiaTokyoDateTime {
    date: String,
    time: String,
}

///
/// 日本時間型のフォーマット出力
///
impl fmt::Display for AsiaTokyoDateTime {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // timezone : Asia / Tokyo
        write!(f, "{}T{}+0900", self.date, self.time)
    }
}

///
/// 初値
///
struct Open<T>(T);

///
/// 高値
///
struct High<T>(T);

///
/// 安値
///
struct Low<T>(T);

///
/// 終値
///
struct Close<T>(T);

///
/// 出来高
///
struct Volume<T>(T);

///
/// 行の値と構造
///
struct Row<T> {
    date_time: AsiaTokyoDateTime,
    open: Open<T>,
    high: High<T>,
    low: Low<T>,
    close: Close<T>,
    volume: Volume<T>,
}

///
/// 入力から行を読む
///
fn read_file<T: FromStr>(stream: &mut BufRead) -> Vec<Row<T>> {
    let mut line = String::new();
    // 先頭2行は読み捨てる
    let _ = stream.read_line(&mut line);
    let _ = stream.read_line(&mut line);
    line.clear();

    let mut line_number: u64 = 3;
    let mut built = Vec::new();
    //
    while stream.read_line(&mut line).unwrap() > 0 {
        // 空行は無視する
        if !line.trim().is_empty() {
            built.push(parse_line(line_number, &line).unwrap());
        }
        line.clear();
        line_number = line_number + 1;
    }
    return built;
}

///
/// タブ区切りの行を分解して、値と構造を得る
///
fn parse_line<T: FromStr>(line_number: u64, line: &str) -> Result<Row<T>, String> {
    let errmsg = |s: &str| format!("line {}, No parse of '{}'.", line_number, s);
    let parts: Vec<&str> = line.split('\t').map(|a| a.trim()).collect();
    match parts.as_slice() {
        [d, t, o, h, l, c, v] => {
            return Ok(Row {
                date_time: AsiaTokyoDateTime {
                    date: d.to_string(),
                    time: t.to_string(),
                },
                open: Open(o.parse().map_err(|_| errmsg(o))?),
                high: High(h.parse().map_err(|_| errmsg(h))?),
                low: Low(l.parse().map_err(|_| errmsg(l))?),
                close: Close(c.parse().map_err(|_| errmsg(c))?),
                volume: Volume(v.parse().map_err(|_| errmsg(v))?),
            });
        }
        _ => return Err(format!("line {}, Column mismatch.", line_number)),
    }
}

///
/// テクニカル指標のオプションとフォーマット出力
///
struct MacdPeriods {
    fast: u32,
    slow: u32,
    signal: u32,
}

impl fmt::Display for MacdPeriods {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:.2},{:.2},{:.2}", self.fast, self.slow, self.signal)
    }
}

struct BbandsOpt {
    period: u32,
    dev_up: TA_Real,
    dev_down: TA_Real,
}

impl fmt::Display for BbandsOpt {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{:.2},{:.2},{:.2}",
            self.period, self.dev_up, self.dev_down
        )
    }
}

struct SarOpt {
    acceleration: TA_Real,
    maximum: TA_Real,
}

impl fmt::Display for SarOpt {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:.2},{:.2}", self.acceleration, self.maximum)
    }
}

struct AdoscPeriods {
    fast: u32,
    slow: u32,
}

impl fmt::Display for AdoscPeriods {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:.2},{:.2}", self.fast, self.slow)
    }
}

///
/// テクニカル指標
///
enum Indicator {
    Dema(u32),
    Ema(u32),
    Kama(u32),
    Mama(u32),
    Sma(u32),
    T3(u32),
    Tema(u32),
    Trima(u32),
    Wma(u32),
    Macd(MacdPeriods),
    Bbands(BbandsOpt),
    Rsi(u32),
    Roc(u32),
    Mom(u32),
    Sar(SarOpt),
    WillR(u32),
    Adx(u32),
    AdxR(u32),
    PlusDI(u32),
    MinusDI(u32),
    Ad,
    Adosc(AdoscPeriods),
    Obv,
}

///
/// 列
///
struct Column {
    caption: String,
    begin_idx: usize,
    values: Vec<TA_Real>,
}

impl Column {
    fn new(
        name: &str,
        opt: Option<&fmt::Display>,
        begin_idx: usize,
        values: Vec<TA_Real>,
    ) -> Column {
        Column {
            caption: match opt {
                None => name.to_string(),
                Some(a) => format!("{}({})", name, a),
            },
            begin_idx: begin_idx,
            values: values,
        }
    }
    ///
    /// 列見出しを出す
    ///
    fn show_caption(&self) -> String {
        format!("{:>width$}", self.caption, width = self.width())
    }
    ///
    /// ix行の値を出す
    ///
    fn show_nth(&self, ix: usize) -> String {
        let offset = ix as isize - self.begin_idx as isize;
        if offset < 0 {
            return format!("{:>width$}", "N/A", width = self.width());
        } else {
            return format!(
                "{:width$.2}",
                self.values[offset as usize],
                width = self.width()
            );
        }
    }
    ///
    /// 列の幅
    ///
    fn width(&self) -> usize {
        return cmp::max(self.caption.len(), COLUMN_WIDTH_MIN);
    }
}

///
/// 単なる入力行の転置行列
///
#[allow(dead_code)]
struct Prices {
    date_time: Vec<AsiaTokyoDateTime>,
    open: Open<Vec<TA_Real>>,
    high: High<Vec<TA_Real>>,
    low: Low<Vec<TA_Real>>,
    close: Close<Vec<TA_Real>>,
    volume: Volume<Vec<TA_Real>>,
}

impl Prices {
    ///
    /// 行を転置して格納する
    ///
    fn new(rows: &Vec<Row<TA_Real>>) -> Prices {
        Prices {
            date_time: rows.iter().map(|a| a.date_time.clone()).collect(),
            open: Open(rows.iter().map(|a| a.open.0).collect()),
            high: High(rows.iter().map(|a| a.high.0).collect()),
            low: Low(rows.iter().map(|a| a.low.0).collect()),
            close: Close(rows.iter().map(|a| a.close.0).collect()),
            volume: Volume(rows.iter().map(|a| a.volume.0).collect()),
        }
    }
    ///
    /// 値を陳列して出力する
    ///
    fn disp(&self, cols: Vec<&Column>) -> io::Result<()> {
        // 出力に出す入力の値
        let close: Column = {
            let Close(ref close_prices) = self.close;
            Column::new("Close", None, 0, close_prices.clone())
        };
        // 出力に出す入力の値を含んだ出力値の列
        let columns: Vec<&Column> = vec![vec![&close], cols]
            .into_iter()
            .flat_map(|a| a)
            .collect();
        // 行バッファ(サイズは適当)
        let mut buff = String::with_capacity(1024);
        //
        // 行バッファに見だしを蓄積していく
        //
        buff.push_str(&format!(
            "{:>width$}",
            "DateTime",
            width = DATE_TIME_COLUMN_WIDTH
        ));
        // それぞれの見だしを','区切りで結合する
        for v in &columns {
            buff.push(',');
            buff.push_str(&v.show_caption());
        }
        buff.push('\n');
        //
        // 各行を出力する
        //
        let mut writer = BufWriter::new(io::stdout());
        for (ix, date_time) in self.date_time.iter().enumerate() {
            // 日付
            buff.push_str(&format!("{}", date_time));
            // それぞれの値を','区切りで結合する
            for v in &columns {
                buff.push(',');
                buff.push_str(&v.show_nth(ix));
            }
            buff.push('\n');
            // 出力する
            writer.write(buff.as_bytes())?;
            buff.clear();
        }
        writer.flush()
    }
    ///
    /// 指定のテクニカル指標を計算する
    ///
    fn calculate(&self, targets: &[Indicator]) -> Result<Vec<Column>, String> {
        // TA-libの初期化
        unsafe {
            match TA_Initialize() {
                TA_RetCode::TA_SUCCESS => (),
                a => panic!("fail to TA_Initialize, err: {:?}", a),
            }
            // EMAが安定するまでの数
            match TA_SetUnstablePeriod(TA_FuncUnstId::TA_FUNC_UNST_EMA, 20) {
                TA_RetCode::TA_SUCCESS => (),
                a => panic!("fail to TA_SetUnstablePeriod, err: {:?}", a),
            }
        }
        // それぞれのテクニカル指標を計算する
        let built = targets
            .iter()
            .map(|a| self.calc_singlar(a))
            .collect::<Result<Vec<Vec<Column>>, TA_RetCode>>();
        // TA-libの終了
        unsafe {
            TA_Shutdown();
        }
        // テクニカル指標の計算結果を返す
        match built {
            Ok(a) => return Ok(a.into_iter().flat_map(|v| v).collect::<Vec<Column>>()),
            Err(rc) => {
                //
                // 失敗したらTA-libから失敗原因を得て返却
                //
                let (enm, inf): (&ffi::CStr, &ffi::CStr) = unsafe {
                    let mut rcinfo: TA_RetCodeInfo = mem::zeroed();
                    TA_SetRetCodeInfo(rc, &mut rcinfo);
                    (
                        ffi::CStr::from_ptr(rcinfo.enumStr),
                        ffi::CStr::from_ptr(rcinfo.infoStr),
                    )
                };
                return Err(format!(
                    "{}:{}",
                    enm.to_str().unwrap(),
                    inf.to_str().unwrap()
                ));
            }
        }
    }
    ///
    /// テクニカル指標を計算する(内部用)
    ///
    fn calc_singlar(&self, target: &Indicator) -> Result<Vec<Column>, TA_RetCode> {
        match target {
            Indicator::Dema(op) => self.calc_ma("DEMA", TA_MAType::TA_MAType_DEMA, *op),
            Indicator::Ema(op) => self.calc_ma("EMA", TA_MAType::TA_MAType_EMA, *op),
            Indicator::Kama(op) => self.calc_ma("KAMA", TA_MAType::TA_MAType_KAMA, *op),
            Indicator::Mama(op) => self.calc_ma("MAMA", TA_MAType::TA_MAType_MAMA, *op),
            Indicator::Sma(op) => self.calc_ma("SMA", TA_MAType::TA_MAType_SMA, *op),
            Indicator::T3(op) => self.calc_ma("T3", TA_MAType::TA_MAType_T3, *op),
            Indicator::Tema(op) => self.calc_ma("TEMA", TA_MAType::TA_MAType_TEMA, *op),
            Indicator::Trima(op) => self.calc_ma("TRIMA", TA_MAType::TA_MAType_TRIMA, *op),
            Indicator::Wma(op) => self.calc_ma("WMA", TA_MAType::TA_MAType_WMA, *op),
            Indicator::Macd(op) => self.calc_macd(op),
            Indicator::Bbands(op) => self.calc_bbands(op),
            Indicator::Rsi(op) => self.calc_rsi(*op),
            Indicator::Roc(op) => self.calc_roc(*op),
            Indicator::Mom(op) => self.calc_mom(*op),
            Indicator::Sar(op) => self.calc_sar(op),
            Indicator::WillR(op) => self.calc_willr(*op),
            Indicator::Adx(op) => self.calc_adx(*op),
            Indicator::AdxR(op) => self.calc_adxr(*op),
            Indicator::PlusDI(op) => self.calc_plus_di(*op),
            Indicator::MinusDI(op) => self.calc_minus_di(*op),
            Indicator::Ad => self.calc_ad(),
            Indicator::Adosc(op) => self.calc_adosc(op),
            Indicator::Obv => self.calc_obv(),
        }
    }
    ///
    /// Moving Average
    ///
    fn calc_ma(&self, name: &str, typ: TA_MAType, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_MA(
                0,
                close_prices.len() as i32 - 1,
                close_prices.as_ptr(),
                period as i32,
                typ,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new(name, Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// MACD - Moving Average Convergence/Divergence
    ///
    fn calc_macd(&self, period: &MacdPeriods) -> Result<Vec<Column>, TA_RetCode> {
        let Close(ref close_prices) = self.close;
        let mut macd_out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut signal_out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut histogram_out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_MACD(
                0,
                close_prices.len() as i32 - 1,
                close_prices.as_ptr(),
                period.fast as i32,
                period.slow as i32,
                period.signal as i32,
                &mut out_begin,
                &mut out_size,
                macd_out.as_mut_ptr(),
                signal_out.as_mut_ptr(),
                histogram_out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                macd_out.set_len(out_size as usize);
                signal_out.set_len(out_size as usize);
                histogram_out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("MACD", Some(&period), out_begin as usize, macd_out),
            Column::new("Signal", Some(&period), out_begin as usize, signal_out),
            Column::new("Hist", Some(&period), out_begin as usize, histogram_out),
        ]);
    }
    ///
    /// BBANDS - Bollinger Bands
    ///
    fn calc_bbands(&self, opt: &BbandsOpt) -> Result<Vec<Column>, TA_RetCode> {
        let Close(ref close_prices) = self.close;
        let mut up_out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut mid_out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut low_out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_BBANDS(
                0,
                close_prices.len() as i32 - 1,
                close_prices.as_ptr(),
                opt.period as i32,
                opt.dev_up,
                opt.dev_down,
                TA_MAType::TA_MAType_SMA,
                &mut out_begin,
                &mut out_size,
                up_out.as_mut_ptr(),
                mid_out.as_mut_ptr(),
                low_out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                up_out.set_len(out_size as usize);
                mid_out.set_len(out_size as usize);
                low_out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("BBUP", Some(&opt), out_begin as usize, up_out),
            Column::new("BBMID", Some(&opt), out_begin as usize, mid_out),
            Column::new("BBLOW", Some(&opt), out_begin as usize, low_out),
        ]);
    }
    ///
    /// RSI - Relative Strength Index
    ///
    fn calc_rsi(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_RSI(
                0,
                close_prices.len() as i32 - 1,
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("RSI", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// ROC - Rate of change : ((price/prevPrice)-1)*100
    ///
    fn calc_roc(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_ROC(
                0,
                close_prices.len() as i32 - 1,
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("ROC", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// MOM - Momentum
    ///
    fn calc_mom(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_MOM(
                0,
                close_prices.len() as i32 - 1,
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("MOM", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// SAR - Parabolic SAR
    ///
    fn calc_sar(&self, opt: &SarOpt) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let mut out: Vec<TA_Real> = Vec::with_capacity(high_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_SAR(
                0,
                high_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                opt.acceleration,
                opt.maximum,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![Column::new("SAR", Some(opt), out_begin as usize, out)]);
    }
    ///
    /// WILLR - Williams %R
    ///
    fn calc_willr(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_WILLR(
                0,
                close_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("Williams%R", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// ADX - Average Directional Movement Index
    ///
    fn calc_adx(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_ADX(
                0,
                close_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("ADX", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// ADXR - Average Directional Movement Index Rating
    ///
    fn calc_adxr(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_ADXR(
                0,
                close_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("ADXR", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// PLUS_DI - Plus Directional Indicator
    ///
    fn calc_plus_di(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_PLUS_DI(
                0,
                close_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("+DI", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// MINUS_DI - Minus Directional Indicator
    ///
    fn calc_minus_di(&self, period: u32) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let Close(ref close_prices) = self.close;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_MINUS_DI(
                0,
                close_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                close_prices.as_ptr(),
                period as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("-DI", Some(&period), out_begin as usize, out),
        ]);
    }
    ///
    /// AD - Chaikin A/D Line
    ///
    fn calc_ad(&self) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let Close(ref close_prices) = self.close;
        let Volume(ref volume) = self.volume;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_AD(
                0,
                close_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                close_prices.as_ptr(),
                volume.as_ptr(),
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("Chaikin A/D Line", None, out_begin as usize, out),
        ]);
    }
    ///
    /// ADOSC - Chaikin A/D Oscillator
    ///
    fn calc_adosc(&self, period: &AdoscPeriods) -> Result<Vec<Column>, TA_RetCode> {
        let High(ref high_prices) = self.high;
        let Low(ref low_prices) = self.low;
        let Close(ref close_prices) = self.close;
        let Volume(ref volume) = self.volume;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_ADOSC(
                0,
                close_prices.len() as i32 - 1,
                high_prices.as_ptr(),
                low_prices.as_ptr(),
                close_prices.as_ptr(),
                volume.as_ptr(),
                period.fast as i32,
                period.slow as i32,
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new(
                "Chaikin A/D Oscillator",
                Some(&period),
                out_begin as usize,
                out,
            ),
        ]);
    }
    ///
    /// OBV - On Balance Volume
    ///
    fn calc_obv(&self) -> Result<Vec<Column>, TA_RetCode> {
        let Close(ref close_prices) = self.close;
        let Volume(ref volume) = self.volume;
        let mut out: Vec<TA_Real> = Vec::with_capacity(close_prices.len());
        let mut out_begin: TA_Integer = 0;
        let mut out_size: TA_Integer = 0;

        unsafe {
            let ret_code = TA_OBV(
                0,
                close_prices.len() as i32 - 1,
                close_prices.as_ptr(),
                volume.as_ptr(),
                &mut out_begin,
                &mut out_size,
                out.as_mut_ptr(),
            );
            if ret_code == TA_RetCode::TA_SUCCESS {
                out.set_len(out_size as usize);
            } else {
                return Err(ret_code);
            }
        }
        return Ok(vec![
            Column::new("On Balance Volume", None, out_begin as usize, out),
        ]);
    }
}

///
/// エントリポイント
///
fn main() {
    let this_program_name = env::args().next().unwrap();
    let input_file_name = match env::args().nth(1) {
        Some(a) => a,
        None => {
            eprintln!("Usage: {} [file or '-' standard input].", this_program_name);
            return;
        }
    };
    let ta_version_string: &ffi::CStr = unsafe { ffi::CStr::from_ptr(TA_GetVersionString()) };
    eprintln!(
        "made by \"{}\" program, with \"TA-lib {}\"",
        this_program_name,
        ta_version_string.to_str().unwrap()
    );
    //
    // 入力を読み込む
    //
    let prices = if input_file_name == "-" {
        let mut reader = BufReader::new(io::stdin());
        Prices::new(&read_file(&mut reader))
    } else {
        let mut reader = BufReader::new(File::open(&input_file_name).unwrap());
        Prices::new(&read_file(&mut reader))
    };
    //
    // 計算する対象のテクニカル指標
    //
    let indicators = [
        // number: 00
        vec![
            Indicator::Sma(12),
            Indicator::Ema(12),
            Indicator::Wma(12),
            Indicator::Dema(12),
            Indicator::Tema(12),
            Indicator::Trima(12),
            Indicator::Kama(12),
            Indicator::Mama(12),
            Indicator::T3(12),
        ],
        // number: 01
        vec![
            Indicator::Ema(12),
            Indicator::Ema(26),
            Indicator::Macd(MacdPeriods {
                fast: 12,
                slow: 26,
                signal: 9,
            }),
        ],
        // number: 02
        vec![
            Indicator::Bbands(BbandsOpt {
                period: 25,
                dev_up: 1.5,
                dev_down: 1.5,
            }),
            Indicator::Rsi(14),
            Indicator::Roc(14),
            Indicator::Mom(14),
            Indicator::Sar(SarOpt {
                acceleration: 0.04,
                maximum: 0.2,
            }),
            Indicator::WillR(14),
        ],
        // number: 03
        vec![
            Indicator::Adx(9),
            Indicator::AdxR(9),
            Indicator::PlusDI(14),
            Indicator::MinusDI(14),
        ],
        // number: 04
        vec![
            Indicator::Ad,
            Indicator::Adosc(AdoscPeriods { fast: 3, slow: 10 }),
            Indicator::Obv,
        ],
    ];
    //
    // テクニカル指標の計算と出力
    //
    for inds in indicators.iter() {
        let columns = prices.calculate(inds).unwrap();
        let _ = prices.disp(columns.iter().collect());
    }
}

Rust言語は今回始めて使うのでチョットよくワカラナイ。解説できない。

解説

C言語ソースをRust言語に移植しただけだから見比べてみてほしい(TA-libラッパーが薄いのでそのままですやん)。

あとC言語のエグさを知っているとRust言語はとてもイイ。おすすめ。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください