Skip to content

BUG: Scale overlay indicator columns of FractionalBacktest trades - #1426

Open
wavebyrd wants to merge 1 commit into
kernc:masterfrom
wavebyrd:fix/fractional-trade-indicators
Open

wavebyrd wants to merge 1 commit into
kernc:masterfrom
wavebyrd:fix/fractional-trade-indicators

Conversation

@wavebyrd

Copy link
Copy Markdown

FractionalBacktest.run() scales EntryPrice, ExitPrice, SL, TP and the overlay indicators back up after the run, but the Entry_<indicator> / Exit_<indicator> columns in stats._trades stayed at the fractional scale, so they no longer matched the prices next to them:

from backtesting.lib import FractionalBacktest
from backtesting.test import BTCUSD
from backtesting.test._test import SmaCross

stats = FractionalBacktest(BTCUSD['2015':], SmaCross, fractional_unit=1/1e6, cash=100,
                           finalize_trades=True).run(fast=2, slow=3)
t = stats['_trades'].iloc[0]
print(t['EntryPrice'], t['Entry_SMA(C,2)'])  # 236.69 0.00023414, now 236.69 234.14

Those columns are now scaled for overlay indicators too, using the same column names as _stats.py (including the _0, _1 suffixes for multi-dimensional indicators). Added an assertion to test_FractionalBacktest, which fails without the fix; the rest of the suite and flake8 pass.

@arhancanli

Copy link
Copy Markdown

I ran this branch (pull/1426/head, Python 3.12, current pandas) against a strategy with a one-dimensional overlay (SMA), a two-row overlay (name='band', rolling max over rolling min) and a non-overlay indicator (name='ret', a percent change), with fractional_unit=1/1e6 on BTCUSD['2015':].

First trade, entry price 261.7:

Entry_SMA(C,2)  273.945     Entry_SMA(C,3)  259.803
Entry_band_0    286.11      Entry_band_1    261.78
Entry_ret       9.294

The overlay columns now sit in the same units as EntryPrice, including the _0/_1 suffixed ones, which matches the suffix rule in _stats.py lines 82-85 (len(ind) > 1). The non-overlay ret column is untouched, which is what you want: it is a percentage, not a price.

The trades.columns.intersection(cols) guard also covers Entry_/Exit_ columns that are absent when there are no trades (if len(trades_df) and strategy_instance in _stats.py), so an empty run does not raise.

One thing I did not test and the diff doesn't settle: a non-overlay indicator that is still price-denominated, such as an ATR or a High - Low spread. Before this PR those were already left in fractional units in _indicators, and overlay is the only switch the class looks at, so the new trade columns stay consistent with the plotted arrays. If that is intended, a sentence in the FractionalBacktest docstring ("only overlay indicators are rescaled") would save the next reader from finding it by surprise.

@arhancanli

Copy link
Copy Markdown

Ran this on f403424 (Python 3.12, BTCUSD from 2015, fractional_unit=1/1e6) with a strategy using two overlay SMAs, an SL, and one non-overlay indicator (overlay=False, constant 1). On master, Entry_SMA(C,2) for trade 0 is 0.000274 next to EntryPrice 261.7. With this branch it is 273.945, Exit_SMA(C,3) comes out as 259.82 and the non-overlay column stays 1.0, so the overlay filter is respected.

One thing I could not tell from the diff: the multi-dimensional case (_0, _1 suffixes). The test only covers the single-output SMA. Is there an existing indicator that returns a 2-row array with overlay=True that could be added to the test, so the suffix branch is exercised too?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants