Merge branch 'develop' into feat/cancel_order
This commit is contained in:
commit
c1a34396d0
@ -9,7 +9,7 @@ from collections import deque
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from pandas import DataFrame, to_timedelta
|
from pandas import DataFrame, Timedelta, Timestamp, to_timedelta
|
||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import (FULL_DATAFRAME_THRESHOLD, Config, ListPairsWithTimeframes,
|
from freqtrade.constants import (FULL_DATAFRAME_THRESHOLD, Config, ListPairsWithTimeframes,
|
||||||
@ -206,9 +206,11 @@ class DataProvider:
|
|||||||
existing_df, _ = self.__producer_pairs_df[producer_name][pair_key]
|
existing_df, _ = self.__producer_pairs_df[producer_name][pair_key]
|
||||||
|
|
||||||
# CHECK FOR MISSING CANDLES
|
# CHECK FOR MISSING CANDLES
|
||||||
timeframe_delta = to_timedelta(timeframe) # Convert the timeframe to a timedelta for pandas
|
# Convert the timeframe to a timedelta for pandas
|
||||||
local_last = existing_df.iloc[-1]['date'] # We want the last date from our copy
|
timeframe_delta: Timedelta = to_timedelta(timeframe)
|
||||||
incoming_first = dataframe.iloc[0]['date'] # We want the first date from the incoming
|
local_last: Timestamp = existing_df.iloc[-1]['date'] # We want the last date from our copy
|
||||||
|
# We want the first date from the incoming
|
||||||
|
incoming_first: Timestamp = dataframe.iloc[0]['date']
|
||||||
|
|
||||||
# Remove existing candles that are newer than the incoming first candle
|
# Remove existing candles that are newer than the incoming first candle
|
||||||
existing_df1 = existing_df[existing_df['date'] < incoming_first]
|
existing_df1 = existing_df[existing_df['date'] < incoming_first]
|
||||||
@ -221,7 +223,7 @@ class DataProvider:
|
|||||||
# we missed some candles between our data and the incoming
|
# we missed some candles between our data and the incoming
|
||||||
# so return False and candle_difference.
|
# so return False and candle_difference.
|
||||||
if candle_difference > 1:
|
if candle_difference > 1:
|
||||||
return (False, candle_difference)
|
return (False, int(candle_difference))
|
||||||
if existing_df1.empty:
|
if existing_df1.empty:
|
||||||
appended_df = dataframe
|
appended_df = dataframe
|
||||||
else:
|
else:
|
||||||
|
@ -308,7 +308,7 @@ class IDataHandler(ABC):
|
|||||||
timerange=timerange_startup,
|
timerange=timerange_startup,
|
||||||
candle_type=candle_type
|
candle_type=candle_type
|
||||||
)
|
)
|
||||||
if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data, True):
|
if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data):
|
||||||
return pairdf
|
return pairdf
|
||||||
else:
|
else:
|
||||||
enddate = pairdf.iloc[-1]['date']
|
enddate = pairdf.iloc[-1]['date']
|
||||||
@ -316,7 +316,7 @@ class IDataHandler(ABC):
|
|||||||
if timerange_startup:
|
if timerange_startup:
|
||||||
self._validate_pairdata(pair, pairdf, timeframe, candle_type, timerange_startup)
|
self._validate_pairdata(pair, pairdf, timeframe, candle_type, timerange_startup)
|
||||||
pairdf = trim_dataframe(pairdf, timerange_startup)
|
pairdf = trim_dataframe(pairdf, timerange_startup)
|
||||||
if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data):
|
if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data, True):
|
||||||
return pairdf
|
return pairdf
|
||||||
|
|
||||||
# incomplete candles should only be dropped if we didn't trim the end beforehand.
|
# incomplete candles should only be dropped if we didn't trim the end beforehand.
|
||||||
|
@ -34,6 +34,7 @@ class Bybit(Exchange):
|
|||||||
"ohlcv_candle_limit": 200,
|
"ohlcv_candle_limit": 200,
|
||||||
"ohlcv_has_history": True,
|
"ohlcv_has_history": True,
|
||||||
"mark_ohlcv_timeframe": "4h",
|
"mark_ohlcv_timeframe": "4h",
|
||||||
|
"funding_fee_timeframe": "8h",
|
||||||
"stoploss_on_exchange": True,
|
"stoploss_on_exchange": True,
|
||||||
"stoploss_order_types": {"limit": "limit", "market": "market"},
|
"stoploss_order_types": {"limit": "limit", "market": "market"},
|
||||||
}
|
}
|
||||||
|
@ -36,3 +36,34 @@ class Kucoin(Exchange):
|
|||||||
'stop': 'loss'
|
'stop': 'loss'
|
||||||
})
|
})
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
def create_order(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
pair: str,
|
||||||
|
ordertype: str,
|
||||||
|
side: BuySell,
|
||||||
|
amount: float,
|
||||||
|
rate: float,
|
||||||
|
leverage: float,
|
||||||
|
reduceOnly: bool = False,
|
||||||
|
time_in_force: str = 'GTC',
|
||||||
|
) -> Dict:
|
||||||
|
|
||||||
|
res = super().create_order(
|
||||||
|
pair=pair,
|
||||||
|
ordertype=ordertype,
|
||||||
|
side=side,
|
||||||
|
amount=amount,
|
||||||
|
rate=rate,
|
||||||
|
leverage=leverage,
|
||||||
|
reduceOnly=reduceOnly,
|
||||||
|
time_in_force=time_in_force,
|
||||||
|
)
|
||||||
|
# Kucoin returns only the order-id.
|
||||||
|
# ccxt returns status = 'closed' at the moment - which is information ccxt invented.
|
||||||
|
# Since we rely on status heavily, we must set it to 'open' here.
|
||||||
|
# ref: https://github.com/ccxt/ccxt/pull/16674, (https://github.com/ccxt/ccxt/pull/16553)
|
||||||
|
res['type'] = ordertype
|
||||||
|
res['status'] = 'open'
|
||||||
|
return res
|
||||||
|
@ -750,13 +750,15 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.exchange.name, order['filled'], order['amount'],
|
self.exchange.name, order['filled'], order['amount'],
|
||||||
order['remaining']
|
order['remaining']
|
||||||
)
|
)
|
||||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
amount = safe_value_fallback(order, 'filled', 'amount', amount)
|
||||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
enter_limit_filled_price = safe_value_fallback(
|
||||||
|
order, 'average', 'price', enter_limit_filled_price)
|
||||||
|
|
||||||
# in case of FOK the order may be filled immediately and fully
|
# in case of FOK the order may be filled immediately and fully
|
||||||
elif order_status == 'closed':
|
elif order_status == 'closed':
|
||||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
amount = safe_value_fallback(order, 'filled', 'amount', amount)
|
||||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
enter_limit_filled_price = safe_value_fallback(
|
||||||
|
order, 'average', 'price', enter_limit_requested)
|
||||||
|
|
||||||
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
||||||
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
||||||
|
@ -44,7 +44,7 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
|
|||||||
|
|
||||||
sum_daily = (
|
sum_daily = (
|
||||||
results.resample(resample_freq, on='close_date').agg(
|
results.resample(resample_freq, on='close_date').agg(
|
||||||
{"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0)
|
{"profit_ratio_after_slippage": 'sum'}).reindex(t_index).fillna(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
total_profit = sum_daily["profit_ratio_after_slippage"] - risk_free_rate
|
total_profit = sum_daily["profit_ratio_after_slippage"] - risk_free_rate
|
||||||
|
@ -46,7 +46,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
|
|||||||
|
|
||||||
sum_daily = (
|
sum_daily = (
|
||||||
results.resample(resample_freq, on='close_date').agg(
|
results.resample(resample_freq, on='close_date').agg(
|
||||||
{"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0)
|
{"profit_ratio_after_slippage": 'sum'}).reindex(t_index).fillna(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
total_profit = sum_daily["profit_ratio_after_slippage"] - minimum_acceptable_return
|
total_profit = sum_daily["profit_ratio_after_slippage"] - minimum_acceptable_return
|
||||||
|
@ -172,7 +172,7 @@ class Order(_DECL_BASE):
|
|||||||
|
|
||||||
def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]:
|
def to_json(self, entry_side: str, minified: bool = False) -> Dict[str, Any]:
|
||||||
resp = {
|
resp = {
|
||||||
'amount': self.amount,
|
'amount': self.safe_amount,
|
||||||
'safe_price': self.safe_price,
|
'safe_price': self.safe_price,
|
||||||
'ft_order_side': self.ft_order_side,
|
'ft_order_side': self.ft_order_side,
|
||||||
'order_filled_timestamp': int(self.order_filled_date.replace(
|
'order_filled_timestamp': int(self.order_filled_date.replace(
|
||||||
|
@ -90,7 +90,7 @@ async def _process_consumer_request(
|
|||||||
|
|
||||||
elif type == RPCRequestType.ANALYZED_DF:
|
elif type == RPCRequestType.ANALYZED_DF:
|
||||||
# Limit the amount of candles per dataframe to 'limit' or 1500
|
# Limit the amount of candles per dataframe to 'limit' or 1500
|
||||||
limit = min(data.get('limit', 1500), 1500) if data else None
|
limit = int(min(data.get('limit', 1500), 1500)) if data else None
|
||||||
pair = data.get('pair', None) if data else None
|
pair = data.get('pair', None) if data else None
|
||||||
|
|
||||||
# For every pair in the generator, send a separate message
|
# For every pair in the generator, send a separate message
|
||||||
|
@ -437,6 +437,7 @@ def test_dp__add_external_df(default_conf_usdt):
|
|||||||
# Add the same dataframe again - dataframe size shall not change.
|
# Add the same dataframe again - dataframe size shall not change.
|
||||||
res = dp._add_external_df('ETH/USDT', df, last_analyzed, timeframe, CandleType.SPOT)
|
res = dp._add_external_df('ETH/USDT', df, last_analyzed, timeframe, CandleType.SPOT)
|
||||||
assert res[0] is True
|
assert res[0] is True
|
||||||
|
assert isinstance(res[1], int)
|
||||||
assert res[1] == 0
|
assert res[1] == 0
|
||||||
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
||||||
assert len(df) == 24
|
assert len(df) == 24
|
||||||
@ -446,6 +447,7 @@ def test_dp__add_external_df(default_conf_usdt):
|
|||||||
|
|
||||||
res = dp._add_external_df('ETH/USDT', df2, last_analyzed, timeframe, CandleType.SPOT)
|
res = dp._add_external_df('ETH/USDT', df2, last_analyzed, timeframe, CandleType.SPOT)
|
||||||
assert res[0] is True
|
assert res[0] is True
|
||||||
|
assert isinstance(res[1], int)
|
||||||
assert res[1] == 0
|
assert res[1] == 0
|
||||||
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
||||||
assert len(df) == 48
|
assert len(df) == 48
|
||||||
@ -455,6 +457,7 @@ def test_dp__add_external_df(default_conf_usdt):
|
|||||||
|
|
||||||
res = dp._add_external_df('ETH/USDT', df3, last_analyzed, timeframe, CandleType.SPOT)
|
res = dp._add_external_df('ETH/USDT', df3, last_analyzed, timeframe, CandleType.SPOT)
|
||||||
assert res[0] is True
|
assert res[0] is True
|
||||||
|
assert isinstance(res[1], int)
|
||||||
assert res[1] == 0
|
assert res[1] == 0
|
||||||
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
||||||
# New length = 48 + 12 (since we have a 12 hour offset).
|
# New length = 48 + 12 (since we have a 12 hour offset).
|
||||||
@ -478,6 +481,7 @@ def test_dp__add_external_df(default_conf_usdt):
|
|||||||
res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT)
|
res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT)
|
||||||
assert res[0] is False
|
assert res[0] is False
|
||||||
# 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00
|
# 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00
|
||||||
|
assert isinstance(res[1], int)
|
||||||
assert res[1] == 36
|
assert res[1] == 36
|
||||||
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
|
||||||
# New length = 61 + 1
|
# New length = 61 + 1
|
||||||
@ -488,4 +492,5 @@ def test_dp__add_external_df(default_conf_usdt):
|
|||||||
res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT)
|
res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT)
|
||||||
assert res[0] is False
|
assert res[0] is False
|
||||||
# 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00
|
# 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00
|
||||||
|
assert isinstance(res[1], int)
|
||||||
assert res[1] == 0
|
assert res[1] == 0
|
||||||
|
@ -49,8 +49,8 @@ EXCHANGES = {
|
|||||||
"orderListId": -1,
|
"orderListId": -1,
|
||||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||||
"transactTime": 1674493798550,
|
"transactTime": 1674493798550,
|
||||||
"price": "15.00000000",
|
"price": "15.50000000",
|
||||||
"origQty": "1.00000000",
|
"origQty": "1.10000000",
|
||||||
"executedQty": "0.00000000",
|
"executedQty": "0.00000000",
|
||||||
"cummulativeQuoteQty": "0.00000000",
|
"cummulativeQuoteQty": "0.00000000",
|
||||||
"status": "NEW",
|
"status": "NEW",
|
||||||
@ -74,8 +74,8 @@ EXCHANGES = {
|
|||||||
"orderListId": -1,
|
"orderListId": -1,
|
||||||
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
|
||||||
"transactTime": 1674493798550,
|
"transactTime": 1674493798550,
|
||||||
"price": "15.00000000",
|
"price": "15.50000000",
|
||||||
"origQty": "1.00000000",
|
"origQty": "1.10000000",
|
||||||
"executedQty": "0.00000000",
|
"executedQty": "0.00000000",
|
||||||
"cummulativeQuoteQty": "0.00000000",
|
"cummulativeQuoteQty": "0.00000000",
|
||||||
"status": "NEW",
|
"status": "NEW",
|
||||||
@ -106,12 +106,12 @@ EXCHANGES = {
|
|||||||
{'id': '63d6742d0adc5570001d2bbf7'}, # create order
|
{'id': '63d6742d0adc5570001d2bbf7'}, # create order
|
||||||
{
|
{
|
||||||
'id': '63d6742d0adc5570001d2bbf7',
|
'id': '63d6742d0adc5570001d2bbf7',
|
||||||
'symbol': 'NAKA-USDT',
|
'symbol': 'SOL-USDT',
|
||||||
'opType': 'DEAL',
|
'opType': 'DEAL',
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
'side': 'buy',
|
'side': 'buy',
|
||||||
'price': '30',
|
'price': '15.5',
|
||||||
'size': '0.1',
|
'size': '1.1',
|
||||||
'funds': '0',
|
'funds': '0',
|
||||||
'dealFunds': '0.032626',
|
'dealFunds': '0.032626',
|
||||||
'dealSize': '0.1',
|
'dealSize': '0.1',
|
||||||
@ -168,6 +168,23 @@ EXCHANGES = {
|
|||||||
'futures': True,
|
'futures': True,
|
||||||
'leverage_tiers_public': True,
|
'leverage_tiers_public': True,
|
||||||
'leverage_in_spot_market': True,
|
'leverage_in_spot_market': True,
|
||||||
|
'sample_order': [
|
||||||
|
{
|
||||||
|
"orderId": "1274754916287346280",
|
||||||
|
"orderLinkId": "1666798627015730",
|
||||||
|
"symbol": "SOLUSDT",
|
||||||
|
"createTime": "1674493798550",
|
||||||
|
"orderPrice": "15.5",
|
||||||
|
"orderQty": "1.1",
|
||||||
|
"orderType": "LIMIT",
|
||||||
|
"side": "BUY",
|
||||||
|
"status": "NEW",
|
||||||
|
"timeInForce": "GTC",
|
||||||
|
"accountId": "5555555",
|
||||||
|
"execQty": "0",
|
||||||
|
"orderCategory": "0"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
'huobi': {
|
'huobi': {
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
@ -306,12 +323,18 @@ class TestCCXTExchange():
|
|||||||
po = exch._api.parse_order(order)
|
po = exch._api.parse_order(order)
|
||||||
assert isinstance(po['id'], str)
|
assert isinstance(po['id'], str)
|
||||||
assert po['id'] is not None
|
assert po['id'] is not None
|
||||||
if len(order.keys()) > 1:
|
if len(order.keys()) < 5:
|
||||||
|
# Kucoin case
|
||||||
|
assert po['status'] == 'closed'
|
||||||
|
continue
|
||||||
assert po['timestamp'] == 1674493798550
|
assert po['timestamp'] == 1674493798550
|
||||||
assert isinstance(po['datetime'], str)
|
assert isinstance(po['datetime'], str)
|
||||||
assert isinstance(po['timestamp'], int)
|
assert isinstance(po['timestamp'], int)
|
||||||
assert isinstance(po['price'], float)
|
assert isinstance(po['price'], float)
|
||||||
|
assert po['price'] == 15.5
|
||||||
|
assert po['symbol'] == 'SOL/USDT'
|
||||||
assert isinstance(po['amount'], float)
|
assert isinstance(po['amount'], float)
|
||||||
|
assert po['amount'] == 1.1
|
||||||
assert isinstance(po['status'], str)
|
assert isinstance(po['status'], str)
|
||||||
else:
|
else:
|
||||||
pytest.skip(f"No sample order available for exchange {exchange_name}")
|
pytest.skip(f"No sample order available for exchange {exchange_name}")
|
||||||
|
Loading…
Reference in New Issue
Block a user