forked from izifortune/bitcoin-arbitrage
-
Notifications
You must be signed in to change notification settings - Fork 0
/
handler.js
157 lines (130 loc) · 4.77 KB
/
handler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
const AWS = require('aws-sdk');
const Bitstamp = require('bitstamp');
const Kraken = require('kraken-api');
const Winston = require('winston');
const logger = Winston.createLogger({
transports: [new Winston.transports.Console()],
});
AWS.config.update({ region: process.env.AWS_REGION });
const ssmClient = new AWS.SSM();
const snsClient = new AWS.SNS();
let KrakenClient = null;
let BitstampClient = null;
class OrderSubmissionError extends Error {
constructor(...args) {
super(...args);
Error.captureStackTrace(this, this.constructor);
}
}
module.exports.watch = async (event, context, callback) => {
logger.info(event);
await loadConfigs();
callback(null, await tradePairs(event));
};
export const tradePairs = async (event) => {
const {
amount, pair, spreadThreshold, TargetArn,
} = event;
const asset = pair.base + pair.quote;
const krakenPrice = getKrakenPrice(asset);
const bitstampPrice = getBitstampPrice(asset);
const spread = calculateSpread(krakenPrice, bitstampPrice);
logger.info({
message: 'Pulled Ticker info',
asset,
krakenPrice,
bitstampPrice,
spread,
spreadThreshold,
});
if (spread < spreadThreshold) {
return { message: 'Spred below threshold', spread, spreadThreshold };
} else if (event.dryRun) {
return { message: 'Skipping trade due to dry run' };
}
let [buyExchange, sellExchange] = ['bitstamp', 'kraken'];
let [buyMethod, sellMethod] = [placeBitsampOrder, placeKrakenOrder];
let tradeVolume = (amount / bitstampPrice).toFixed(6);
if (krakenPrice < bitstampPrice) {
[buyExchange, sellExchange] = ['kraken', 'bitstamp'];
[buyMethod, sellMethod] = [placeKrakenOrder, placeBitsampOrder];
tradeVolume = (amount / krakenPrice).toFixed(6);
}
const coerceOrderId = resp => resp.id || resp.txid;
const buyResponse = buyMethod(asset, 'buy', tradeVolume);
const buyOrderId = coerceOrderId(buyResponse);
if (!buyOrderId) {
throw new OrderSubmissionError({ buyResponse, buyExchange });
}
const sellResponse = sellMethod(asset, 'sell', tradeVolume);
const sellOrderId = coerceOrderId(sellResponse);
if (!sellOrderId) {
throw new OrderSubmissionError({ sellResponse, sellExchange });
}
const message = `successfully bought ${amount} and sold ${tradeVolume} of ${pair}`;
logger.info(message);
return snsClient.publish({ MessageStructure: 'string', Message: message, TargetArn }).promise();
};
export const loadConfigs = async () => {
const keyspace = '/prod/bitcoin-arbitrage';
if (!BitstampClient) {
const bitstampParams = await ssmClient.getParameter({ Name: `${keyspace}/bitstamp_keys` })
.promise().then(data => data.Parameter.Value.split(','));
BitstampClient = new Bitstamp(...bitstampParams);
}
if (!KrakenClient) {
const krakenParams = await ssmClient.getParameter({ Name: `${keyspace}/kraken_keys` })
.promise().then(data => data.Parameter.Value.split(','));
KrakenClient = new Kraken(...krakenParams);
}
};
export const placeBitsampOrder = (pair, side, volume) => new Promise((resolve, reject) => {
const handler = (err, resp) => {
if (err) { return reject(err); }
logger.info(resp);
return resolve(resp);
};
if (side === 'buy') {
return BitstampClient.buyMarket(pair.toLowerCase(), volume, handler);
} else if (side === 'sell') {
return BitstampClient.sellMarket(pair.toLowerCase(), volume, handler);
}
return reject(new OrderSubmissionError({ side }));
});
export const getBitstampPrice = async pair => new Promise((resolve, reject) => BitstampClient.ticker(pair.toLowerCase(), (err, resp) => {
if (err) { return reject(err); }
logger.info(resp);
return resolve(resp.ask);
}));
export const placeKrakenOrder = async (pair, side, volume) => {
const symbol = formatToKrakenPair(pair);
const params = {
pair: symbol, ordertype: 'market', type: side, volume,
};
const resp = await KrakenClient.api('AddOrder', params).result;
logger.info(resp);
return resp;
};
export const getKrakenPrice = async (pair) => {
const symbol = formatToKrakenPair(pair);
const resp = await KrakenClient.api('Ticker', { pair: symbol });
logger.info(resp);
return resp.result[symbol].a[0];
};
// kraken uses a special syntax for ticker pair
export const formatToKrakenPair = (pair) => {
const krakenSymbols = pair.slice(3).map((symbol) => {
const fiatCurrencies = ['USD', 'EUR', 'JPY', 'GBP', 'CAD'];
switch (symbol) {
case fiatCurrencies.includes(symbol): return `Z${symbol}`;
case 'BTC': return 'XXBT';
default: return `X${symbol}`;
}
});
return krakenSymbols.join('');
};
// returned in percentage points
export const calculateSpread = (krakenPrice, bitstampPrice) => {
const [lower, higher] = [krakenPrice, bitstampPrice].sort();
return (100 - ((lower / higher) * 100)).toFixed(2);
};