Merge pull request #9407 from stash86/bt-metrics

add entries, exits, and mix_tags API endpoints
This commit is contained in:
Matthias 2023-11-11 16:10:10 +01:00 committed by GitHub
commit da647735b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 8 deletions

View File

@ -141,6 +141,9 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
| `logs` | Shows last log messages. | `logs` | Shows last log messages.
| `status` | Lists all open trades. | `status` | Lists all open trades.
| `count` | Displays number of trades used and available. | `count` | Displays number of trades used and available.
| `entries [pair]` | Shows profit statistics for each enter tags for given pair (or all pairs if pair isn't given). Pair is optional.
| `exits [pair]` | Shows profit statistics for each exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.
| `mix_tags [pair]` | Shows profit statistics for each combinations of enter tag + exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.
| `locks` | Displays currently locked pairs. | `locks` | Displays currently locked pairs.
| `delete_lock <lock_id>` | Deletes (disables) the lock by id. | `delete_lock <lock_id>` | Deletes (disables) the lock by id.
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance. | `profit` | Display a summary of your profit/loss from close trades and some stats about your performance.

View File

@ -95,6 +95,30 @@ class Count(BaseModel):
total_stake: float total_stake: float
class Entry(BaseModel):
enter_tag: str
profit_ratio: float
profit_pct: float
profit_abs: float
count: int
class Exit(BaseModel):
exit_reason: str
profit_ratio: float
profit_pct: float
profit_abs: float
count: int
class MixTag(BaseModel):
mix_tag: str
profit: float
profit_pct: float
profit_abs: float
count: int
class PerformanceEntry(BaseModel): class PerformanceEntry(BaseModel):
pair: str pair: str
profit: float profit: float

View File

@ -12,15 +12,15 @@ from freqtrade.exceptions import OperationalException
from freqtrade.rpc import RPC from freqtrade.rpc import RPC
from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload,
BlacklistResponse, Count, DailyWeeklyMonthly, BlacklistResponse, Count, DailyWeeklyMonthly,
DeleteLockRequest, DeleteTrade, DeleteLockRequest, DeleteTrade, Entry,
ExchangeListResponse, ForceEnterPayload, ExchangeListResponse, Exit, ForceEnterPayload,
ForceEnterResponse, ForceExitPayload, ForceEnterResponse, ForceExitPayload,
FreqAIModelListResponse, Health, Locks, Logs, FreqAIModelListResponse, Health, Locks, Logs,
OpenTradeSchema, PairHistory, PerformanceEntry, MixTag, OpenTradeSchema, PairHistory,
Ping, PlotConfig, Profit, ResultMsg, ShowConfig, PerformanceEntry, Ping, PlotConfig, Profit,
Stats, StatusMsg, StrategyListResponse, ResultMsg, ShowConfig, Stats, StatusMsg,
StrategyResponse, SysInfo, Version, StrategyListResponse, StrategyResponse, SysInfo,
WhitelistResponse) Version, WhitelistResponse)
from freqtrade.rpc.api_server.deps import get_config, get_exchange, 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
@ -52,7 +52,8 @@ logger = logging.getLogger(__name__)
# 2.31: new /backtest/history/ delete endpoint # 2.31: new /backtest/history/ delete endpoint
# 2.32: new /backtest/history/ patch endpoint # 2.32: new /backtest/history/ patch endpoint
# 2.33: Additional weekly/monthly metrics # 2.33: Additional weekly/monthly metrics
API_VERSION = 2.33 # 2.34: new entries/exits/mix_tags endpoints
API_VERSION = 2.34
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()
@ -83,6 +84,21 @@ def count(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_count() return rpc._rpc_count()
@router.get('/entries', response_model=List[Entry], tags=['info'])
def entries(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_enter_tag_performance(pair)
@router.get('/exits', response_model=List[Exit], tags=['info'])
def exits(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_exit_reason_performance(pair)
@router.get('/mix_tags', response_model=List[MixTag], tags=['info'])
def mix_tags(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_mix_tag_performance(pair)
@router.get('/performance', response_model=List[PerformanceEntry], tags=['info']) @router.get('/performance', response_model=List[PerformanceEntry], tags=['info'])
def performance(rpc: RPC = Depends(get_rpc)): def performance(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_performance() return rpc._rpc_performance()

View File

@ -112,6 +112,30 @@ class FtRestClient:
""" """
return self._get("count") return self._get("count")
def entries(self, pair=None):
"""Returns List of dicts containing all Trades, based on buy tag performance
Can either be average for all pairs or a specific pair provided
:return: json object
"""
return self._get("entries", params={"pair": pair} if pair else None)
def exits(self, pair=None):
"""Returns List of dicts containing all Trades, based on exit reason performance
Can either be average for all pairs or a specific pair provided
:return: json object
"""
return self._get("exits", params={"pair": pair} if pair else None)
def mix_tags(self, pair=None):
"""Returns List of dicts containing all Trades, based on entry_tag + exit_reason performance
Can either be average for all pairs or a specific pair provided
:return: json object
"""
return self._get("mix_tags", params={"pair": pair} if pair else None)
def locks(self): def locks(self):
"""Return current locks """Return current locks

View File

@ -1063,6 +1063,63 @@ def test_api_performance(botclient, fee):
'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}] 'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}]
def test_api_entries(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot)
# Empty
rc = client_get(client, f"{BASE_URI}/entries")
assert_response(rc)
assert len(rc.json()) == 0
create_mock_trades(fee)
rc = client_get(client, f"{BASE_URI}/entries")
assert_response(rc)
response = rc.json()
assert len(response) == 2
resp = response[0]
assert resp['enter_tag'] == 'TEST1'
assert resp['count'] == 1
assert resp['profit_pct'] == 0.5
def test_api_exits(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot)
# Empty
rc = client_get(client, f"{BASE_URI}/exits")
assert_response(rc)
assert len(rc.json()) == 0
create_mock_trades(fee)
rc = client_get(client, f"{BASE_URI}/exits")
assert_response(rc)
response = rc.json()
assert len(response) == 2
resp = response[0]
assert resp['exit_reason'] == 'sell_signal'
assert resp['count'] == 1
assert resp['profit_pct'] == 0.5
def test_api_mix_tag(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot)
# Empty
rc = client_get(client, f"{BASE_URI}/mix_tags")
assert_response(rc)
assert len(rc.json()) == 0
create_mock_trades(fee)
rc = client_get(client, f"{BASE_URI}/mix_tags")
assert_response(rc)
response = rc.json()
assert len(response) == 2
resp = response[0]
assert resp['mix_tag'] == 'TEST1 sell_signal'
assert resp['count'] == 1
assert resp['profit_pct'] == 0.5
@pytest.mark.parametrize( @pytest.mark.parametrize(
'is_short,current_rate,open_trade_value', 'is_short,current_rate,open_trade_value',
[(True, 1.098e-05, 15.0911775), [(True, 1.098e-05, 15.0911775),