Segmentation Faultぐ

コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

ハニーポットを自宅に設置しVPS経由で公開する

下記を読んで自分もハニーポットを植えてみたいと思いました。

tk-secu.hateblo.jp

どうせなら色々なハニーポット試したいし、攻撃の様子もいい感じに可視化したい。
だったらT-Potを植えればいいじゃない!ということで環境を構築してみました。


環境構成

環境構築のしやすさと運用コストの観点から下記の構成を取りました。
VPSを経由して自宅のサーバを公開する方法です。


f:id:segmentation-fault:20200510145227p:plain
T-Pot環境構成例

補足
・クラウドに設置するのが一番楽だが、T-Potの動作条件を満たそうと思ったらレンタル料が高額になってしまう。(RAMがネック)
・VPSを使わない場合、インターネット側から自宅サーバへの通信を通すのが難しい。

(参考)T-Potの動作条件(Standard Installation)
・6-8GB RAM
・128GB SSD
- system-requirements


注意事項
・事前にVPSの利用規約をお確かめください。
・運用には細心の注意を払ってください。
・当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますので、ご了承ください。


T-Potのインストール

インストール方法はいくつか公開されており、ネットで検索した感じPrebuilt ISO Imageを使う方が多いようです。

が、自分の環境ではPrebuilt ISO Imageだとインストールが上手くいかなかった(パーティションを認識してくれずエラー終了する)ので、Post-Install Userでインストールしました。

サーバとして用意した実機はLiva-Zです。
8Gメモリ x 2枚、120GB SSDを追加しました。














Debianのインストール

T-PotのREADMEにはDebian 9.7 (Stretch)が要件に書いてありますが、私はDebian 10.3を使いました。(よく確認せず最新をダウンロードしてしまった)

余計なエラー等にハマりたくない方は、なるべく要件に合わせたほうがよいでしょう。

概要
(1)Debianのインストール用ISOイメージをダウンロードしてメディアに焼く。
(2)インストール用メディアを用意したサーバに接続してメディアからブート。
(3)インストールガイドに従ってインストール実施。



今回、インストール時のパーティション構成は"/"にディスクの全容量を割り当てました。

f:id:segmentation-fault:20200510154652p:plain

T-Potのハニーポットはdockerコンテナで構成されており、デフォルトでは実データが/var/lib/docker配下に格納されます。

何も考えず/varにパーティションをきると/varがすぐにディスク100%になってしまうので注意です。


Debianのインストールはこちらから。
Downloading Debian CD/DVD images via HTTP/FTP



T-Potのダウンロード&インストール

下記記載の手順にしたがって実行します。
gitが必要なので事前にインストールしましょう。

https://github.com/dtag-dev-sec/tpotce#post-install-user

# apt install git
# git clone https://github.com/dtag-dev-sec/tpotce
# cd tpotce/iso/installer/
# ./install.sh --type=user


エディションはStandardを選択しました。

f:id:segmentation-fault:20200510154723p:plain

WEBの管理画面(Kibana)にログインするためのユーザ名+パスワードを設定します。

f:id:segmentation-fault:20200510154733p:plain

実行したシェルスクリプトが終了し再起動すればT-Potの完成です。

f:id:segmentation-fault:20200510154831p:plain



VPS-IX2105間のIPsec-VPN

VPSにSoftEther VPNをインストールして、下記を参考にして同様に設定していきます。

NEC ルータ等からの EtherIP を用いた VPN 接続方法 - SoftEther VPN プロジェクト


正常にVPNが張れているかコマンド等で確認しましょう。

IX2105-HOME(config)# show ike sa
ISAKMP SA - 1 configured, 1 created
Local address is 192.168.0.2, port is 4500
Remote address is 203.0.113.1, port is 4500
  IKE policy name is ike-policy
  Direction is initiator
  Initiator's cookie is 0x****************
  Responder's cookie is 0x****************
  Exchange type is aggressive mode
  NAT-Traversal RFC3947
  NAT detected at local side
  State is established
  Authentication method is pre-shared
  Encryption algorithm is aes-128
  Hash algorithm is sha1
  DH group is modp1024, lifetime is 550 seconds
  #ph1 success: 1, #ph1 failure: 0
  #ph1 hash err: 0, #ph1 timeout: 0, #ph1 resend: 0
  #ph2 success: 1, #ph2 failure: 0
  #ph2 hash err: 0, #ph2 timeout: 0, #ph2 resend: 0
  
  
IX2105-HOME(config)# show ipsec sa
IPsec SA - 1 configured, 2 created
Interface is Tunnel0.0
  Key policy map name is ipsec-map
    UDP encapsulation Tunnel mode, 4-over-4, autokey-map
    Local address is 192.168.0.2, port is 4500
    Remote address is 203.0.113.1, port is 4500
    Outgoing interface is GigaEthernet0.0
    Interface MTU is 1422, path MTU is 1500
    Inbound:
      ESP, SPI is 0x********(********)
        Transform is ESP-AES-128-HMAC-SHA-96
        Remaining lifetime is 1445 seconds
      Replay detection support is on
    Outbound:
      ESP, SPI is 0x********(********)
        Transform is ESP-AES-128-HMAC-SHA-96
        Remaining lifetime is 1445 seconds
      Replay detection support is on
    Perfect forward secrecy is off



VPSのファイアウォール

ひとまず、SSHのハニーポットであるCowrieだけ試したいので、T-Potへの転送は22番ポートだけ設定しています。

また、T-Potの管理用ポート(64294, 64295, 64297)へのアクセスはVPS経由 + VPS側で自宅のIPアドレスのみ受け付けるようにフィルタするようにしました。

$ sudo firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: ssh
  ports: 
  protocols:
  masquerade: no
  forward-ports: port=22:proto=tcp:toport=22:toaddr=192.168.1.3
  sourceports:
  icmp-blocks: echo-request
  rich rules:
        rule family="ipv4" source address="198.51.100.1" forward-port port="64294" protocol="tcp" to-port="64294" to-addr="192.168.1.3"
        rule family="ipv4" source address="198.51.100.1" forward-port port="64297" protocol="tcp" to-port="64297" to-addr="192.168.1.3"
        rule family="ipv4" source address="198.51.100.1" forward-port port="64295" protocol="tcp" to-port="64295" to-addr="192.168.1.3"
        rule family="ipv4" source address="198.51.100.1" port port="500" protocol="udp" accept
        rule family="ipv4" source address="198.51.100.1" port port="4500" protocol="udp" accept


動作確認

構築が終わったら、T-Potにアクセスできるか動作確認してみましょう。


下記は5日間程度動かしてみたときの様子です。

f:id:segmentation-fault:20200510162628p:plain



参考



Linux PCのデスクトップ画面をブラウザから監視する

やりたいこと

こんな感じでブラウザから外部のPC(Linux)のモニターに表示している内容をできる限りリアルタイムで表示したい。

f:id:segmentation-fault:20200226211949p:plain


HLS(HTTP Live Streaming)

HLS(HTTP Live Streaming)を使えば、多少遅延はあるがほぼリアルタイムの映像をみることができるようです。Youtubeとかで良くあるLive配信的なやつですね。

具体的には、ffmpegを使ってLinux PCのモニターの表示内容(X11)を録画 + HLS用ファイルとして出力 + Nginx等のWebサーバを立てて、HLS用ファイルにアクセス可能にします。

ただ、HLSはAppleが開発した仕組みであり、現時点で標準サポートしているブラウザSafariだけっぽいです。

が、世の中には優秀な素晴らしい方々がおり、Video.jsを使えば他のブラウザでもLive Stremingの動画再生が可能になります(感謝)。

実施例

ここでは、Virtual-BOXにCentOS7(GUIモード) + WEBサーバ + HLSファイルの出力処理を構築 --> ホスト側のブラウザからCentOSのWEBサーバにアクセスし、CentOSのGUI画面を表示してみます。


事前準備

まずは必要なパッケージを取得します。

nginxのインストール

$ sudo vim /etc/yum.repos.conf.d/nginx.repo
$ cat /etc/yum.repos.conf.d/nginx.repo
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1
$ sudo yum -y install nginx 

ffmpegのインストール

$ sudo yum -y install epel-release http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
$ sudo yum -y install ffmpeg

WEBサーバの設定

今回の例ではhttp://<IPアドレス>/hlsにアクセスした際に動画が見えるようにします。

必要なDirectoryを掘ります。

$ sudo mkdir /var/www/html/hls
$ sudo mkdir /var/www/html/hls/static/js
$ sudo mkdir /var/www/html/hls/static/css


VideoJsを配置します。

$ cd /var/www/html/hls
$ tree
└── static
    ├── css
    │   └── video-js.min.css
    └── js
        ├── video.min.js
        ├── videojs-contrib-hls.min.js
        └── videojs-contrib-media-sources.min.js


index.htmlを作成します。

$ sudo vim /var/www/html/hls/index.html
$ cat /var/www/html/hls/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Video.jsのお試し</title>
  <link href="http://192.168.56.103/hls/static/css/video-js.min.css" rel="stylesheet">
  <script src="./static/js/video.min.js"></script>
  <script src="./static/js/videojs-contrib-media-sources.min.js"></script>
  <script src="./static/js/videojs-contrib-hls.min.js"></script> 
</head>
<body>
<h2>ffmpeg + HLSでデクストップ画面をモニタリング</h2>
<video id="test" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto"
 width="640" height="360" data-setup="{}">
  <source src="./output.m3u8" type="application/x-mpegURL">
</video>
</body>
</html>


nginxのconfigを編集します。

$ sudo vim /etc/nginx/conf/default.conf
$ cat /etc/nginx/conf/default.conf
...(途中略)...
        location / {
            root   /var/www/html;
            index index.html;
        }
...(途中略)...


ポート80番号を開放します。

$ sudo firewall-cmd --add-service=http --zone=public --permanent
$ sudo firewall-cmd --reload


nginxを起動します。

$ sudo systemctl start nginx


HLS開始

下記のコマンドでCentOSのGUI画面を録画しHLS形式のファイルで出力します。

$ cd /var/www/html/hls
$ sudo ffmpeg -f x11grab -r 60 -s 800x600 -i :0.0+0,0 -vcodec h264 -b:v 250K -pix_fmt yuv420p -segment_format mpegts  -hls_time 5 -hls_list_size 5 -hls_flags delete_segments output.m3u8


実行結果

ChromeからHLS配信用サーバ(Virtual-BOX上のCentOS)にアクセスしてみます。
20秒〜30秒の遅延はありますが、いい感じに監視できていますね。
※動画は面倒なのでgif画像です。

f:id:segmentation-fault:20200226212522g:plain


その他の方法

遅延を避けるには、WebRTCといった技術を駆使すればできるらしいです。
ビデオチャットとかのイメージですね。

◆構成
仮想Webカメラ(v4l2loopback) + シグナリングサーバ

◆内容
監視対象のPCに仮想的なWebカメラ(v4l2loopback)導入する、そしてWebカメラにffmpegで映像を流し込み、Webカメラで撮影している内容をシグナリングサーバに送信する。
監視を行うクライアントはブラウザからシグナリングサーバにアクセスして映像を見る。

...みたいな構成を組めばできるようです。Node.JsやWebSocket等の知識が必要になります
ちょっとばかり調べてみましたが、何言ってるか良く分からなかったので断念しました...

参考
https://medium.com/@alexcambose/webcam-live-streaming-with-websockets-and-base64-64b1b4992db8
https://github.com/umlaeute/v4l2loopback


リンク

https://tools.ietf.org/html/rfc8216
https://videojs.com




sshでログインしている端末の出力をのぞき見る

やりたいこと

  • 自分は端末Aでログインしている状態。
    端末Aから別の端末である端末Bのコマンドの入力、応答をのぞき見たい。
  • できればC言語で実装したい


実現方法

ググってみたところ、perl製のttylogを使ったり、shellスクリプトで頑張ればできるようです。
どちらも共通するのはstraceの出力を加工していること。

sshdに対してstraceを実行し、read(fd=13)の第2引数を取っています。

参考
orebibou.com

strace自体はptraceを使って実装されているので、マネすればc言語でもいけそうです。


ptraceについて

manページ見てもよくわからなかったのですが、下記の流れで実装すれば良いみたいです。

attach
↓
option設定
↓
システムコールをcatch
↓
レジスタから引数を解析

- システムコールの番号、引数はレジスタ(struct user_regs_struct)で判別する。<br>
- readの第2引数(buf)の中身を取得するにはPTRACE_PEEKDATAを使う。


X86のレジスタ対応表(とりあえずreadの捕捉に必要なものだけ)

種目 レジスタ(メンバ名)
システムコールの番号 orig_rax
リターン値 rax
第1引数 rdi
第2引数 rsi
第3引数 rdx


ソースコード

ttytrace.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/syscall.h>

#define FD_TERMINAL (13)

static int read_args(pid_t pid, long addr, size_t size)
{
    int rc = 0;
    long dat = 0;
    size_t datsz = 0;

    for (int i = 0; i < size; i+= sizeof(long)) {
        errno = 0;
        dat = ptrace(PTRACE_PEEKDATA, pid, addr + i, NULL);
        int err = errno;
        if (err != 0) {
            perror("ptrace");
            rc = -err;
            break;
        }
 
        if (i + sizeof(long) > size) {
            write(1, (void*)&dat, size % sizeof(long));
        } else {
            write(1, (void*)&dat, sizeof(long));
        }
    }

    return rc;
}


static int
get_syscall_args_read(
    pid_t pid,
    struct user_regs_struct *regs)
{
    int rc = 0;

#if defined(__x86_64__)
    /* skip other than "read" */
    if (!((regs->orig_rax == SYS_read) &&
         (regs->rdi == FD_TERMINAL) &&
         ((ssize_t)regs->rax > 0))) {
        goto end;
    }

    rc = read_args(pid, (long)regs->rsi, (size_t)regs->rax);
    if (rc < 0) {
        goto end;
    }
#endif   

 end:

    return rc;
}

static void start_trace(pid_t pid)
{
    int rc = 0;
    int status = 0;
    struct user_regs_struct regs;

    rc = ptrace(PTRACE_ATTACH, pid, NULL, NULL);
    if (rc < 0) {
        perror("ptrace");
        goto end;
    }

    ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_TRACESYSGOOD);

    while (1) {
        ptrace(PTRACE_SYSCALL, pid, NULL, NULL);

        rc = waitpid(pid, &status, 0);
        if (rc < 0) {
            perror("waitpid");
            goto detach_end;
        }

        if (WIFEXITED(status)) {
            break;
        }

        rc = ptrace(PTRACE_GETREGS, pid, NULL, &regs);
        if (rc < 0) {
            perror("ptrace");
            goto detach_end;
        }

        rc = get_syscall_args_read(pid, &regs);
        if (rc < 0) {
            goto detach_end;
        }
    }

  detach_end:
    ptrace(PTRACE_DETACH, pid, NULL, NULL);

  end:
    return;
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("%s <pid>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    pid_t pid = atoi(argv[1]);

    start_trace(pid);

    return 0;
}


ビルド

gcc -o ttytrace ttytrace.c -std=gnu99


実行

監視したい端末のsshdのpidを指定する。
実行にはroot権限が必要です。

sudo ./ttytrace <sshdのpid>


実行に成功していれば、ttylogとかと同様に監視対象の端末での操作内容がそのまま表示されます。
意外と短い実装で済んでよかった。


Javascriptでテトリスを作る

Javascriptのサンプルとしてテトリスは定番ですが、そういえば今まで挑戦してなかったの作ってみました。

canvasを使って落下ブロック(テトリミノ)が7種、色が6種のザ定番ぽいものを作ります。

完成品


Chromeでしか動作確認してません。

a : 左に移動
s : 右に移動
d : 下に移動
f : 右に回転


ソースコード


<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
</head>
<body>
<!-- canvas -->
<canvas id="tetris" width="300" height="600"></canvas>
<script type="text/javascript">
(function() {
    /* 全体のコンフィグ */
    var CONFIG = {
        'width'      : 0,                /* 横幅 */
        'hegith'     : 0,                /* 縦幅 */
        'xsqs'       : 10,               /* 横方眼数 */
        'ysqs'       : 20,               /* 縦方眼数 */
        'color'      : "rgb(65,65,65)",  /* 盤面の色 */
        'grid_color' : "black",          /* 格子の色 */
        'grid_width' : 2,                /* 格子の幅 */
    };

    /* 方眼 */
    var SQUARES;
    var squre = function() {
        this.color = 0;
    };

    /* 色リスト */
    var COLOR = {
        "default" : CONFIG.color,
        "red"     : "rgb(255,0,0)",
        "green"   : "rgb(0,255,0)",
        "blue"    : "rgb(0,0,255)",
        "yellow"  : "rgb(255,255,0)",
        "cyan"    : "rgb(0,255,255)",
        "magenta" : "rgb(255,0,255)",
    };

    var COLOR_LIST = [
        COLOR.red,
        COLOR.green,
        COLOR.blue,
        COLOR.yellow,
        COLOR.cyan,
        COLOR.magenta,
    ];

    /* ブロックの定義 */
    var BLOCK;
    var block = function () {
        this.color = COLOR_LIST[Math.floor(Math.random() * (COLOR_LIST.length))];
        this.shape = BLOCK_LIST[Math.floor(Math.random() * (BLOCK_LIST.length))];
        this.x = (CONFIG.xsqs / 2) - 1;
        this.y = 0;
        return this;
    };

    var BLOCK_BOX = [
        [0,0,0,0],
        [0,1,1,0],
        [0,1,1,0],
        [0,0,0,0]
    ];
    
    var BLOCK_BAR = [
        [1,1,1,1],
        [0,0,0,0],
        [0,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_LL = [
        [1,1,1,0],
        [1,0,0,0],
        [0,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_LN = [
        [1,0,0,0],
        [1,1,1,0],
        [0,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_NL = [
        [1,0,0,0],
        [1,1,0,0],
        [0,1,0,0],
        [0,0,0,0]
    ];

    var BLOCK_NN = [
        [0,1,0,0],
        [1,1,0,0],
        [1,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_T = [
        [1,0,0,0],
        [1,1,0,0],
        [1,0,0,0],
        [0,0,0,0]
    ];

    var BLOCK_LIST = [
        BLOCK_BOX,
        BLOCK_BAR,
        BLOCK_LL,
        BLOCK_LN,
        BLOCK_NL,
        BLOCK_NN,
        BLOCK_T,
    ];
    
    /* 描画コンテキスト */
    var CTX;

    /* タイマー */
    var fallTimer;

    /* キー入力 */
    var KEY_DOWN = {
        "a"     : 65, /* left */
        "s"     : 83, /* right */
        "d"     : 68, /* down */
        "f"     : 70, /* turn right */
        "left"  : 37,
        "right" : 39,
        "down"  : 40,
        "space" : 32, /* turn right */
    };
    

    /* 初期化処理 */
    function initialize() {

        /* canvasの取得 */
        CTX = document.getElementById("tetris").getContext('2d');
        if (!CTX) {
            return false;
        }

        CONFIG.width = CTX.canvas.width;
        CONFIG.height = CTX.canvas.height;

        /* keydown event */
        document.onkeydown = moveBlock;

        /* 盤面の生成 */
        SQUARES = createSquares(CONFIG);

        /* 落下ブロックの生成 */
        BLOCK = block();

        /* テトリスの起動 */
        drawTetris(CTX, CONFIG, SQUARES, BLOCK);
        fallTimer = setInterval(tetris, 400, CTX, CONFIG, SQUARES, BLOCK);
    }

    /* ブロックの移動 */
   function moveBlock() {
 
        var key = event.keyCode;

        switch(key) {
        case KEY_DOWN.a :
        case KEY_DOWN.left :
            moveLeftBlock();
            break;
        case KEY_DOWN.s :
        case KEY_DOWN.right :
            moveRightBlock();
            break;
        case KEY_DOWN.f :
        case KEY_DOWN.space :
            turnRightBlock();
            break;
        case KEY_DOWN.d :
        case KEY_DOWN.down :
            moveDownBlock();
            break;
        default:
            break;
        }
   }

    /* 左に移動 */
    function moveLeftBlock() {

        var isEnabeMove = true;

        for (var y = 0 ; y < BLOCK.shape[0].length; y++) {
            for (var x = 0; x < BLOCK.shape.length; x++) {

                if (BLOCK.shape[x][y] == 0) {
                    continue;
                }

                /* これ以上左に行けない */
                if (((BLOCK.x + x) <= 0) ||
                    ((SQUARES[BLOCK.x + x - 1][BLOCK.y + y].color != CONFIG.color))) {
                    isEnabeMove = false;
                    break;
                }
            }
        }

        if (isEnabeMove) {
           BLOCK.x--;
        }
    }

    /* 右に移動 */
    function moveRightBlock() {

        isEnabeMove = true;

        for (var y = 0 ; y < BLOCK.shape[0].length; y++) {
            for (var x = 0; x < BLOCK.shape.length; x++) {
                if (BLOCK.shape[x][y] != 0) {
                    /* これ以上右に行けない */
                    if (((BLOCK.x + x) >= (CONFIG.xsqs - 1)) || 
                        ((SQUARES[BLOCK.x + x + 1][BLOCK.y + y].color != CONFIG.color))) {
                        isEnabeMove = false;
                        break;
                    }
                }
            }
        }

       if (isEnabeMove) {
           BLOCK.x++;
       }
    };

    /* 下に加速 */
    function moveDownBlock() {
        if (!isLanded(CONFIG, SQUARES, BLOCK)) {
            BLOCK.y++;
        }
    };

    /* ターンできるか */
    function isEnableToTurn(tmp) {
        for (var x = 0; x < BLOCK.shape.length; x++) {
            for (var y = 0; y < BLOCK.shape[0].length; y++) {
                if (tmp[x][y] != 0) {
                    if (((BLOCK.x + x) < 0) || (CONFIG.xsqs <= (BLOCK.x + x)) ||
                        ((BLOCK.y + y) < 0) || (CONFIG.ysqs <= (BLOCK.y + y)) ||
                        SQUARES[BLOCK.x + x][BLOCK.y + y].color != CONFIG.color) {
                    
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /* ブロックの右回転 */
    function turnRightBlock() {
        
        var tmp = Array(BLOCK.shape.length);
        for (var x = 0; x < BLOCK.shape.length; x++) {
            tmp[x] = Array(BLOCK.shape[0].length);
        }        

        for (var x = 0; x < BLOCK.shape.length; x++) {
            for (var y = 0; y < BLOCK.shape[0].length; y++) {
                tmp[x][y] = BLOCK.shape[y][BLOCK.shape[0].length - 1 - x];
            }
        }
     
        if (isEnableToTurn(tmp)) {
            BLOCK.shape = tmp;
        }
    }

    /* 方眼の作成 */
    function createSquares(c) {
        var sqrs = new Array(c.ysqs);

        for (var y = 0; y < c.ysqs; y++) {
            sqrs[y] = new Array(c.xsqs);
        }
        
        for (var x = 0; x < c.xsqs; x++) {
            for (var y = 0;  y < c.ysqs; y++) {
                sqrs[x][y] = new squre();
                sqrs[x][y].color = c.color;
            }
        }

        return sqrs;
    }

    /* 盤面の描画 */
    function drawTetris(ctx, c, sqrs, blk) {
        drawBack(ctx, c);   
        drawSquare(ctx, c, sqrs);
        drawBlock(ctx, c, blk);
        drawGrid(ctx, c);
    }

    /* 背景の描画 */
    function drawBack(ctx, c) {
        ctx.fillStyle = c.color;
        ctx.fillRect(0, 0, c.width, c.height);
        
        ctx.strokeStyle = c.grid_color;
        ctx.lineWidth = c.grid_width;
        
        ctx.beginPath();
        ctx.stroke();
    }

    /* 格子(グリッド)の描画 */
    function drawGrid(ctx, c) {

        ctx.beginPath();

        /* 縦のグリッド線 */
        for(var x = c.width/c.xsqs; x < c.width; x+= c.width/c.xsqs) {
            ctx.moveTo(x, 0);
            ctx.lineTo(x, c.height);
        } 
        
        /* 横のグリッド線 */
        for(var y = c.height/c.ysqs; y < c.height; y+= c.height/c.ysqs) {
            ctx.moveTo(0, y);
            ctx.lineTo(c.width, y);
        }
        
        ctx.stroke();
    }

    /* 方眼の描画 */
    function drawSquare(ctx, c, blocks) {
        var x_sp; /* 描画開始座標(x) */
        var y_sp; /* 描画開始座標(y) */
        var w; /* 横幅 */
        var h; /* 縦幅 */

        ctx.beginPath();
        for (var x = 0; x < c.xsqs; x++) {
            for (var y = 0; y < c.ysqs; y++) {
                if (blocks[x][y].color == COLOR.default) {
                    continue;
                }
            
                w = (c.width/c.xsqs);
                h = (c.height/c.ysqs);
                x_sp = x * w;
                y_sp = y * h;
                
                ctx.fillStyle = blocks[x][y].color;
                ctx.fillRect(x_sp, y_sp, w, h);
            }
        }
        ctx.stroke();
    };

    /* 落下ブロックの描画 */
    function drawBlock(ctx, c, blk) {
        var width = c.width/c.xsqs;
        var height = c.height/c.ysqs;

        ctx.fillStyle = blk.color;
        for (var x = 0; x < blk.shape.length; x++) {
            for (var y = 0; y < blk.shape[0].length; y++) {
                if (blk.shape[x][y] != 0) {
                    if (((blk.x + x) >= 0) && ((blk.x + x) <= c.xsqs) &&
                        ((blk.y + y) >= 0) && ((blk.y + y) <= c.ysqs)) {
                       
                        ctx.fillRect((blk.x + x) * width, (blk.y + y) * height, 
                                     width, height);
                    }
                }
            }
        }
    };

    /* ブロックの着地 */
    function addBlock2Board(c, squres, blk) {

        /* 盤面に着色 */
        for (var x = 0; x < blk.shape.length; x++) {
            for (var y = 0; y < blk.shape[0].length; y++) {

                if (blk.shape[x][y] == 0) {
                    continue;
                }
            
                if (blk.x + x <= c.xsqs && blk.y + y <= c.ysqs) {
                    squres[blk.x + x][blk.y + y].color = blk.color;
                }
            }
        }
    }

    /* ブロックの落下 */
    function isLanded(c, squres, blk) {

        /* 着地判定 */
        for (var x = 0; x < blk.shape.length; x++) {
            for (var y = 0; y < blk.shape[0].length; y++) {

                if (blk.shape[x][y] != 0) {
                    /* 地面と接触 */
                    if ((blk.y + y) == (c.ysqs - 1)) {
                        return true;
                    }

                    /* 既設ブロックと接触 */
                    if (squres[blk.x + x][blk.y + y + 1].color != c.color) {
                        return true;
                    }
                }
            } 
        }

        /* Not着地 */
        return false;
    }

    /* GameOverですか? */
    function isGameOver(c, squres) {
        for (var x = c.xsqs/2 - 2 ; x < c.xsqs/2 + 2; x++) {
            if (squres[x][0].color != c.color) {
                return true;
            }
        }
            
        return false;
    }

    /* 行削除 */
    function eraseLine(c, squres) {

        for (var y = 0; y < c.ysqs; y++) {
            /* is enable to erase ? */
            var flag = true;
            for (var x = 0; x < c.xsqs; x++) {
                if (squres[x][y].color == c.color) {
                    flag = false;
                    break;
                }
            }

            if(!flag) {
                continue;
            }

            /* do to erase */
            for (var x = 0; x < c.xsqs; x++) {
                squres[x][y].color = c.color;
            }
            
            if (y != 0) {
                for (var y1 = y; y1 != 0; y1--) {
                    for (var x = 0; x < c.xsqs; x++) {
                        squres[x][y1].color = squres[x][y1-1].color;
                    }
                }
            }
        }
    }

    /* main処理 */
    function tetris(ctx, c, squres, blk) {

        if (isLanded(c, squres, blk)) {
            /* ブロックの着地 */
            addBlock2Board(c, squres, blk);

            /* 削除処理 */
            eraseLine(c, squres);

            /* Game Over ? */
            if (isGameOver(c,squres)) {
                drawTetris(ctx, c, squres, blk);
                clearInterval(fallTimer);
            } else {
                /* 新しいブロックの生成 */
                blk = block();
            }
        } else {
            /* 落下 */
            blk.y++;
        }

        /* 盤面の描画 */
        drawTetris(ctx, c, squres, blk);
    }

    window.addEventListener('load', initialize, false);

} )();
</script>
<div>a : 左に移動</div>
<div>s : 右に移動</div>
<div>d : 下に移動</div>
<div>f : 右に回転</div>
</body>
</html>


もう少しシンプルに作れたかも。



Python3サンプルコード集(その2)

Pythonのサンプルコードです。

ネットワーク系が中心となります。

書かなくなるとすぐ忘れてしまうので備忘録として残しておきます。

全てクラアント-サーバの通信のサンプルです。

TCP


tcp_client.py

import os
import sys
import socket

if __name__ == '__main__':

    address = ('127.0.0.1', 50000)
    datasize = 4096

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(address)
    
    msg = 'Hello TCP'
    s.send(msg.encode())
    s.close()

tcp_server.py

import os
import sys
import socket

if __name__ == '__main__':

    address = ('127.0.0.1', 50000)
    datasize = 4096

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(address)
    s.listen(1)

    client, addr = s.accept()
    data = client.recv(datasize)
    
    sys.stdout.write("receive : {}\n".format(data.decode()))
    s.close()

UDP


udp_client.py

import os
import sys
import socket

if __name__ == '__main__':
    
    server_address = ('127.0.0.1', 50001)
    datasize = 4096

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    msg = 'Hello UDP'
    s.sendto(msg.encode(), server_address)
    s.close()

udp_server.py

import os
import sys
import socket

if __name__ == '__main__':
    
    bind_address = ('127.0.0.1', 50001)
    datasize = 4096
    
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind(bind_address)
    data, client = s.recvfrom(datasize)
    sys.stdout.write("receive : {}\n".format(data.decode()))

    s.close()


UNIXドメイン(DATAGRAM型)


unix_dgram_client.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_dgram'

    s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

    s.connect(socket_path)

    msg = 'Hello unix domain (dgram)'
    s.send(msg.encode())

    s.close()

unix_dgram_server.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_dgram'
    datasize = 4096

    s = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)

    s.bind(socket_path)

    data, client = s.recvfrom(datasize)

    sys.stdout.write("received : {}\n".format(data.decode()))

    os.unlink(socket_path)
    s.close()


UNIXドメイン(STREAM型)


unix_stream_client.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_stream'

    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

    s.connect(socket_path)

    msg = 'Hello unix domain (stream)'
    s.send(msg.encode())

    s.close()

unix_stream_server.py

import os
import sys
import socket

if __name__ == '__main__':
    
    socket_path = './unix_stream'
    datasize = 4096

    s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

    s.bind(socket_path)
    s.listen(1)

    client, addr = s.accept()

    data = client.recv(datasize)

    sys.stdout.write("received : {}\n".format(data.decode()))

    os.unlink(socket_path)
    s.close()


TCP(selectで待つ)


tcp_select.py

import os
import sys
import socket
import select

if __name__ == '__main__':
    
    address = ('127.0.0.1', 50000)
    datasize = 4096
    maxconn = 10
    sockets = []
    
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    s.bind(address)
    s.listen(maxconn)
    sockets.append(s)    

    try:
        readfds = list(sockets)
        while True:
            
            sys.stdout.write("waiting...\n")
            r,w,x = select.select(readfds, [], [])
            
            for s in r:
                if s in sockets:    
                    conn, addr = s.accept()
                    readfds.append(conn)
                    sys.stdout.write("connected.\n")
                else:
                    data = s.recv(datasize)
                    if len(data) == 0:
                        sys.stdout.write("disconnected.\n")
                        readfds.remove(s)
                        s.close()
                        break
                    sys.stdout.write("received {}\n".format(data.decode()))
    finally:
        s.close()


TCP-UDP-UNIXを全てselectで待つ


all_select.py

import os
import sys
import socket
import select
import signal

if __name__ == '__main__':
    
    address1 = ('127.0.0.1', 50000)
    address2 = ('127.0.0.1', 50001)
    socket_path1 = "./unix_stream"
    socket_path2 = "./unix_dgram"
    datasize = 4096
    maxconn = 1
    sockets = []
    conn_unix = None
    conn_tcp = None

    tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp.bind(address1)
    tcp.listen(maxconn)

    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp.bind(address2)

    unix_s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
    unix_s.bind(socket_path1)
    unix_s.listen(maxconn)

    unix_d = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    unix_d.bind(socket_path2)

    sockets.append(tcp)
    sockets.append(udp)
    sockets.append(unix_s)
    sockets.append(unix_d)

    try:
        readfds = list(sockets)
        while True:
            
            sys.stdout.write("waiting...\n")
            r,w,x = select.select(readfds, [], [])
            
            for s in r:
                if s in sockets:
                    if s == tcp:
                        conn_tcp, addr_tcp = s.accept()
                        readfds.append(conn_tcp)
                        sys.stdout.write("tcp connected.\n")
                    if s == udp:
                        data, client = s.recvfrom(datasize)
                        sys.stdout.write("received {}\n".format(data.decode()))
                    if s == unix_s:
                        conn_unix, addr_unix = s.accept()
                        readfds.append(conn_unix)
                        sys.stdout.write("unix connected.\n")
                    if s == unix_d:
                        data, client = s.recvfrom(datasize)
                        sys.stdout.write("received {}\n".format(data.decode()))
                else:
                    if s == conn_tcp:
                        data = s.recv(datasize)
                        if len(data) == 0:
                            sys.stdout.write("tcp disconnected.\n")
                            readfds.remove(s)
                            s.close()
                            break
                        sys.stdout.write("received {}\n".format(data.decode()))
                    if s == conn_unix:
                        data = s.recv(datasize)
                        if len(data) == 0:
                            sys.stdout.write("unix disconnected.\n")
                            readfds.remove(s)
                            s.close()
                            break
                        sys.stdout.write("received {}\n".format(data.decode()))
    except KeyboardInterrupt:
        tcp.close()
        udp.close()
        os.unlink(socket_path1)
        os.unlink(socket_path2)
        unix_s.close()
        unix_d.close()


エラー処理は上手く書けないのでごめんなさい。

www.segmentation-fault.xyz



Nginx+uWSGI+viewvcでSubversionのリポジトリをブラウザから閲覧する

ViewVC: Repository Browsingを使ってLinux(CentOS7)上に構築したリポジトリをブラウザから参照可能にしてみます。

これで、Linuxが使えないポンコツな上司から修正内容を問われても、URLをコピペしてメールするだけで済むようになります(?)

Git環境向けはまた今度。

事前準備



必要なパッケージを先にインストールしておきます。

$ sudo yum -y install python python-devel svn subversion-python policycoreutils-devel
$ sudo yum -y groupinstall "Development Tools"

参照するリポジトリ



今回のテスト用に/opt/svn/reposを作ります。

$ sudo mkdir -p /opt/svn/
$ cd /opt/svn/
$ sudo svnadmin create repos
$ sudo chown -R user:user repos/


コミットログも見れるか確認するため作成したリポジトリに適当なファイルを追加します。

$ cd ~/
$ svn co file:///opt/svn/repos
$ cd repos
$ mkdir -p branches  tag  trunk
$ echo "Hello World" > trunk/README
$ svn add *
$ svn commit -m "Create New Repository." 


Nginxのインストール



Nginxをyumでインストールするため、リポジトリにnginxのパッケージ取得先を追記します。

$ sudo vi /etc/yum.repos.d/nginx.repo
$ cat /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/7/$basearch/
gpgcheck=0
enabled=0


以下のコマンドでインストールできればOK

$ sudo yum -y --enablerepo=nginx install nginx


続いて、nginxのコンフィグです。

/viewvcでアクセスした場合にuWSGIに処理させるようにします。
/viewvc-staticはviewvcのロゴ等の画像ファイルを参照するための記述です。

$ sudo vi /etc/nginx/conf.d/default.conf
$ diff -u /etc/nginx/conf.d/default.conf.orig /etc/nginx/conf.d/default.conf
--- /etc/nginx/conf.d/default.conf.orig 2018-09-23 19:20:22.455255538 +0900
+++ /etc/nginx/conf.d/default.conf      2018-09-23 20:58:28.830092221 +0900
@@ -41,5 +41,14 @@
     #location ~ /\.ht {
     #    deny  all;
     #}
+
+    location /viewvc {
+        include uwsgi_params;
+        uwsgi_pass 127.0.0.1:3031;
+    }
+
+    location /viewvc-static {
+        alias /usr/local/viewvc/templates/docroot;
+    }
 }


このままではファイアウォールとSELinuxに接続が弾かれるので予めポートの開放とSELinuxを無効化しておきます。

ファイアウォールの設定

$ sudo firewall-cmd --permanent --add-port=3031/tcp --zone=public 
$ sudo firewall-cmd --permanent --zone public --add-service http
$ sudo firewall-cmd --reload

SELinuxの無効化

sudo semanage permissive -a httpd_t


設定が終ったらnginxを起動します。

$ sudo systemctl enable nginx
$ sudo systemctl start nginx


uWSGIのインストール



公式の方法はこちら

今回、uWSGIはpipでインストールするので、まずはpipを使えるようにします。

$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
$ sudo python get-pip.py


pipが準備できたらuwsgiを追加します。

$ sudo pip install uwsgi


追加できたら、systemctlで起動できるように下記ファイルを作ります。

/etc/uwsgi/uwsgi.ini

$ sudo mkdir -p /etc/uwsgi
$ sudo vi /etc/uwsgi/uwsgi.ini
$ cat /etc/uwsgi/uwsgi.ini
[uwsgi]
socket = 127.0.0.1:3031
wsgi-file = /usr/local/viewvc/bin/wsgi/viewvc.wsgi
uid = nginx
gid = nginx
log = /var/log/uwsgi
master = true


$ sudo vim /etc/systemd/system/uwsgi.service
$ cat /etc/systemd/system/uwsgi.service
[Unit]
Description=uWSGI
After=syslog.target

[Service]
ExecStart=/usr/bin/uwsgi --ini /etc/uwsgi/uwsgi.ini
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target


設定が終ったら起動します。

$ sudo systemctl enable uwsgi
$ sudo systemctl start uwsgi


Viewvcのインストール



こちらから最新版を取ってきます。

$ curl http://www.viewvc.org/downloads/viewvc-1.1.26.tar.gz -o viewvc-1.1.26.tar.gz
$ tar xvf viewvc-1.1.26.tar.gz
$ cd xvf viewvc-1.1.26
$ sudo ./viewvc-install
  <インストール先について質問されるが全て<Enter>を押下>


ViewVCのパスとコンフィグをNginx、uWSGIに記載した内容に合わせます。
所有者もnginxに変更します。

$ cd /usr/local
$ sudo chown -R nginx:nginx viewvc-1.1.26/
$ sudo ln -s viewvc-1.1.26 viewvc
$ sudo chown nginx:nginx viewvc
$ cd /usr/local/viewvc
$ sudo vi viewvc.conf
$ diff -u viewvc.conf.orig viewvc.conf
--- viewvc.conf.orig    2018-09-23 19:28:57.955853140 +0900
+++ viewvc.conf 2018-09-23 20:18:35.987344907 +0900
@@ -109,7 +109,7 @@
 ## svn_roots = svnrepos: /opt/svn/,
 ##             anotherrepos: /usr/local/svn/repos2
 ##
-#svn_roots =
+svn_roots = viewvc:/opt/svn/repos

 ## root_parents: Specifies a list of directories under which any
 ## number of repositories may reside.  You can specify multiple root
@@ -658,7 +658,7 @@
 ## still be based on the global default template set per 'template_dir'
 ## above, not on 'template_dir' as overridden for a given root.
 ##
-#docroot =
+docroot = /viewvc-static

 ## show_subdir_lastmod: Show last changelog message for CVS subdirectories
 ##


結果



ブラウザにURLを入力してリポジトリの内容が参照できたら成功です。

今回の例では、"http://(サーバーのIPアドレス)/viewvc "にアクセスします。

f:id:segmentation-fault:20180923212800p:plain


参考


ViewVC: Repository Browsing

https://pip.pypa.io/en/stable/

https://uwsgi-docs.readthedocs.io/en/latest/index.html

https://nginx.org/

https://subversion.apache.org/




ZAIFのAPIで仮想通貨の積立スクリプトを作る(python)

pythonのお勉強の一環として、ちょこちょこ仮想通貨の自動取引の実装にトライしております。

今回はzaifでBTC/BCH/ETH/MONA/XEM(NEM)を500円分買い注文するプログラムを組んでみます。
作ったスクリプトとcronやsystemdのタイマーと組み合わせることで定期積立を実現できます。



はじめに


zaifを選択する理由は、国内の取引所の中ではAPIで操作できる通貨種類が多いからです。

現在日本円で手に入る主要な仮想通貨としては、

  • Bitcoin(BTC)
  • Bitcoin Cash(BCH)
  • Ethereum(ETH)
  • Ethereum classic(ETC)
  • XEM(NEM)
  • Monacoin(MONA)
  • Ripple(XRP)
  • Lisk(LSK)

このあたりでしょうか。

Zaifは大体カバーできてますね。

かつ販売ではなく取引ができるのでAPIのpairに指定できるというわけです。
(BitFlyerとCoinCheckはBitcoinしかpairに設定できないはず)

また、zaifには積立のサービスがありますが少額だと手数料の割合がそこそこ高いので自分で実装するとお得です。ただしセキュリティ面については自己責任で。


zaif.jp



実装方針


売りの実装は難しい(利益や損切りを考えると面倒)ので、まずは買いの実装だけやってみます。
バグって誤発注するのが怖いので予算は少額に設定しておきます。

  • 定期的に一定額(500円で買える分だけ)を購入する(積立)
  • 指値はpythonスクリプトを実行した時点の最終取引額
  • 最小注文単位の金額が500円を超える場合は注文しない
  • 約定判定はなし

実装にあたっては下記のAPIを使用します。

currency_pairs — Zaif api document v1.1.1 ドキュメント
last_price — Zaif api document v1.1.1 ドキュメント
trade — Zaif api document v1.1.1 ドキュメント


APIを利用するには事前にアカウント設定から取引用のキーを取得しておく必要があります。セキュリティ確保のため送信元IPアドレスの制限もしておくと良いでしょう。


ソースコード



zaif_fund.py

# -*- coding: utf-8 -*-
import sys
import json
import requests
import hmac
import hashlib
import time
from datetime import datetime
import urllib.parse
import math

class zaifApi:
    def __init__(self, key, key_secret, endpoint):
        self.key = key
        self.key_secret = key_secret
        self.endpoint = endpoint

    def get(self, path):
        nonce = int(datetime.now().strftime('%s'))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.get(
                self.endpoint + path ,
                headers = self.__get_header(self.key, nonce, signature))

    def post(self, method, params):
        nonce = int(datetime.now().strftime('%s'))
        payload = {
           "method": method,
           "nonce": nonce,
        }
        payload.update(params)
        encoded_payload = urllib.parse.urlencode(payload)

        return requests.post(
                self.endpoint,
                data = payload,
                headers = self.__get_header(encoded_payload))

    def delete(self,path):
        nonce = str(int(time.time()))
        text = nonce + self.endpoint + path

        signature = hmac.new(
                bytes(self.key_secret.encode('ascii')),
                bytes(text.encode('ascii')),
                hashlib.sha256).hexdigest()

        return requests.delete(
                self.endpoint+path,
                headers = self.__get_header(self.key, nonce))

    def __get_header(self, params):
        signature = hmac.new(
                bytearray(self.key_secret.encode('utf-8')),
                digestmod=hashlib.sha512)
        signature.update(params.encode('utf-8'))

        return {
            'key': self.key,
            'sign': signature.hexdigest()
        }


def getLastPrice(pair):

    urlbase = 'https://api.zaif.jp/api/1/last_price/'

    response = requests.get(urlbase+pair)
    if response.status_code != 200:
        return None

    last_price = response.json()

    return last_price['last_price']

def getItemUnitMin(pair):

    urlbase = 'https://api.zaif.jp/api/1/currency_pairs/'

    response = requests.get(urlbase+pair)
    if response.status_code != 200:
        return None

    currency_pair = response.json()

    return currency_pair[0]['item_unit_min']

if __name__ == '__main__':

    argv = sys.argv
    argc = len(argv)

    if (argc != 3):
        print('usage: python %s <key> <secret key>' % argv[0])
        quit()

    key = argv[1]
    key_secret = argv[2]

    endpoint = 'https://api.zaif.jp/tapi'

    # 予算
    budget_jpy = 500

    # 取引対象の通貨
    pairs = [
        'btc_jpy',
        'bch_jpy',
        'eth_jpy',
        'xem_jpy',
        'mona_jpy',
    ]

    # Instance
    zaif = zaifApi(key,key_secret,endpoint)

    for p in pairs:
        # 認証に使うnonceのための遅延処理
        time.sleep(1.1)

        # 取引通貨の通貨情報を取得
        item_unit_min = getItemUnitMin(p)
        if (item_unit_min == None):
            # 失敗した場合は中断
            continue

        # 最終取引価格を取得
        last_price = getLastPrice(p)
        if (last_price == None):
            # 失敗した場合は中断
            continue

        # 注文価格が予算を超えた場合は中断
        if ((item_unit_min * last_price) > budget_jpy):
            continue

        # 注文量の計算
        # buget_jpy / last_price = 123.456789, item_unit_min = 0.01の場合, amount = 123.45になるような計算を行う
        # floadの場合, 最小桁付近で計算誤差がでるためroundで桁を調整する
        amount = round((math.floor((budget_jpy / last_price) / item_unit_min) * item_unit_min), int(math.log10(1/item_unit_min)))

        # 小数点を使わない場合は整数に変換する
        if (amount.is_integer()):
            amount = int(amount)

        if (last_price.is_integer()):
            last_price = int(last_price)

        # リクエストのパラメータ設定
        method = 'trade'
        params = {
            'currency_pair': p,
            'action' : 'bid',
            'price' : last_price,
            'amount': amount,
        }

        print(params)
        print('total  = {0:.2f} yen' .format(amount*last_price))

        # 注文の送信
        response = zaif.post(method, params)
        if response.status_code != 200:
            continue

        print(response.json())
        print('')


実行結果


$ /usr/local/bin/python3 zaif_fund.py 
usage: python zaif_fund.py <key> <secret key>


$ /usr/local/bin/python3 zaif_fund.py  'あなたのAPIキー' 'あなたのプライベートキー'
{'amount': 0.0005, 'currency_pair': 'btc_jpy', 'action': 'bid', 'price': 936820}
total  = 468.41 yen
{'return': {'order_id': 0, 'remains': 0.0, 'received': 0.0005, 'funds': {'保有資産の一覧'}}, 'success': 1}

{'amount': 0.0031, 'currency_pair': 'bch_jpy', 'action': 'bid', 'price': 159200}
total  = 493.52 yen
{'return': {'order_id': 0, 'remains': 0.0, 'received': 0.0030907, 'funds': {'保有資産の一覧'6}}, 'success': 1}

{'amount': 0.0065, 'currency_pair': 'eth_jpy', 'action': 'bid', 'price': 76800}
total  = 499.20 yen
{'return': {'order_id': xxxxxxxxx, 'remains': 0.0065, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1}

{'amount': 13.6, 'currency_pair': 'xem_jpy', 'action': 'bid', 'price': 36.55}
total  = 497.08 yen
{'return': {'order_id': xxxxxxxxx, 'remains': 13.6, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1}

{'amount': 1, 'currency_pair': 'mona_jpy', 'action': 'bid', 'price': 442.5}
total  = 442.50 yen
{'return': {'order_id': xxxxxxxxx, 'remains': 1.0, 'received': 0.0, 'funds': {'保有資産の一覧'}}, 'success': 1}



応答のremainsが0.0以外のものは、板に注文が残っていて、0.0の場合は既に取引が完了しています。

約定の判定処理はありませんが、上げトレンドが続くようなことでもない限り大体翌日ぐらいまでには約定する気がします。

今のところ2ヶ月ほど週1回の頻度で実行してますが、注文が未約定のまま残ったことは無いです。


仮想通貨を取引所に置きっぱなしが怖い人は送金するAPIの組み合わせることで別walletへの退避も自動で出来ますね。