プログラミングでアイデアを具現化したい

あらゆるものを具現化するためにプログラミングを始めました。主にC#

ビットコイン(BTC/JPY)の時間帯別値幅を出してみた

tos5511.hatenablog.com
以前投稿した記事と似た内容です。
ビットコインには時間帯による癖があるのか調査してみました。


ちょっと手持ちのサンプルが少ないんで統計値とはまだ呼べないかもですが。

select TB_A.tradedate
      ,MAX(case when TB_A.時間='00' then TB_A.値幅 else 0 end)'00'
    ,MAX(case when TB_A.時間='01' then TB_A.値幅 else 0 end)'01'
    ,MAX(case when TB_A.時間='02' then TB_A.値幅 else 0 end)'02'
    ,MAX(case when TB_A.時間='03' then TB_A.値幅 else 0 end)'03'
    ,MAX(case when TB_A.時間='04' then TB_A.値幅 else 0 end)'04'
    ,MAX(case when TB_A.時間='05' then TB_A.値幅 else 0 end)'05'
      ,MAX(case when TB_A.時間='06' then TB_A.値幅 else 0 end)'06'
    ,MAX(case when TB_A.時間='07' then TB_A.値幅 else 0 end)'07'
    ,MAX(case when TB_A.時間='08' then TB_A.値幅 else 0 end)'08'
    ,MAX(case when TB_A.時間='09' then TB_A.値幅 else 0 end)'09'
    ,MAX(case when TB_A.時間='10' then TB_A.値幅 else 0 end)'10'
    ,MAX(case when TB_A.時間='11' then TB_A.値幅 else 0 end)'11'
    ,MAX(case when TB_A.時間='12' then TB_A.値幅 else 0 end)'12'
    ,MAX(case when TB_A.時間='13' then TB_A.値幅 else 0 end)'13'
    ,MAX(case when TB_A.時間='14' then TB_A.値幅 else 0 end)'14'
    ,MAX(case when TB_A.時間='15' then TB_A.値幅 else 0 end)'15'
    ,MAX(case when TB_A.時間='16' then TB_A.値幅 else 0 end)'16'
    ,MAX(case when TB_A.時間='17' then TB_A.値幅 else 0 end)'17'
    ,MAX(case when TB_A.時間='18' then TB_A.値幅 else 0 end)'18'
    ,MAX(case when TB_A.時間='19' then TB_A.値幅 else 0 end)'19'
    ,MAX(case when TB_A.時間='20' then TB_A.値幅 else 0 end)'20'
    ,MAX(case when TB_A.時間='21' then TB_A.値幅 else 0 end)'21'
    ,MAX(case when TB_A.時間='22' then TB_A.値幅 else 0 end)'22'
    ,MAX(case when TB_A.時間='23' then TB_A.値幅 else 0 end)'23'
from 
(
SELECT convert(nvarchar,[TickTime],112) tradedate
      ,left(convert(nvarchar,[TickTime],108),2) 時間
    ,MAX(([Ask]+[Bid])/2)-min(([Ask]+[Bid])/2) 値幅
  FROM [VCTS].[dbo].[price]
  where [TickTime]between 'from' and 'to'
  and [Broker]='broker'
  and [Ccy]='ccy'
  and [Ask]>0 and [Bid]>0
  group by convert(nvarchar,[TickTime],112),left(convert(nvarchar,[TickTime],108),2)
)TB_A
group by TB_A.tradedate

f:id:tos5511:20170930024733p:plain

9月20日から29日まで、9日間の集計です。
上段がBTC/JPY、下段がUSD/JPY。
ドル円の21日3時はFOMCあったので、エクセルの見栄え上、書式外しています。

うーん、よくわからん(笑)為替のように動きやすい時間や曜日があるのかも?
という仮説を検証してみたいがもう少しデータを溜めないとだめか、
そもそもそんな特徴はないのかw

f:id:tos5511:20170930030440p:plain
おまけでBitflyerの過去24時間のvolume推移。
月曜から水曜にかけて出来高が落ちていくのはもしかしてサラリーマンが疲れてしまっているせいか(笑)
なわけないか、単純にマーケット次第だろう。

SQLiteをやめてSQLServerにプライスを格納することにしたので・・

個人用メモ

実装が楽だしDBの引越しも超楽だからってことで
仮想通貨のプライスを格納するDBにSQLiteを採用していたのだが、
SQLiteのマネージャーツールで良いものがない。
MS様のツールでしかプログラムを書けない私は
コードスニペットや諸々親切な機能から卒業できない)
結局、SQLServerでプライスを保存するように路線変更することにして、
金曜帰宅後着手していたらこんな時間に・・

といってもハリボテだし、アプリの稼働場所(VPS)などの問題もあるので、
アプリから一旦はSQLiteに書き込んで、週に1回csvSQLServer
インサートするという構想(笑)


久しぶりで少し忘れているところもあったので、諸々コード記録。

データベース接続クラス

using System;
using System.Configuration;
using System.Data.SqlClient;
using System.Threading;
using System.Windows.Forms;

namespace VCTS_DB_InsertApp {
    class DBConnection {
        public static DBConnection Provider = new DBConnection();

        private SqlConnection _conn;
        private SqlCommand _cpPrice;

        private int _retry;
        private int _retrySleep;

        private DBConnection() {
            _retry = int.Parse(ConfigurationManager.AppSettings["retry"].ToString());
            _retrySleep = int.Parse(ConfigurationManager.AppSettings["retry_sleep"].ToString());
            _conn = new SqlConnection(ConfigurationManager.ConnectionStrings["DBConn"].ConnectionString);
            _cpPrice = new SqlCommand("[dbo].[price_insert]", _conn);
            _cpPrice.CommandType = System.Data.CommandType.StoredProcedure;

            _cpPrice.Parameters.Add(new SqlParameter("@in_TickTime", System.Data.SqlDbType.DateTime));
            _cpPrice.Parameters.Add(new SqlParameter("@in_Broker", System.Data.SqlDbType.VarChar,50));
            _cpPrice.Parameters.Add(new SqlParameter("@in_Ccy", System.Data.SqlDbType.VarChar,50));
            _cpPrice.Parameters.Add(new SqlParameter("@in_Bid", System.Data.SqlDbType.Decimal));
            _cpPrice.Parameters.Add(new SqlParameter("@in_Ask", System.Data.SqlDbType.Decimal));
            _cpPrice.Parameters.Add(new SqlParameter("@in_SP", System.Data.SqlDbType.Decimal));
            _cpPrice.Parameters.Add(new SqlParameter("@in_Volume", System.Data.SqlDbType.Decimal));
        }

        public void Close() {
            if (_conn != null) _conn.Close();
        }

        public void WritePrice(Price price) {
            _cpPrice.Parameters["@in_TickTime"].Value = price.TickTime;
            _cpPrice.Parameters["@in_Broker"].Value = price.Broker;
            _cpPrice.Parameters["@in_Ccy"].Value = price.CCY;
            _cpPrice.Parameters["@in_Bid"].Value = price.Bid;
            _cpPrice.Parameters["@in_Ask"].Value = price.Ask;
            _cpPrice.Parameters["@in_SP"].Value = price.SP;
            _cpPrice.Parameters["@in_Volume"].Value = price.Volume;

            int cnt = 0;
            while (true) {
                try {
                    _conn.Open();
                    _cpPrice.ExecuteNonQuery();
                    break;
                }
                catch(Exception ex){
                    cnt++;
                    if (cnt > _retry) throw;
                    MessageBox.Show(ex.ToString());
                    Thread.Sleep(_retrySleep);
                }
                finally {
                    if (_conn != null) _conn.Close();
                }
            }
        }
    }
}

データベース書き込み処理

private void ReadCsv(string filePath) {
  var lines = File.ReadLines(filePath);
    foreach (string line in lines) {
      string[] items = line.Split(',');
        try {
          Price price = new Price {
              TickTime = DateTime.Parse(items[0]),
                Broker = items[1],
                CCY = items[2],
                Bid = decimal.Parse(items[3]),
                Ask = decimal.Parse(items[4]),
                SP = decimal.Parse(items[5]),
                Volume = decimal.Parse(items[6]),
          };
          DBConnection.Provider.WritePrice(price);
            DBConnection.Provider.Close();
        }
         catch {
           continue;
         }
   }
}

f:id:tos5511:20170923024346p:plain

無事、SQLServer引越しツールが完成。

BitFlyer PublicAPI Tickerについて

最近は仮想通貨のシステムトレードに興味を持ってまして、
先週あたりから複数業者でtickデータを取り込み始めています。
といっても、API制限があったりして(それも業者毎に異なっていたり)
全Tick収集できるわけでもないですから、10秒に1回の頻度にしてます。

で、その過程でこれは悩む人いるのでは?
と思ったのがBitFlyerさんのAPI

https://lightning.bitflyer.jp/docs?lang=ja&_ga=2.3157363.259268394.1503217679-1068925718.1503217679#ticker

f:id:tos5511:20170902173712p:plain

戻り値としては他の取引所と同様のjson形式でなんら問題はないのですが、
私が悩んだのはvolumeとvolume_by_productの違い。
親切な事に解説が記載されているのはvolumeのみでbyproductの定義はなし。
byproductってくらいなので、通貨ペア単独の出来高の事なんだとは思いますが、
ではvolumeって何の出来高なの?まさかイーサリアムとかも含まれる?

調査すればすぐにわかる事なんですが、なぜ仕様書に2つのvolumeの定義の違いが
記載されてないのか。みりゃーわかるでしょ?みたいなレベルなんでしょうかw
とても親切です。問い合わせしても返信は今のところなし。ggrksって事なんでしょう。
ということで、volumeとvolume_by_productが何を指しているのか、調べてみました。


わかりやすいようにキャプチャを取得していきます。


まずはTickerで得られる戻り値
f:id:tos5511:20170902174100p:plain
ここでは各volumeの値は以下となります。
volume:217652.11957087
volume_by_product:16447.51661226

完全同時刻ではありませんが、その時に取得した取引画面上の出来高が以下キャプチャ。

f:id:tos5511:20170902174949p:plain

どうやら、volume_by_productはtickerで呼び出している銘柄(この場合BTCJPYの現物)
volumeは現物、FX、先物の合計のようです。さすがにETH/BTCなどは含まない。

つか、FXのvolume半端ないな。bitflyerのFXは常に1万円くらいレートが高いので
どうかなと思っていましたがこんなに出来高が多いとは。。レバレッジ効果すごいです。
まあレバレッジ聞いていようが、取引は取引ですし、他の取引所のAPIで返却される
volumeもおそらくレバレッジボリュームを含んでいると思わる為、
DBに格納する値にはvolumeを採用しました。


細かいところなんですが、同じ疑問をもった方の参考になれば幸いです。