autocoin2
BitCoin's Forex automatic trading program. Using Bitflyer's API, we have automated cryptocurrency trading with node.js. How nice it would be if you could make more money when you were sleeping, in the bathroom, or 24 hours a day. .. : gem: I want to have fun and make money automatically! I tried to make it with such bad human motivation.
** Suddenly the conclusion is ... unfortunately not profitable: scream: Rather, it is decreasing. .. ** **
However, if you tune it, you may make a profit. (* We are not responsible for any damage. *) Please do so at your own risk!
** Code is available on Github **
--Supports both sell and buy positions --Weighted trading judgment by multiple algorithms --Saving trading history with MongoDB --Line notification of transaction start --If you exceed the threshold of the profit and loss amount, you will be notified by Line --A function to automatically release the position after a certain number of days have passed. --A function to suppress the acquisition of new positions 30 minutes before the date change --ON / OFF with one tap from iphone even when you are out with Apple Home cooperation --Regular human trading is possible in parallel while the program is running
.
├── 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
The entry point for this program. Buying and selling is repeated by turning the code in a loop process.
'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');
//Trading interval(Seconds)
const tradeInterval = 180;
//Trading volume
const orderSize = 0.01;
//swap days
const swapDays = 3;
//Price difference threshold for notifications
const infoThreshold = 100;
//PsychAlgo settings;Positive line count
const psychoParam = {
'range': 10,
'ratio': 0.7,
};
//crossAlgo setting value:Moving average width
const crossParam = {
'shortMA': 5,
'longMA': 30,
};
//Bollinger band settings
const BBOrder = {
//order
'period': 10,
'sigma': 1.7
};
const BBProfit = {
//Profitability
'period': 10,
'sigma': 1
};
const BBLossCut = {
//Loss cut:Judgment on a daily basis
'period': 10,
'sigma': 2.5
};
//Algorithm weighting:Set unused to 0
const algoWeight = {
// 'psychoAlgo': 0,
// 'crossAlgo': 0,
// 'bollingerAlgo': 1,
'psychoAlgo': 0.1,
'crossAlgo': 0.2,
'bollingerAlgo': 0.7,
};
//Threshold for transaction judgment
const algoThreshold = 0.3;
//Loss cut threshold
const lossCutThreshold = 0.5;
(async function () {
let sumProfit = 0;
let beforeProfit = null;
const nowTime = moment();
const collateral = await bitflyer.fetch2('getcollateral', 'private', 'GET');
//(Minutes)Record creation
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);
//Notify Line of automatic trading start
const strTime = nowTime.format('YYYY/MM/DD HH:mm:ss');
const message = `\n Automatic trading start\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');
//Check the operating status of the exchange
let health = await bitflyer.fetch2('getboardstate');
if (health.state !== 'RUNNING') {
//If abnormal, at the beginning of while
console.log('Operation status of the exchange:', health);
await utils.sleep(tradeInterval * 1000);
continue;
}
//Get current price
const ticker = await bitflyer.fetchTicker('FX_BTC_JPY');
const nowPrice = ticker.close;
//Update record
algo.records.push(nowPrice);
algo.records.shift()
//Initialize Param for algorithm
let bbRes = null;
let totalEva = 0;
algo.initEva();
//Common algorithm
let crossRes = algo.crossAlgo(crossParam.shortMA, crossParam.longMA);
let psychoRes = algo.psychoAlgo(psychoParam.range, psychoParam.ratio)
//Examine open interest
const jsonOpenI = await bitflyer.fetch2('getpositions', 'private', 'GET', {product_code: "FX_BTC_JPY"});
const openI = utils.chkOpenI(jsonOpenI)
//Common display
console.log('================');
console.log('time:', strTime);
console.log('nowPrice: ', nowPrice);
//If there is open interest
if (openI.side) {
//Common display of open interest
console.log('');
console.log('Open interest content');
console.log(openI);
let diffDays = nowTime.diff(openI.open_date, 'days');
//If the swap days are exceeded
if (diffDays >= swapDays) {
//Return open interest to 0
label = 'Reset open interest because swap days have been exceeded'
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 the number of days is not exceeded
//If you are making a profit
if (openI.pnl > 0) {
label = 'Profitability'
bbRes = algo.bollingerAlgo(BBProfit.period, BBProfit.sigma, openI.price);
totalEva = algo.tradeAlgo(algoWeight)
//There is a downward signal on the open interest
if (openI.side === 'BUY' && totalEva < -algoThreshold) {
await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
sumProfit += openI.pnl;
flag = 'SELL';
//There is a rising signal on open interest
} else if (openI.side === 'SELL' && totalEva > algoThreshold) {
await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
sumProfit += openI.pnl;
flag = 'BUY';
}
} else {
//If you are losing
label = 'Loss cut';
//Algorithm judgment on a daily basis
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)
//Even though I'm losing, there are signs that a big trend is going down with buying
if (openI.side === 'BUY' && totalEva < -lossCutThreshold) {
await bitflyer.createMarketSellOrder('FX_BTC_JPY', openI.size);
sumProfit += openI.pnl;
flag = 'SELL';
//Even though I'm losing, I have a sell and a big trend is rising
} else if (openI.side === 'SELL' && totalEva > lossCutThreshold) {
await bitflyer.createMarketBuyOrder('FX_BTC_JPY', openI.size);
sumProfit += openI.pnl;
flag = 'BUY';
}
}
}
//If you settle the open interest
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 notification(If the threshold is exceeded)
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 {
//Alert initialization
beforeProfit = sumProfit;
}
} else {
//If you do not have open interest
//Swap point support 23:30-0:00 Do not order
const limitDay = moment().hours(23).minutes(30).seconds(0)
if (nowTime.isSameOrAfter(limitDay)) {
console.log(' ');
console.log('Supporting swap points_23:30-0:00');
//Move to the beginning while not accepting orders
await utils.sleep(tradeInterval * 1000);
continue;
}
//Use Bollinger to order
bbRes = algo.bollingerAlgo(BBOrder.period, BBOrder.sigma);
totalEva = algo.tradeAlgo(algoWeight)
if (totalEva > algoThreshold) {
//Open a position with [Buy]
await bitflyer.createMarketBuyOrder('FX_BTC_JPY', orderSize);
flag = 'BUY';
} else if (totalEva < -algoThreshold) {
//Open with [Sell]
await bitflyer.createMarketSellOrder('FX_BTC_JPY', orderSize);
flag = 'SELL';
}
//If you get open interest
if (flag) {
label = 'Open interest acquisition';
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: Trade interval. The shortest is 60 seconds. --orderSize: Number of orders --swapDays: The number of days you want to keep open interest. If you exceed it, let it go. --infoThreshold: Line Amount range for notification. Line will announce that you will lose or gain more than the set amount. --Psychparam: Parameters used for the psychological line algorithm. --Period --Ratio --crossParam: Parameters used for the Golden Cross / Dead Cross algorithm. --Short-term moving average period --Long-term moving average period --BBOrder / BBProfit / BBLossCut: Parameters used for the Bollinger Bands algorithm. It is divided because the judgment material is different for each open interest acquisition / profit settlement / loss cut. --Judgment period
It is a rough processing flow.
--Start trading Obtain the transaction details before code execution from cryptowatch to use as a judgment material. Notify that "automatic trading has started" on Line
--Start a trading loop Loop around the set transaction interval.
--Check the operating status of the bitflyer exchange If abnormal, move to the beginning of the loop
--Get the current bitcoin price
--Common algorithm Evaluate cross functions and psychological functions
--Obtain the contents of the open position (position) you are holding.
--If you are holding open interest, check the number of days you are holding open interest. If the retention days are longer than the specified number of days, let go of the open interest (Swap gold and to avoid being salted.)
--When the retention period is short ――If you are profitable, buy and sell based on position and algorithm judgment
--If there is a loss, loss cut by algorithm judgment with daily material The reason why the loss cut is used on a daily basis is that the index is too fluid for the minute bar and it is necessary to make a judgment on a big trend. In fact, I used to use the minute leg in the old days, but it was easy to accumulate small losses as well as losing the gain chance due to being swayed by a fairly small blur. It is possible to change the minute and hourly bars, so it may be a good idea to adjust them.
--If a certain amount of profit or loss occurs, you will be notified by Line.
--If you do not have open interest
--If it is 30 minutes just before the date change, loop without trading. To avoid getting swap money early after getting open interest.
--If it is not just before the date, the open position is acquired by the algorithm judgment.
It summarizes the buying and selling algorithms.
const gauss = require('gauss');
module.exports = class Algo {
constructor(records) {
this.records = records;
//Evaluation points of each algorithm
//Rising signal:+Down signal:-
this.eva = {
'psychoAlgo': 0,
'crossAlgo': 0,
'bollingerAlgo': 0
};
}
psychoAlgo(range, ratio, list = this.records) {
//Judging buying and selling by the ratio of positive lines
let countHigh = 0
//Count the number of positive rays in any period
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) {
//Judging buying and selling at the Golden Dead Cross
//Moving average creation
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));
//Use 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) {
//Weighted and comprehensive transaction decisions
let totalEva = 0
//Multiply the evaluation points by their respective weights and add them together
for (const [key, value] of Object.entries(this.eva)) {
totalEva += value * weight[key];
}
totalEva = Math.round(totalEva * 100) / 100
return totalEva
}
initEva() {
//Initialize all evaluation points
Object.keys(this.eva).forEach(key => {
this.eva[key] = 0;
});
}
}
tradeAlgo()
A transaction decision is a compound decision based on multiple algorithms. Each algorithm holds an evaluation point. Each algorithm gives an evaluation of either -1 or +1 depending on the material data and setting parameters. Positive number (+1) is an uptrend Negative number (-1) is downtrend Multiply each algorithm by the evaluation points and their weights, and finally add them all together to calculate the total evaluation points.
Execute the transaction if the absolute value of the total evaluation points exceeds the threshold value in app.js. Whether to trade in a buy or sell position is determined by app.js depending on the situation.
If you want to add a new algorithm in the future, refer to the procedure below.
--Algo class --this.eva (adding the same evaluation point as the method name) --Add algorithm as method
bollingerAlgo()
Bollinger Bands is a judgment algorithm that uses a moving average and standard deviation. Roughly speaking, the larger the absolute value of the standard deviation, the stronger the ability to return to the mean. I won't touch on it in detail, but this explanation is easy to understand. Explanation of Monex, Inc.
Uses 4 variables.
--Judgment period --Standard deviation to threshold --Price you want to judge --Price movement list
Extract the period you want to judge from the price movement list. Next, the upper value and lower value of the specified standard deviation are calculated based on the extracted list. Finally, if the price is out of the calculated upper and lower standard deviation bands, an evaluation point is added.
If the price is lower than the lower value Its price is lower than the trend, so it is easy to start rising. Add +1 as an uptrend
When the price is higher than the upper value Its price is higher than the trend, so it is easy to turn down. Add -1 as a downtrend
psychoAlgo()
Algorithm judgment using investor psychology. An algorithm that predicts price fluctuations by determining that if either buying or selling is biased in succession, the tendency will continue, or the opposite will soon occur, and many trades will be made. This page is easy to understand. Explanation of Monex, Inc.
Uses 3 variables.
--Judgment range --Judgment ratio --Price list
Narrow down to the price list for the set period, Find out the number of prices that are higher than the previous price and the ratio of the total price. Compare using the ratio judged as the ratio value, and add evaluation points as an uptrend and a downtrend.
crossAlgo()
Golden cross that the long-term moving average penetrates the short-term moving average from bottom to top. The opposite is dead cross. Golden is a sign of an uptrend and dead cross is a sign of a downtrend.
Uses 3 variables.
--Short period --Long term --Price list
As explained above, if the short-term moving average is higher than the long-term moving average, an evaluation point of +1 is given as an uptrend. If the opposite is true, add -1 to the evaluation point as a dead cross. Since I wanted to add more trend momentum, I used the Exponential Moving Average, which emphasizes the latest price movements.
Use cryptowatch API to get OHLC immediately after program execution. OHLC stands for open / high / low / close and candlestick data.
Line will notify you every time a transaction starts and a certain amount of profit or loss occurs.
This page is easy to understand about Line Nnotify. Try using LINE notify
Record sales transactions with MongoDB. Since the transaction content is json acquisition from bitflyer API and not standard data, I chose MongoDB of NoSQL.
It is operated by Docker container, but the data is persisted by volumes. The first Docker container launch creates a volume directory: container_data.
It summarizes other utility functions.
The program on AWS can be turned ON / OFF with one tap from iphone and Apple Watch.
Please refer to the following for details. Official homebridge
[Philips_Hue is linked with API! ~ Make Raspberry Pi HomeKit](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)
Although it is the transaction content saved in MongoDB, we recommend using the IDE to view it. Browsing directly from MongoDB is quite painful because of the json format. If it is an IDE such as IntelliJ, it will be nice and easy to see. Please refer to the past article for the setting method of IntelliJ. [Summary of setting method to operate AWS from 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)
It takes time for MongoDB to become writable. This is to create container_data for the volume directory. Start it for the first time with a margin, and if it is not written after a while, restart Docker again. In my case, it works fine from the second time onwards.
The container_data of the created volume directory is created with root privileges. If you want to delete it, add sudo. In my inadvertent experience, when rebuilding Docker, I didn't notice this directory permission and got an error and got a little addicted.
Enjoy and never realize your unearned income dream: skull :: skull: It's persistent, but it's not always profitable, so please take your own risk. : point_up_tone3:
Possibly My algorithm, or the only parameter I don't like, may be profitable with an algorithm added by someone. .. ..
In such a case, please tell me secretly: musical_note: Then, if you like, please enjoy it as a hobby.
Recommended Posts