Skip to content

Commit d723f3e

Browse files
committed
feat(backtesting): add delete_source option to migrate_backtests
Allows the migration to remove each legacy directory or bundle after its destination has been written successfully. Deletion happens per-backtest inside the worker, so interrupted runs leave only the not-yet-migrated sources behind. Exposed via the --delete-source CLI flag.
1 parent 01c8154 commit d723f3e

3 files changed

Lines changed: 42 additions & 10 deletions

File tree

docusaurus/docs/Getting Started/backtesting.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ n = migrate_backtests(
109109
write_index=True, # also build index.parquet
110110
include_ohlcv=False,
111111
skip_existing=True, # resume-safe (default)
112+
delete_source=False, # set True to remove originals
112113
)
113114
print(f"migrated {n} backtests")
114115
```
@@ -122,6 +123,11 @@ iaf migrate-backtests \
122123
--workers 8
123124
```
124125

126+
Pass `--delete-source` (or `delete_source=True` in Python) to remove
127+
each legacy directory/bundle after its destination has been written
128+
successfully. Sources are deleted per-backtest, so an interrupted
129+
run leaves only the not-yet-migrated ones behind.
130+
125131
## Reporting
126132

127133
Use [Backtest Reports](/docs/Getting%20Started/backtest-reports) to turn

investing_algorithm_framework/cli/cli.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,16 @@ def mcp(directory):
281281
"--no-skip-existing", is_flag=True, default=False,
282282
help="Re-migrate even if the destination bundle already exists.",
283283
)
284+
@click.option(
285+
"--delete-source", is_flag=True, default=False,
286+
help=(
287+
"Delete each source directory/bundle after its destination "
288+
"has been written successfully. Use with care."
289+
),
290+
)
284291
def migrate_backtests_cmd(
285-
src, dst, workers, no_index, include_ohlcv, no_skip_existing
292+
src, dst, workers, no_index, include_ohlcv, no_skip_existing,
293+
delete_source,
286294
):
287295
"""Convert a directory of legacy backtest folders into the bundled
288296
binary format introduced in issue #487.
@@ -306,6 +314,7 @@ def migrate_backtests_cmd(
306314
write_index=not no_index,
307315
include_ohlcv=include_ohlcv,
308316
skip_existing=not no_skip_existing,
317+
delete_source=delete_source,
309318
)
310319
click.echo(f"Migrated {n} backtest(s) from {src} to {dst}")
311320

investing_algorithm_framework/domain/backtesting/backtest_utils.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -567,20 +567,32 @@ def load_backtests(
567567

568568
def _migrate_one(args):
569569
"""Worker entry point: open *src* (legacy dir or bundle), write
570-
*dst* as a bundle, return the destination path.
571-
572-
Doing load+save in one worker call keeps each backtest's data in
573-
a single process — avoiding the cost of pickling fully-decoded
574-
Backtest objects back to the parent process. This roughly halves
575-
peak memory usage for large migrations and is faster end-to-end.
570+
*dst* as a bundle, optionally delete *src*, return the
571+
destination path.
572+
573+
Doing load+save (and optionally delete) in one worker call keeps
574+
each backtest's data in a single process — avoiding the cost of
575+
pickling fully-decoded Backtest objects back to the parent
576+
process. This roughly halves peak memory usage for large
577+
migrations and is faster end-to-end.
576578
"""
577-
src, dst, include_ohlcv, ohlcv_store = args
579+
src, dst, include_ohlcv, ohlcv_store, delete_source = args
578580
bt = _open_bundle(src) if is_bundle_file(src) else Backtest.open(src)
579-
return str(_save_bundle(
581+
out = str(_save_bundle(
580582
bt, dst,
581583
include_ohlcv=include_ohlcv,
582584
ohlcv_store=ohlcv_store,
583585
))
586+
if delete_source and os.path.abspath(src) != os.path.abspath(out):
587+
import shutil
588+
if os.path.isdir(src):
589+
shutil.rmtree(src, ignore_errors=True)
590+
elif os.path.isfile(src):
591+
try:
592+
os.remove(src)
593+
except OSError:
594+
pass
595+
return out
584596

585597

586598
def migrate_backtests(
@@ -592,6 +604,7 @@ def migrate_backtests(
592604
write_index: bool = True,
593605
include_ohlcv: bool = False,
594606
skip_existing: bool = True,
607+
delete_source: bool = False,
595608
) -> int:
596609
"""Rewrite a directory of legacy backtest folders (or existing
597610
``.iafbt`` bundles) as ``.iafbt`` bundles in *dst_dir*.
@@ -613,6 +626,10 @@ def migrate_backtests(
613626
include_ohlcv: Include OHLCV data in the destination bundles.
614627
skip_existing: Skip backtests whose destination bundle already
615628
exists. Allows resuming an interrupted migration.
629+
delete_source: If True, delete each source directory / bundle
630+
**after** its destination bundle has been written
631+
successfully. The source is left intact when it is the
632+
same path as the destination. Use with care.
616633
617634
Returns:
618635
Number of backtests migrated (excluding skipped ones).
@@ -654,7 +671,7 @@ def migrate_backtests(
654671
dst = str(dst_dir / f"{base}{BUNDLE_EXT}")
655672
if skip_existing and os.path.isfile(dst):
656673
continue
657-
plan.append((src, dst, include_ohlcv, ohlcv_store))
674+
plan.append((src, dst, include_ohlcv, ohlcv_store, delete_source))
658675

659676
if not plan:
660677
return 0

0 commit comments

Comments
 (0)