aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIsmaël Bouya <ismael.bouya@normalesup.org>2018-04-30 14:21:41 +0200
committerIsmaël Bouya <ismael.bouya@normalesup.org>2018-04-30 14:21:41 +0200
commit52ea19aa73348a523b3b884e2a7fb749b2bf4f19 (patch)
tree1325863772f475fa00953a34489ab94fbe7f828a
parentfcb18fead0e92ddc075416e11934b62afa3e2ba3 (diff)
downloadTrader-52ea19aa73348a523b3b884e2a7fb749b2bf4f19.tar.gz
Trader-52ea19aa73348a523b3b884e2a7fb749b2bf4f19.tar.zst
Trader-52ea19aa73348a523b3b884e2a7fb749b2bf4f19.zip
Fix price imprecision due to floats
-rw-r--r--ccxt_wrapper.py75
-rw-r--r--main.py2
-rw-r--r--tests/test_ccxt_wrapper.py138
-rw-r--r--tests/test_main.py4
4 files changed, 143 insertions, 76 deletions
diff --git a/ccxt_wrapper.py b/ccxt_wrapper.py
index d2c9b4c..f30c7d2 100644
--- a/ccxt_wrapper.py
+++ b/ccxt_wrapper.py
@@ -232,39 +232,9 @@ class poloniexE(poloniex):
232 232
233 return all_balances 233 return all_balances
234 234
235 def create_exchange_order(self, symbol, type, side, amount, price=None, params={}):
236 return super().create_order(symbol, type, side, amount, price=price, params=params)
237
238 def create_margin_order(self, symbol, type, side, amount, price=None, lending_rate=None, params={}):
239 if type == 'market':
240 raise ExchangeError(self.id + ' allows limit orders only')
241 self.load_markets()
242 method = 'privatePostMargin' + self.capitalize(side)
243 market = self.market(symbol)
244 price = float(price)
245 amount = float(amount)
246 if lending_rate is not None:
247 params = self.extend({"lendingRate": lending_rate}, params)
248 response = getattr(self, method)(self.extend({
249 'currencyPair': market['id'],
250 'rate': self.price_to_precision(symbol, price),
251 'amount': self.amount_to_precision(symbol, amount),
252 }, params))
253 timestamp = self.milliseconds()
254 order = self.parse_order(self.extend({
255 'timestamp': timestamp,
256 'status': 'open',
257 'type': type,
258 'side': side,
259 'price': price,
260 'amount': amount,
261 }, response), market)
262 id = order['id']
263 self.orders[id] = order
264 return self.extend({'info': response}, order)
265
266 def order_precision(self, symbol): 235 def order_precision(self, symbol):
267 return 8 236 self.load_markets()
237 return self.markets[symbol]['precision']['price']
268 238
269 def transfer_balance(self, currency, amount, from_account, to_account): 239 def transfer_balance(self, currency, amount, from_account, to_account):
270 result = self.privatePostTransferBalance({ 240 result = self.privatePostTransferBalance({
@@ -382,14 +352,49 @@ class poloniexE(poloniex):
382 352
383 def create_order(self, symbol, type, side, amount, price=None, account="exchange", lending_rate=None, params={}): 353 def create_order(self, symbol, type, side, amount, price=None, account="exchange", lending_rate=None, params={}):
384 """ 354 """
385 Wrapped to handle margin and exchange accounts 355 Wrapped to handle margin and exchange accounts, and get decimals
386 """ 356 """
357 if type == 'market':
358 raise ExchangeError(self.id + ' allows limit orders only')
359 self.load_markets()
387 if account == "exchange": 360 if account == "exchange":
388 return self.create_exchange_order(symbol, type, side, amount, price=price, params=params) 361 method = 'privatePost' + self.capitalize(side)
389 elif account == "margin": 362 elif account == "margin":
390 return self.create_margin_order(symbol, type, side, amount, price=price, lending_rate=lending_rate, params=params) 363 method = 'privatePostMargin' + self.capitalize(side)
364 if lending_rate is not None:
365 params = self.extend({"lendingRate": lending_rate}, params)
391 else: 366 else:
392 raise NotImplementedError 367 raise NotImplementedError
368 market = self.market(symbol)
369 response = getattr(self, method)(self.extend({
370 'currencyPair': market['id'],
371 'rate': self.price_to_precision(symbol, price),
372 'amount': self.amount_to_precision(symbol, amount),
373 }, params))
374 timestamp = self.milliseconds()
375 order = self.parse_order(self.extend({
376 'timestamp': timestamp,
377 'status': 'open',
378 'type': type,
379 'side': side,
380 'price': price,
381 'amount': amount,
382 }, response), market)
383 id = order['id']
384 self.orders[id] = order
385 return self.extend({'info': response}, order)
386
387 def price_to_precision(self, symbol, price):
388 """
389 Wrapped to avoid float
390 """
391 return ('{:.' + str(self.markets[symbol]['precision']['price']) + 'f}').format(price).rstrip("0").rstrip(".")
392
393 def amount_to_precision(self, symbol, amount):
394 """
395 Wrapped to avoid float
396 """
397 return ('{:.' + str(self.markets[symbol]['precision']['amount']) + 'f}').format(amount).rstrip("0").rstrip(".")
393 398
394 def common_currency_code(self, currency): 399 def common_currency_code(self, currency):
395 """ 400 """
diff --git a/main.py b/main.py
index 13c2240..a461207 100644
--- a/main.py
+++ b/main.py
@@ -63,7 +63,7 @@ def get_user_market(config_path, user_id, debug=False):
63 if debug: 63 if debug:
64 args.append("--debug") 64 args.append("--debug")
65 args = parse_args(args) 65 args = parse_args(args)
66 pg_config = parse_config(args) 66 pg_config, redis_config = parse_config(args)
67 market_id, market_config, user_id = list(fetch_markets(pg_config, str(user_id)))[0] 67 market_id, market_config, user_id = list(fetch_markets(pg_config, str(user_id)))[0]
68 return market.Market.from_config(market_config, args, 68 return market.Market.from_config(market_config, args,
69 pg_config=pg_config, market_id=market_id, 69 pg_config=pg_config, market_id=market_id,
diff --git a/tests/test_ccxt_wrapper.py b/tests/test_ccxt_wrapper.py
index 10e334d..9ddfbc1 100644
--- a/tests/test_ccxt_wrapper.py
+++ b/tests/test_ccxt_wrapper.py
@@ -110,7 +110,23 @@ class poloniexETest(unittest.TestCase):
110 retry_call.assert_not_called() 110 retry_call.assert_not_called()
111 111
112 def test_order_precision(self): 112 def test_order_precision(self):
113 self.assertEqual(8, self.s.order_precision("FOO")) 113 self.s.markets = {
114 "FOO": {
115 "precision": {
116 "price": 5,
117 "amount": 6,
118 }
119 },
120 "BAR": {
121 "precision": {
122 "price": 7,
123 "amount": 8,
124 }
125 }
126 }
127 with mock.patch.object(self.s, "load_markets") as load_markets:
128 self.assertEqual(5, self.s.order_precision("FOO"))
129 load_markets.assert_called_once()
114 130
115 def test_transfer_balance(self): 131 def test_transfer_balance(self):
116 with self.subTest(success=True),\ 132 with self.subTest(success=True),\
@@ -167,33 +183,6 @@ class poloniexETest(unittest.TestCase):
167 } 183 }
168 self.assertEqual(expected, self.s.margin_summary()) 184 self.assertEqual(expected, self.s.margin_summary())
169 185
170 def test_create_order(self):
171 with mock.patch.object(self.s, "create_exchange_order") as exchange,\
172 mock.patch.object(self.s, "create_margin_order") as margin:
173 with self.subTest(account="unspecified"):
174 self.s.create_order("symbol", "type", "side", "amount", price="price", lending_rate="lending_rate", params="params")
175 exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
176 margin.assert_not_called()
177 exchange.reset_mock()
178 margin.reset_mock()
179
180 with self.subTest(account="exchange"):
181 self.s.create_order("symbol", "type", "side", "amount", account="exchange", price="price", lending_rate="lending_rate", params="params")
182 exchange.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params")
183 margin.assert_not_called()
184 exchange.reset_mock()
185 margin.reset_mock()
186
187 with self.subTest(account="margin"):
188 self.s.create_order("symbol", "type", "side", "amount", account="margin", price="price", lending_rate="lending_rate", params="params")
189 margin.assert_called_once_with("symbol", "type", "side", "amount", lending_rate="lending_rate", price="price", params="params")
190 exchange.assert_not_called()
191 exchange.reset_mock()
192 margin.reset_mock()
193
194 with self.subTest(account="unknown"), self.assertRaises(NotImplementedError):
195 self.s.create_order("symbol", "type", "side", "amount", account="unknown")
196
197 def test_parse_ticker(self): 186 def test_parse_ticker(self):
198 ticker = { 187 ticker = {
199 "high24hr": "12", 188 "high24hr": "12",
@@ -439,11 +428,13 @@ class poloniexETest(unittest.TestCase):
439 self.assertEqual(expected_doge, result["DOGE"]) 428 self.assertEqual(expected_doge, result["DOGE"])
440 self.assertEqual(expected_btc, result["BTC"]) 429 self.assertEqual(expected_btc, result["BTC"])
441 430
442 def test_create_margin_order(self): 431 def test_create_order(self):
443 with self.assertRaises(market.ExchangeError): 432 with self.subTest(type="market"),\
444 self.s.create_margin_order("FOO", "market", "buy", "10") 433 self.assertRaises(market.ExchangeError):
434 self.s.create_order("FOO", "market", "buy", "10")
445 435
446 with mock.patch.object(self.s, "load_markets") as load_markets,\ 436 with self.subTest(type="limit", account="margin"),\
437 mock.patch.object(self.s, "load_markets") as load_markets,\
447 mock.patch.object(self.s, "privatePostMarginBuy") as margin_buy,\ 438 mock.patch.object(self.s, "privatePostMarginBuy") as margin_buy,\
448 mock.patch.object(self.s, "privatePostMarginSell") as margin_sell,\ 439 mock.patch.object(self.s, "privatePostMarginSell") as margin_sell,\
449 mock.patch.object(self.s, "market") as market_mock,\ 440 mock.patch.object(self.s, "market") as market_mock,\
@@ -460,26 +451,97 @@ class poloniexETest(unittest.TestCase):
460 ptp.return_value = D("0.1") 451 ptp.return_value = D("0.1")
461 atp.return_value = D("12") 452 atp.return_value = D("12")
462 453
463 order = self.s.create_margin_order("BTC_ETC", "margin", "buy", "12", price="0.1") 454 order = self.s.create_order("BTC_ETC", "limit", "buy", "12",
455 account="margin", price="0.1")
464 self.assertEqual(123, order["id"]) 456 self.assertEqual(123, order["id"])
465 margin_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")}) 457 margin_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
466 margin_sell.assert_not_called() 458 margin_sell.assert_not_called()
467 margin_buy.reset_mock() 459 margin_buy.reset_mock()
468 margin_sell.reset_mock() 460 margin_sell.reset_mock()
469 461
470 order = self.s.create_margin_order("BTC_ETC", "margin", "sell", "12", lending_rate="0.01", price="0.1") 462 order = self.s.create_order("BTC_ETC", "limit", "sell",
463 "12", account="margin", lending_rate="0.01", price="0.1")
471 self.assertEqual(456, order["id"]) 464 self.assertEqual(456, order["id"])
472 margin_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"}) 465 margin_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12"), "lendingRate": "0.01"})
473 margin_buy.assert_not_called() 466 margin_buy.assert_not_called()
474 467
475 def test_create_exchange_order(self): 468 with self.subTest(type="limit", account="exchange"),\
476 with mock.patch.object(market.ccxt.poloniex, "create_order") as create_order: 469 mock.patch.object(self.s, "load_markets") as load_markets,\
477 self.s.create_order("symbol", "type", "side", "amount", price="price", params="params") 470 mock.patch.object(self.s, "privatePostBuy") as exchange_buy,\
471 mock.patch.object(self.s, "privatePostSell") as exchange_sell,\
472 mock.patch.object(self.s, "market") as market_mock,\
473 mock.patch.object(self.s, "price_to_precision") as ptp,\
474 mock.patch.object(self.s, "amount_to_precision") as atp:
478 475
479 create_order.assert_called_once_with("symbol", "type", "side", "amount", price="price", params="params") 476 exchange_buy.return_value = {
477 "orderNumber": 123
478 }
479 exchange_sell.return_value = {
480 "orderNumber": 456
481 }
482 market_mock.return_value = { "id": "BTC_ETC", "symbol": "BTC_ETC" }
483 ptp.return_value = D("0.1")
484 atp.return_value = D("12")
485
486 order = self.s.create_order("BTC_ETC", "limit", "buy", "12",
487 account="exchange", price="0.1")
488 self.assertEqual(123, order["id"])
489 exchange_buy.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
490 exchange_sell.assert_not_called()
491 exchange_buy.reset_mock()
492 exchange_sell.reset_mock()
493
494 order = self.s.create_order("BTC_ETC", "limit", "sell",
495 "12", account="exchange", lending_rate="0.01", price="0.1")
496 self.assertEqual(456, order["id"])
497 exchange_sell.assert_called_once_with({"currencyPair": "BTC_ETC", "rate": D("0.1"), "amount": D("12")})
498 exchange_buy.assert_not_called()
499
500 with self.subTest(account="unknown"), self.assertRaises(NotImplementedError),\
501 mock.patch.object(self.s, "load_markets") as load_markets:
502 self.s.create_order("symbol", "type", "side", "amount", account="unknown")
480 503
481 def test_common_currency_code(self): 504 def test_common_currency_code(self):
482 self.assertEqual("FOO", self.s.common_currency_code("FOO")) 505 self.assertEqual("FOO", self.s.common_currency_code("FOO"))
483 506
484 def test_currency_id(self): 507 def test_currency_id(self):
485 self.assertEqual("FOO", self.s.currency_id("FOO")) 508 self.assertEqual("FOO", self.s.currency_id("FOO"))
509
510 def test_amount_to_precision(self):
511 self.s.markets = {
512 "FOO": {
513 "precision": {
514 "price": 5,
515 "amount": 6,
516 }
517 },
518 "BAR": {
519 "precision": {
520 "price": 7,
521 "amount": 8,
522 }
523 }
524 }
525 self.assertEqual("0.0001", self.s.amount_to_precision("FOO", D("0.0001")))
526 self.assertEqual("0.0000001", self.s.amount_to_precision("BAR", D("0.0000001")))
527 self.assertEqual("0.000001", self.s.amount_to_precision("FOO", D("0.000001")))
528
529 def test_price_to_precision(self):
530 self.s.markets = {
531 "FOO": {
532 "precision": {
533 "price": 5,
534 "amount": 6,
535 }
536 },
537 "BAR": {
538 "precision": {
539 "price": 7,
540 "amount": 8,
541 }
542 }
543 }
544 self.assertEqual("0.0001", self.s.price_to_precision("FOO", D("0.0001")))
545 self.assertEqual("0.0000001", self.s.price_to_precision("BAR", D("0.0000001")))
546 self.assertEqual("0", self.s.price_to_precision("FOO", D("0.000001")))
547
diff --git a/tests/test_main.py b/tests/test_main.py
index b650870..55b1382 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -103,7 +103,7 @@ class MainTest(WebMockTestCase):
103 mock.patch("main.parse_config") as main_parse_config: 103 mock.patch("main.parse_config") as main_parse_config:
104 with self.subTest(debug=False): 104 with self.subTest(debug=False):
105 main_parse_args.return_value = self.market_args() 105 main_parse_args.return_value = self.market_args()
106 main_parse_config.return_value = "pg_config" 106 main_parse_config.return_value = ["pg_config", "redis_config"]
107 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)] 107 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
108 m = main.get_user_market("config_path.ini", 1) 108 m = main.get_user_market("config_path.ini", 1)
109 109
@@ -114,7 +114,7 @@ class MainTest(WebMockTestCase):
114 main_parse_args.reset_mock() 114 main_parse_args.reset_mock()
115 with self.subTest(debug=True): 115 with self.subTest(debug=True):
116 main_parse_args.return_value = self.market_args(debug=True) 116 main_parse_args.return_value = self.market_args(debug=True)
117 main_parse_config.return_value = "pg_config" 117 main_parse_config.return_value = ["pg_config", "redis_config"]
118 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)] 118 main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3)]
119 m = main.get_user_market("config_path.ini", 1, debug=True) 119 m = main.get_user_market("config_path.ini", 1, debug=True)
120 120