Merge pull request #1 from italodamato/develop
This commit is contained in:
commit
402747525f
@ -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)
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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 = []
|
||||||
|
@ -235,9 +235,10 @@ 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['sell_reason']}, " +
|
||||||
f"{row['trade_duration']} min",
|
f"{row['trade_duration']} min",
|
||||||
axis=1)
|
axis=1)
|
||||||
trade_buys = go.Scatter(
|
trade_buys = go.Scatter(
|
||||||
|
@ -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):
|
||||||
|
@ -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'])
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -238,18 +238,25 @@ 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 + ")"
|
||||||
|
|
||||||
|
if self._config.get('position_adjustment_enable', False):
|
||||||
|
columns = ['ID', 'Pair', 'Since', profitcol, '# Buys']
|
||||||
|
else:
|
||||||
columns = ['ID', 'Pair', 'Since', profitcol]
|
columns = ['ID', 'Pair', 'Since', profitcol]
|
||||||
return trades_list, columns, fiat_profit_sum
|
return trades_list, columns, fiat_profit_sum
|
||||||
|
|
||||||
@ -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}')
|
||||||
|
|
||||||
|
if not stake_amount:
|
||||||
# gen stake amount
|
# gen stake amount
|
||||||
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
|
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})
|
||||||
|
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user