FXボーグ | テクニカル実験室

テクニカル分析を使った自動売買プログラムの開発に挑戦!

スポンサーリンク

1pipフィルターを使ってDUKASCOPYのTickデータを97%削減

このところ仕事や私事が忙しくブログが書けずにいましたが、ようやく落ち着いてきました。・・・で、非時系列のisokinetic チャートを使ったバックテスト環境を自作しようかと考えていたのですが、改めてNinjaTrader8の機能を確認したところ、全てデフォルト機能で対応できることが判明しました・・・。(結構ショック)

NinjaTraderのよいところ

  1. 1.ティックデータをインポートできる(AskとBidの両方に対応)
  2. 2.ティックベースのバックテストに対応。
  3. 3.バーの生成ロジックをカスタマイズできる。
  4. 4.カスタムバー(3.で定義したバー)でバックテスト可能。 

ただしティックが使えるといっても、そのままではサイズがでか過ぎて速度的に使いモノになりません。例えばUSDJPYのティックデータは5年で5GBを超えてしまいます。

そこで1pip未満の変動を無視するようにしたところ118MBまでサイズを落とせました。

f:id:fxborg:20170730183453p:plainこれならバックテスト速度も期待できそうです。

ティックデータの入手

ティックデータの取得にはJForexTickstoryなどのソフトを利用すると簡単です。今回はTickstoryを使用しました。

NinjaTrader8にインポートするにはそれ用の書式に変換してあげる必要があるのですが、手作業ではとても無理なので変換用のプログラムを書きました。

このプログラムを使うにはTickstoryからダウンロードする際に、以下のフォーマットで保存する必要があります。

Tickstory上の出力形式 

 {Timestamp:yyyy.MM.dd HH:mm:ss.fff},{AskPrice},{BidPrice},{AskVolume:F0},{BidVolume:F0}

(詳しくはリファレンスを参照のこと)

出力例 

Timestamp,Ask Price,Bid Price,Ask Volume,Bid Volume
2012.01.12 01:02:10.100,1.24271,1.24233,1,1
2012.02.13 02:04:10.332,1.24262,1.24243,1,2

ちなみにこのフォーマットはJForexの出力形式と一緒です。またNinjaTraderの時間はGMT時間なので出力の際に合わせる必要があります。

変換プログラム

Tickデータはかなりでかいので1pip未満の変動はカットすることにしました。また、NinjaTraderにはAsk+Bid+Closeの3種のデータを用意する必要があるのでこれにも対応しました。

#include <windows.h>
#include <time.h>
#include <iostream>
#include <fstream>
#include <string>
#include <locale>


struct tick_type {
	time_t time;
	float bid;
	float ask;
	float bid_vol;
	float ask_vol;
};


int copy(const std::string & src, const std::string & dist,const float threshold)
{
	//---
	std::cout << "Threshold:" << threshold << std::endl;
	//---
	//---
	float prev_price = 0.0;
	int prev_hour = -1;
	//---
	std::ifstream fin(src);
	if (fin.bad()) {
		std::cout << "File Open Error:" << src  << std::endl;
		return 1;
	}
	std::ofstream fask;
	fask.open((dist+".Ask.txt").c_str());
	if (fask.bad())
	{
		std::cout << "File Open Error:" << dist <<".Ask.txt"<< std::endl;
		return 1;
	}
	fask.clear();
	std::cout << "writing :" << dist << ".Ask.txt" << std::endl;

	std::ofstream fbid;
	fbid.open((dist+".Bid.txt").c_str());
	if (fbid.bad())
	{
		std::cout << "File Open Error:" << dist << ".Bid.txt" << std::endl;
		return 1;
	}

	fbid.clear();
	std::cout << "writing :" << dist << ".Bid.txt" << std::endl;

	std::ofstream fmid;
	fmid.open((dist + ".Last.txt").c_str());
	if (fmid.bad())
	{
		std::cout << "File Open Error:" << dist << ".Last.txt" << std::endl;
		return 1;
	}

	fmid.clear();
	std::cout << "writing :" << dist << ".Last.txt" << std::endl;
	//---

	std::string str;
	int n = 0;
	while (std::getline(fin, str))
	{
		if (++n % 100000 == 0) std::cout << ".";
		struct tm tm = {0};
		tick_type tick;
		int msec;
		int res = sscanf_s(str.c_str(), "%4d.%2d.%2d %2d:%2d:%2d.%3d,%f,%f,%f,%f",
			&tm.tm_year,
			&tm.tm_mon,
			&tm.tm_mday,
			&tm.tm_hour,
			&tm.tm_min,
			&tm.tm_sec,
			&msec,
			&tick.ask,
			&tick.bid,
			&tick.ask_vol,
			&tick.bid_vol);
	
		if(res!=11){
			std::cout << "(Err)-->" << str << std::endl;
			continue;
		}
		// create tick data
		tm.tm_year -= 1900;
		tm.tm_mon -= 1;
		tick.time = mktime(&tm);
		// check time
		int hour = int(tick.time / 3600);
		float price = (tick.bid + tick.ask)*0.5f;
		// check price
		if (threshold > std::abs(price - prev_price))
		{
			if (prev_hour == hour) continue;
			else prev_hour = hour;
		}
		prev_price = price;

		char stime[20];
		strftime(stime, sizeof(stime), "%Y%m%d %H%M%S", std::localtime(&tick.time));
		char msec_str[4];
		sprintf_s(msec_str, sizeof(msec_str), "%03d", msec);
		fask << stime << " " << msec_str << "0000;" << tick.ask << ";" << max(1,int(tick.ask_vol)) << std::endl;
		fbid << stime << " " << msec_str << "0000;" << tick.bid << ";" << max(1, int(tick.bid_vol)) << std::endl;
		fmid << stime << " " << msec_str << "0000;" << (tick.ask+tick.bid)*0.5F << ";" << max(1, int(tick.ask_vol+tick.bid_vol)) << std::endl;
	}

	fin.close();
	fask.flush();
	fask.close();
	fbid.flush();
	fbid.close();
	fmid.flush();
	fmid.close();
	std::cout << "done." << std::endl;

	return 0;
}

int main(int argc, char *argv[])
{
	float threshold = 0.0001f;
	std::string src_path;
	std::string dist_path;
	if (argc != 4)
	{
		std::cout << "parameter count does not match." << std::endl;
		std::cout << "  ticoptimizer   " << std::endl;
		return 2;
	}
	else
	{
		src_path = argv[1];
		dist_path = argv[2];
		threshold = std::stof(argv[3]);

	}
	return copy(src_path, dist_path,threshold);
}

なんともイケてないプログラムですがこれでサイズが3%以下になります。

▼使用法

ticksconv.exe "<元ファイル名>"  "<先ファイル名>"  "しきい値"

例)
ticksconv.exe ".\USDJPY.csv" ".\USDJPY" "0.01"
ticksconv.exe ".\EURUSD.csv" ".\EURUSD" "0.0001"

 NinjaTraderへインポート

NinjaTraderへ取り込む際はファイル名が通貨名となるので注意が必要です。

USDJPYという通貨名にしたい場合は

  1. USDJPY.Ask.txt
  2. USDJPY.Bid.txt
  3. USDJPY.Last.txt

のようなファイル名にします。インポートはメニューから「Tools」-「Import」-「HistoricalData...」を選択します。

f:id:fxborg:20170730183957p:plain

Histrical DataウィンドウのData Typeをを切り替えながら「Import」ボタンでそれに対応したファイルを選択します。

DataType:Last を選択する際に「Generate 'Minute' bars from imported tick data.」にチェックを入れると各分足が生成されます。

ウインドウの下にあるEditタブを選択するとインポートしたデータを見ることが出来ます。

f:id:fxborg:20170730184730p:plain

最後に

今回はTickデータを圧縮しNinjaTrader8にインポートするまでを行いました。

今回の作業を通じて1pip以上の価格変動が全ティックの3%未満のティックのみで作られているということが分りました。このような例外的なティックの動きを単純な時系列データで捉えるのはかなり厳しいのではないかという気がします。

次回はNinjaTrader 8版のisokinetic chartを作りたいと思います。

こちらからどうぞ
ticksconv.zip