Search

BTC 물량 기준 Arbitrage 분석

태그
서비스기획
프로그래밍
최종수정일
2019/07/19
기여도
100%
소속
(주)아이디어컴즈
프로젝트 규모
Medium
Behind Story
열심히 criteria를 짜고 적용시켜봤지만, 19년도 당시에도 통과하는 경우는 2~3일에 몇번 꼴이었으며 발생하는 수익도 거의 미미했음.
기여항목(기획&디자인)
-
포트폴리오 작성 일시
2022/09/01

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
복사