Fix UTC handling of timestamp() conversation in fetch_my_trades

This commit is contained in:
Matthias 2019-11-08 06:55:07 +01:00
parent ad2289c34c
commit da57396d07
4 changed files with 39 additions and 10 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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',