• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

freqtrade / freqtrade / 4131167254

pending completion
4131167254

push

github-actions

GitHub
Merge pull request #7983 from stash86/bt-metrics

16866 of 17748 relevant lines covered (95.03%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.25
/freqtrade/rpc/api_server/api_backtest.py
1
import asyncio
1✔
2
import logging
1✔
3
from copy import deepcopy
1✔
4
from datetime import datetime
1✔
5
from typing import Any, Dict, List
1✔
6

7
from fastapi import APIRouter, BackgroundTasks, Depends
1✔
8
from fastapi.exceptions import HTTPException
1✔
9

10
from freqtrade.configuration.config_validation import validate_config_consistency
1✔
11
from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result
1✔
12
from freqtrade.enums import BacktestState
1✔
13
from freqtrade.exceptions import DependencyException
1✔
14
from freqtrade.misc import deep_merge_dicts
1✔
15
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest,
1✔
16
                                                  BacktestResponse)
17
from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode
1✔
18
from freqtrade.rpc.api_server.webserver import ApiServer
1✔
19
from freqtrade.rpc.rpc import RPCException
1✔
20

21

22
logger = logging.getLogger(__name__)
1✔
23

24
# Private API, protected by authentication
25
router = APIRouter()
1✔
26

27

28
@router.post('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
1✔
29
# flake8: noqa: C901
30
async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: BackgroundTasks,
1✔
31
                             config=Depends(get_config), ws_mode=Depends(is_webserver_mode)):
32
    """Start backtesting if not done so already"""
33
    if ApiServer._bgtask_running:
1✔
34
        raise RPCException('Bot Background task already running')
1✔
35

36
    if ':' in bt_settings.strategy:
1✔
37
        raise HTTPException(status_code=500, detail="base64 encoded strategies are not allowed.")
1✔
38

39
    btconfig = deepcopy(config)
1✔
40
    settings = dict(bt_settings)
1✔
41
    if settings.get('freqai', None) is not None:
1✔
42
        settings['freqai'] = dict(settings['freqai'])
×
43
    # Pydantic models will contain all keys, but non-provided ones are None
44

45
    btconfig = deep_merge_dicts(settings, btconfig, allow_null_overrides=False)
1✔
46
    try:
1✔
47
        btconfig['stake_amount'] = float(btconfig['stake_amount'])
1✔
48
    except ValueError:
×
49
        pass
×
50

51
    # Force dry-run for backtesting
52
    btconfig['dry_run'] = True
1✔
53

54
    # Start backtesting
55
    # Initialize backtesting object
56
    def run_backtest():
1✔
57
        from freqtrade.optimize.optimize_reports import (generate_backtest_stats,
1✔
58
                                                         store_backtest_stats)
59
        from freqtrade.resolvers import StrategyResolver
1✔
60
        asyncio.set_event_loop(asyncio.new_event_loop())
1✔
61
        try:
1✔
62
            # Reload strategy
63
            lastconfig = ApiServer._bt_last_config
1✔
64
            strat = StrategyResolver.load_strategy(btconfig)
1✔
65
            validate_config_consistency(btconfig)
1✔
66

67
            if (
1✔
68
                not ApiServer._bt
69
                or lastconfig.get('timeframe') != strat.timeframe
70
                or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail')
71
                or lastconfig.get('timerange') != btconfig['timerange']
72
            ):
73
                from freqtrade.optimize.backtesting import Backtesting
1✔
74
                ApiServer._bt = Backtesting(btconfig)
1✔
75
                ApiServer._bt.load_bt_data_detail()
1✔
76
            else:
77
                ApiServer._bt.config = btconfig
1✔
78
                ApiServer._bt.init_backtest()
1✔
79
            # Only reload data if timeframe changed.
80
            if (
1✔
81
                not ApiServer._bt_data
82
                or not ApiServer._bt_timerange
83
                or lastconfig.get('timeframe') != strat.timeframe
84
                or lastconfig.get('timerange') != btconfig['timerange']
85
            ):
86
                ApiServer._bt_data, ApiServer._bt_timerange = ApiServer._bt.load_bt_data()
1✔
87

88
            lastconfig['timerange'] = btconfig['timerange']
1✔
89
            lastconfig['timeframe'] = strat.timeframe
1✔
90
            lastconfig['protections'] = btconfig.get('protections', [])
1✔
91
            lastconfig['enable_protections'] = btconfig.get('enable_protections')
1✔
92
            lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet')
1✔
93

94
            ApiServer._bt.enable_protections = btconfig.get('enable_protections', False)
1✔
95
            ApiServer._bt.strategylist = [strat]
1✔
96
            ApiServer._bt.results = {}
1✔
97
            ApiServer._bt.load_prior_backtest()
1✔
98

99
            ApiServer._bt.abort = False
1✔
100
            if (ApiServer._bt.results and
1✔
101
                    strat.get_strategy_name() in ApiServer._bt.results['strategy']):
102
                # When previous result hash matches - reuse that result and skip backtesting.
103
                logger.info(f'Reusing result of previous backtest for {strat.get_strategy_name()}')
1✔
104
            else:
105
                min_date, max_date = ApiServer._bt.backtest_one_strategy(
1✔
106
                    strat, ApiServer._bt_data, ApiServer._bt_timerange)
107

108
                ApiServer._bt.results = generate_backtest_stats(
1✔
109
                    ApiServer._bt_data, ApiServer._bt.all_results,
110
                    min_date=min_date, max_date=max_date)
111

112
            if btconfig.get('export', 'none') == 'trades':
1✔
113
                store_backtest_stats(
1✔
114
                    btconfig['exportfilename'], ApiServer._bt.results,
115
                    datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
116
                    )
117

118
            logger.info("Backtest finished.")
1✔
119

120
        except DependencyException as e:
1✔
121
            logger.info(f"Backtesting caused an error: {e}")
1✔
122
            pass
1✔
123
        finally:
124
            ApiServer._bgtask_running = False
1✔
125

126
    background_tasks.add_task(run_backtest)
1✔
127
    ApiServer._bgtask_running = True
1✔
128

129
    return {
1✔
130
        "status": "running",
131
        "running": True,
132
        "progress": 0,
133
        "step": str(BacktestState.STARTUP),
134
        "status_msg": "Backtest started",
135
    }
136

137

138
@router.get('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
1✔
139
def api_get_backtest(ws_mode=Depends(is_webserver_mode)):
1✔
140
    """
141
    Get backtesting result.
142
    Returns Result after backtesting has been ran.
143
    """
144
    from freqtrade.persistence import LocalTrade
1✔
145
    if ApiServer._bgtask_running:
1✔
146
        return {
1✔
147
            "status": "running",
148
            "running": True,
149
            "step": ApiServer._bt.progress.action if ApiServer._bt else str(BacktestState.STARTUP),
150
            "progress": ApiServer._bt.progress.progress if ApiServer._bt else 0,
151
            "trade_count": len(LocalTrade.trades),
152
            "status_msg": "Backtest running",
153
        }
154

155
    if not ApiServer._bt:
1✔
156
        return {
1✔
157
            "status": "not_started",
158
            "running": False,
159
            "step": "",
160
            "progress": 0,
161
            "status_msg": "Backtest not yet executed"
162
        }
163

164
    return {
1✔
165
        "status": "ended",
166
        "running": False,
167
        "status_msg": "Backtest ended",
168
        "step": "finished",
169
        "progress": 1,
170
        "backtest_result": ApiServer._bt.results,
171
    }
172

173

174
@router.delete('/backtest', response_model=BacktestResponse, tags=['webserver', 'backtest'])
1✔
175
def api_delete_backtest(ws_mode=Depends(is_webserver_mode)):
1✔
176
    """Reset backtesting"""
177
    if ApiServer._bgtask_running:
1✔
178
        return {
1✔
179
            "status": "running",
180
            "running": True,
181
            "step": "",
182
            "progress": 0,
183
            "status_msg": "Backtest running",
184
        }
185
    if ApiServer._bt:
1✔
186
        ApiServer._bt.cleanup()
1✔
187
        del ApiServer._bt
1✔
188
        ApiServer._bt = None
1✔
189
        del ApiServer._bt_data
1✔
190
        ApiServer._bt_data = None
1✔
191
        logger.info("Backtesting reset")
1✔
192
    return {
1✔
193
        "status": "reset",
194
        "running": False,
195
        "step": "",
196
        "progress": 0,
197
        "status_msg": "Backtest reset",
198
    }
199

200

201
@router.get('/backtest/abort', response_model=BacktestResponse, tags=['webserver', 'backtest'])
1✔
202
def api_backtest_abort(ws_mode=Depends(is_webserver_mode)):
1✔
203
    if not ApiServer._bgtask_running:
1✔
204
        return {
1✔
205
            "status": "not_running",
206
            "running": False,
207
            "step": "",
208
            "progress": 0,
209
            "status_msg": "Backtest ended",
210
        }
211
    ApiServer._bt.abort = True
1✔
212
    return {
1✔
213
        "status": "stopping",
214
        "running": False,
215
        "step": "",
216
        "progress": 0,
217
        "status_msg": "Backtest ended",
218
    }
219

220

221
@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], tags=['webserver', 'backtest'])
1✔
222
def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)):
1✔
223
    # Get backtest result history, read from metadata files
224
    return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results')
1✔
225

226

227
@router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest'])
1✔
228
def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)):
1✔
229
    # Get backtest result history, read from metadata files
230
    fn = config['user_data_dir'] / 'backtest_results' / filename
1✔
231
    results: Dict[str, Any] = {
1✔
232
        'metadata': {},
233
        'strategy': {},
234
        'strategy_comparison': [],
235
    }
236

237
    load_and_merge_backtest_result(strategy, fn, results)
1✔
238
    return {
1✔
239
        "status": "ended",
240
        "running": False,
241
        "step": "",
242
        "progress": 1,
243
        "status_msg": "Historic result",
244
        "backtest_result": results,
245
    }
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc