[Automatischer Handel mit Bitcoin] Eine Geschichte über den Betrieb mit Docker unter AWS mit ON / OFF- und Line-Benachrichtigung mit 1 TAP von Apple Watch für unterwegs

autocoin2

Das automatische FX-Handelsprogramm von BitCoin. Mit der Bitflyer-API haben wir den Handel mit virtuellen Währungen mit node.js automatisiert. Wie schön wäre es, wenn das Geld im Schlaf, auf der Toilette und 24 Stunden am Tag frei erhöht werden könnte. .. : gem: Ich möchte Spaß haben und automatisch Geld verdienen! Ich habe versucht, es mit einer so schlechten menschlichen Motivation zu schaffen. iOS の画像.jpgスクリーンショット2020-09-2010.05.38.png

** Plötzlich ist die Schlussfolgerung ... leider nicht rentabel: schreien: Vielmehr nimmt es ab. .. ** **.

Wenn Sie es jedoch einstellen, können Sie einen Gewinn erzielen. (* Wir haften nicht für Schäden. *) Bitte tun Sie dies auf eigenes Risiko!

** Code auf Github veröffentlicht **

Charakteristisch

Systemübersicht

autocoin2.png

Technologie verwendet

Verzeichnisaufbau

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

Haupt: app.js.

Der Einstiegspunkt für dieses Programm. Das Kaufen und Verkaufen wird wiederholt, indem der Code in einem Schleifenprozess gedreht wird.


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

//Handelsintervall(Sekunden)
const tradeInterval = 180;
//Transaktionsvolumen
const orderSize = 0.01;
//Tage tauschen
const swapDays = 3;
//Preisdifferenzschwelle für Benachrichtigung
const infoThreshold = 100;

//PsychAlgo-Einstellungen;Positive Zeilenzahl
const psychoParam = {
  'range': 10,
  'ratio': 0.7,
};
//crossAlgo-Einstellwert:Gleitende Durchschnittsbreite
const crossParam = {
  'shortMA': 5,
  'longMA': 30,
};

//Bollinger-Band-Einstellwert
const BBOrder = {
  //bestellen
  'period': 10,
  'sigma': 1.7
};
const BBProfit = {
  //Rentabilität
  'period': 10,
  'sigma': 1
};
const BBLossCut = {
  //Verlustschnitt:Tägliches Urteil
  'period': 10,
  'sigma': 2.5
};

//Algorithmusgewichtung:Setzen Sie unbenutzt auf 0
const algoWeight = {
  // 'psychoAlgo': 0,
  // 'crossAlgo': 0,
  // 'bollingerAlgo': 1,
  'psychoAlgo': 0.1,
  'crossAlgo': 0.2,
  'bollingerAlgo': 0.7,
};
//Transaktionsentscheidungsschwelle
const algoThreshold = 0.3;
//Verlustschwelle
const lossCutThreshold = 0.5;


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

  //(Protokoll)Datensatzerstellung
  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);

  //Benachrichtigen Sie Line über den automatischen Handelsstart
  const strTime = nowTime.format('YYYY/MM/DD HH:mm:ss');
  const message = `\n Automatischer Handelsstart\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');

    //Überprüfen Sie den Betriebsstatus der Vermittlungsstelle
    let health = await bitflyer.fetch2('getboardstate');
    if (health.state !== 'RUNNING') {
      //Wenn abnormal, zu Beginn der Zeit
      console.log('Betriebsstatus der Vermittlungsstelle:', health);
      await utils.sleep(tradeInterval * 1000);
      continue;
    }

    //Aktuellen Preis abrufen
    const ticker = await bitflyer.fetchTicker('FX_BTC_JPY');
    const nowPrice = ticker.close;

    //Datensatz aktualisieren
    algo.records.push(nowPrice);
    algo.records.shift()

    //Initialisieren Sie Param für den Algorithmus
    let bbRes = null;
    let totalEva = 0;
    algo.initEva();
    //Gemeinsamer Algorithmus
    let crossRes = algo.crossAlgo(crossParam.shortMA, crossParam.longMA);
    let psychoRes = algo.psychoAlgo(psychoParam.range, psychoParam.ratio)

    //Untersuchen Sie offenes Interesse
    const jsonOpenI = await bitflyer.fetch2('getpositions', 'private', 'GET', {product_code: "FX_BTC_JPY"});
    const openI = utils.chkOpenI(jsonOpenI)

    //Gemeinsame Anzeige
    console.log('================');
    console.log('time:', strTime);
    console.log('nowPrice: ', nowPrice);


    //Wenn es offenes Interesse gibt
    if (openI.side) {
      //Gemeinsame Anzeige von offenem Interesse
      console.log('');
      console.log('Open-Interest-Inhalte');
      console.log(openI);

      let diffDays = nowTime.diff(openI.open_date, 'days');
      //Wenn die Swap-Tage überschritten werden
      if (diffDays >= swapDays) {
        //Geben Sie das offene Interesse auf 0 zurück
        label = 'Open Interest zurücksetzen, da die Swap-Tage überschritten wurden'

        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 {
        //Wenn die Anzahl der Tage nicht überschritten wird
        //Wenn Sie Gewinn machen
        if (openI.pnl > 0) {
          label = 'Rentabilität'
          bbRes = algo.bollingerAlgo(BBProfit.period, BBProfit.sigma, openI.price);
          totalEva = algo.tradeAlgo(algoWeight)

          //Es gibt ein Abwärtssignal für das offene Interesse
          if (openI.side === 'BUY' && totalEva < -algoThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //Es gibt ein steigendes Signal für Open Interest
          } else if (openI.side === 'SELL' && totalEva > algoThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';

          }
        } else {
          //Wenn Sie verlieren
          label = 'Verlustschnitt';

          //Tägliche Beurteilung des Algorithmus
          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)

          //Obwohl ich verliere, gibt es Anzeichen dafür, dass ein großer Trend beim Kauf nach unten geht
          if (openI.side === 'BUY' && totalEva < -lossCutThreshold) {
            await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'SELL';

            //Obwohl ich verliere, habe ich einen Verkauf und es ist ein großer Trend und ein Zeichen des Aufstiegs
          } else if (openI.side === 'SELL' && totalEva > lossCutThreshold) {
            await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
            sumProfit += openI.pnl;
            flag = 'BUY';
          }
        }
      }

      //Wenn Sie das offene Interesse begleichen
      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);
      }

      //Leitungsbenachrichtigung(Wenn der Schwellenwert überschritten wird)
      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 {
        //Alarminitialisierung
        beforeProfit = sumProfit;
      }


    } else {
      //Wenn Sie kein offenes Interesse haben

      //Austauschpunktunterstützung 23:30-0:00 Nicht bestellen
      const limitDay = moment().hours(23).minutes(30).seconds(0)
      if (nowTime.isSameOrAfter(limitDay)) {
        console.log(' ');
        console.log('Unterstützende Swap-Punkte_23:30-0:00');
        //Gehen Sie zum Anfang, ohne Bestellungen anzunehmen
        await utils.sleep(tradeInterval * 1000);
        continue;
      }

      //Verwenden Sie Bollinger, um zu bestellen
      bbRes = algo.bollingerAlgo(BBOrder.period, BBOrder.sigma);
      totalEva = algo.tradeAlgo(algoWeight)

      if (totalEva > algoThreshold) {
        //Eröffnen Sie eine Position mit [Kaufen]
        await bitflyer.createMarketBuyOrder('FX_BTC_JPY', orderSize);
        flag = 'BUY';

      } else if (totalEva < -algoThreshold) {
        //Eröffne eine Position mit [Verkaufen]
        await bitflyer.createMarketSellOrder('FX_BTC_JPY', orderSize);
        flag = 'SELL';
      }

      //Wenn Sie offenes Interesse bekommen
      if (flag) {
        label = 'Open Interest Akquisition';

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


Beschreibung der Hyperparameter

--tradeInterval: Handelsintervall. Der kürzeste ist 60 Sekunden. --orderSize: Anzahl der Bestellungen --swapDays: Die Anzahl der Tage, die Sie offen halten möchten. Wenn Sie es überschreiten, lassen Sie es los. --infoThreshold: Zeilenbetrag für die Benachrichtigung. Line gibt bekannt, dass Sie mehr als den festgelegten Betrag verlieren oder gewinnen werden. --PsychParam: Parameter, die für den Algorithmus für psychologische Linien verwendet werden. --Zeitraum --Verhältnis --crossParam: Parameter, die für den Golden Cross / Dead Cross-Algorithmus verwendet werden.

Einführung Erklärung der Verzweigung und des Flusses

Es ist ein grober Verarbeitungsablauf.

--Start Handelsschleife Schleife um das festgelegte Transaktionsintervall.

--Überprüfen Sie den Betriebsstatus des Bitflyer-Austauschs Wenn dies nicht der Fall ist, gehen Sie zum Anfang der Schleife

--Wenn die Aufbewahrungsfrist kurz ist

Algorithmus: algo.js

Es fasst die Kauf- und Verkaufsalgorithmen zusammen.

const gauss = require('gauss');

module.exports = class Algo {

  constructor(records) {
    this.records = records;

    //Bewertungspunkte jedes Algorithmus
    //Steigendes Signal:+Abwärtssignal:-
    this.eva = {
      'psychoAlgo': 0,
      'crossAlgo': 0,
      'bollingerAlgo': 0
    };
  }

  psychoAlgo(range, ratio, list = this.records) {
    //Beurteilung von Kauf und Verkauf anhand des Verhältnisses positiver Linien

    let countHigh = 0
    //Zählen Sie die Anzahl der positiven Strahlen in einem beliebigen Zeitraum
    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) {
    //Kauf und Verkauf am Goldenen Toten Kreuz beurteilen

    //Erstellung mit gleitendem Durchschnitt
    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) {
    //Bollinger Band

    const prices = new gauss.Vector(list.slice(-period));
    //Verwenden Sie 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) {
    //Gewichtete und umfassende Transaktionsbeurteilung

    let totalEva = 0
    //Multiplizieren Sie die Bewertungspunkte mit ihren jeweiligen Gewichten und addieren Sie sie
    for (const [key, value] of Object.entries(this.eva)) {
      totalEva += value * weight[key];
    }

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

    return totalEva
  }


  initEva() {
    //Initialisieren Sie alle Bewertungspunkte
    Object.keys(this.eva).forEach(key => {
      this.eva[key] = 0;
    });
  }

}

Kombinierte Bewertung

tradeAlgo()

Eine Transaktionsentscheidung ist eine zusammengesetzte Entscheidung, die auf mehreren Algorithmen basiert. Enthält Bewertungspunkte für jeden Algorithmus. Jeder Algorithmus gibt abhängig von den Materialdaten und den Einstellparametern eine Bewertung von entweder -1 oder +1. Positive Zahl (+1) ist ein Aufwärtstrend Negative Zahl (-1) ist Abwärtstrend Multiplizieren Sie jeden Algorithmus mit den Bewertungspunkten und ihren Gewichten und addieren Sie sie schließlich alle, um die Gesamtbewertungspunkte zu berechnen.

Wenn der Absolutwert der gesamten Bewertungspunkte den Schwellenwert in app.js überschreitet, wird die Transaktion ausgeführt. Ob in der Kauf- oder Verkaufsposition gehandelt wird, wird je nach Situation von app.js festgelegt.

Informationen zum Hinzufügen eines Algorithmus

Wenn Sie in Zukunft einen neuen Algorithmus hinzufügen möchten, lesen Sie das folgende Verfahren.

--Algo Klasse --this.eva (Hinzufügen des gleichen Bewertungspunkts wie der Methodenname)

Bollinger Band

bollingerAlgo()

Das Bollinger-Band ist ein Beurteilungsalgorithmus, der gleitende Durchschnitte und Standardabweichungen verwendet. Grob gesagt ist die Fähigkeit, zum Durchschnitt zurückzukehren, umso größer, je größer der Absolutwert der Standardabweichung ist. Ich werde nicht im Detail darauf eingehen, aber diese Erklärung ist leicht zu verstehen. Kommentar zu Manex Securities

Verwendet 4 Variablen.

Extrahieren Sie den Zeitraum, den Sie beurteilen möchten, aus der Preisbewegungsliste. Als nächstes werden der obere Wert und der untere Wert der angegebenen Standardabweichung basierend auf der extrahierten Liste berechnet. Wenn der Preis außerhalb der berechneten oberen und unteren Standardabweichungsbänder liegt, wird schließlich ein Bewertungspunkt hinzugefügt.

Wenn der Preis niedriger als der niedrigere Wert ist Sein Preis ist niedriger als der Trend, so dass es leicht ist, zu steigen. Addiere +1 als Aufwärtstrend

Wenn der Preis höher als der obere Wert ist Sein Preis ist höher als der Trend, so dass es leicht abzulehnen ist. Addiere -1 als Abwärtstrend

Psychologische Linie

psychoAlgo()

Algorithmusbeurteilung unter Verwendung der Anlegerstimmung. Ein Algorithmus, der Preisschwankungen vorhersagt, indem er bestimmt, dass, wenn entweder Kauf oder Verkauf kontinuierlich voreingenommen sind, die Tendenz anhält oder das Gegenteil bald eintritt und viele Käufe und Verkäufe getätigt werden. Diese Seite ist leicht zu verstehen. Kommentar zu Manex Securities

Verwenden Sie 3 Variablen.

Eingrenzen auf die Preisliste für den festgelegten Zeitraum, Finden Sie die Anzahl der Preise heraus, die höher als der vorherige Preis sind, und das Verhältnis zum Gesamtpreis. Vergleichen Sie anhand des Verhältnisses, das als Verhältniswert bewertet wird, und fügen Sie Bewertungspunkte als Auf- und Abwärtstrend hinzu.

Goldenes Kreuz, totes Kreuz

crossAlgo()

Goldenes Kreuz, dass die Linie des langfristigen gleitenden Durchschnitts die Linie des kurzfristigen gleitenden Durchschnitts von unten nach oben durchdringt. Das Gegenteil ist totes Kreuz. Golden ist ein Zeichen für einen Aufwärtstrend und Dead Cross ist ein Zeichen für einen Abwärtstrend.

Verwenden Sie 3 Variablen.

Wie oben erläutert, wird ein Bewertungspunkt von +1 als Aufwärtstrend angegeben, wenn der kurzfristige gleitende Durchschnitt höher als der langfristige gleitende Durchschnitt ist. Wenn das Gegenteil der Fall ist, addieren Sie -1 zum Bewertungspunkt als totes Kreuz. Da ich mehr Trenddynamik hinzufügen wollte, habe ich den exponentiellen gleitenden Durchschnitt verwendet, der die neuesten Preisbewegungen hervorhebt.

OHLC erhalten: crypto.js

Verwenden Sie die Cryptowatch-API, um OHLC unmittelbar nach der Programmausführung abzurufen. OHLC steht für Open / High / Low / Close und Candlestick-Daten.

Leitungsbenachrichtigung: line.js

Line benachrichtigt Sie jedes Mal, wenn eine Transaktion beginnt und ein bestimmter Gewinn oder Verlust auftritt.

Diese Seite ist über Line Nnotify leicht zu verstehen. Versuchen Sie, LINE notify zu verwenden

Transaktionsdetails speichern: mongo.js

Erfassen Sie Verkaufstransaktionen mit MongoDB. Da der Transaktionsinhalt eine JSON-Erfassung von der Bitflyer-API und keine Standarddaten ist, habe ich MongoDB von NoSQL gewählt.

Es wird vom Docker-Container betrieben, die Daten bleiben jedoch in Volumes erhalten. Beim ersten Start des Docker-Containers wird ein Volume-Verzeichnis erstellt: container_data.

Sonstiges: utils.js

Andere Dienstprogrammfunktionen sind zusammengefasst.

Verbunden mit Apple Home

Das Programm auf AWS kann mit einem Fingertipp von iPhone und Apple Watch ein- und ausgeschaltet werden.

  1. Stellen Sie Docker auf der AWS EC2-Instanz bereit
  2. Erweitern Sie homebridge zu Ihrem Home Raspberry Pi und verknüpfen Sie die Ausführungs-Shell-Datei

Weitere Informationen finden Sie im Folgenden. Offizielle Homebridge

[Philips_Hue mit API verknüpft! ~ Raspberry Home Kit erstellen](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)

Zeigen Sie die Transaktions-DB auf IntelliJ an

Obwohl die Transaktionsdetails in MongoDB gespeichert sind, empfehlen wir, sie mithilfe der IDE anzuzeigen. Das Surfen direkt aus MongoDB ist aufgrund des JSON-Formats ziemlich schmerzhaft. Wenn es sich um eine IDE wie IntelliJ handelt, ist sie schön und leicht zu sehen. スクリーンショット 2020-09-20 10.18.45.png スクリーンショット 2020-09-20 10.11.57.png Informationen zur Einstellungsmethode von IntelliJ finden Sie im vorherigen Artikel. [Zusammenfassung der Einstellungsmethode zum Betreiben von AWS über 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)

wichtiger Punkt

Erster Docker-Start

Es dauert einige Zeit, bis MongoDB beschreibbar ist. Hiermit werden container_data für das Volume-Verzeichnis erstellt. Starten Sie es zum ersten Mal mit einem Rand. Wenn es nach einer Weile nicht mehr geschrieben wird, starten Sie Docker erneut. In meinem Fall funktioniert es ab dem zweiten Mal einwandfrei.

Docker Volume container_data verfügt über Root-Rechte

Die container_data des erstellten Volume-Verzeichnisses werden mit Root-Rechten erstellt. Wenn Sie es löschen möchten, fügen Sie sudo hinzu. Nach meiner versehentlichen Erfahrung war ich beim Neuerstellen von Docker ein wenig fehlerabhängig, ohne diese Verzeichnisberechtigung zu bemerken.

Am Ende

Genieße und verwirklichte niemals deinen Traum vom Arbeitsloseneinkommen: Skull :: Skull: Es ist hartnäckig, aber nicht immer rentabel. Gehen Sie also bitte Ihr eigenes Risiko ein. : point_up_tone3:

Möglicherweise Mein Algorithmus oder der einzige Parameter, den ich nicht mag, kann mit einem von jemandem hinzugefügten Algorithmus rentabel sein. .. ..

In einem solchen Fall sag es mir bitte heimlich: music_note: Dann, wenn Sie möchten, genießen Sie es bitte als Hobby.

Recommended Posts

[Automatischer Handel mit Bitcoin] Eine Geschichte über den Betrieb mit Docker unter AWS mit ON / OFF- und Line-Benachrichtigung mit 1 TAP von Apple Watch für unterwegs
Eine Geschichte über das Erreichen der League Of Legends-API mit JAVA
Eine Geschichte, die mit der Einführung von Web Apple Pay zu kämpfen hatte
Eine Geschichte, der ich mit der automatischen Starteinstellung von Tomcat 8 unter CentOS 8 zweimal verfallen war
Die Geschichte eines Game Launcher mit automatischer Ladefunktion [Java]
Ein kurzer Hinweis zur Verwendung von jshell mit dem offiziellen Docker-Image des JDK
[Jackson] Eine Geschichte über das Konvertieren des Rückgabewerts des Big Decimal-Typs mit einem benutzerdefinierten Serializer.