| @@ -5,6 +5,7 @@ from freqtrade.exchange.exchange import Exchange | |||||||
| # isort: on | # isort: on | ||||||
| from freqtrade.exchange.bibox import Bibox | from freqtrade.exchange.bibox import Bibox | ||||||
| from freqtrade.exchange.binance import Binance | from freqtrade.exchange.binance import Binance | ||||||
|  | from freqtrade.exchange.bitpanda import Bitpanda | ||||||
| from freqtrade.exchange.bittrex import Bittrex | from freqtrade.exchange.bittrex import Bittrex | ||||||
| from freqtrade.exchange.bybit import Bybit | from freqtrade.exchange.bybit import Bybit | ||||||
| from freqtrade.exchange.coinbasepro import Coinbasepro | from freqtrade.exchange.coinbasepro import Coinbasepro | ||||||
|   | |||||||
							
								
								
									
										37
									
								
								freqtrade/exchange/bitpanda.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								freqtrade/exchange/bitpanda.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | """ Bitpanda exchange subclass """ | ||||||
|  | import logging | ||||||
|  | from datetime import datetime, timezone | ||||||
|  | from typing import Dict, List, Optional | ||||||
|  |  | ||||||
|  | from freqtrade.exchange import Exchange | ||||||
|  |  | ||||||
|  |  | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Bitpanda(Exchange): | ||||||
|  |     """ | ||||||
|  |     Bitpanda exchange class. Contains adjustments needed for Freqtrade to work | ||||||
|  |     with this exchange. | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     def get_trades_for_order(self, order_id: str, pair: str, since: datetime, | ||||||
|  |                              params: Optional[Dict] = None) -> List: | ||||||
|  |         """ | ||||||
|  |         Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. | ||||||
|  |         The "since" argument passed in is coming from the database and is in UTC, | ||||||
|  |         as timezone-native datetime object. | ||||||
|  |         From the python documentation: | ||||||
|  |             > Naive datetime instances are assumed to represent local time | ||||||
|  |         Therefore, calling "since.timestamp()" will get the UTC timestamp, after applying the | ||||||
|  |         transformation from local timezone to UTC. | ||||||
|  |         This works for timezones UTC+ since then the result will contain trades from a few hours | ||||||
|  |         instead of from the last 5 seconds, however fails for UTC- timezones, | ||||||
|  |         since we're then asking for trades with a "since" argument in the future. | ||||||
|  |  | ||||||
|  |         :param order_id order_id: Order-id as given when creating the order | ||||||
|  |         :param pair: Pair the order is for | ||||||
|  |         :param since: datetime object of the order creation time. Assumes object is in UTC. | ||||||
|  |         """ | ||||||
|  |         params = {'to': int(datetime.now(timezone.utc).timestamp() * 1000)} | ||||||
|  |         return super().get_trades_for_order(order_id, pair, since, params) | ||||||
| @@ -1091,7 +1091,8 @@ class Exchange: | |||||||
|     # Fee handling |     # Fee handling | ||||||
|  |  | ||||||
|     @retrier |     @retrier | ||||||
|     def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List: |     def get_trades_for_order(self, order_id: str, pair: str, since: datetime, | ||||||
|  |                              params: Optional[Dict] = None) -> List: | ||||||
|         """ |         """ | ||||||
|         Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. |         Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id. | ||||||
|         The "since" argument passed in is coming from the database and is in UTC, |         The "since" argument passed in is coming from the database and is in UTC, | ||||||
| @@ -1115,8 +1116,10 @@ class Exchange: | |||||||
|         try: |         try: | ||||||
|             # Allow 5s offset to catch slight time offsets (discovered in #1185) |             # Allow 5s offset to catch slight time offsets (discovered in #1185) | ||||||
|             # since needs to be int in milliseconds |             # since needs to be int in milliseconds | ||||||
|  |             _params = params if params else {} | ||||||
|             my_trades = self._api.fetch_my_trades( |             my_trades = self._api.fetch_my_trades( | ||||||
|                 pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000)) |                 pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000), | ||||||
|  |                 params=_params) | ||||||
|             matched_trades = [trade for trade in my_trades if trade['order'] == order_id] |             matched_trades = [trade for trade in my_trades if trade['order'] == order_id] | ||||||
|  |  | ||||||
|             self._log_exchange_response('get_trades_for_order', matched_trades) |             self._log_exchange_response('get_trades_for_order', matched_trades) | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								tests/exchange/test_bitpanda.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								tests/exchange/test_bitpanda.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | from datetime import datetime | ||||||
|  | from unittest.mock import MagicMock | ||||||
|  |  | ||||||
|  | from tests.conftest import get_patched_exchange | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_get_trades_for_order(default_conf, mocker): | ||||||
|  |     exchange_name = 'bitpanda' | ||||||
|  |     order_id = 'ABCD-ABCD' | ||||||
|  |     since = datetime(2018, 5, 5, 0, 0, 0) | ||||||
|  |     default_conf["dry_run"] = False | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) | ||||||
|  |     api_mock = MagicMock() | ||||||
|  |  | ||||||
|  |     api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV', | ||||||
|  |                                                         'order': 'ABCD-ABCD', | ||||||
|  |                                                         'info': {'pair': 'XLTCZBTC', | ||||||
|  |                                                                  'time': 1519860024.4388, | ||||||
|  |                                                                  'type': 'buy', | ||||||
|  |                                                                  'ordertype': 'limit', | ||||||
|  |                                                                  'price': '20.00000', | ||||||
|  |                                                                  'cost': '38.62000', | ||||||
|  |                                                                  'fee': '0.06179', | ||||||
|  |                                                                  'vol': '5', | ||||||
|  |                                                                  'id': 'ABCD-ABCD'}, | ||||||
|  |                                                         'timestamp': 1519860024438, | ||||||
|  |                                                         'datetime': '2018-02-28T23:20:24.438Z', | ||||||
|  |                                                         'symbol': 'LTC/BTC', | ||||||
|  |                                                         'type': 'limit', | ||||||
|  |                                                         'side': 'buy', | ||||||
|  |                                                         'price': 165.0, | ||||||
|  |                                                         'amount': 0.2340606, | ||||||
|  |                                                         'fee': {'cost': 0.06179, 'currency': 'BTC'} | ||||||
|  |                                                         }]) | ||||||
|  |     exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) | ||||||
|  |  | ||||||
|  |     orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since) | ||||||
|  |     assert len(orders) == 1 | ||||||
|  |     assert orders[0]['price'] == 165 | ||||||
|  |     assert api_mock.fetch_my_trades.call_count == 1 | ||||||
|  |     # since argument should be | ||||||
|  |     assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int) | ||||||
|  |     assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC' | ||||||
|  |     # Same test twice, hardcoded number and doing the same calculation | ||||||
|  |     assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000 | ||||||
|  |     # bitpanda requires "to" argument. | ||||||
|  |     assert 'to' in api_mock.fetch_my_trades.call_args[1]['params'] | ||||||
		Reference in New Issue
	
	Block a user