バツイチとインケンのエンジニアブログ
プログラムやプログラムじゃないこと

Rustで株ボットつくってみた

2017-04-14
書いた人 : バツイチ
カテゴリ : rust | タグ : Google Finance API, 株

会社の同僚に触発され、Rustの勉強も兼ねてSlackボットを作ってみました。

使ったライブラリなどを含めてご紹介させて頂きます。
※ 作成時のRustのバージョンは1.15.1です。

全体像

構成はシンプルです。
cronで30分毎にSlackボット(Rust CLI)を実行します。
Slackボットは起動すると指定株価一覧が記載されたCSVを読み込み、そこに記載された株の価格情報をGoogle Finance APIに取得しにいきます。
前日の終値から3%以上上昇もしくは3%以上下落した株の価格をSlackにメッセージとして送信します。

ソースコードはこちらに公開しています。
https://github.com/x1-/finance
※ Google Finance APIの詳細はこちらの記事をどうぞ。

プロジェクトの作成

Cargoを使ってプロジェクトのテンプレートを作成します。
CLIなのでバイナリ実行可能なように –bin オプションを付与します。

cargo new finance --bin

financeという名称で作成しました。

cd finance/
tree
.
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files

Cargo.toml に依存を書きます。

[dependencies]
chrono = "0.3"
csv = "0.14"
docopt = "0.7"
env_logger = "0.3"
futures = "^0.1.7"
hyper = "0.10"
hyper-openssl = "^0.2.1"
lazy_static = "^0.2.2"
regex = "^0.2.1"
rustc-serialize = "*"
slack-hook = "0.3"
time = "0.1"
tokio-core = "^0.1.3"

多くのバージョンに ^ を付けて、パッチ・バージョンまで指定していますが、このように書くと「マイナー・バージョンが同じで、記載したパッチ・バージョンより高いバージョンのものがあればそちらを使う」ようになります。

メイン・プログラム

プログラムはシンプルで、APIのクライアントを記述した api_client.rs と main.rsの2ファイルのみとなっています。

全容は GitHub掲載のとおりですが、 メイン・プログラムを記述した main.rs を順に説明させて頂こうと思います。

いろいろな構造体の定義

はじめに、構造体をたくさん定義しています。
docoptで実行時引数を受け取るための構造体、CSVの各レコードを表す構造体、APIのレスポンスを受け取る構造体などです。

下記はCSVを解釈するための構造体です。

#[derive(Debug, RustcDecodable)]
struct Record {
    code: String,
    name: String,
    market: String
}

Debug を付与しないと printできません。
RustcDecodable を付与すると csv でパースできるようになります。
csv は内部的に rustc_serialize を使っているのですね。

docopt用usage文字列の作成

const USAGE: &'static str = r"
to notice kabu rate of up or down at slack-channel.
Usage:
  finance --tick=<tick> --ratio=<ratio> [--webhook=<url>] [--term=<term>] [--data=<csv>]
  finance --version
Options:
  -h --help       Show this message.
  --version       Show version.
  --tick=<tick>   candle tick interval by seconds [default 86400].
  --ratio=<ratio> the threshold ratio of price up or down [default 0.1].
  --webhook=<url> webhook url of slack integration. if empty, do not send slack [default empty].
  --term=<term>   the term of measuring price [default 7d].
  --data=<csv>    the csv listed the stocks [default ./data/stocks.csv].
";

ここの部分はdocoptに渡すusage文字列を定義しています。
pythonのdocoptと似ていますが、pythonのものほど柔軟性はありません。。。
tick などに default値を定義していますが、これが解釈されてデフォルト値として扱われるわけではありません。
引数が渡されなかった場合、空文字として扱われます。
よって数値引数が渡されなかった場合、エラーとなります。
clapだとどうなのか気になるところ。

各クライアントの生成

Google FinanceのAPIクライアントは hyper のHTTPクライアントを使って作成しました。
SSLを解釈できるHTTPクライアントが hyper くらいしかありませんでした。

    let client = api_client::Ssl::new();

Slack投稿クライアントにはslack-hookを使いました。
今回は株の上昇幅を投稿するだけだったのでシンプルなslack-hookを選択したのですが、会話するなら Yobotとかなのかな。

    let slack = Slack::new( args.flag_webhook.as_str() );

値動きを追う株

Slackに投稿する対象の株を data/stocks.csvに定義しています。
※ 将来的にはDB管理もありかなと。

CSVファイルの読み込みには便利なcsvライブラリを使っています。
これを使うとCSVファイルを RustcDecodable な構造体のベクターにマッピングできます。

    let mut file = csv::Reader::from_file( args.flag_data ).unwrap();

データ・ファイルのパスも実行時引数としていますが、これはデフォルトを data/stocks.csv にしています。

データを取得して条件に該当する株価をSlackにポスト

CSVから取得したレコードを1行つ処理し、 Google Finance APIに現在の株価を問い合わせます。
前日の終値から3%以上上昇もしくは3%以上下落した株の価格をSlackにメッセージとして送信します。

    for r in file.decode() {
        let r: Record = r.unwrap();

        let url = format!(
            "{uri}&p={term}&i={tick}&x={market}&q={code}",
            uri = URL_BASE,
            term = args.flag_term, tick = args.flag_tick, market = "TYO", code = r.code );

        let res = &client.sync_get( &url );

        let data: Result<Vec<Stock>, String> = data_to_struct( res, args.flag_tick );
        let rprice: Result<ComparedPrice, String> = data.and_then( |d| close_rate( &d ) );

        match rprice {
            Ok( ref p ) if p.ratio >= args.flag_ratio || p.ratio < -(args.flag_ratio) => {
                let payload = slack_payload( r.code, r.name, p.current, p.previous, p.ratio );
                if let Ok(ref s) = slack {
                    let res = s.send( &payload );
                    println!("res: {:?}", res);
                }
                println!("found: {:?}", payload);
            },
            Ok( ref p )  => println!( "rate is less than {th}. ratio:{ratio:.3}, [{code}:{name}, now:{current}]", th = args.flag_ratio, ratio = p.ratio, code = r.code, name = r.name, current = p.current ),
            _ => println!( "cannot calculate ratio" )
        }
    }

投稿アイコンに絵文字を使えるので、上昇した株は :chart_with_upwards_trend: 、下降した株は :chart_with_downwards_trend: を使って上昇/下降をわかりやすくしています。

ざっとこんな感じのプログラム構成になっています。
私はこれを使って仕事中に注目している株の値動きをウォッチしています。

使ったライブラリまとめ

ライブラリ 用途
chrono 日時文字列を変換したり、日時計算したり。
csv APIから取得したデータをパース。
docopt コマンドライン引数のパースに。clapにしておけばよかったかなあ…
env_logger ログを出力。
hyper HTTPクライアントを作成。
hyper-openssl APIがSSLだったので。
lazy_static グローバル変数の実行時初期化に。
regex 正規表現に。
slack-hook Slackにメッセージを送信。
このエントリーをはてなブックマークに追加
Tweet

← imply を使ってリアルタイム集計
DruidのデータをSupersetで可視化する →

 

最近書いた記事

  • Ryzen7 3800XT でmini ITXオープンフレームPCを作る
  • Pythonで機械学習入門 競馬予測
  • HP ENVY 15 クリエイターモデルレビューとRAID0解除
  • JRA-VAN データラボを使って、競馬データを収集する
  • Surface Pro 3 にubuntu18.04を入れる

カテゴリー

  • Android
  • Apache Flink
  • API
  • AWS
  • bazel
  • BigQuery
  • Cassandra
  • Docker
  • Druid
  • Elasticsearch
  • Git
  • Golang
  • gradle
  • HDFS
  • JavaScript
  • jvm
  • Linux
  • MongoDB
  • MySQL
  • Nginx
  • Nodejs
  • PaaS
  • PHP
  • Python
  • RabbitMQ
  • Raspberry Pi
  • React Native
  • Redis
  • Riak
  • rust
  • scala
  • Scheme
  • SEO
  • solr
  • Spark
  • spray
  • Sublime Text
  • Swift
  • Tableau
  • Unity
  • WebIDE
  • Wordpress
  • Youtube
  • ひとこと
  • カンファレンス
  • スケジューラ
  • マイクロマウス
  • 広告
  • 技術じゃないやつ
  • 株
  • 機械学習
  • 競馬
  • 自作キーボード
  • 自然言語処理

アーカイブ

  • 2021年4月
  • 2021年2月
  • 2021年1月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年10月
  • 2019年9月
  • 2019年8月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年11月
  • 2018年9月
  • 2018年5月
  • 2018年3月
  • 2018年2月
  • 2017年9月
  • 2017年8月
  • 2017年6月
  • 2017年4月
  • 2017年3月
  • 2017年1月
  • 2016年10月
  • 2016年9月
  • 2016年8月
  • 2016年6月
  • 2016年5月
  • 2016年4月
  • 2016年3月
  • 2016年2月
  • 2016年1月
  • 2015年12月
  • 2015年11月
  • 2015年10月
  • 2015年9月
  • 2015年8月
  • 2015年6月
  • 2015年5月
  • 2015年2月
  • 2015年1月
  • 2014年12月
  • 2014年11月
  • 2014年9月
  • 2014年6月
  • 2014年5月
  • 2014年3月
  • 2014年2月
  • 2014年1月
  • 2013年12月
  • 2013年11月
  • 2013年10月
  • 2013年9月
  • 2013年8月

書いた人

  • バツイチちゃん
  • インケンくん

このブログについて

エンジニアとしての考え方が間逆な2人がしょーもないこと書いてます。

バツイチ

アイコン

IT業界で働くエンジニアです。名前の通りバツイチです。
理論や抽象的概念が好きだけど人に説明するのが下手。

インケン

アイコン

バツイチちゃんと同じ業界で働いています。
理論とか開発手法とかは正直どうでもよくて、
生活する上で役に立つことに使いたい

Copyright 2025 バツイチとインケンのエンジニアブログ