1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | var _ = require('underscore');
|
11 |
|
12 | var Util = require("util");
|
13 | var util = require('./util')
|
14 | var events = require("events");
|
15 | var log = require('./log');
|
16 | var async = require('async');
|
17 |
|
18 | var Manager = function(conf, checker) {
|
19 | this.exchangeSlug = conf.exchange.toLowerCase();
|
20 |
|
21 |
|
22 | var Exchange = require('./exchanges/' + this.exchangeSlug);
|
23 | this.exchange = new Exchange(conf);
|
24 |
|
25 |
|
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 |
|
64 | Util.inherits(Manager, events.EventEmitter);
|
65 |
|
66 | Manager.prototype.validCredentials = function() {
|
67 | return !this.checkExchange();
|
68 | }
|
69 |
|
70 | Manager.prototype.checkExchange = function() {
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
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 |
|
138 | Manager.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 |
|
146 | Manager.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 |
|
154 | Manager.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
|
163 | Manager.prototype.getFund = function(fund) {
|
164 | return _.find(this.portfolio, function(f) { return f.name === fund});
|
165 | }
|
166 | Manager.prototype.getBalance = function(fund) {
|
167 | return this.getFund(fund).amount;
|
168 | }
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 | Manager.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 |
|
190 | if(this.infinityOrderExchange)
|
191 | amount = 10000;
|
192 | else
|
193 | amount = this.getBalance(this.currency) / this.ticker.ask;
|
194 |
|
195 |
|
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 |
|
206 | if(this.infinityOrderExchange)
|
207 | amount = 10000;
|
208 | else
|
209 | amount = this.getBalance(this.asset);
|
210 |
|
211 |
|
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 |
|
227 | Manager.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 |
|
235 |
|
236 |
|
237 | Manager.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 |
|
249 |
|
250 |
|
251 | Manager.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 |
|
262 | Manager.prototype.noteOrder = function(order) {
|
263 | this.order = order;
|
264 |
|
265 |
|
266 | setTimeout(this.checkOrder, util.minToMs(0.5));
|
267 | }
|
268 |
|
269 |
|
270 |
|
271 | Manager.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 |
|
285 | module.exports = Manager;
|