[Trading automatique de Bitcoin] Histoire de fonctionnement avec Docker d'AWS, avec notification ON / OFF et ligne avec 1 TAP d'Apple Watch en déplacement

autocoin2

Programme de trading automatique FX de BitCoin. En utilisant l'API de Bitflyer, nous avons automatisé le trading de devises virtuelles avec node.js. Comme ce serait merveilleux si l'argent pouvait être augmenté librement pendant le sommeil, dans les toilettes et 24 heures sur 24. .. : gemme: Je veux m'amuser et gagner de l'argent automatiquement! J'ai essayé de réussir avec une si mauvaise motivation humaine. iOS の画像.jpgスクリーンショット2020-09-2010.05.38.png

** Du coup, la conclusion est ... malheureusement pas rentable: cri: Au contraire, il diminue. .. ** **

Cependant, si vous le réglez, vous pouvez faire un profit. (* Nous ne sommes responsables d'aucun dommage. *) Veuillez le faire à vos risques et périls!

** Code publié sur Github **

Caractéristique

Présentation du système

autocoin2.png

Technologie utilisée

Structure du répertoire

.
├── 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

Principal: app.js

Le point d'entrée de ce programme. L'achat et la vente sont répétés en tournant le code dans un processus en boucle.


'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');

//Intervalle de négociation(Secondes)
const tradeInterval = 180;
//Volume de transaction
const orderSize = 0.01;
//jours d'échange
const swapDays = 3;
//Seuil de différence de prix pour la notification
const infoThreshold = 100;

//Paramètres PsychAlgo;Nombre de lignes positif
const psychoParam = {
  'range': 10,
  'ratio': 0.7,
};
//Valeur du paramètre crossAlgo:Largeur moyenne mobile
const crossParam = {
  'shortMA': 5,
  'longMA': 30,
};

//Valeur de réglage de la bande de Bollinger
const BBOrder = {
  //ordre
  'period': 10,
  'sigma': 1.7
};
const BBProfit = {
  //Rentabilité
  'period': 10,
  'sigma': 1
};
const BBLossCut = {
  //Coupe de perte:Jugement au quotidien
  'period': 10,
  'sigma': 2.5
};

//Pondération d'algorithme:Définir inutilisé sur 0
const algoWeight = {
  // 'psychoAlgo': 0,
  // 'crossAlgo': 0,
  // 'bollingerAlgo': 1,
  'psychoAlgo': 0.1,
  'crossAlgo': 0.2,
  'bollingerAlgo': 0.7,
};
//Seuil de décision de transaction
const algoThreshold = 0.3;
//Seuil de coupure de perte
const lossCutThreshold = 0.5;


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

  //(Minutes)Création d'enregistrement
  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);

  //Notifier la ligne du démarrage automatique du trading
  const strTime = nowTime.format('YYYY/MM/DD HH:mm:ss');
  const message = `\n Démarrage automatique du trading\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');

    //Vérifiez l'état de fonctionnement de l'échange
    let health = await bitflyer.fetch2('getboardstate');
    if (health.state !== 'RUNNING') {
      //Si anormal, au début
      console.log('État de fonctionnement de l'échange:', health);
      await utils.sleep(tradeInterval * 1000);
      continue;
    }

    //Obtenez le prix actuel
    const ticker = await bitflyer.fetchTicker('FX_BTC_JPY');
    const nowPrice = ticker.close;

    //Mettre à jour l'enregistrement
    algo.records.push(nowPrice);
    algo.records.shift()

    //Initialiser le paramètre pour l'algorithme
    let bbRes = null;
    let totalEva = 0;
    algo.initEva();
    //Algorithme commun
    let crossRes = algo.crossAlgo(crossParam.shortMA, crossParam.longMA);
    let psychoRes = algo.psychoAlgo(psychoParam.range, psychoParam.ratio)

    //Examiner l'intérêt ouvert
    const jsonOpenI = await bitflyer.fetch2('getpositions', 'private', 'GET', {product_code: "FX_BTC_JPY"});
    const openI = utils.chkOpenI(jsonOpenI)

    //Affichage commun
    console.log('================');
    console.log('time:', strTime);
    console.log('nowPrice: ', nowPrice);


    //S'il y a un intérêt ouvert
    if (openI.side) {
      //Affichage commun de l'intérêt ouvert
      console.log('');
      console.log('Contenu d'intérêt ouvert');
      console.log(openI);

      let diffDays = nowTime.diff(openI.open_date, 'days');
      //Si les jours d'échange sont dépassés
      if (diffDays >= swapDays) {
        //Renvoie l'intérêt ouvert à 0
        label = 'Réinitialiser l'intérêt ouvert car les jours d'échange ont été dépassés'

        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 {
        //Si le nombre de jours n'est pas dépassé
        //Si vous faites un profit
        if (openI.pnl > 0) {
          label = 'Rentabilité'
          bbRes = algo.bollingerAlgo(BBProfit.period, BBProfit.sigma, openI.price);
          totalEva = algo.tradeAlgo(algoWeight)

          //Il y a un signal à la baisse sur l'intérêt ouvert
          if (openI.side === 'BUY' && totalEva < -algoThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //Il y a un signal croissant sur l'intérêt ouvert
          } else if (openI.side === 'SELL' && totalEva > algoThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';

          }
        } else {
          //Si tu perds
          label = 'Coupe de perte';

          //Jugement d'algorithmes au quotidien
          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)

          //Même si je perds, il y a des signes qu'une grande tendance est à la baisse avec l'achat
          if (openI.side === 'BUY' && totalEva < -lossCutThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //Même si je perds, j'ai une vente et c'est une grosse tendance et un signe de hausse
          } else if (openI.side === 'SELL' && totalEva > lossCutThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';
          }
        }
      }

      //Si vous réglez l'intérêt ouvert
      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);
      }

      //Notification de ligne(Si le seuil est dépassé)
      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 {
        //Initialisation d'alerte
        beforeProfit = sumProfit;
      }


    } else {
      //Si vous n'avez pas d'intérêt ouvert

      //Prise en charge des points d'échange 23:30-0:00 Ne pas commander
      const limitDay = moment().hours(23).minutes(30).seconds(0)
      if (nowTime.isSameOrAfter(limitDay)) {
        console.log(' ');
        console.log('Prise en charge des points d'échange_23:30-0:00');
        //Aller au début sans accepter les commandes
        await utils.sleep(tradeInterval * 1000);
        continue;
      }

      //Utilisez Bollinger pour commander
      bbRes = algo.bollingerAlgo(BBOrder.period, BBOrder.sigma);
      totalEva = algo.tradeAlgo(algoWeight)

      if (totalEva > algoThreshold) {
        //Ouvrez une position avec [Acheter]
        await bitflyer.createMarketBuyOrder('FX_BTC_JPY', orderSize);
        flag = 'BUY';

      } else if (totalEva < -algoThreshold) {
        //Ouvrez une position avec [Vendre]
        await bitflyer.createMarketSellOrder('FX_BTC_JPY', orderSize);
        flag = 'SELL';
      }

      //Si vous obtenez un intérêt ouvert
      if (flag) {
        label = 'Acquisition d'intérêts ouverts';

        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);
  }
})();


Description de l'hyper paramètre

--tradeInterval: Intervalle d'échange. Le plus court est de 60 secondes. --orderSize: Nombre de commandes --swapDays: le nombre de jours pendant lesquels vous souhaitez garder un intérêt ouvert. Si vous le dépassez, laissez-le aller. --infoThreshold: plage de quantité de ligne pour la notification. Line annoncera que vous perdrez ou gagnerez plus que le montant fixé. --PsychParam: Paramètres utilisés pour l'algorithme de ligne psychologique. --Période --Rapport --crossParam: Paramètres utilisés pour l'algorithme Golden Cross / Dead Cross.

Introduction explication du branchement et du flux

C'est un flux de traitement approximatif.

--Vérifiez l'état de fonctionnement de l'échange de bitflyer En cas d'anomalie, passez au début de la boucle

--Lorsque la période de conservation est courte

--Si un certain montant de profit ou de perte se produit, vous en serez informé par Line.

--Si vous n'avez pas d'intérêt ouvert

――Si c'est 30 minutes juste avant le changement de date, bouclez sans trading. Pour éviter d'obtenir de l'argent de swap plus tôt après avoir obtenu des intérêts ouverts.

--Si ce n'est pas juste avant la date, la position ouverte est acquise par le jugement de l'algorithme.

Algorithme: algo.js

Il résume les algorithmes d'achat et de vente.

const gauss = require('gauss');

module.exports = class Algo {

  constructor(records) {
    this.records = records;

    //Points d'évaluation de chaque algorithme
    //Signal montant:+Signal bas:-
    this.eva = {
      'psychoAlgo': 0,
      'crossAlgo': 0,
      'bollingerAlgo': 0
    };
  }

  psychoAlgo(range, ratio, list = this.records) {
    //Juger l'achat et la vente par le ratio de lignes positives

    let countHigh = 0
    //Comptez le nombre de rayons positifs dans n'importe quelle période
    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) {
    //Juger l'achat et la vente à la Golden Dead Cross

    //Création de moyenne mobile
    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) {
    //Bande de Bollinger

    const prices = new gauss.Vector(list.slice(-period));
    //Utiliser 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) {
    //Jugement de transaction pondéré et complet

    let totalEva = 0
    //Multipliez les points d'évaluation par leurs poids respectifs et additionnez-les
    for (const [key, value] of Object.entries(this.eva)) {
      totalEva += value * weight[key];
    }

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

    return totalEva
  }


  initEva() {
    //Initialiser tous les points d'évaluation
    Object.keys(this.eva).forEach(key => {
      this.eva[key] = 0;
    });
  }

}

Évaluation combinée

tradeAlgo()

Une décision de transaction est une décision composée basée sur plusieurs algorithmes. Contient des points d'évaluation pour chaque algorithme. Chaque algorithme donne une note de -1 ou +1 en fonction des données du matériau et des paramètres de réglage. Le nombre positif (+1) est une tendance à la hausse Le nombre négatif (-1) est une tendance à la baisse Multipliez chaque algorithme par les points d'évaluation et leurs poids, puis additionnez-les tous ensemble pour calculer le total des points d'évaluation.

Si la valeur absolue du total des points d'évaluation dépasse le seuil dans app.js, la transaction est exécutée. Le trading en position d'achat ou de vente est déterminé par app.js en fonction de la situation.

À propos de l'ajout d'un algorithme

Si vous souhaitez ajouter un nouvel algorithme ultérieurement, reportez-vous à la procédure ci-dessous.

--Classe d'Algo --this.eva (en ajoutant le même point d'évaluation que le nom de la méthode) --Ajout d'un algorithme comme méthode

Bande de Bollinger

bollingerAlgo()

La bande de Bollinger est un algorithme de jugement qui utilise des moyennes mobiles et des écarts types. En gros, plus la valeur absolue de l'écart-type est élevée, plus la capacité de revenir à la moyenne est forte. Je n'y reviendrai pas en détail, mais cette explication est facile à comprendre. Commentaire sur Manex Securities

Utilise 4 variables.

Extrayez la période que vous souhaitez juger de la liste des mouvements de prix. Ensuite, la valeur supérieure et la valeur inférieure de l'écart type spécifié sont calculées en fonction de la liste extraite. Enfin, si le prix est en dehors des bandes d'écart type supérieur et inférieur calculées, un point d'évaluation est ajouté.

Si le prix est inférieur à la valeur inférieure Son prix est inférieur à la tendance, il est donc facile de commencer à augmenter. Ajouter +1 comme tendance haussière

Lorsque le prix est supérieur à la valeur supérieure Son prix est supérieur à la tendance, il est donc facile de le refuser. Ajouter -1 comme tendance baissière

Ligne psychologique

psychoAlgo()

Jugement d'algorithme utilisant le sentiment des investisseurs. Un algorithme qui prédit les fluctuations de prix en déterminant que si l'achat ou la vente est biaisé en permanence, la tendance se poursuivra, ou l'inverse se produira bientôt, et de nombreux achats et ventes seront effectués. Cette page est facile à comprendre. Commentaire sur Manex Securities

Utilisez 3 variables.

Se limiter à la liste de prix pour la période définie, Découvrez le nombre de prix supérieurs au prix précédent et le ratio du prix total. Comparez en utilisant le ratio jugé comme la valeur du ratio et ajoutez des points d'évaluation en tant que tendance à la hausse et à la baisse.

Croix d'or, croix morte

crossAlgo()

Croix d'or que la ligne moyenne mobile à long terme pénètre la ligne moyenne mobile à court terme de bas en haut. Le contraire est une croix morte. L'or est le signe d'une tendance haussière et la croix morte est le signe d'une tendance baissière.

Utilisez 3 variables.

--Courte période --Long terme --Liste de prix

Comme expliqué ci-dessus, si la moyenne mobile à court terme est supérieure à la moyenne mobile à long terme, un point d'évaluation de +1 est donné comme une tendance à la hausse. Si le contraire est vrai, ajoutez -1 au point d'évaluation en tant que croix morte. Puisque je voulais ajouter plus d'élan de tendance, j'ai utilisé la moyenne mobile exponentielle, qui met l'accent sur les derniers mouvements de prix.

Obtention d'OHLC: crypto.js

Utilisez l'API cryptowatch pour obtenir OHLC immédiatement après l'exécution du programme. OHLC est synonyme de données ouvertes / élevées / basses / fermées et chandeliers.

Notification de ligne: line.js

Line vous informera chaque fois qu'une transaction commence et qu'un certain montant de profit ou de perte se produit.

Cette page est facile à comprendre sur Line Nnotify. Essayez d'utiliser LINE notify

Enregistrer les détails de la transaction: mongo.js

Enregistrez les transactions de vente avec MongoDB. Étant donné que le contenu de la transaction est une acquisition json à partir de l'API bitflyer et non des données standard, j'ai choisi MongoDB de NoSQL.

Il est géré par le conteneur Docker, mais les données sont conservées dans des volumes. Le premier lancement du conteneur Docker crée un répertoire de volume: container_data.

Autre: utils.js

D'autres fonctions utilitaires sont résumées.

Lié avec Apple Home

Le programme sur AWS peut être activé / désactivé en un seul clic depuis l'iPhone et l'Apple Watch.

  1. Déployez Docker sur une instance AWS EC2
  2. Développez homebridge vers votre Raspberry Pi domestique et liez le fichier shell d'exécution

Veuillez consulter ce qui suit pour plus de détails. Homebridge officiel

[Philips_Hue lié à l'API! ~ Make Raspberry Home Kit](https://qiita.com/akinko/items/58c650f99f25fc7e3cb5#%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4%E3%82 % 92homekit% E5% 8C% 96% E3% 81% 99% E3% 82% 8B)

Afficher la base de données de transaction sur IntelliJ

Bien que les détails de la transaction soient enregistrés dans MongoDB, nous vous recommandons d'utiliser l'EDI pour les afficher. Naviguer directement depuis MongoDB est assez pénible en raison du format json. S'il s'agit d'un IDE tel qu'IntelliJ, ce sera agréable et facile à voir. スクリーンショット 2020-09-20 10.18.45.png スクリーンショット 2020-09-20 10.11.57.png Veuillez vous référer à l'article précédent pour la méthode de paramétrage d'IntelliJ. [Résumé de la méthode de configuration pour faire fonctionner AWS à partir d'IntelliJ](https://qiita.com/akinko/items/d7001a8fe3ac87e1790c#db%E3%81%A8%E3%81%AE%E7%B0%A1%E5%8D% 98% E6% 8E% A5% E7% B6% 9A)

point important

Premier lancement de Docker

Il faut du temps pour que MongoDB devienne accessible en écriture. Il s'agit de créer container_data pour le répertoire de volume. Démarrez-le pour la première fois avec une marge, et s'il n'est pas écrit après un certain temps, redémarrez Docker. Dans mon cas, cela fonctionne bien à partir de la deuxième fois.

Docker Volume container_data a les privilèges root

Le container_data du répertoire de volume créé est créé avec les privilèges root. Si vous souhaitez le supprimer, ajoutez sudo. Dans mon expérience par inadvertance, lors de la reconstruction de Docker, j'étais un peu accro à une erreur sans remarquer cette autorisation de répertoire.

À la fin

Profitez et ne réalisez jamais votre rêve de revenus sans emploi: skull :: skull: C'est persistant, mais ce n'est pas toujours rentable, alors prenez vos propres risques. : point_up_tone3:

Peut-être Mon algorithme, ou le seul paramètre que je n'aime pas, peut être rentable avec un algorithme ajouté par quelqu'un. .. ..

Dans un tel cas, dites-moi en secret: musical_note: Ensuite, si vous le souhaitez, profitez-en comme passe-temps.

Recommended Posts

[Trading automatique de Bitcoin] Histoire de fonctionnement avec Docker d'AWS, avec notification ON / OFF et ligne avec 1 TAP d'Apple Watch en déplacement
Une histoire sur l'utilisation de l'API League Of Legends avec JAVA
Une histoire qui a eu du mal avec l'introduction de Web Apple Pay
Une histoire à laquelle j'étais accro à deux reprises avec le paramètre de démarrage automatique de Tomcat 8 sur CentOS 8
L'histoire de la création d'un lanceur de jeu avec une fonction de chargement automatique [Java]
Une note rapide sur l'utilisation de jshell avec l'image Docker officielle du JDK
[Jackson] Une histoire sur la conversion de la valeur de retour du type BigDecimal avec un sérialiseur personnalisé.