Skip to main content

Model-free financial label and backtest validation engine.

Project description

finShell

Python validation engine for financial labels and cross-validated selectors.

Installation

python -m pip install .

The standard installation includes plotting, CPCV, block bootstrap, null tests, triple-barrier labels, logistic selectors, and sealed out-of-sample validation. Input columns are mapped with ColumnRoleMap, so source schemas do not need to use finShell's internal names.

Notebook Walkthrough

This deterministic example creates an original triple-barrier classification label from seeded mock OHLC data. The mock market has persistent latent signal, irregular noise, and a small adverse drift so its unfiltered equity is not a stylized sine wave. It is an API example, not evidence of a tradable edge.

1. Generate and audit the label

The first operation after creating the label is a same-count random-path audit. The figure compares the favorable-label equity path with the null paths and their dashed pointwise p95 boundary, then reports class balance.

import numpy as np
import pandas as pd
import finshell as fs

rows = 480
rng = np.random.default_rng(2024)
signal = np.zeros(rows)
innovations = rng.normal(0.0, 0.75, rows)
for index in range(1, rows):
    signal[index] = 0.82 * signal[index - 1] + innovations[index]
signal = (signal - signal.mean()) / signal.std()
market_noise = rng.normal(0.0, 0.004, rows - 1)
next_returns = 0.0040 * np.tanh(signal[:-1]) + market_noise - 0.0010
close = np.empty(rows)
close[0] = 100.0
for index in range(rows - 1):
    close[index + 1] = close[index] * (1.0 + next_returns[index])
intrabar = np.abs(rng.normal(0.0012, 0.0005, rows))

frame = pd.DataFrame({
    "event_time": pd.date_range("2024-01-01", periods=rows, freq="1h", tz="UTC"),
    "signal_feature": signal,
    "high": close * (1.0 + intrabar),
    "low": close * (1.0 - intrabar),
    "close": close,
    "side": np.ones(rows, dtype=int),
})
study = fs.ValidationStudy(
    frame,
    roles=fs.ColumnRoleMap(
        timestamp="event_time", high="high", low="low", close="close", side="side"
    ),
    artifact_dir="assets/readme",
)
label = study.audit_label(
    fs.TripleBarrierConfig(profit_take=0.015, stop_loss=0.012, vertical_bars=8),
    null_tests=fs.NullTestConfig(
        random_simulations=300, stored_random_paths=40, random_seed=7
    ),
)
print(
    f"passed={label.passed} favorable={label.summary['favorable_count']} "
    f"real_total={label.summary['real_total']:.4f} "
    f"random_p95={label.summary['random_final_p95']:.4f} "
    f"p_value={label.summary['p_value']:.4f}"
)
passed=True favorable=73 real_total=1.0950 random_p95=-0.1088 p_value=0.0000

Label audit

2. Fit inside CPCV folds

LogisticSelector is fitted separately on every block-bootstrap training path. Validation and test rows are never included in those fits, and the final 20% quarantine remains sealed. The left panel shows the distribution across CPCV fold means. The right panel preserves the hierarchy: each CPCV permutation is a row containing its validation and test block-bootstrap distributions.

cv = study.fit_selector(
    fs.LogisticSelector(
        features=["signal_feature"], threshold=0.30, random_state=13
    ),
    cpcv=fs.CPCVConfig(
        n_groups=6, holdout_groups=2, validate_groups=1, max_splits=4
    ),
    bootstrap=fs.FoldBlockBootstrapConfig(
        replicates=4, block_bars=8, random_seed=13
    ),
)
print(
    f"passed={cv.passed} valid_bootstrap_fits={cv.summary['valid_bootstrap_fits']} "
    f"validate_return={cv.summary['mean_validate_total_return']:.4f} "
    f"test_return={cv.summary['mean_test_total_return']:.4f}"
)
passed=True valid_bootstrap_fits=16 validate_return=0.0206 test_return=0.0589

CPCV selector distributions

3. Audit selected quarantine trades

The already-fitted selector scores the sealed quarantine without refitting. Its equity path must beat both same-count random selection p95 and the unfiltered no-selector equity path.

oos = study.audit_oos(
    null_tests=fs.NullTestConfig(
        random_simulations=300, stored_random_paths=40, random_seed=17
    )
)
print(
    f"passed={oos.passed} selected={oos.summary['selected_count']} "
    f"selected_total={oos.summary['selected_total']:.4f} "
    f"random_p95={oos.summary['random_final_p95']:.4f} "
    f"no_selector={oos.summary['no_selector_total']:.4f} "
    f"p_value={oos.summary['p_value']:.4f}"
)
passed=True selected=36 selected_total=0.3088 random_p95=0.1589 no_selector=0.1981 p_value=0.0000

Out-of-sample selected-trade audit

4. Validate economic paths

The last stage block-bootstraps the selected OOS backtest outcomes into account- balance paths. Each path ends at the configured upper balance, lower balance, or trade-count horizon. The single chart reports the probability of every resolution state and the median number of trades to resolution.

economics = study.validate_economics(
    paths=300,
    block_bars=4,
    random_seed=23,
    initial_balance=100_000,
    upper_balance=105_000,
    lower_balance=95_000,
    max_trades=12,
)
print(
    f"passed={economics.passed} paths={economics.summary['bootstrap_paths']} "
    f"upper_hit={economics.summary['upper_hit_probability']:.1%} "
    f"lower_hit={economics.summary['lower_hit_probability']:.1%} "
    f"vertical={economics.summary['vertical_probability']:.1%} "
    f"median_resolution={economics.summary['median_resolution_trades']:.1f} trades"
)
passed=True paths=300 upper_hit=89.7% lower_hit=1.0% vertical=9.3% median_resolution=5.0 trades

Triple-barrier economic validation

Data Assumptions

  • Input may be a pandas DataFrame, CSV file, or Parquet file.
  • Timestamps must be parseable; ingestion normalizes them to UTC and sorts them.
  • ColumnRoleMap maps user-defined column names into the validation contract.
  • OHLC and side columns are required when finShell generates triple-barrier labels.
  • Outcomes are additive per-event returns used for cumulative equity and path tests.
  • Randomized and bootstrap results are deterministic for a fixed seed.
  • Quarantine data is the final chronological 20% by default and is not used in CPCV.
  • Economic barrier paths resample contiguous selected-trade outcomes from the OOS backtest and compound them from the configured initial account balance.

Run the test suite with:

.\.venv\Scripts\python.exe -m pytest

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

finshell-0.3.2.tar.gz (41.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

finshell-0.3.2-py3-none-any.whl (36.3 kB view details)

Uploaded Python 3

File details

Details for the file finshell-0.3.2.tar.gz.

File metadata

  • Download URL: finshell-0.3.2.tar.gz
  • Upload date:
  • Size: 41.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for finshell-0.3.2.tar.gz
Algorithm Hash digest
SHA256 77ac52d323e5ef2cfe83a91dfbbbe58f8a46715906e0c36bdd11728f7df2fe74
MD5 4294cc55f53f256f589de80b5e4ba8fd
BLAKE2b-256 b758a2b9153ec0e9ac4f7e02eef35e0d29f51f81eb388909d578ca87d5cfff3b

See more details on using hashes here.

File details

Details for the file finshell-0.3.2-py3-none-any.whl.

File metadata

  • Download URL: finshell-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 36.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for finshell-0.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4adb24b4452f721d69124f5c7613f34a7630e64b3c33faeb70bcb6208c2c586e
MD5 125ffaa547440f0403866d8fee622c02
BLAKE2b-256 8499edd002fea1dcd9cbbe5babb1a5c4ed20154a0647d6e1f984f38d54e8eb02

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page