Web Audio APIで仮想通貨の売買シグナル音を出してみる

前回、Electronで板情報を表示しましたが、今回はWeb Audio APIを触ってみたいので、前回のプログラムに売買シグナルを音声で通知する機能をつけてみたい思います。

* 前回の記事（[Electron+Vueで仮想通貨の板情報を表示する](https://qiita.com/tadko/items/58386dc437169573ef3c)）

# Web Audio APIとは
Web Audio APIは音声を処理・合成するためのWebアプリケーション向けのJavaScript APIです。

# 実装する
売買シグナルを出すために、システムトレードの戦略が必要なので、戦略の基底クラスを作っておきます。

```Javascript:modules/strategies/strategy.js
/**
 * Base Strategy
 */
export default class Strategy {
  constructor (bitflyerClient) {
    this._client = bitflyerClient
  }
  run () {}
}

```

次に実際の戦略を実装するのですが、今回は売買ルール自体は、本記事の本題ではありませんので、以下の良記事のロジックを、手抜きしまして、そのままJavascript化して使わせて頂きます（すみません..）

* [仮想通貨自動取引入門](https://qiita.com/shionhonda/items/bd2a7aaf143eff4972c4)

売買のシグナルを、ストリーム的に受けたいので、RxJSを使いObservableオブジェクトを返すようにしています。

```Javascript:modules/strategies/simple.js
import Rx from 'rxjs/Rx'
import * as math from 'mathjs'
import Strategy from './strategy'

/**
 * Simple Strategy
 */
export default class SimpleStrategy extends Strategy {
  run () {
    const itr = 20
    const minBtc = 0.001
    const th2 = 1 / 800
    const th1 = th2 * 0.7

    let ltps = []
    let smas = []

    const that = this
    function signal () {
      return that._client.getTicker()
        .then((ticker) => {
          if (ltps.length === 0) ltps = Array(itr).fill(ticker.ltp)
          if (smas.length === 0) smas = Array(2).fill(ticker.ltp)
          ltps.shift()
          ltps.push(ticker.ltp)
          smas.shift()
          smas.push(math.mean(ltps))

          return that._client.getBalance()
        }).then((balance) => {
          const jpy = balance[0].available
          const btc = balance[1].available

          const r = (smas[1] - smas[0]) / smas[0]

          if (r > 0) {
            const amtJpy = that._compute(r, th1, th2) * jpy
            const amtBtc = amtJpy / ltps[itr - 1]
            if (amtBtc > minBtc) {
              const req = {
                product_code: 'BTC_JPY',
                child_order_type: 'LIMIT',
                side: 'BUY',
                price: ltps[itr - 1],
                size: amtBtc,
                minute_to_expire: 10,
                time_in_force: 'GTC'
              }

              return new Promise((resolve) => {
                resolve(req)
              })
            }
          }
          if (r < 0) {
            const amtBtc = that._compute(-r, th1, th2) * btc
            if (amtBtc > minBtc) {
              const req = {
                product_code: 'BTC_JPY',
                child_order_type: 'MARKET',
                side: 'SELL',
                size: amtBtc,
                minute_to_expire: 10,
                time_in_force: 'GTC'
              }

              return new Promise((resolve) => {
                resolve(req)
              })
            }
          }
        })
    }

    var source = Rx.Observable.interval(60000)
      .flatMap(function (i) {
        return Rx.Observable.fromPromise(signal())
      })
      .filter(v => !!v)
    return source
  }

  _compute (x, th1, th2) {
    let out = 0
    if (x < 0 && x < th1) {
      out = 0
    } else if (th1 <= x && x <= th2) {
      out = -1 / (th1 - th2) ** 2 * (x - th2) ** 2 + 1
    } else if (th2 < x) {
      out = 1
    }
    return out
  }
}

```

次に売買シグナルを受け取って、音を鳴らしますが、Web Audio APIで音を鳴らすこと自体は、以下のコードだけで簡単に実装できます。

```javascript
const ctx = new AudioContext()
const oscillator = ctx.createOscillator()
const gainNode = ctx.createGain()
oscillator.type = 'sine' //波系
oscillator.detune.value = 0
gainNode.gain.value = 0.6 //音量
oscillator.connect(gainNode)
gainNode.connect(ctx.destination)
oscillator.frequency.value = 780 //周波数
oscillator.start()
```

最後に、前回のプログラムに、売買シグナルを受け取って、この音を出すコードを追加します。

```html:Btc.vue
<template>
  <main>
    <section class="section">
      <div class="container">
        <div class="columns">
          <div class="column">
            <h1 class="title">Bitcoin ask/bid volume</h1>
            <h2 class="subtitle">
            </h2>
            <board></board>
          </div>
          <div class="column">
            <balance></balance>
          </div>
        </div>
      </div>
    </section>
  </main>
</template>


<script>
  import Board from './Fx/Board'
  import Balance from './Fx/Balance'
  import { BitFlyerClient } from '@tadko/bitflyer-client'
  import SimpleStrategy from '../modules/strategies/simple'

  export default {
    name: 'fx',
    components: { Board, Balance },
    created () {
      const bitflyerClient = new BitFlyerClient(process.env.BITFLYER_KEY, process.env.BITFLYER_SECRET)
      this.ctx = new AudioContext()
      new SimpleStrategy(bitflyerClient)
        .run()
        .subscribe((signal) => {
          if (signal.side === 'BUY') {
            this.soundBuy()
          } else if (signal.side === 'SELL') {
            this.soundSell()
          }
        })
    },
    methods: {
      soundBuy () {
        const oscillator = this.ctx.createOscillator()
        const gainNode = this.ctx.createGain()
        oscillator.type = 'sine'
        oscillator.detune.value = 0
        gainNode.gain.value = 0.6
        oscillator.connect(gainNode)
        gainNode.connect(this.ctx.destination)
        oscillator.frequency.value = 780
        oscillator.start()
        oscillator.stop(this.ctx.currentTime + 0.4)
        this.$toast.open({
          message: 'Buy!',
          type: 'is-success'
        })
      },
      soundSell () {
        const oscillator = this.ctx.createOscillator()
        const gainNode = this.ctx.createGain()
        oscillator.type = 'sine'
        oscillator.detune.value = 0
        gainNode.gain.value = 0.6
        oscillator.connect(gainNode)
        gainNode.connect(this.ctx.destination)
        oscillator.frequency.value = 400
        oscillator.start()
        oscillator.stop(this.ctx.currentTime + 0.4)
        this.$toast.open({
          message: 'Sell!',
          type: 'is-danger'
        })
      }
    }
  }
</script>

<style>
</style>

```

実際に動作させると、こんな感じです！...肝心のシグナル音が伝わらないですが。
（Sell、Buyをトースト表示してます。）

実際の売買自体はコード上していないので、ご注意ください。
実際に仮想通貨の自動売買して試してみたいところですが、、軍資金もないのでまたの機会に。

![buy.png](https://qiita-image-store.s3.amazonaws.com/0/121799/1cb3b5d9-8600-6dcf-58ed-5e50beddbcba.png)

![sell.png](https://qiita-image-store.s3.amazonaws.com/0/121799/2d090bec-0951-5ebf-d252-8324e856de7f.png)