UNPKG

8.19 kBJavaScriptView Raw
1/*
2
3 The portfolio manager is responsible for making sure that
4 all decisions are turned into orders and make sure these orders
5 get executed. Besides the orders the manager also keeps track of
6 the client's portfolio.
7
8*/
9
10var _ = require('underscore');
11// var EventEmitter = require('events').EventEmitter;
12var Util = require("util");
13var util = require('./util')
14var events = require("events");
15var log = require('./log');
16var async = require('async');
17
18var Manager = function(conf, checker) {
19 this.exchangeSlug = conf.exchange.toLowerCase();
20
21 // create an exchange
22 var Exchange = require('./exchanges/' + this.exchangeSlug);
23 this.exchange = new Exchange(conf);
24
25 // state
26 this.conf = conf;
27 this.portfolio = {};
28 this.fee;
29 this.order;
30 this.action;
31
32 this.currency = conf.currency || 'USD';
33 this.asset = conf.asset || 'BTC';
34
35 var error = this.checkExchange();
36 if(error && !checker)
37 throw error;
38
39 _.bindAll(this);
40
41 if(checker)
42 return;
43
44 log.debug('getting balance & fee from', this.exchange.name);
45 var prepare = function() {
46 this.starting = false;
47
48 log.info('trading at', this.exchange.name, 'ACTIVE');
49 log.info(this.exchange.name, 'trading fee will be:', this.fee * 100 + '%');
50 log.info('current', this.exchange.name, 'portfolio:');
51 _.each(this.portfolio, function(fund) {
52 log.info('\t', fund.name + ':', fund.amount);
53 });
54 this.emit('ready');
55 };
56
57 async.series([
58 this.setPortfolio,
59 this.setFee
60 ], _.bind(prepare, this));
61}
62
63// teach our Manager events
64Util.inherits(Manager, events.EventEmitter);
65
66Manager.prototype.validCredentials = function() {
67 return !this.checkExchange();
68}
69
70Manager.prototype.checkExchange = function() {
71 // what kind of exchange are we dealing with?
72 //
73 // name: slug of exchange
74 // direct: does this exchange support MKT orders?
75 // infinityOrder: is this an exchange that supports infinity
76 // orders? (which means that it will accept orders bigger then
77 // the current balance and order at the full balance instead)
78 // currencies: all the currencies supported by the exchange
79 // implementation in gekko.
80 // assets: all the assets supported by the exchange implementation
81 // in gekko.
82 var exchanges = [
83 {
84 name: 'mtgox',
85 direct: true,
86 infinityOrder: true,
87 currencies: [
88 'USD', 'EUR', 'GBP', 'AUD', 'CAD', 'CHF', 'CNY',
89 'DKK', 'HKD', 'PLN', 'RUB', 'SGD', 'THB'
90 ],
91 assets: ['BTC'],
92 requires: ['key', 'secret'],
93 minimalOrder: { amount: 0.01, unit: 'asset' }
94 },
95 {
96 name: 'btce',
97 direct: false,
98 infinityOrder: false,
99 currencies: ['USD', 'RUR', 'EUR'],
100 assets: ['BTC'],
101 requires: ['key', 'secret'],
102 minimalOrder: { amount: 0.01, unit: 'asset' }
103 },
104 {
105 name: 'bitstamp',
106 direct: false,
107 infinityOrder: false,
108 currencies: ['USD'],
109 assets: ['BTC'],
110 requires: ['user', 'password'],
111 minimalOrder: { amount: 1, unit: 'currency' }
112 }
113 ];
114 var exchange = _.find(exchanges, function(e) { return e.name === this.exchangeSlug }, this);
115 if(!exchange)
116 return 'Gekko does not support the exchange ' + this.exchangeSlug;
117
118 this.directExchange = exchange.direct;
119 this.infinityOrderExchange = exchange.infinityOrder;
120 if(_.indexOf(exchange.currencies, this.currency) === -1)
121 return 'Gekko does not support the currency ' + this.currency + ' at ' + this.exchange.name;
122
123 if(_.indexOf(exchange.assets, this.asset) === -1)
124 return 'Gekko does not support the asset ' + this.asset + ' at ' + this.exchange.name;
125
126 var ret;
127 _.each(exchange.requires, function(req) {
128 if(!this.conf[req])
129 ret = this.exchange.name + ' requires "' + req + '" to be set in the config';
130 }, this);
131
132 this.minimalOrder = exchange.minimalOrder;
133
134 return ret;
135
136}
137
138Manager.prototype.setPortfolio = function(callback) {
139 var set = function(err, portfolio) {
140 this.portfolio = portfolio;
141 callback();
142 };
143 this.exchange.getPortfolio(_.bind(set, this));
144}
145
146Manager.prototype.setFee = function(callback) {
147 var set = function(err, fee) {
148 this.fee = fee;
149 callback();
150 };
151 this.exchange.getFee(_.bind(set, this));
152}
153
154Manager.prototype.setTicker = function(callback) {
155 var set = function(err, ticker) {
156 this.ticker = ticker;
157 callback();
158 }
159 this.exchange.getTicker(_.bind(set, this));
160}
161
162// return the [fund] based on the data we have in memory
163Manager.prototype.getFund = function(fund) {
164 return _.find(this.portfolio, function(f) { return f.name === fund});
165}
166Manager.prototype.getBalance = function(fund) {
167 return this.getFund(fund).amount;
168}
169
170// This function makes sure order get to the exchange
171// and initiates follow up to make sure the orders will
172// get executed. This is the backbone of the portfolio
173// manager.
174//
175// How this is done depends on a couple of things:
176//
177// is this a directExchange? (does it support MKT orders)
178// is this a infinityOrderExchange (does it support order
179// requests bigger then the current balance?)
180Manager.prototype.trade = function(what) {
181 if(what !== 'BUY' && what !== 'SELL')
182 return;
183
184 var act = function() {
185 var amount, price;
186
187 if(what === 'BUY') {
188
189 // do we need to specify the amount we want to buy?
190 if(this.infinityOrderExchange)
191 amount = 10000;
192 else
193 amount = this.getBalance(this.currency) / this.ticker.ask;
194
195 // can we just create a MKT order?
196 if(this.directExchange)
197 price = false;
198 else
199 price = this.ticker.ask;
200
201 this.buy(amount, price);
202
203 } else if(what === 'SELL') {
204
205 // do we need to specify the amount we want to sell?
206 if(this.infinityOrderExchange)
207 amount = 10000;
208 else
209 amount = this.getBalance(this.asset);
210
211 // can we just create a MKT order?
212 if(this.directExchange)
213 price = false;
214 else
215 price = this.ticker.bid;
216
217 this.sell(amount, price);
218 }
219 };
220 async.series([
221 this.setTicker,
222 this.setPortfolio
223 ], _.bind(act, this));
224
225}
226
227Manager.prototype.getMinimum = function(price) {
228 if(this.minimalOrder.unit === 'currency')
229 return minimum = this.minimalOrder.amount / price;
230 else
231 return minimum = this.minimalOrder.amount;
232}
233
234// first do a quick check to see whether we can buy
235// the asset, if so BUY and keep track of the order
236// (amount is in asset quantity)
237Manager.prototype.buy = function(amount, price) {
238 var currency = this.getFund(this.currency);
239 var minimum = this.getMinimum(price);
240 if(amount > minimum) {
241 log.info('attempting to BUY', this.asset, 'at', this.exchange.name);
242 this.exchange.buy(amount, price, this.noteOrder);
243 this.action = 'BUY';
244 } else
245 log.info('wanted to buy but insufficient', this.currency, '(' + amount * price + ') at', this.exchange.name);
246}
247
248// first do a quick check to see whether we can sell
249// the asset, if so SELL and keep track of the order
250// (amount is in asset quantity)
251Manager.prototype.sell = function(amount, price) {
252 var asset = this.getFund(this.asset);
253 var minimum = this.getMinimum(price);
254 if(amount > minimum) {
255 log.info('attempting to SELL', this.asset, 'at', this.exchange.name);
256 this.exchange.sell(amount, price, this.noteOrder);
257 this.action = 'SELL';
258 } else
259 log.info('wanted to sell but insufficient', this.asset, '(' + amount + ') at', this.exchange.name);
260}
261
262Manager.prototype.noteOrder = function(order) {
263 this.order = order;
264 // if after 30 seconds the order is still there
265 // we cancel and calculate & make a new one
266 setTimeout(this.checkOrder, util.minToMs(0.5));
267}
268
269// check wether the order got fully filled
270// if it is not: cancel & instantiate a new order
271Manager.prototype.checkOrder = function() {
272 var finish = function(err, filled) {
273 if(!filled) {
274 log.info(this.action, 'order was not (fully) filled, canceling and creating new order');
275 this.exchange.cancelOrder(this.order);
276 return this.trade(this.action);
277 }
278
279 log.info(this.action, 'was succesfull');
280 }
281
282 this.exchange.checkOrder(this.order, _.bind(finish, this));
283}
284
285module.exports = Manager;