Fix UTC handling of timestamp() conversation in fetch_my_trades
This commit is contained in:
		| @@ -7,7 +7,7 @@ from typing import Dict | |||||||
|  |  | ||||||
| import numpy as np | import numpy as np | ||||||
| import pandas as pd | import pandas as pd | ||||||
| import pytz | from datetime import timezone | ||||||
|  |  | ||||||
| from freqtrade import persistence | from freqtrade import persistence | ||||||
| from freqtrade.misc import json_load | from freqtrade.misc import json_load | ||||||
| @@ -106,8 +106,8 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame: | |||||||
|                "stop_loss", "initial_stop_loss", "strategy", "ticker_interval"] |                "stop_loss", "initial_stop_loss", "strategy", "ticker_interval"] | ||||||
|  |  | ||||||
|     trades = pd.DataFrame([(t.pair, |     trades = pd.DataFrame([(t.pair, | ||||||
|                             t.open_date.replace(tzinfo=pytz.UTC), |                             t.open_date.replace(tzinfo=timezone.utc), | ||||||
|                             t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None, |                             t.close_date.replace(tzinfo=timezone.utc) if t.close_date else None, | ||||||
|                             t.calc_profit(), t.calc_profit_percent(), |                             t.calc_profit(), t.calc_profit_percent(), | ||||||
|                             t.open_rate, t.close_rate, t.amount, |                             t.open_rate, t.close_rate, t.amount, | ||||||
|                             (round((t.close_date.timestamp() - t.open_date.timestamp()) / 60, 2) |                             (round((t.close_date.timestamp() - t.open_date.timestamp()) / 60, 2) | ||||||
|   | |||||||
| @@ -9,12 +9,11 @@ Includes: | |||||||
| import logging | import logging | ||||||
| import operator | import operator | ||||||
| from copy import deepcopy | from copy import deepcopy | ||||||
| from datetime import datetime | from datetime import datetime, timezone | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import Any, Dict, List, Optional, Tuple | from typing import Any, Dict, List, Optional, Tuple | ||||||
|  |  | ||||||
| import arrow | import arrow | ||||||
| import pytz |  | ||||||
| from pandas import DataFrame | from pandas import DataFrame | ||||||
|  |  | ||||||
| from freqtrade import OperationalException, misc | from freqtrade import OperationalException, misc | ||||||
| @@ -56,10 +55,10 @@ def trim_dataframe(df: DataFrame, timerange: TimeRange) -> DataFrame: | |||||||
|     Trim dataframe based on given timerange |     Trim dataframe based on given timerange | ||||||
|     """ |     """ | ||||||
|     if timerange.starttype == 'date': |     if timerange.starttype == 'date': | ||||||
|         start = datetime.fromtimestamp(timerange.startts, tz=pytz.utc) |         start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) | ||||||
|         df = df.loc[df['date'] >= start, :] |         df = df.loc[df['date'] >= start, :] | ||||||
|     if timerange.stoptype == 'date': |     if timerange.stoptype == 'date': | ||||||
|         stop = datetime.fromtimestamp(timerange.stopts, tz=pytz.utc) |         stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) | ||||||
|         df = df.loc[df['date'] <= stop, :] |         df = df.loc[df['date'] <= stop, :] | ||||||
|     return df |     return df | ||||||
|  |  | ||||||
|   | |||||||
| @@ -875,6 +875,22 @@ class Exchange: | |||||||
|  |  | ||||||
|     @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) -> 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. | ||||||
|  |         """ | ||||||
|         if self._config['dry_run']: |         if self._config['dry_run']: | ||||||
|             return [] |             return [] | ||||||
|         if not self.exchange_has('fetchMyTrades'): |         if not self.exchange_has('fetchMyTrades'): | ||||||
| @@ -882,7 +898,8 @@ 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 | ||||||
|             my_trades = self._api.fetch_my_trades(pair, int((since.timestamp() - 5) * 1000)) |             my_trades = self._api.fetch_my_trades( | ||||||
|  |                 pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000)) | ||||||
|             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] | ||||||
|  |  | ||||||
|             return matched_trades |             return matched_trades | ||||||
|   | |||||||
| @@ -1586,8 +1586,20 @@ def test_name(default_conf, mocker, exchange_name): | |||||||
|  |  | ||||||
| @pytest.mark.parametrize("exchange_name", EXCHANGES) | @pytest.mark.parametrize("exchange_name", EXCHANGES) | ||||||
| def test_get_trades_for_order(default_conf, mocker, exchange_name): | def test_get_trades_for_order(default_conf, mocker, exchange_name): | ||||||
|  |     """ | ||||||
|  |     Crucial part in this test is the "since" calculation. | ||||||
|  |     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. | ||||||
|  |     """ | ||||||
|     order_id = 'ABCD-ABCD' |     order_id = 'ABCD-ABCD' | ||||||
|     since = datetime(2018, 5, 5, tzinfo=timezone.utc) |     since = datetime(2018, 5, 5, 0, 0, 0) | ||||||
|     default_conf["dry_run"] = False |     default_conf["dry_run"] = False | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) |     mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True) | ||||||
|     api_mock = MagicMock() |     api_mock = MagicMock() | ||||||
| @@ -1623,7 +1635,8 @@ def test_get_trades_for_order(default_conf, mocker, exchange_name): | |||||||
|     assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC' |     assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC' | ||||||
|     # Same test twice, hardcoded number and doing the same calculation |     # Same test twice, hardcoded number and doing the same calculation | ||||||
|     assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000 |     assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000 | ||||||
|     assert api_mock.fetch_my_trades.call_args[0][1] == int(since.timestamp() - 5) * 1000 |     assert api_mock.fetch_my_trades.call_args[0][1] == int(since.replace( | ||||||
|  |         tzinfo=timezone.utc).timestamp() - 5) * 1000 | ||||||
|  |  | ||||||
|     ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, |     ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, | ||||||
|                            'get_trades_for_order', 'fetch_my_trades', |                            'get_trades_for_order', 'fetch_my_trades', | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user