Description
지정된 2개 이상의 암호화폐 거래소에서, 무위험으로 BTC 물량을 늘려가는 로직을 작성. 각 거래소의 수수료를 고려하여 언제 거래를 진행해야 하는지, 그리고 이때 수익은 얼마나 발생하는지를 수학적으로 분석했다. 이후 Node.js Express 서버와 암호화폐 라이브러리 ccxt를 활용하여 실제 코드로 작성, 거래 criteria를 만족하는 경우가 얼마나 빈번하게 발생하는지 실험을 진행했다.
Documentation
Drafts
Codes
"use strict";
const ccxt = require("ccxt");
const async = require("async");
var moment = require("moment"); //현재 시간 기록용 라이브러리
var fs = require("fs");
var file = "successLog\\successLog.txt"; //file to log successes
// Crypto order : BTC, ETH, EOS, XRP, BCH
// Details for each exchange
const UPBIT = new ccxt.upbit({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const BINANCE = new ccxt.binance({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const BITFINEX = new ccxt.bitfinex({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const KRAKEN = new ccxt.kraken({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const BITLISH = new ccxt.bitlish({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const OKEX = new ccxt.okex({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const HITBTC = new ccxt.hitbtc({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const BITTREX = new ccxt.bittrex({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const POLONIEX = new ccxt.poloniex({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const COSS = new ccxt.coss({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const KUCOIN = new ccxt.kucoin({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const LBANK = new ccxt.lbank({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const BITFOREX = new ccxt.bitforex({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const BITZ = new ccxt.bitz({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
const BIBOX = new ccxt.bibox({
enableRateLimit: true,
btcWithdrawlFee: 0.0005,
altWithdrawlFee: [0.01, 0, 1, 0.001, 0.002],
initBtcBalance: 1,
initAltBalance: [50, 2300, 32000, 31, 100]
});
// Exchanges and markets we use
const EXCHANGES = [
UPBIT, //0
BINANCE, //1
POLONIEX, //2
//KRAKEN, //3, API 불안정? 오류 잡아야함
//BITLISH, //4, 유동성이 너무 없음, ban
//OKEX, //5
HITBTC, //6
BITTREX, //7
//COSS,
KUCOIN
//LBANK,
//BITFOREX, 짱깨
//BITZ
//BIBOX 짱깨
];
const MARKETS = ["ETH/BTC", "EOS/BTC", "XRP/BTC", "BCH/BTC", "DASH/BTC"];
// 양 거래소 및 ALT 설정
var ALT_INDEX = 1; // 0: ETH, 1: EOS, 2: XRP, 3: BCH, 4: DASH
// 성공 로그 (terminal)
var successLog = [];
// Environment Variables Setting for algorithm
const xMin = 1; //minimum order amount of ETH
const xMax = 10; //maximum order amount of ETH
const btcInit = 1; //initial amount of BTC
const altInit = [50, 2300, 32000, 31, 100]; //initial amount of ETH
const safetyFactor = 0.7; //safety factor for preventing undetermined order
const rateLimit = 0.3; //limit for rate difference between initial eth amount and current eth amount
const decreaseLimit = 0.3; //limit for decreasement of ALT price in fiat market
const undeterminedLimit = 5; //limit for sum of eth from undetermined orders
const rateLimitBal = 0.3; //limit for rate between whole BTC value and ETH value
const orderDiff = 0.01; //Difference between buy order amount and sell order amount when balancing process is ON
const tickerTimeInterval = 1500; //time interval for every ticker
// 전역변수들, temporarily
var count = 0;
var revenue = 0;
var fetchcount = 0;
var ORDERBOOKS = [];
var exStatus = []; //0 or undefined면 거래가능, 1이면 직전거래에서 매도, 2면 매수
var lastPrice = [];
const fetchFromAllEx = () => {
fetchcount = fetchcount + 1;
//console.log(`Fetch #${fetchcount}`);
EXCHANGES.forEach(async (exchange, index) => {
ORDERBOOKS[index] = await exchange.fetchOrderBook(MARKETS[ALT_INDEX]);
//console.log(`orderbook ${index} loaded`);
});
//callback(null, "fetch complete");
};
const tradingCriteriaForAll = () => {
//console.log("Trading criteria check");
ORDERBOOKS.forEach((orderbook, index) => {
let temp = index;
let temporderbook = orderbook;
ORDERBOOKS.forEach((orderbook, index) => {
if (index > temp) {
let fbp1 = temporderbook.bids[0][0]; //first bid price
let fba1 = temporderbook.bids[0][1]; //first bid amount
let fap1 = temporderbook.asks[0][0]; //first ask price
let faa1 = temporderbook.asks[0][1]; //first ask amount
let fbp2 = orderbook.bids[0][0];
let fba2 = orderbook.bids[0][1];
let fap2 = orderbook.asks[0][0];
let faa2 = orderbook.asks[0][1];
let altWithdrawlFee1 = EXCHANGES[temp].altWithdrawlFee[ALT_INDEX];
let altWithdrawlFee2 = EXCHANGES[index].altWithdrawlFee[ALT_INDEX];
let btcWithdrawlFee1 = EXCHANGES[temp].btcWithdrawlFee;
let btcWithdrawlFee2 = EXCHANGES[index].btcWithdrawlFee;
let tradingFee1 = EXCHANGES[temp].fees.trading.taker;
let tradingFee2 = EXCHANGES[index].fees.trading.taker;
console.log(
`${EXCHANGES[temp].name}, ${fbp1}, ${fap1} vs ${EXCHANGES[index].name}, ${fbp2}, ${fap2}`
);
if (
fbp1 >
(fap2 *
(altInit[ALT_INDEX] * rateLimit + altWithdrawlFee2) *
(1 + tradingFee2) +
btcWithdrawlFee1) /
(altInit[ALT_INDEX] * rateLimit * (1 - tradingFee1)) &&
fbp1 != lastMatch[0]
) {
//status = 1;
//console.log("Trading criteria success");
count = count + 1;
revenue =
revenue +
fbp1 * Math.min(fba1, faa2) * (1 - tradingFee1) -
fap2 * Math.min(fba1, faa2) * (1 + tradingFee2);
fs.appendFile(
file,
`${EXCHANGES[temp].name} vs ${
EXCHANGES[index].name
}, ${moment().format(
"YYYY-MM-DD HH:mm:ss"
)}\n${fbp1} vs ${fap2}\nCommon Amount : ${Math.min(
fba1,
faa2
)}\nBTC used : ${fbp1 *
Math.min(fba1, faa2)} BTC\nRevenue addition : ${fbp1 *
Math.min(fba1, faa2) *
(1 - tradingFee1) -
fap2 *
Math.min(fba1, faa2) *
(1 +
tradingFee2)}\nTotal revenue : ${revenue}\n----------------------------------\n`,
err => {
if (err) throw err;
console.log("A new success was appended to file!");
}
);
successLog.push(
`${EXCHANGES[temp].name} vs ${
EXCHANGES[index].name
}, ${moment().format("YYYY-MM-DD HH:mm:ss")}, ${fbp1 *
Math.min(fba1, faa2) *
(1 - tradingFee1) -
fap2 * Math.min(fba1, faa2) * (1 + tradingFee2)}`
);
} else if (
fbp2 >
(fap1 *
(altInit[ALT_INDEX] * rateLimit + altWithdrawlFee1) *
(1 + tradingFee1) +
btcWithdrawlFee2) /
(altInit[ALT_INDEX] * rateLimit * (1 - tradingFee2))
) {
//status = 2;
//console.log("Trading criteria success");
count = count + 1;
revenue =
revenue +
fbp2 * Math.min(fba2, faa1) * (1 - tradingFee2) -
fap1 * Math.min(fba2, faa1) * (1 + tradingFee1);
fs.appendFile(
file,
`${EXCHANGES[temp].name} vs ${
EXCHANGES[index].name
}, ${moment().format(
"YYYY-MM-DD HH:mm:ss"
)}\n${fap1} vs ${fbp2}\nCommon Amount : ${Math.min(
fba2,
faa1
)}\nBTC used : ${fbp1 *
Math.min(fba1, faa2)} BTC\nRevenue addition : ${fbp2 *
Math.min(fba2, faa1) *
(1 - tradingFee2) -
fap1 *
Math.min(fba2, faa1) *
(1 +
tradingFee1)}\nTotal revenue : ${revenue}\n----------------------------------\n`,
err => {
if (err) throw err;
console.log("A new success was appended to file!");
}
);
successLog.push(
`${EXCHANGES[temp].name} vs ${
EXCHANGES[index].name
}, ${moment().format("YYYY-MM-DD HH:mm:ss")}, ${fbp2 *
Math.min(fba2, faa1) *
(1 - tradingFee2) -
fap1 * Math.min(fba2, faa1) * (1 + tradingFee1)}`
);
} else {
//status = 0;
//console.log("Trading criteria failed");
}
}
});
});
console.log(
"진입조건만족 횟수 : " +
count +
", 현재 시간 " +
moment().format("YYYY-MM-DD HH:mm:ss")
);
console.log(`총 수익 : ${revenue} BTC`);
console.log("----------------성공목록-----------------");
console.log(successLog);
console.log("----------------------------------------");
//callback(null, "criteria complete");
};
//두 호가창에서 모의 거래를 실시한다.
//const tradingProcess = () => {
// if (statusstatus === 1) {
// // 1번 거래소의 매수 제 1호가(fbp1,아래쪽)가 2번 거래소의 매도 제 1호가(fap2,윗쪽)보다 높을 때
// // 그럼 우리는 이제 1번 거래소에서는 매도, 2번 거래소에서는 매수를 하면 되겠죠? 구독과 좋아요 알림설정까지!
// // fbp1에 물려있는 물량 fba1, fap2에 물려있는 물량 faa2
// let x = 3;
// } else if (status === 2) {
// // 2번 거래소의 매수 제 1호가(fbp2,아래쪽)가 1번 거래소의 매도 제 1호가(fap1,윗쪽)보다 높을 때
// // 그럼 우리는 이제 2번 거래소에서는 매도, 1번 거래소에서는 매수를 하면 되겠죠?
// // fbp2에 물려있는 물량 fba2, fap1에 물려있는 물량 faa1
// }
// console.log(`상태 : ${status}`);
//};
/* ------------------------------------------------------------------------------------------------------- */
/* ------------------------------------이 밑으로는 실행 커맨드---------------------------------------------- */
/* ------------------------------------------------------------------------------------------------------- */
setInterval(fetchFromAllEx, 1010);
setInterval(tradingCriteriaForAll, 1010);
JavaScript
복사