From da3c42bbbc577ef9a83aa0d257430ab6ff9bd00a Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sat, 11 Nov 2023 15:16:40 +0900 Subject: [PATCH 1/6] add entries, exits, and mix_tags API endpoints --- docs/rest-api.md | 3 +++ freqtrade/rpc/api_server/api_v1.py | 11 +++++++++++ scripts/rest_client.py | 24 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/docs/rest-api.md b/docs/rest-api.md index 666056a65..ff111c2ce 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -141,6 +141,9 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `logs` | Shows last log messages. | `status` | Lists all open trades. | `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. | `delete_lock ` | Deletes (disables) the lock by id. | `profit` | Display a summary of your profit/loss from close trades and some stats about your performance. diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 8b1bb2a48..ed2746d40 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -82,6 +82,17 @@ def balance(rpc: RPC = Depends(get_rpc), config=Depends(get_config)): def count(rpc: RPC = Depends(get_rpc)): return rpc._rpc_count() +@router.get('/entries', tags=['info', 'trading']) +def entries(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): + return rpc._rpc_enter_tag_performance(pair) + +@router.get('/exits', tags=['info', 'trading']) +def exits(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): + return rpc._rpc_exit_reason_performance(pair) + +@router.get('/mix_tags', tags=['info', 'trading']) +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']) def performance(rpc: RPC = Depends(get_rpc)): diff --git a/scripts/rest_client.py b/scripts/rest_client.py index dfe50cc2c..5970b0c5b 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -112,6 +112,30 @@ class FtRestClient: """ 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): """Return current locks From 0738cae4a0e262cb83b88b0ee11e845fb3b67e1b Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sat, 11 Nov 2023 15:23:20 +0900 Subject: [PATCH 2/6] fix pre-commit --- freqtrade/rpc/api_server/api_v1.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index ed2746d40..0f0e286e3 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -82,18 +82,22 @@ def balance(rpc: RPC = Depends(get_rpc), config=Depends(get_config)): def count(rpc: RPC = Depends(get_rpc)): return rpc._rpc_count() + @router.get('/entries', tags=['info', 'trading']) def entries(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_enter_tag_performance(pair) + @router.get('/exits', tags=['info', 'trading']) def exits(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_exit_reason_performance(pair) + @router.get('/mix_tags', tags=['info', 'trading']) 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']) def performance(rpc: RPC = Depends(get_rpc)): return rpc._rpc_performance() From 05e36f7b21110a5f90f04de406d2f7e36900c4e4 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sat, 11 Nov 2023 15:33:15 +0900 Subject: [PATCH 3/6] increase API version --- freqtrade/rpc/api_server/api_v1.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 0f0e286e3..65593cba8 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -52,7 +52,8 @@ logger = logging.getLogger(__name__) # 2.31: new /backtest/history/ delete endpoint # 2.32: new /backtest/history/ patch endpoint # 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. router_public = APIRouter() From de68850d283199c899146a9e95e819c1aba38105 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Nov 2023 14:31:18 +0100 Subject: [PATCH 4/6] Don't tag informative endpoints as "trading" --- freqtrade/rpc/api_server/api_v1.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 65593cba8..24c9c6549 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -84,17 +84,17 @@ def count(rpc: RPC = Depends(get_rpc)): return rpc._rpc_count() -@router.get('/entries', tags=['info', 'trading']) +@router.get('/entries', tags=['info']) def entries(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_enter_tag_performance(pair) -@router.get('/exits', tags=['info', 'trading']) +@router.get('/exits', tags=['info']) def exits(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_exit_reason_performance(pair) -@router.get('/mix_tags', tags=['info', 'trading']) +@router.get('/mix_tags', tags=['info']) def mix_tags(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)): return rpc._rpc_mix_tag_performance(pair) From 2ef716e94c678fd0d41f9bafb7239380aa796630 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Nov 2023 14:43:24 +0100 Subject: [PATCH 5/6] Add response_models for new endpoints --- freqtrade/rpc/api_server/api_schemas.py | 24 ++++++++++++++++++++++++ freqtrade/rpc/api_server/api_v1.py | 20 ++++++++++---------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 97f6251bc..17df22488 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -95,6 +95,30 @@ class Count(BaseModel): 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): pair: str profit: float diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 24c9c6549..f19010945 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -12,15 +12,15 @@ from freqtrade.exceptions import OperationalException from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, BlacklistResponse, Count, DailyWeeklyMonthly, - DeleteLockRequest, DeleteTrade, - ExchangeListResponse, ForceEnterPayload, + DeleteLockRequest, DeleteTrade, Entry, + ExchangeListResponse, Exit, ForceEnterPayload, ForceEnterResponse, ForceExitPayload, FreqAIModelListResponse, Health, Locks, Logs, - OpenTradeSchema, PairHistory, PerformanceEntry, - Ping, PlotConfig, Profit, ResultMsg, ShowConfig, - Stats, StatusMsg, StrategyListResponse, - StrategyResponse, SysInfo, Version, - WhitelistResponse) + MixTag, OpenTradeSchema, PairHistory, + PerformanceEntry, Ping, PlotConfig, Profit, + ResultMsg, ShowConfig, Stats, StatusMsg, + StrategyListResponse, StrategyResponse, SysInfo, + Version, WhitelistResponse) from freqtrade.rpc.api_server.deps import get_config, get_exchange, get_rpc, get_rpc_optional from freqtrade.rpc.rpc import RPCException @@ -84,17 +84,17 @@ def count(rpc: RPC = Depends(get_rpc)): return rpc._rpc_count() -@router.get('/entries', tags=['info']) +@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', tags=['info']) +@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', tags=['info']) +@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) From 7d0ecfde93624807ade3d8d131806e2bef229cff Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Nov 2023 14:43:43 +0100 Subject: [PATCH 6/6] Add tests to ensure responses are as expected --- tests/rpc/test_rpc_apiserver.py | 57 +++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 4b2866c16..e94509b40 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1063,6 +1063,63 @@ def test_api_performance(botclient, fee): '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( 'is_short,current_rate,open_trade_value', [(True, 1.098e-05, 15.0911775),