20200921のdockerに関する記事は7件です。

Rails 6 + MySQLの環境構築をDocker composeで作る

Dockerの公式サイトにはRailsアプリケーション用のDocker composeチュートリアルがあるが、少し情報が古くRails 6ではうまく動かなかったので、Rails 6で動かすための方法を載せておく。基本は公式チュートリアルの手順に従っているため、Rails 6用に変更したところを中心に補足を入れている。

DBはPostgresではなくMySQLを使う方法を載せておく。

プロジェクトディレクトリの準備

mkdir myrailsapp
cd myrailsapp

設定ファイルの準備

Dockerfile
FROM ruby:2.5

## nodejsとyarnはwebpackをインストールする際に必要
# yarnパッケージ管理ツールをインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn

RUN apt-get update -qq && apt-get install -y nodejs yarn
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

Rails 6ではWebpackerの実行のためにyarnが必要になるのでDockerfileにyarnのインストールの手順を加える。ruby:2.5のイメージで普通にapt-get yarnでインストールすると0.32+gitという変なバージョンがインストールされてしまい後にエラーになるので、yarn公式サイトの指示にしたがってレポジトリを追加した後でapt-get install yarnする。

参考: https://qiita.com/shunichi_com/items/4dca141d8b9342c51a04

postgresは使用しないため除外。

Gemfile
source 'https://rubygems.org'
gem 'rails', '~>6'

GemfileでRails 6系を指定。このGemfileはRailsプロジェクト作成後にRailsプロジェクトの内容で上書きされることになる。

touch Gemfile.lock
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

この辺りは公式のサンプルのままなので説明省略。

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"
    volumes:
      - ./tmp/db:/var/lib/mysql

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

MySQLを使う形に設定を変更。

Railsプロジェクトの作成

docker-compose run web rails new . --force --no-deps --database=mysql

--database=mysqlでDB設定をMySQLにした状態でプロジェクト作成。

docker-compose build

DB接続設定

database.yml
development:
  <<: *default
  database: myapp_development
  host: db
  username: root
  password: password

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: myapp_test
  host: db
  username: root
  password: password

developmenttestがdocker-composeで起動したMySQLイメージdbを使うように設定を記述。

DB作成

docker-compose run web rails db:create

Webpackerの導入

docker-compose run web rails webpacker:install 

Rails 6からSprocketsに代わりWebpackerが使われるようになったためこのステップが必要に。

イメージの起動

docker-compose up

Scaffold

docker-compose run web rails g scaffold article title:string body:text published_at:timestamp
docker-compose run web rails db:migrate

docker-composeを使って開発をする場合は、scaffold含めgenerate系のコマンド、マイグレーションもdocker-compose run webで実行する。

docker-compose up

スクリーンショット 2020-09-22 0.07.07.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10 PROにDockerをインストールする

Windows10 PROにDockerをインストールする

0. 前提条件

  • Windows 10 PRO [Hyper-Vを使用するため]

1. Hyper-Vを有効にする

  • [スタート] > [Windows システム ツール] > [コントロール パネル]
  • [プログラムと機能]
  • [Windowsの機能の有効化または無効化]
  • [Hyper-V]にチェック[ON]する。

2. Dockerをダウンロードする

Docker公式サイトからダウンロードする。
ファイルサイズは2020/09/21時点で388MB程度。
Docker : Install Docker Desktop on Windows
Docker : Stable と Edgeの違い

3. Dockerをインストールする

ダウンロードしたをウィザードに従ってインストールする。(念のために管理者として実行する)

※(疑問点)「Enable WSL 2 Windows Features」があると、Hyper-Vはいらないの?今回はHyper-V+このチェックありで進める。

あとで問題が出てくるので、先にすべて記事を読んでから、このチェックボックスに反応を決めてもらった方がいいかも。。。

再起動する。再起動後に下記メッセージが表示される。
WSL2_message.png

メッセージの中にあるURLにアクセスする。
ms : Windows 10 用 Windows Subsystem for Linux のインストール ガイド

なんか特定の Linux ディストリビューションをインストールしないといけないみたいなので、この設定を回避する。

DockerのSettingsで「Use the WSL 2 based engine」のチェックを外すことにする。

Docker Desktop is running.となる。

4. 動作確認

Windows PowerShell

docker version

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails チュートリ

Rubyのバージョン番号
ところで、Herokuのデプロイするとき、もしかしたら次のような警告メッセージを目にしたことがあるかもしれません。

WARNING:
   You have not declared a Ruby version in your Gemfile.
   To set your Ruby version add this line to your Gemfile:
   ruby '2.1.5'

(これは「Rubyのバージョンを明示的に指定してください」というメッセージですが) 経験的には、本書のようなチュートリアルの段階では明示的に指定しない方がスムーズに進むことが多いので、この警告は現時点では無視してしまった方がよいでしょう。というのも、サンプルアプリケーションでRubyのバージョンを常に最新に保っておくと、多大な不都合に繋がりかねないからです15 。また、本書のサンプルアプリケーションにおいては、ローカルで使っているバージョンと本番環境のバージョンが異なっていても、違いが生じることはほぼ無いでしょう。とは言うものの、次の点は頭の片隅に置いておいてください。それは、仕事でHerokuを使ったアプリケーションを動かす場合はGemfileでRubyのバージョンを明示しておいた方が賢明である、という点です。これによって開発環境と本番環境の互換性を最大限に高めることができるので、(バージョンの差異による誤作動やエラーなどが無くなり) オススメです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Desktop WSL 2 backend 日本語訳

https://docs.docker.com/docker-for-windows/wsl/ の日本語訳。

Windows Subsystem for Linux (WSL) 2 は、 Microsoft によって構築された完全な Linux カーネルです。
エミュレーションなしで Linux コンテナーをネイティブに実行できるようにするため、アーキテクチャーに大きな変更が加えられています。
WSL 2 で Docker デスクトップを実行すると、ユーザーは Linux ワークスペースを活用でき、 Linux と Windows の両方のビルドスクリプトを維持する必要がなくなります。
さらに WSL 2 はファイルシステムの共有と起動時間を改善し、 Docker デスクトップユーザーがクールな新機能にアクセスできるようにします。

Docker Desktop は WSL 2 の動的メモリ割り当て機能を使用して、リソース消費を大幅に改善します。
つまり Docker デスクトップは必要な量の CPU とメモリリソースのみを使用し、 CPU とメモリを集中的に使用するコンテナーの構築などのタスクをより高速に実行できるようにします。

さらに WSL 2 では、コールドスタート後に Docker デーモンを起動するのに必要な時間が大幅に速くなりました。
以前のバージョンの Docker デスクトップのほぼ 1 分と比較して、 Docker デーモンの起動には 10 秒もかかりません。

前提条件

Docker Desktop WSL 2 バックエンドをインストールする前に、次の手順を完了する必要があります。

Windows 10 バージョン 2004 以降をインストールします。
Docker Desktop Edge リリースは、 Windows 10 バージョン 1903 以降もサポートしています。
Windows で WSL 2 機能を有効にします。
詳細な手順については Microsoft のドキュメントを参照してください。
Linux カーネル更新パッケージをダウンロードしてインストールします。

ダウンロード

Docker Desktop Stable 2.3.0.2 以降のリリースをダウンロードします。

インストール

Docker Desktop Stable 2.3.0.2 リリースをインストールする前に、 前提条件 セクションで説明されている手順を完了していることを確認してください。

通常のインストール手順に従って Docker デスクトップをインストールします。
サポートされているシステムを実行している場合、 Docker Desktop はインストール中に WSL 2 を有効にするように求めます。
画面に表示される情報を読み、 WSL 2 を有効にして続行します。
Windows の [スタート] メニューから Docker デスクトップを起動します。

Docker メニューから 設定 > 一般 を選択します。

[WSL 2 ベースのエンジンを使用する] チェックボックスをオンにします。

WSL 2 をサポートするシステムに Docker デスクトップをインストールした場合、このオプションはデフォルトで有効になります。

[適用して再起動] をクリックします。

ディストリビューションが WSL 2 モードで実行されることを確認します。
WSL は v1 モードと v2 モードの両方でディストリビューションを実行できます。

WSL モードを確認するには、次のコマンドを実行します。

wsl.exe -l -v

既存の Linux ディストリビューションを v2 にアップグレードするには、次のコマンドを実行します。

wsl.exe --set-version (distro name) 2

今後のインストールのデフォルトバージョンとして v2 を設定するには、次のコマンドを実行します。

wsl.exe --set-default-version 2

Docker デスクトップが再起動したら [設定] > [リソース] > [WSL 統合] に移動します。

WSL 統合は、デフォルトの WSL ディストリビューションで有効になります。
デフォルトの WSL ディストリビューションを変更するには wsl --set-default <distro name> を実行します。

たとえば Ubuntu をデフォルトの WSL ディストリビューションとして設定するには wsl --set-default ubuntu を実行します。

オプションで WSL 2 を有効にする追加のディストリビューションを選択します。

[適用して再起動] をクリックします。

Docker と WSL 2 で開発する

次のセクションでは Docker と WSL 2 を使用してアプリケーションの開発を開始する方法について説明します。
Docker と WSL 2 を使用して最高の開発エクスペリエンスを実現するには、デフォルトの Linux ディストリビューションにコードを置くことをお勧めします。
DockerDesktop で WSL 2 を有効にした後、 Linux ディストリビューション内のコードで作業を開始できます。
理想的には Windows で IDE を使用できます。
VSCode を使用している場合、このワークフローは非常に簡単です。

VSCode を開き、 Remote-WSL 拡張機能をインストールします。
この拡張機能により、 Linux ディストリビューションのリモートサーバーと Windows の IDE クライアントを操作できます。

これで VSCode によるリモート作業を開始できます。
上記を行うには、ターミナルを開いて次のように入力します。

wsl
code .

これにより、デフォルトの Linux ディストリビューションにリモートで接続された新しい VSCode が開きます。
これは、画面の下部隅で確認できます。

[スタート] メニューにデフォルトの Linux ディストリビューションの名前を入力して開き、コードを実行することもできます。

VSCode にいるときは VSCode のターミナルを使用してコードをプルし、 Windows マシンからネイティブで作業を開始できます。

ベストプラクティス

ファイルをバインドマウントするときにファイルシステムのパフォーマンスを最大限に引き出すには:

Windowsファイルシステムではなく、LinuxファイルシステムのLinuxコンテナーにバインドマウントされるソースコードとその他のデータを格納します。
( docker run -v <host-path>:<container-path> を使用します)
Linux コンテナーは元のファイルが Linux ファイルシステムに保存されている場合のみ、ファイル変更イベント( inotify イベント)を受け取ります。
ファイルが Windows ホストからではなく Linux ファイルシステムからバインドマウントされると、パフォーマンスが大幅に向上します。
したがって docker run -v /mnt/c/users:/users ( /mnt/c は Windows からマウントされます)を避けてください。
代わりに Linux シェルから docker run -v ~/my-project:/sources <my-image> のようなコマンドを使用します。
ここで ~ は Linux シェルによって $HOME に展開されます。

docker-desktop-data VHDX のサイズが気になる場合、または変更する必要がある場合は、 Windows に組み込まれている WSL ツールをご覧ください。
CPU またはメモリの使用量に懸念がある場合は、 WSL 2 ユーティリティ VM に割り当てるメモリ、 CPU 、スワップサイズの制限を設定できます。
Docker デスクトップでの WSL 2 の使用との潜在的な競合を回避するには、 Docker デスクトップをインストールする前に、 Linux ディストリビューションから直接インストールされた以前のバージョンの Docker エンジンと CLI をアンインストールする必要があります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Apache/PandocでMarkdownをHTMLで配信するDockerコンテナの作成

はじめに

ApacheとPandocを組み込んだDockerコンテナを作成してみたので体験を共有します。

Pandocを使用するとMarkdownをHTMLやPDFに変換できます。
これをApacheに組み込むとMarkdownをダイナミックにHTMLに変換できます。
こうして作ったWebサイトをDockerコンテナに入れると構築や配布が楽になります。
たとえばAmazon AWSのコンテナサービスに登録するなど利用範囲が広がります。

Dockerファイルほかソースはgithubで、作成済みのDockerイメージはDocker Hubで公開済みです(末尾参照)。

Pandoc

ローカルPCで(たとえばWindows WSLを用いて)Pandocコンバータを使ってMarkdownをHTMLに変換するには:

$ ls
 test.md
$ cat test.md
 # test.md
 Hello **Wonderful** World.
$ pandoc test.md > test.html
$ ls
 test.md
 test.html
$ cat test.html
 <h1 id="test.md">test.md</h1>
 <p>Hello <strong>Wonderful</strong> World.</p>
$

Apache

ApacheでMarkdownファイルの拡張子mdが参照された場合にPandocが実行されるようにすればHTMLへの自動変換が実現できます。
それにはApacheの外部フィルタというしくみを使うのが簡単です。
Apacheで次のような設定をすれば、Markdownファイルが参照された場合に指定のフィルタが実行されます。
ここではpandoc-filterという名前です。

[local.conf]

LoadModule ext_filter_module modules/mod_ext_filter.so

ExtFilterDefine pandoc-filter \
        mode=output intype=text/markdown outtype=text/html \
        cmd="/usr/local/apache2/pandoc-filter"

<Directory /usr/local/apache2/htdocs>
        SetOutputFilter pandoc-filter
        AddType text/markdown .md
</Directory>

説明します。
- LoadModuleで外部フィルタを利用可能とするext_filter_moduleをロード。
- ExtFilterDefineでフィルタが対象とするMIMEタイプとコマンドのパスを指定。
- Directory内は、指定ディレクトリ内で拡張子mdを持つファイルが参照された場合にフィルタを実行してもらう設定。

Apacheフィルタ

Pandocを呼び出すApacheフィルタを作成しました。
標準入力で渡されるMarkdownソースをPandocを呼び出して標準出力に変換結果のHTMLを出力するBashシェルスクリプトです。

[pandoc-filter]

 #!/bin/bash
 BASENAME=$(basename -s .md $DOCUMENT_URI)
 /usr/bin/pandoc -f gfm -t html5 -c "/pandoc-gfm.css" \
    -T "Converted" -M title=${BASENAME}

補足します。
- 参照されたMarkdownファイルのパスがDOCUMENT_URI環境変数で渡される。
 たとえば「/test.md」など。ここからPandocに渡すタイトルを取る。
- -fは変換元の形式、-tは変換先の形式、-cはスタイルシートの場所、
-T-M title=で出力されるHTMLのタイトルを指定。
- Pandocは何も指定しないと標準入力から読み込み標準出力へ出力する。

Docker作成

上記のApache設定ファイルとフィルタスクリプトを組み込んだDockerコンテナを作成します。

$ ls
 Dockerfile
 usr-local-apache2/
$ ls usr-local-apache2
 local.conf
 pandoc-filter
$ cat Dockerfile
 FROM httpd:2.4
 RUN apt-get update && apt-get install -y pandoc
 COPY ./usr-local-apache2 /usr/local/apache2/
 RUN echo 'Include local.conf' >> /usr/local/apache2/conf/httpd.conf
$ docker build -t pandoc:test .

順に説明します。
- Dockerコンテナを作る場合にはビルド手順を書いたDockerfileを作成する。
- FROMで元になるDockerイメージを指定。
公式DockerハブからLinuxとApacheを備えたhttpdというイメージを取得。
- RUNでPandocをインストール。
- COPYで先に用意したApache設定ファイルとフィルタをコンテナにコピー。
コピー先は/usr/local/apachce配下。
- Dockerのビルドコマンドを実行。

Dockerコンテナ実行

Dockerコンテナができたら作成したホスト上で実行してみます。

$ docker run --detach -publish 8080:80 \
    --mount type=bind,src=/var/docker/pandoc/htdocs,dst=/usr/local/apache2/htdocs,ro \
    pandoc:test

順に説明します。
- --detachはコンテナをバックグラウンド(デーモンとして)実行する指定。
- --publishで外部からもアクセスできるようホストマシンのポートとコンテナ内で使用するポートを結びつける。
- --mountはコンテナ内のドキュメントディレクトリの/usr/local/apache2/htdocsをホスト側のディレクトリに結びつける。
そうしないといちいちコンテナにファイルを追加しなくてはなりません。

テスト

まずコンテナを実行しているホストマシン内から:

$ curl localhost:8080/test.md

続いてホストマシンの外部からブラウザでアクセスします。

http://example.com:8080/test.md

使用環境

  • Docker CE 19.03.12
  • ベースイメージ: 公式 httpd:2.4
  • テスト環境: Raspberry Pi 3B (raspiban/debian10-buster)、クラウド上のVPS(同じくDebian10-Buster)

参考リンク

DockerfileやApache設定ファイル、Apacheフィルタのソースはgithubで公開しました。
git cloneしてdocker buildすればコンテナが作成できます。

作成したイメージはDocker Hubからも取得できます。

横浜工文社の関連ページ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Bitcoinを自動売買】AWSのDockerで運用してみた話、外出先のAppleWatchから1TAPでON/OFF&Line通知付き

autocoin2

BitCoinのFX自動売買プログラム。
BitflyerのAPIを利用して、node.jsにて仮想通貨トレードを自動化しました。
寝てる時、トイレ中、24時間中、お金が勝手に増えてくれたら、どんなに素敵だろう。。:gem:
楽して自動的に儲かりたい!そんなダメ人間モチベーションで作ってみました。
iOS の画像.jpgスクリーンショット 2020-09-20 10.05.38.png

いきなり結論ですが、、、残念ながら儲かりません:scream:
むしろ、減っています。。

ですが、チューニングしたら、ひょっとしたら儲かり出すかもしれません。
(損害を受けても当方は一切責任はありません。)
あくまで、自己責任でお願いします!

Githubにコード公開しています

特徴

  • 売り・買いポジション両方対応
  • 複数アルゴリズムによる重み付け売買判断
  • MongoDBによる売買履歴の保存
  • 取引開始をLine通知
  • 損得金額の閾値を超えたら、Lineにて通知
  • 一定の日数が経過したら、ポジションを自動で手放す機能
  • 日付変更30分前には、新たなポジション取得を抑制する機能
  • Apple Home連携で外出先でもiphoneから1タップでON/OFF
  • プログラム稼働中でも、並行して通常の人的トレードも可能

システム概要

autocoin2.png

使用技術

  • Node.js
  • Docker
  • AWS
  • MongoDB
  • shell
  • Raspberry Pi

ディレクトリ構成

.
├── autocoin
│  ├── algo.js
│  ├── app.js
│  ├── config.js
│  ├── crypto.js
│  ├── line.js
│  ├── mongo.js
│  └── utils.js
├── container_data
├── homebridge_AWS
│  ├── startAWS.sh
│  └── stopAWS.sh
├── .env
├── Dockerfile
└── docker-compose.yml

メイン:app.js

このプログラムのエントリーポイント。
ループ処理でコードを廻すことで売買を繰り返します。

'use strict';
const config = require('./config');
const moment = require('moment');
const ccxt = require('ccxt');
const bitflyer = new ccxt.bitflyer(config);

const Crypto = require('./crypto')
const Mongo = require('./mongo');
const mongo = new Mongo();
const Line = require('./line');
const line = new Line(config.line_token)
const utils = require('./utils');
const Algo = require('./algo');

//取引間隔(秒)
const tradeInterval = 180;
//取引量
const orderSize = 0.01;
//swap日数
const swapDays = 3;
//通知用の価格差閾値
const infoThreshold = 100;

//psychoAlgoの設定値;陽線カウント
const psychoParam = {
  'range': 10,
  'ratio': 0.7,
};
//crossAlgoの設定値:移動平均幅
const crossParam = {
  'shortMA': 5,
  'longMA': 30,
};

//ボリンジャーバンド設定値
const BBOrder = {
  //注文
  'period': 10,
  'sigma': 1.7
};
const BBProfit = {
  //利確
  'period': 10,
  'sigma': 1
};
const BBLossCut = {
  //損切り:日足で判断
  'period': 10,
  'sigma': 2.5
};

// アルゴリズムの重み付け:未使用は0にする
const algoWeight = {
  // 'psychoAlgo': 0,
  // 'crossAlgo': 0,
  // 'bollingerAlgo': 1,
  'psychoAlgo': 0.1,
  'crossAlgo': 0.2,
  'bollingerAlgo': 0.7,
};
//取引判断の閾値
const algoThreshold = 0.3;
//ロスカットの閾値
const lossCutThreshold = 0.5;


(async function () {
  let sumProfit = 0;
  let beforeProfit = null;
  const nowTime = moment();
  const collateral = await bitflyer.fetch2('getcollateral', 'private', 'GET');

  //(分)レコード作成
  const crypto = new Crypto();
  const beforeHour = crossParam.longMA * tradeInterval;
  const timeStamp = nowTime.unix() - beforeHour;
  let records = await crypto.getOhlc(tradeInterval, timeStamp);

  const algo = new Algo(records);

  //Lineに自動売買スタートを通知
  const strTime = nowTime.format('YYYY/MM/DD HH:mm:ss');
  const message = `\n 自動売買スタート\n date: ${strTime}\n collateral: ${collateral.collateral}`;
  line.notify(message);


  while (true) {
    let flag = null;
    let label = "";
    let tradeLog = null;

    const nowTime = moment();
    const strTime = nowTime.format('YYYY/MM/DD HH:mm:ss');

    //取引所の稼働状況を確認
    let health = await bitflyer.fetch2('getboardstate');
    if (health.state !== 'RUNNING') {
      // 異常ならwhileの先頭に
      console.log('取引所の稼働状況:', health);
      await utils.sleep(tradeInterval * 1000);
      continue;
    }

    //現在価格を取得
    const ticker = await bitflyer.fetchTicker('FX_BTC_JPY');
    const nowPrice = ticker.close;

    //レコードを更新
    algo.records.push(nowPrice);
    algo.records.shift()

    //アルゴリズム用Paramを初期化
    let bbRes = null;
    let totalEva = 0;
    algo.initEva();
    //共通アルゴリズム
    let crossRes = algo.crossAlgo(crossParam.shortMA, crossParam.longMA);
    let psychoRes = algo.psychoAlgo(psychoParam.range, psychoParam.ratio)

    //建玉を調べる
    const jsonOpenI = await bitflyer.fetch2('getpositions', 'private', 'GET', {product_code: "FX_BTC_JPY"});
    const openI = utils.chkOpenI(jsonOpenI)

    //共通表示
    console.log('================');
    console.log('time:', strTime);
    console.log('nowPrice: ', nowPrice);


    // 建玉がある場合
    if (openI.side) {
      //建玉の共通表示
      console.log('');
      console.log('建玉内容');
      console.log(openI);

      let diffDays = nowTime.diff(openI.open_date, 'days');
      // swap日数を超えているなら
      if (diffDays >= swapDays) {
        // 建玉を0に戻す
        label = 'swap日数を超えているため建玉をリセット'

        if (openI.side === 'BUY') {
          await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
          flag = 'SELL';

        } else {
          await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
          flag = 'BUY';
        }
        sumProfit += openI.pnl;

      } else {
        // 日数を超えてないなら
        //  利益が出ている場合
        if (openI.pnl > 0) {
          label = '利確'
          bbRes = algo.bollingerAlgo(BBProfit.period, BBProfit.sigma, openI.price);
          totalEva = algo.tradeAlgo(algoWeight)

          //買い建玉で、下降シグナルが出ている
          if (openI.side === 'BUY' && totalEva < -algoThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //売り建玉で、上昇シグナルが出ている
          } else if (openI.side === 'SELL' && totalEva > algoThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';

          }
        } else {
          //  損してる場合
          label = 'ロスカット';

          //日足でアルゴリズム判断
          const dayPeriods = 60 * 60 * 24;
          const lossTimeStamp = nowTime.unix() - dayPeriods * BBLossCut.period;
          let dayRecords = await crypto.getOhlc(dayPeriods, lossTimeStamp);

          crossRes = algo.crossAlgo(crossParam.shortMA, crossParam.longMA, dayRecords);
          psychoRes = algo.psychoAlgo(psychoParam.range, psychoParam.ratio, dayRecords);
          bbRes = algo.bollingerAlgo(BBLossCut.period, BBLossCut.sigma, openI.price, dayRecords);
          totalEva = algo.tradeAlgo(algoWeight)

          //損してるのに、買いを持ってて大きなトレンドが下がり兆候
          if (openI.side === 'BUY' && totalEva < -lossCutThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //損してるのに、売りを持ってて大きなトレンドで上がり兆候
          } else if (openI.side === 'SELL' && totalEva > lossCutThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';
          }
        }
      }

      //建玉を精算したなら、
      if (flag) {
        tradeLog = {
          flag: flag,
          label: label,
          sumProfit: sumProfit,
          profit: openI.pnl,
          nowPrice: nowPrice,
          openPrice: openI.price,
          strTime: strTime,
          created_at: nowTime._d,
          openI: openI,
          bollinger: bbRes,
          cross: crossRes,
          psycho: psychoRes,
          totalEva: totalEva,
        };
        mongo.insert(tradeLog);

        console.log('');
        console.log(label);
        console.log(tradeLog);
      }

      // Line通知(閾値を超えたら)
      if (beforeProfit !== null) {
        const profit = openI.pnl;
        const diff = Math.abs(sumProfit + profit - beforeProfit);
        if (diff >= infoThreshold) {
          const message = `\n date: ${strTime}\n sumProfit: ${sumProfit}\n profit: ${profit}\n collateral: ${collateral.collateral}`;
          line.notify(message);
          beforeProfit = sumProfit + profit;
        }
      } else {
        //アラート初期化
        beforeProfit = sumProfit;
      }


    } else {
      //建玉を持ってない場合

      //スワップポイント対応 23:30-0:00 注文しない
      const limitDay = moment().hours(23).minutes(30).seconds(0)
      if (nowTime.isSameOrAfter(limitDay)) {
        console.log(' ');
        console.log('スワップポイント対応中_23:30-0:00');
        //注文を受け付けない while先頭に移動
        await utils.sleep(tradeInterval * 1000);
        continue;
      }

      // 注文する ボリンジャーを使用
      bbRes = algo.bollingerAlgo(BBOrder.period, BBOrder.sigma);
      totalEva = algo.tradeAlgo(algoWeight)

      if (totalEva > algoThreshold) {
        //【買い】で建玉する
        await bitflyer.createMarketBuyOrder('FX_BTC_JPY', orderSize);
        flag = 'BUY';

      } else if (totalEva < -algoThreshold) {
        //【売り】で建玉する
        await bitflyer.createMarketSellOrder('FX_BTC_JPY', orderSize);
        flag = 'SELL';
      }

      //建玉を取得したなら、
      if (flag) {
        label = '建玉取得';

        tradeLog = {
          flag: flag,
          label: label,
          sumProfit: sumProfit,
          nowPrice: nowPrice,
          bollinger: bbRes,
          cross: crossRes,
          psycho: psychoRes,
          totalEva: totalEva,
          strTime: strTime,
          created_at: nowTime._d,
        };
        mongo.insert(tradeLog);

        console.log('');
        console.log(label);
        console.log(tradeLog);
      }
    }

    console.log('');
    console.log('★sumProfit: ', sumProfit);
    console.log('');
    await utils.sleep(tradeInterval * 1000);
  }
})();


ハイパーパラメーターの説明

  • tradeInterval: 取引間隔。最短は60秒。
  • orderSize: 注文数
  • swapDays: 建玉の保持したい日数。超過したら手放す。
  • infoThreshold: Line告知用の金額幅。設定額を超えた損得をするとLine告知する。
  • psychoParam: サイコロジカルラインのアルゴリズムに使用するパラメーター。
    • 期間
    • 比率
  • crossParam: ゴールデンクロス・デッドクロスのアルゴリズムに使用するパラメーター。
    • 短期移動平均線の期間
    • 長期移動平均線の期間
  • BBOrder / BBProfit / BBLossCut: ボリンジャーバンドのアルゴリズムに使用するパラメーター。 建玉取得 / 利益確定 / 損切りごとに判断材料が異なるため分けています。
    • 判断期間
    • 標準偏差
  • algoWeight: 各アルゴリズムの重み(重要度)を設定。 重要度はただの比率なので合計1になるような調整をおすすめ。
  • algoThreshold: 取引判断の閾値。 アルゴリズムの複合判断された値がこの値以上(以下)であれば取引。
  • lossCutThreshold: ロスカット判断の閾値。 複合判断された値がこの値以上(以下)であればロスカット。

分岐、流れの紹介説明

大まかな処理の流れです。

  • 売買スタート
    判断材料とするため、cryptowatchからコード実行前の取引内容を取得する。
    Lineで「自動売買スタート」したことを通知

  • 取引ループを開始
    設定した取引間隔でループを廻す。

  • bitflyerの取引所の稼働状況を確認
    異常であれば、ループの先頭に移動

  • 現在のbitcoinの価格を取得

  • 共通利用アルゴリズム
    クロス関数、サイコロジカル関数の評価をする

  • 保持している建玉(ポジション)の内容を取得する。

  • 建玉を保持している場合、建玉の保持日数を調べる
    保持日数が、指定日数より長ければ、建玉を手放す
    (swap金と、塩漬けされるのを回避するため。)

  • 保持日数が短い場合

  • 利益が出ていれば、ポジションと、アルゴリズム判断により売買

  • 損が出ていれば、日足材料でのアルゴリズム判断によりロスカット
    ロスカットが日足利用なのは、分足だと指標が流動的過ぎ、大きなトレンドで判断が必要と考慮したため。
    実際、昔は分足を使っていたのですが、かなり細かいブレに振り回され利得チャンスを失った上、小さな損を積み上げやすかったです。
    分足、時間足に変更は可能なので調整してみるのもいいかもしれません。

  • 一定額の損得が発生したら、Lineで通知。

  • 建玉を持っていない場合、

  • 日付変更直前の30分なら、取引せずにループ。
    建玉取得して早々にswap金が発生するのを避けるため。

  • 日付直前じゃなければ、アルゴリズム判断により建玉を取得する。

アルゴリズム:algo.js

売買アルゴリズムをまとめています。

const gauss = require('gauss');

module.exports = class Algo {

  constructor(records) {
    this.records = records;

    // 各アルゴリズムの評価ポイント
    //上昇シグナル:+  下降シグナル:-
    this.eva = {
      'psychoAlgo': 0,
      'crossAlgo': 0,
      'bollingerAlgo': 0
    };
  }

  psychoAlgo(range, ratio, list = this.records) {
    //  陽線の割合で売買を判断する

    let countHigh = 0
    //  任意期間の陽線回数をカウント
    for (let i = range; i > 0; i--) {
      const before = list[list.length - i - 1];
      const after = list[list.length - i];

      if (before <= after) {
        countHigh += 1;
      }
    }

    let psychoRatio = 0;
    psychoRatio = countHigh / range;
    if (psychoRatio >= ratio) {
      this.eva['psychoAlgo'] = 1;
    } else if (psychoRatio <= 1 - ratio) {
      this.eva['psychoAlgo'] = -1;
    }

    return psychoRatio;
  }


  crossAlgo(shortMA, longMA, list = this.records) {
    //ゴールデン・デッドクロスで売買を判断する

    //移動平均作成
    const prices = new gauss.Vector(list);
    const shortValue = prices.ema(shortMA).pop();
    const longValue = prices.ema(longMA).pop();

    if (shortValue >= longValue) {
      this.eva['crossAlgo'] = 1;
    } else if (shortValue < longValue) {
      this.eva['crossAlgo'] = -1;
    }

    return {'shortValue': shortValue, 'longValue': longValue};
  }


  bollingerAlgo(period, sigma, price = this.records.slice(-1)[0], list = this.records) {
    //  ボリンジャーバンド

    const prices = new gauss.Vector(list.slice(-period));
    //SMA使用
    const sma = prices.sma(period).pop();
    const stdev = prices.stdev()

    const upper = Math.round(sma + stdev * sigma);
    const lower = Math.round(sma - stdev * sigma);

    if (price <= lower) {
      this.eva['bollingerAlgo'] = 1;
    } else if (price >= upper) {
      this.eva['bollingerAlgo'] = -1;
    }

    return {'upper': upper, 'lower': lower}
  }


  tradeAlgo(weight) {
    //  重み付けして総合的な取引判断

    let totalEva = 0
    //評価ポイントにそれぞれの重みを掛けて足し合わせる
    for (const [key, value] of Object.entries(this.eva)) {
      totalEva += value * weight[key];
    }

    totalEva = Math.round(totalEva * 100) / 100

    return totalEva
  }


  initEva() {
    //全評価ポイントを初期化
    Object.keys(this.eva).forEach(key => {
      this.eva[key] = 0;
    });
  }

}

複合評価

tradeAlgo()

取引判断は複数のアルゴリズムによる複合判断です。
アルゴリズムごとにそれぞれ評価ポイントを保持。
各アルゴリズムは材料データと設定パラメータにより、-1か、+1どちらかの評価をつける。
正数(+1)は上昇トレンド
負数(-1)は下降トレンド
各アルゴリズムごとに評価ポイントとその重みで掛け算し、最後に全て足し合わせて総合評価ポイントを算出します。

app.js内で閾値より、総合評価ポイントの絶対値が上回っていれば取引を実行。
買/売どちらのポジションで取引するかは、状況に応じてapp.jsで判断。

アルゴリズムの追加について

今後、新たなアルゴリズムを追加したい場合は以下の手順を参考。

  • Algoクラス

    • this.eva(メソッド名と同じ評価ポイントの追加)
    • methodとしてアルゴリズムの追加
  • app.js

    • 重み付けの追加
    • 評価させたい箇所でメソッドを追加
      (恐らく共通アルゴリズムとしてcrossAlgo()などと同じ箇所が多いと思います。)

ボリンジャーバンド

bollingerAlgo()

ボリンジャーバンドは、移動平均線と標準偏差を使った判断アルゴリズム。
ざっくり、標準偏差の絶対値が大きければ大きいほど平均に回帰する力が強くなるっていう考え方。
細かく触れないですが、こちらの説明が分かりやすいです。
マネックス証券解説

4つの変数を使用。

  • 判断期間
  • 閾値にする標準偏差
  • 判断したい値段
  • 値動きリスト

値動きリストから判断したい期間を取り出す。
次に、取り出したリストをもとに指定した標準偏差のupper値とlower値を算出する。
最後に、算出した上下の標準偏差帯より、価格がはみ出していれば評価ポイントをつける。

lower値より価格が低い場合
その価格はトレンドより低めに付けられているので、上昇に転じやすい。
上昇トレンドとして、+1をつける

upper値より価格が高い場合
その価格はトレンドより高めに付けられているので、下降に転じやすい。
下降トレンドとして、 -1をつける

サイコロジカルライン

psychoAlgo()

投資家心理を利用したアルゴリズム判断。
売り買いのどちらかが連続して偏った場合、更にその傾向は続く、またはそろそろ逆が出ると判断して多くの売買が行われることで価格変動を予想するアルゴリズム。
このページが分かりやすいです。
マネックス証券解説

3つの変数を使用。

  • 判断範囲
  • 判断する比率
  • 値段リスト

設定期間の値段リストに絞り込み、
一つ前の値段より上昇している値段の数と全体の値段の割合を調べる。
割合値と判断する比率を利用比較して、上昇トレンド、下降トレンドと評価ポイントを付ける。

ゴールデンクロス、デッドクロス

crossAlgo()

長期移動平均線が、短期移動平均線を下から上に突き抜けることをゴールデンクロス。その逆がデッドクロス。
ゴールデンは上昇トレンドになるサインで、デッドクロスは下降トレンドのサイン。

3つの変数を使用。

  • 短期期間
  • 長期間
  • 値段リスト

上記説明の通り、短期移動平均が長期移動平均より上なら上昇トレンドとして評価ポイント+1をつける。
その逆ならデッドクロスとして評価ポイントに-1をつける。
なお、よりトレンドの勢いを加味したかったので、直近値動きをより重視する指数平滑移動平均線(Exponential Moving Average)を使用しました。

OHLCの取得:crypto.js

プログラム実行直後のOHLC取得にcryptowatchAPIを使用。
OHLCはopen/high/low/closeとローソク足データのことです。

Line通知:line.js

取引開始と、一定額以上の損得が発生する毎にLineから通知をします。

Line Nnotifyに関してはこちらのページが分かりやすいです。
LINE notify を使ってみる

取引内容の保存:mongo.js

MongoDBで、売買取引を記録。
取引内容はbitflyerAPIからのjson取得で定形データでは無いので、NoSQLのMongoDBを選択しました。

Dockerコンテナによる稼働ですが、volumesでデータを永続化しています。
最初のDockerコンテナ立ち上げで、volumeディレクトリ:container_dataが作成されます。

その他:utils.js

その他のユーティリティ関数をまとめています。

Apple Home連動

iphone,AppleWatchから1タップで、AWS上のプログラムのON/OFFできるようにしました。

  1. AWS EC2インスタンスにDockerを展開
  2. 自宅のRaspberry Piにhomebridge展開し、実行のshellファイルを紐付ける

詳しくは以下を参考ください。
公式homebridge

Philips_HueをAPI連動!〜ラズパイをHomeKit化する

IntelliJで取引DBを見る

MongoDBに保存した取引内容ですが、閲覧はIDEの利用をおすすめします。
直接MongoDBからの閲覧は、json形式のためかなり辛いです。
IntelliJなどのIDEでしたら、いい感じに見やすくしてくれます。
スクリーンショット 2020-09-20 10.18.45.png
スクリーンショット 2020-09-20 10.11.57.png
IntelliJの設定方法は、過去記事を参照ください。
IntelliJからAWSを操作する設定方法まとめ

注意点

最初のDocker立ち上げ

MongoDBが書き込み可能になるのに時間かかります。
volumeディレクトリのcontainer_dataを作成するためです。
余裕持って初回起動して、しばらくしても書き込みされなければ、再度Dockerを立ち上げ直してください。
私の場合ですが、2回目以降は問題なく稼働しています。

Docker Volumeのcontainer_dataはroot権限

作成されるvolumeディレクトリのcontainer_dataはroot権限で作られます。削除したい際はsudoを付けてください。
私のうっかり経験ですが、Dockerを再ビルドする際に、このディレクトリ権限に気付かずエラーになって少しハマりました。

終わりに

楽して不労所得の夢は実現しません:skull::skull:
しつこいですが、必ず儲かるわけではないですし、自己責任でお願いします。:point_up_tone3:

ひょっとしたら、
私のアルゴリズムや、パラメーターがイマイチナだけで、誰かが追加したアルゴリズムにより儲かりだすかもしれません。。。

そんな時は、こっそりと教えて下さいね:musical_note:
それでは、よろしければホビーとしてほどほどに楽しんでみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker for macでThe data couldn’t be read because it is missingのエラーが出る場合

mac 10.15.6docker 2.3.0.5

以下のエラーが出ます。

Failed to analyse

The data couldn’t be read because it is missing

以下のページからdocker 2.3.0.4にダウングレードすると起動できました。

https://docs.docker.com/docker-for-mac/release-notes/#docker-desktop-community-2304

issue : https://github.com/docker/for-mac/issues/4526

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む