前回記事はこちら
国内仮想通貨取引所5種(bitFlyer、Coincheck、Zaif、bitbank、QUOINEX)のWeb APIにアクセスして、現在のビットコイン価格を表示するアプリケーションを作成しました。
バージョン情報
Angular + Reduxの環境を使います
Angular: 5.2.9
angular-redux/store: 7.1.1
redux: 3.7.2
angular/material: 5.2.4
Node: 8.1.4
実装
アプリケーション概要
angular-redux/store
を使ったAngular + Reduxのアーキテクチャを採用しました。アプリケーションの処理の大まかな流れですが、AppComponent
から各種取引所APIにアクセスするServiceクラスを呼び出します。各種Serviceクラスは、APIのレスポンス結果を各種Actionクラスに渡し、ActionクラスからRecuderを中継してレスポンス結果(Reducerに整形される)がStoreに渡ります。そしてStoreに渡った整形されたAPIのレスポンス結果をAppComponentのViewテンプレート上で表示します。
bitFlyer APIの呼び出し
仮想通貨取引所bitFlyerのAPIにアクセスして、現在のビットコイン価格を取得します。
Service
BitflyerService
からbitFlyerのHTTP Public APIにアクセスして、Ticker情報を取得しています。
src/app/services/bitflyer.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { BitflyerTickerModel } from '../../state/bitflyer-ticker/bitflyer-ticker.model'; import { BitflyerTickerActions } from '../../state/bitflyer-ticker/bitflyer-ticker.action'; const URLS = { BASE: 'https://api.bitflyer.jp', TICKER: '/v1/getticker' }; @Injectable() export class BitflyerService { constructor( private http: HttpClient, private action: BitflyerTickerActions ) {} getTicker = (): void => { this.http .get(`${URLS.BASE}${URLS.TICKER}`) .map(response => response as BitflyerTickerModel) .subscribe(ticker => this.action.setTicker(ticker)); }; }
Model
src/state/bitflyer-ticker/bitflyer-ticker.model.ts
:
export interface BitflyerTickerModel { best_ask: number; best_ask_size: number; best_bid: number; best_bid_size: number; ltp: number; product_code: string; tick_id: number; timestamp: string; total_ask_depth: number; total_bid_depth: number; volume: number; volume_by_product: number; }
Action
src/state/bitflyer-ticker/bitflyer-ticker.action.ts
:
import { Injectable } from '@angular/core'; import { dispatch } from '@angular-redux/store'; import { FluxStandardAction } from 'flux-standard-action'; import { BitflyerTickerModel } from './bitflyer-ticker.model'; export type BitflyerTickerAction = FluxStandardAction< BitflyerTickerModel, void >; @Injectable() export class BitflyerTickerActions { static BITFLYER_SET_TICKER = 'BITFLYER_SET_TICKER'; @dispatch() setTicker = (ticker: BitflyerTickerModel): BitflyerTickerAction => ({ type: BitflyerTickerActions.BITFLYER_SET_TICKER, payload: ticker, meta: undefined }); }
Reducer
src/state/bitflyer-ticker/bitflyer-ticker.reducer.ts
:
import { Action } from 'redux'; import { BitflyerTickerActions, BitflyerTickerAction } from '../bitflyer-ticker/bitflyer-ticker.action'; import { BitflyerTickerModel } from './bitflyer-ticker.model'; export function bitflyerTickerReducer( lastState: BitflyerTickerModel = null, action: Action ): BitflyerTickerModel { switch (action.type) { case BitflyerTickerActions.BITFLYER_SET_TICKER: return (action as BitflyerTickerAction).payload; default: return lastState; } }
Coincheck APIの呼び出し
仮想通貨取引所CoincheckのAPIにアクセスして、現在のビットコイン価格を取得します。
bitFlyerのAPI呼び出しと、URLとModel(レスポンス結果のプロパティ)以外ほぼ同じなので、Serviceクラスのコードだけ載せます。(以下取引所についても同様)
Service
src/app/services/coincheck.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { CoincheckTickerModel } from '../../state/coincheck-ticker/coincheck-ticker.model'; import { Observable } from 'rxjs/Observable'; import { CoincheckTickerActions } from '../../state/coincheck-ticker/coincheck-ticker.action'; const URLS = { BASE: 'https://coincheck.com', TICKER: '/api/ticker' }; @Injectable() export class CoincheckService { constructor( private http: HttpClient, private action: CoincheckTickerActions ) {} getTicker = (): void => { this.http .get(`${URLS.BASE}${URLS.TICKER}`) .map(response => response as CoincheckTickerModel) .subscribe(ticker => this.action.setTicker(ticker)); }; }
Zaif APIの呼び出し
仮想通貨取引所ZaifのAPIにアクセスして、現在のビットコイン価格を取得します。
Model、Action、Reducerは省略
Service
src/app/services/zaif.service.ts
:
import { ZaifTickerActions } from '../../state/zaif-ticker/zaif-ticker.action'; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { ZaifTickerModel } from '../../state/zaif-ticker/zaif-ticker.model'; const URLS = { BASE: 'https://api.zaif.jp/api/1', TICKER: '/ticker/btc_jpy' }; @Injectable() export class ZaifService { constructor(private http: HttpClient, private action: ZaifTickerActions) {} getTicker = (): void => { this.http .get(`${URLS.BASE}${URLS.TICKER}`) .map(response => response as ZaifTickerModel) .subscribe(ticker => this.action.setTicker(ticker)); }; }
bitbank APIの呼び出し
仮想通貨取引所bitbankのAPIにアクセスして、現在のビットコイン価格を取得します。
Model、Action、Reducerは省略
Service
src/app/services/bitbank.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BitbankTickerResponse } from '../../state/bitbank-ticker/bitbank-ticker.model'; import { BitbankTickerActions } from '../../state/bitbank-ticker/bitbank-ticker.action'; const URLS = { BASE: 'https://public.bitbank.cc', TICKER: '/btc_jpy/ticker' }; @Injectable() export class BitbankService { constructor(private http: HttpClient, private action: BitbankTickerActions) {} getTicker = (): void => { this.http .get(`${URLS.BASE}${URLS.TICKER}`) .map(response => response as BitbankTickerResponse) .subscribe(response => this.action.setTicker(response.data)); }; }
QUOINEX APIの呼び出し
仮想通貨取引所QUOINEXのAPIにアクセスして、現在のビットコイン価格を取得します。
Model、Action、Reducerは省略
Service
src/app/services/quoinex.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { QuoinexTickerActions } from '../../state/quoinex-ticker/quoinex-ticker.action'; import { QuoinexTickerModel } from '../../state/quoinex-ticker/quoinex-ticker.model'; const URLS = { BASE: 'https://api.quoine.com', TICKER: '/products/5' }; @Injectable() export class QuoinexService { constructor(private http: HttpClient, private action: QuoinexTickerActions) {} getTicker = (): void => { this.http .get(`${URLS.BASE}${URLS.TICKER}`) .map(response => response as QuoinexTickerModel) .subscribe(ticker => this.action.setTicker(ticker)); }; }
ExchangeListについて
各種仮想通貨取引所から取得したレスポンス結果を整形して、画面に表示するのに適した形にする役割を持ったクラス・ファンクション群になります。Serviceクラス、Modelクラス、Actionクラス、Reducerがあります。
Service
各種取引所のレスポンス結果から、ビットコイン価格のみを抽出し、それぞれに各取引所の名称をマッピングしています。また、取引所によって、ビットコイン価格がstring型だったりnumber型だったりするので、number型に統一しています。
src/app/services/exchange-list.service.ts
:
import { Injectable } from '@angular/core'; import { ExchangeModel, EXCHANGE_NAME_LIST } from '../../state/exchange-list/exchange-list.model'; import { ExchangeListActions } from '../../state/exchange-list/exchange-list.action'; @Injectable() export class ExchangeListService { constructor(private action: ExchangeListActions) {} createList = ( bitflyerLtp: number, coincheckLast: number, zaifLast: number, bitbankLast: string, quoinexLast: string ): void => { const exchangeList: ExchangeModel[] = []; exchangeList.push( { name: EXCHANGE_NAME_LIST[0], btcPrice: bitflyerLtp }, { name: EXCHANGE_NAME_LIST[1], btcPrice: coincheckLast }, { name: EXCHANGE_NAME_LIST[2], btcPrice: zaifLast }, // converting a string to a number { name: EXCHANGE_NAME_LIST[3], btcPrice: +bitbankLast }, { name: EXCHANGE_NAME_LIST[4], btcPrice: +quoinexLast } ); this.action.setExcahgeList(exchangeList); }; }
Model、Action、Reducerは省略
rootReducer
各種取引所のReducerを結合しています。
src/state/root/reducer.ts
:
import { IAppState } from './store'; import { combineReducers } from 'redux'; import { bitflyerTickerReducer } from '../bitflyer-ticker/bitflyer-ticker.reducer'; import { coincheckTickerReducer } from '../coincheck-ticker/coincheck-ticker.reducer'; import { zaifTickerReducer } from '../zaif-ticker/zaif-ticker.reducer'; import { bitbankTickerReducer } from '../bitbank-ticker/bitbank-ticker.reducer'; import { quoinexTickerReducer } from '../quoinex-ticker/quoinex-ticker.reducer'; import { exchangeListReducer } from '../exchange-list/exchange-list.reducer'; export const rootReducer = combineReducers<IAppState>({ bitflyerTicker: bitflyerTickerReducer, coincheckTicker: coincheckTickerReducer, zaifTicker: zaifTickerReducer, bitbankTicker: bitbankTickerReducer, quoinexTicker: quoinexTickerReducer, exchangeList: exchangeListReducer });
Store
Storeのソースコードは、👇です。
src/state/root/store.ts
:
import { BitflyerTickerModel } from '../bitflyer-ticker/bitflyer-ticker.model'; import { CoincheckTickerModel } from '../coincheck-ticker/coincheck-ticker.model'; import { ZaifTickerModel } from '../zaif-ticker/zaif-ticker.model'; import { BitbankTickerModel } from '../bitbank-ticker/bitbank-ticker.model'; import { QuoinexTickerModel } from '../quoinex-ticker/quoinex-ticker.model'; import { ExchangeModel } from '../exchange-list/exchange-list.model'; export interface IAppState { bitflyerTicker: BitflyerTickerModel; coincheckTicker: CoincheckTickerModel; zaifTicker: ZaifTickerModel; bitbankTicker: BitbankTickerModel; quoinexTicker: QuoinexTickerModel; exchangeList: ExchangeModel[]; } export const INITIAL_STATE: IAppState = { bitflyerTicker: undefined, coincheckTicker: undefined, zaifTicker: undefined, bitbankTicker: undefined, quoinexTicker: undefined, exchangeList: undefined };
Storeを図にすると、👇になります(字が小さくて読めないと思いますが...)
AppComponent
AppComponentでは、各種サービスクラスの呼び出し、angular-redux/store
の@select
デコレーターによる最新のStoreの値の取得、Viewテンプレートによる結果の表示などを行なっています。
src/app/app.component.ts
:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { BitflyerService } from './services/bitflyer.service'; import { CoincheckService } from './services/coincheck.service'; import { select } from '@angular-redux/store'; import { BitflyerTickerModel } from '../state/bitflyer-ticker/bitflyer-ticker.model'; import { CoincheckTickerModel } from '../state/coincheck-ticker/coincheck-ticker.model'; import { ZaifService } from './services/zaif.service'; import { BitbankService } from './services/bitbank.service'; import { QuoinexService } from './services/quoinex.service'; import { ExchangeListService } from './services/exchange-list.service'; import { Subscription } from 'rxjs/Subscription'; import { ExchangeModel } from '../state/exchange-list/exchange-list.model'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit, OnDestroy { displayedColumns = ['name', 'btcPrice']; dataSource: ExchangeModel[]; subscription: Subscription; @select(['bitflyerTicker', 'ltp']) readonly bitflyerLtp$: Observable<number>; @select(['coincheckTicker', 'last']) readonly coincheckLast$: Observable<number>; @select(['zaifTicker', 'last']) readonly zaifLast$: Observable<number>; @select(['bitbankTicker', 'last']) readonly bitbankLast$: Observable<string>; @select(['quoinexTicker', 'last_traded_price']) readonly quoinexLast$: Observable<string>; @select('exchangeList') readonly exchangeList$: Observable<ExchangeModel[]>; constructor( private bitflyerService: BitflyerService, private coincheckService: CoincheckService, private exchangeListService: ExchangeListService, private zaifService: ZaifService, private bitbankService: BitbankService, private quoinexService: QuoinexService ) {} ngOnInit() { this.bitflyerService.getTicker(); this.coincheckService.getTicker(); this.zaifService.getTicker(); this.bitbankService.getTicker(); this.quoinexService.getTicker(); this.subscription = Observable.combineLatest( this.bitflyerLtp$, this.coincheckLast$, this.zaifLast$, this.bitbankLast$, this.quoinexLast$, (bitflyerLtp, coincheckLast, zaifLast, bitbankLast, quoinexLast) => { if ( bitflyerLtp && coincheckLast && zaifLast && bitbankLast && quoinexLast ) { this.exchangeListService.createList( bitflyerLtp, coincheckLast, zaifLast, bitbankLast, quoinexLast ); } } ).subscribe(); this.exchangeList$ .filter(value => !!value) .filter(value => value.length === 5) .subscribe(value => (this.dataSource = value)); } ngOnDestroy() { this.subscription.unsubscribe(); } }
Angular MaterialのTableを使って、データを表示しています。Angular Material Tableについて、こちらの記事を参照してください。また、number
パイプに'0.0-0'
オプションを渡すことによって、小数点を丸めています。
src/app/app.component.html
:
<div class="example-container mat-elevation-z8"> <mat-table #table [dataSource]="dataSource"> <!-- Exchange Name Column --> <ng-container matColumnDef="name"> <mat-header-cell *matHeaderCellDef> 取引所 </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell> </ng-container> <!-- BTC Price Column --> <ng-container matColumnDef="btcPrice"> <mat-header-cell *matHeaderCellDef> BTC価格 </mat-header-cell> <mat-cell *matCellDef="let element"> {{element.btcPrice | number: '0.0-0'}}円 </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row> </mat-table> </div>
動作確認
ソースコードを動作すると、テーブル上に取引所の名前と現在のビットコインの価格が表示されます。もう少し改良すれば、アービトラージとかに使えるかも.....?
なお、動作させる前に、ブラウザのCORSに対するセキュリティを無効にする必要があります。CORSの無効化についての説明は、こちらの記事をどうぞ。
次回の記事はこちら