Alpaca API から米国株の日足データを取得する
- Alpaca API というものを見つけました
- 無料で米国株のシミュレーショントレードを行えるAPIです
- チャートデータなども取得出来ます
- Scala で触りながら遊んでみたいと思います
- Python の方が手っ取り早いんですが、久しぶりに Scala を書きたくなったので。
Outline
- JSON ファイルの読み込み
- Akka を利用した API リクエスト
- JSON -> case class へのマッピング
JSON ファイルの読み込み
アカウントを作成して、API Key を取得します。リクエストヘッダーには API Key を書いてあげる必要があります。
今回は以下のようなJSON形式の設定ファイルに記述してsrc/main/resourece/
に置きます。
(API Key は無効化済みです。念の為。)
{ "endpoint": "https://data.alpaca.markets/v1/", "keyId": "PK3J0PD7T13S25SE2EDI", "secretKey": "7PR6ABecF3MC3QXjH3l2fu5hPML84ZE9QkKxYMDD" }
JSON を読み込むために、play-jsonを利用します。
build.sbt
build.sbt
の libraryDependencies
に以下を追加して
"com.typesafe.play" %% "play-json" % "2.7.2"
src/main/resourece/settings.json
を読み込みます。
val settings: JsValue = Json.parse(Source.fromResource("settings.json").getLines().mkString) val url: String = (settings \ "endpoint").as[String] val keyId: String = (settings \ "keyId").as[String] val secretKey: String = (settings \ "secretKey").as[String]
Source.fromResource()
でsrc/main/resources
配下のファイルにアクセス出来ます。play-json
の記法で(settings \ "endpoint").as[String]
とすることで "endpoint" の value を String で取得できます。
Akka を利用した API リクエスト
設定ファイルから読み込んだ情報をもとにAPIリクエストを行います。
リクエストのライブラリにはAkka-HTTPを利用します。
fetchChart
が main の API 処理です。
package apiclient import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model.headers.RawHeader import akka.http.scaladsl.model.{HttpMethods, HttpRequest, Uri} import akka.http.scaladsl.unmarshalling.Unmarshal import akka.stream.ActorMaterializer import akka.util.Timeout import domain.Bar import play.api.libs.json.{JsValue, Json} import scala.concurrent.Await import scala.concurrent.duration._ class ApiClient(url: String, keyId: String, secretKey: String) { def apply(url: String, keyId: String, secretKey: String): ApiClient = new ApiClient(url, keyId, secretKey) implicit val timeout = Timeout(5 second) implicit val system = ActorSystem() implicit val materialize = ActorMaterializer() implicit val executionContext = system.dispatcher def fetchDailyChart(tickers: Seq[String]): Map[String, List[Bar]] = { val params = "symbols=" + tickers.mkString(",") val jsValue = fetchChart("/bars/1D", params) tickers.map(t => ( t, (jsValue \ t).as[List[JsValue]].map(v => Bar.apply(t, v)) )).toMap } private def fetchChart(path: String, params: String): JsValue = { val uri = Uri(this.url + path).withRawQueryString(params) val h1: RawHeader = RawHeader("APCA-API-KEY-ID", this.keyId) val h2: RawHeader = RawHeader("APCA-API-SECRET-KEY", this.secretKey) val req = HttpRequest(HttpMethods.GET, uri = uri).withHeaders(List(h1, h2)) val res = Await.result(Http().singleRequest(req), timeout.duration) val body = Unmarshal(res.entity).to[String] Await.result(Http().shutdownAllConnectionPools(), timeout.duration) Await.result(system.terminate(), timeout.duration) Json.parse(Await.result(body, timeout.duration)) } }
val params = "symbols=" + tickers.mkString(",") val jsValue = fetchChart("/bars/1D", params)
- API ドキュメント にしたがって、対象銘柄の ticker をカンマ区切りの文字列にします
- 日足のチャートなので
bars/1D
にアクセスします
implicit val timeout = Timeout(5 second) implicit val system = ActorSystem() implicit val materialize = ActorMaterializer() implicit val executionContext = system.dispatcher private def fetchChart(path: String, params: String): JsValue = { val uri = Uri(this.url + path).withRawQueryString(params) val h1: RawHeader = RawHeader("APCA-API-KEY-ID", this.keyId) val h2: RawHeader = RawHeader("APCA-API-SECRET-KEY", this.secretKey) val req = HttpRequest(HttpMethods.GET, uri = uri).withHeaders(List(h1, h2)) val res = Await.result(Http().singleRequest(req), timeout.duration) val body = Unmarshal(res.entity).to[String] Await.result(Http().shutdownAllConnectionPools(), timeout.duration) Await.result(system.terminate(), timeout.duration) Json.parse(Await.result(body, timeout.duration)) }
- URLを組み立てて、リクエストパラメータを
.withRawQueryString()
でセットします - API Key の情報をListにして、
.withHeaders()
でセットしてリクエストします - 実際のリクエスト、レスポンスの処理はこちらの記事 を参考にしています
- body の取得が終わったら connection のクローズ、セッションの終了処理を行います。
system.terminate
だけだとError が出力されてしまいます。Processor actor terminated abruptly on HTTPs connections (akka 2.4.12, akka-http 2.4.11) · Issue #497 · akka/akka-http · GitHub
JSON -> case class へのマッピング
レスポンスのJSONをBar
(いわゆるローソク足ですね)オブジェクトにマッピングします。
以下のようなコンパニオンオブジェクトとして、JsValueからの apply
メソッドを定義することで、レスポンスからのマッピングをスマートに出来るようにします。
package domain import org.joda.time.DateTime import play.api.libs.json.JsValue case class Bar( ticker: String, close: BigDecimal, open: BigDecimal, high: BigDecimal, low: BigDecimal, volume: BigDecimal, time: DateTime ) object Bar { def apply( ticker: String, close: BigDecimal, open: BigDecimal, high: BigDecimal, low: BigDecimal, volume: BigDecimal, time: DateTime ): Bar = new Bar(ticker, close, open, high, low, volume, time) def apply(ticker: String, jsValue: JsValue): Bar = { val close = (jsValue \ "c").as[BigDecimal] val open = (jsValue \ "o").as[BigDecimal] val high = (jsValue \ "h").as[BigDecimal] val low = (jsValue \ "l").as[BigDecimal] val volume = (jsValue \ "v").as[BigDecimal] val time = new DateTime((jsValue \ "t").as[Long] * 1000L) new Bar(ticker, close, open, high, low, volume, time) } }
レスポンスからのマッピングは以下のように行って、[tickercode, List[Bar]]の Map
にします。
val jsValue = fetchChart("/bars/1D", params) tickers.map(t => ( t, (jsValue \ t).as[List[JsValue]].map(v => Bar.apply(t, v)) )).toMap
実際にやってみる
Main
から、以下のように実行します。
val cli = new ApiClient(url, keyId, secretKey) pprint.pprintln(cli.fetchDailyChart(Seq("AAPL")))
- コンソール出力を綺麗にするために
build.sbt
に以下を追加して pprint しています。http://www.lihaoyi.com/PPrint/
"com.lihaoyi" %% "pprint" % "0.5.3"
以下のようにコンソール出力されます。
終わりに
コードは GitHub にupload しています。 github.com
Scala は書きやすいですね。型推論が賢いので冗長な型宣言が少なく、サクサク書けて快適です。
- ちょっとコードというか、構造がイマイチなので要リファクタですね
- テクニカル分析とかもやってみたいなと思います