Merge pull request #1 from italodamato/develop

This commit is contained in:
Italo 2022-01-22 15:40:57 +00:00 committed by GitHub
commit 402747525f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 89 additions and 27 deletions

View File

@ -100,6 +100,9 @@ def get_latest_hyperopt_file(directory: Union[Path, str], predef_filename: str =
if isinstance(directory, str): if isinstance(directory, str):
directory = Path(directory) directory = Path(directory)
if predef_filename: if predef_filename:
if Path(predef_filename).is_absolute():
raise OperationalException(
"--hyperopt-filename expects only the filename, not an absolute path.")
return directory / predef_filename return directory / predef_filename
return directory / get_latest_hyperopt_filename(directory) return directory / get_latest_hyperopt_filename(directory)

View File

@ -248,8 +248,10 @@ def get_strategy_run_id(strategy) -> str:
if k in config: if k in config:
del config[k] del config[k]
# Explicitly allow NaN values (e.g. max_open_trades).
# as it does not matter for getting the hash.
digest.update(rapidjson.dumps(config, default=str, digest.update(rapidjson.dumps(config, default=str,
number_mode=rapidjson.NM_NATIVE).encode('utf-8')) number_mode=rapidjson.NM_NAN).encode('utf-8'))
with open(strategy.__file__, 'rb') as fp: with open(strategy.__file__, 'rb') as fp:
digest.update(fp.read()) digest.update(fp.read())
return digest.hexdigest().lower() return digest.hexdigest().lower()

View File

@ -137,6 +137,7 @@ class HyperoptTools():
} }
if not HyperoptTools._test_hyperopt_results_exist(results_file): if not HyperoptTools._test_hyperopt_results_exist(results_file):
# No file found. # No file found.
logger.warning(f"Hyperopt file {results_file} not found.")
return [], 0 return [], 0
epochs = [] epochs = []

View File

@ -235,11 +235,12 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
# Trades can be empty # Trades can be empty
if trades is not None and len(trades) > 0: if trades is not None and len(trades) > 0:
# Create description for sell summarizing the trade # Create description for sell summarizing the trade
trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, " trades['desc'] = trades.apply(
f"{row['buy_tag']}, " lambda row: f"{row['profit_ratio']:.2%}, " +
f"{row['sell_reason']}, " (f"{row['buy_tag']}, " if row['buy_tag'] is not None else "") +
f"{row['trade_duration']} min", f"{row['sell_reason']}, " +
axis=1) f"{row['trade_duration']} min",
axis=1)
trade_buys = go.Scatter( trade_buys = go.Scatter(
x=trades["open_date"], x=trades["open_date"],
y=trades["open_rate"], y=trades["open_rate"],

View File

@ -277,6 +277,7 @@ class ForceBuyPayload(BaseModel):
pair: str pair: str
price: Optional[float] price: Optional[float]
ordertype: Optional[OrderTypeValues] ordertype: Optional[OrderTypeValues]
stakeamount: Optional[float]
class ForceSellPayload(BaseModel): class ForceSellPayload(BaseModel):

View File

@ -20,7 +20,7 @@ from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, Blac
Stats, StatusMsg, StrategyListResponse, Stats, StatusMsg, StrategyListResponse,
StrategyResponse, SysInfo, Version, StrategyResponse, SysInfo, Version,
WhitelistResponse) WhitelistResponse)
from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional from freqtrade.rpc.api_server.deps import get_config, get_exchange, get_rpc, get_rpc_optional
from freqtrade.rpc.rpc import RPCException from freqtrade.rpc.rpc import RPCException
@ -31,7 +31,8 @@ logger = logging.getLogger(__name__)
# Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen. # Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen.
# 1.11: forcebuy and forcesell accept ordertype # 1.11: forcebuy and forcesell accept ordertype
# 1.12: add blacklist delete endpoint # 1.12: add blacklist delete endpoint
API_VERSION = 1.12 # 1.13: forcebuy supports stake_amount
API_VERSION = 1.13
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()
@ -134,7 +135,9 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g
@router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading']) @router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading'])
def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None ordertype = payload.ordertype.value if payload.ordertype else None
trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype) stake_amount = payload.stakeamount if payload.stakeamount else None
trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount)
if trade: if trade:
return ForceBuyResponse.parse_obj(trade.to_json()) return ForceBuyResponse.parse_obj(trade.to_json())
@ -217,12 +220,14 @@ def pair_candles(pair: str, timeframe: str, limit: Optional[int], rpc: RPC = Dep
@router.get('/pair_history', response_model=PairHistory, tags=['candle data']) @router.get('/pair_history', response_model=PairHistory, tags=['candle data'])
def pair_history(pair: str, timeframe: str, timerange: str, strategy: str, def pair_history(pair: str, timeframe: str, timerange: str, strategy: str,
config=Depends(get_config)): config=Depends(get_config), exchange=Depends(get_exchange)):
# The initial call to this endpoint can be slow, as it may need to initialize
# the exchange class.
config = deepcopy(config) config = deepcopy(config)
config.update({ config.update({
'strategy': strategy, 'strategy': strategy,
}) })
return RPC._rpc_analysed_history_full(config, pair, timeframe, timerange) return RPC._rpc_analysed_history_full(config, pair, timeframe, timerange, exchange)
@router.get('/plot_config', response_model=PlotConfig, tags=['candle data']) @router.get('/plot_config', response_model=PlotConfig, tags=['candle data'])

View File

@ -1,5 +1,7 @@
from typing import Any, Dict, Iterator, Optional from typing import Any, Dict, Iterator, Optional
from fastapi import Depends
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.rpc import RPC, RPCException from freqtrade.rpc.rpc import RPC, RPCException
@ -28,3 +30,11 @@ def get_config() -> Dict[str, Any]:
def get_api_config() -> Dict[str, Any]: def get_api_config() -> Dict[str, Any]:
return ApiServer._config['api_server'] return ApiServer._config['api_server']
def get_exchange(config=Depends(get_config)):
if not ApiServer._exchange:
from freqtrade.resolvers import ExchangeResolver
ApiServer._exchange = ExchangeResolver.load_exchange(
config['exchange']['name'], config)
return ApiServer._exchange

View File

@ -41,6 +41,8 @@ class ApiServer(RPCHandler):
_has_rpc: bool = False _has_rpc: bool = False
_bgtask_running: bool = False _bgtask_running: bool = False
_config: Dict[str, Any] = {} _config: Dict[str, Any] = {}
# Exchange - only available in webserver mode.
_exchange = None
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
""" """

View File

@ -238,19 +238,26 @@ class RPC:
profit_str += f" ({fiat_profit:.2f})" profit_str += f" ({fiat_profit:.2f})"
fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \ fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \
else fiat_profit_sum + fiat_profit else fiat_profit_sum + fiat_profit
trades_list.append([ detail_trade = [
trade.id, trade.id,
trade.pair + ('*' if (trade.open_order_id is not None trade.pair + ('*' if (trade.open_order_id is not None
and trade.close_rate_requested is None) else '') and trade.close_rate_requested is None) else '')
+ ('**' if (trade.close_rate_requested is not None) else ''), + ('**' if (trade.close_rate_requested is not None) else ''),
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)), shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
profit_str profit_str
]) ]
if self._config.get('position_adjustment_enable', False):
filled_buys = trade.select_filled_orders('buy')
detail_trade.append(str(len(filled_buys)))
trades_list.append(detail_trade)
profitcol = "Profit" profitcol = "Profit"
if self._fiat_converter: if self._fiat_converter:
profitcol += " (" + fiat_display_currency + ")" profitcol += " (" + fiat_display_currency + ")"
columns = ['ID', 'Pair', 'Since', profitcol] if self._config.get('position_adjustment_enable', False):
columns = ['ID', 'Pair', 'Since', profitcol, '# Buys']
else:
columns = ['ID', 'Pair', 'Since', profitcol]
return trades_list, columns, fiat_profit_sum return trades_list, columns, fiat_profit_sum
def _rpc_daily_profit( def _rpc_daily_profit(
@ -700,8 +707,8 @@ class RPC:
self._freqtrade.wallets.update() self._freqtrade.wallets.update()
return {'result': f'Created sell order for trade {trade_id}.'} return {'result': f'Created sell order for trade {trade_id}.'}
def _rpc_forcebuy(self, pair: str, price: Optional[float], def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None,
order_type: Optional[str] = None) -> Optional[Trade]: stake_amount: Optional[float] = None) -> Optional[Trade]:
""" """
Handler for forcebuy <asset> <price> Handler for forcebuy <asset> <price>
Buys a pair trade at the given or current price Buys a pair trade at the given or current price
@ -726,14 +733,15 @@ class RPC:
if not self._freqtrade.strategy.position_adjustment_enable: if not self._freqtrade.strategy.position_adjustment_enable:
raise RPCException(f'position for {pair} already open - id: {trade.id}') raise RPCException(f'position for {pair} already open - id: {trade.id}')
# gen stake amount if not stake_amount:
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair) # gen stake amount
stake_amount = self._freqtrade.wallets.get_trade_stake_amount(pair)
# execute buy # execute buy
if not order_type: if not order_type:
order_type = self._freqtrade.strategy.order_types.get( order_type = self._freqtrade.strategy.order_types.get(
'forcebuy', self._freqtrade.strategy.order_types['buy']) 'forcebuy', self._freqtrade.strategy.order_types['buy'])
if self._freqtrade.execute_entry(pair, stakeamount, price, if self._freqtrade.execute_entry(pair, stake_amount, price,
ordertype=order_type, trade=trade): ordertype=order_type, trade=trade):
Trade.commit() Trade.commit()
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first() trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
@ -988,7 +996,7 @@ class RPC:
@staticmethod @staticmethod
def _rpc_analysed_history_full(config, pair: str, timeframe: str, def _rpc_analysed_history_full(config, pair: str, timeframe: str,
timerange: str) -> Dict[str, Any]: timerange: str, exchange) -> Dict[str, Any]:
timerange_parsed = TimeRange.parse_timerange(timerange) timerange_parsed = TimeRange.parse_timerange(timerange)
_data = load_data( _data = load_data(
@ -1003,7 +1011,7 @@ class RPC:
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.resolvers.strategy_resolver import StrategyResolver from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategy = StrategyResolver.load_strategy(config) strategy = StrategyResolver.load_strategy(config)
strategy.dp = DataProvider(config, exchange=None, pairlists=None) strategy.dp = DataProvider(config, exchange=exchange, pairlists=None)
df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair}) df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair})

View File

@ -51,6 +51,12 @@ def test_get_latest_hyperopt_file(testdatadir):
res = get_latest_hyperopt_file(str(testdatadir.parent)) res = get_latest_hyperopt_file(str(testdatadir.parent))
assert res == testdatadir.parent / "hyperopt_results.pickle" assert res == testdatadir.parent / "hyperopt_results.pickle"
# Test with absolute path
with pytest.raises(
OperationalException,
match="--hyperopt-filename expects only the filename, not an absolute path."):
get_latest_hyperopt_file(str(testdatadir.parent), str(testdatadir.parent))
def test_load_backtest_metadata(mocker, testdatadir): def test_load_backtest_metadata(mocker, testdatadir):
res = load_backtest_metadata(testdatadir / 'nonexistant.file.json') res = load_backtest_metadata(testdatadir / 'nonexistant.file.json')

View File

@ -21,6 +21,7 @@ from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timerange from freqtrade.data.history import get_timerange
from freqtrade.enums import RunMode, SellType from freqtrade.enums import RunMode, SellType
from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.misc import get_strategy_run_id
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
from freqtrade.persistence import LocalTrade from freqtrade.persistence import LocalTrade
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
@ -1357,3 +1358,13 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda
for line in exists: for line in exists:
assert log_has(line, caplog) assert log_has(line, caplog)
def test_get_strategy_run_id(default_conf_usdt):
default_conf_usdt.update({
'strategy': 'StrategyTestV2',
'max_open_trades': float('inf')
})
strategy = StrategyResolver.load_strategy(default_conf_usdt)
x = get_strategy_run_id(strategy)
assert isinstance(x, str)

View File

@ -10,7 +10,7 @@ import rapidjson
from freqtrade.constants import FTHYPT_FILEVERSION from freqtrade.constants import FTHYPT_FILEVERSION
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer
from tests.conftest import log_has from tests.conftest import log_has, log_has_re
# Functions for recurrent object patching # Functions for recurrent object patching
@ -24,6 +24,7 @@ def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None:
hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt') hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt')
hyperopt_epochs = HyperoptTools.load_filtered_results(hyperopt.results_file, {}) hyperopt_epochs = HyperoptTools.load_filtered_results(hyperopt.results_file, {})
assert log_has_re("Hyperopt file .* not found.", caplog)
assert hyperopt_epochs == ([], 0) assert hyperopt_epochs == ([], 0)
# Test writing to temp dir and reading again # Test writing to temp dir and reading again

View File

@ -214,11 +214,17 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert "Since" in headers assert "Since" in headers
assert "Pair" in headers assert "Pair" in headers
assert len(result[0]) == 4
assert 'instantly' == result[0][2] assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1] assert 'ETH/BTC' in result[0][1]
assert '-0.41% (-0.06)' == result[0][3] assert '-0.41% (-0.06)' == result[0][3]
assert '-0.06' == f'{fiat_profit_sum:.2f}' assert '-0.06' == f'{fiat_profit_sum:.2f}'
rpc._config['position_adjustment_enable'] = True
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert "# Buys" in headers
assert len(result[0]) == 5
mocker.patch('freqtrade.exchange.Exchange.get_rate', mocker.patch('freqtrade.exchange.Exchange.get_rate',
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
@ -1102,9 +1108,14 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
with pytest.raises(RPCException, with pytest.raises(RPCException,
match=r'Wrong pair selected. Only pairs with stake-currency.*'): match=r'Wrong pair selected. Only pairs with stake-currency.*'):
rpc._rpc_forcebuy('LTC/ETH', 0.0001) rpc._rpc_forcebuy('LTC/ETH', 0.0001)
pair = 'XRP/BTC'
# Test with defined stake_amount
pair = 'LTC/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05)
assert trade.stake_amount == 0.05
# Test not buying # Test not buying
pair = 'XRP/BTC'
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
freqtradebot.config['stake_amount'] = 0 freqtradebot.config['stake_amount'] = 0
patch_get_signal(freqtradebot) patch_get_signal(freqtradebot)

View File

@ -171,7 +171,7 @@ def test_plot_trades(testdatadir, caplog):
assert len(trades) == len(trade_buy.x) assert len(trades) == len(trade_buy.x)
assert trade_buy.marker.color == 'cyan' assert trade_buy.marker.color == 'cyan'
assert trade_buy.marker.symbol == 'circle-open' assert trade_buy.marker.symbol == 'circle-open'
assert trade_buy.text[0] == '3.99%, roi, 15 min' assert trade_buy.text[0] == '3.99%, buy_tag, roi, 15 min'
trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit') trade_sell = find_trace_in_fig_data(figure.data, 'Sell - Profit')
assert isinstance(trade_sell, go.Scatter) assert isinstance(trade_sell, go.Scatter)
@ -179,7 +179,7 @@ def test_plot_trades(testdatadir, caplog):
assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_sell.x) assert len(trades.loc[trades['profit_ratio'] > 0]) == len(trade_sell.x)
assert trade_sell.marker.color == 'green' assert trade_sell.marker.color == 'green'
assert trade_sell.marker.symbol == 'square-open' assert trade_sell.marker.symbol == 'square-open'
assert trade_sell.text[0] == '3.99%, roi, 15 min' assert trade_sell.text[0] == '3.99%, buy_tag, roi, 15 min'
trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss') trade_sell_loss = find_trace_in_fig_data(figure.data, 'Sell - Loss')
assert isinstance(trade_sell_loss, go.Scatter) assert isinstance(trade_sell_loss, go.Scatter)

File diff suppressed because one or more lines are too long