Skip to content

/run

Shortcut that runs single or batch PyBondLab portfolio sorts on the Dickerson bond data straight from Bash, bypassing the orchestrator agent, with templates for single sorts and within-firm sorts.

Category: analysis
Field: finance
License: MIT (repo LICENSE, "Copyright (c) 2025 Alex Dickerson")
Updated: 2026-03-24
Stages: data-analysis · code-generation

/run — Fast Portfolio Sort Execution

Executes batch or single portfolio sorts on the Dickerson bond dataset without spawning an agent. One Bash call, results saved automatically.

Arguments

Text Only
/run <mode> <signals...> [options]
Argument Values Default
mode batch, single required
signals space-separated signal names (e.g., cs ytm bbtm) required
--wfs Use WithinFirmSort instead of SingleSort off
--rating IG, NIG, or omit for all all
--nport Number of portfolios (SingleSort only) 5
--hp Holding period 1

Execution Steps

  1. Parse arguments from the user's /run command
  2. Determine the bond data path — look for Dickerson bond parquet in data/ (check metadata.json for datasets from bonds-wrds-expert), or use the path from canonical local state reported by tools/bootstrap.py audit
  3. Generate the Python script from the template below (fill in signals, options, data path)
  4. Execute via single Bash call using the Python path from canonical local state reported by tools/bootstrap.py audit
  5. Print the mandatory summary table:
Text Only
| Signal | Weight | Mean(%) | t-stat | SR | Turn(%) |

All values annualized (%). Turnover = avg monthly two-way (%).

  1. Follow with 2-3 sentences interpreting the results. Always state the sample period (first and last date in the returns).

Date Filtering

The TRACE-aligned sample filter (2002-08 to 2024-12) should only be applied when: - The data is corporate bond / bond data, AND - The user explicitly requests a TRACE-aligned sample

Otherwise, use the full date range in the data as-is. Do NOT hardcode the TRACE filter by default.

Template: Batch SingleSort

Python
import warnings; warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import PyBondLab as pbl
from PyBondLab.report import ResultsReporter
from PyBondLab.describe.utils import compute_nw_tstat

SIGNALS = {signals}          # e.g., ['cs', 'ytm', 'bbtm']
RATING = {rating}            # None, 'IG', or 'NIG'
NPORT = {nport}              # e.g., 5
HP = {hp}                    # e.g., 1
MNEMONIC = '{mnemonic}'      # e.g., 'batch_3s'

SCRIPT = '<generated by /run skill>'

data = pd.read_parquet('{data_path}')
## TRACE-aligned filter: ONLY apply if corporate bond data AND user requests TRACE-aligned sample
## data = data[(data['date'] >= '2002-08-01') & (data['date'] <= '2024-12-31')]
data['spc_rat'] = data['spc_rat'].astype('float64')

batch = pbl.BatchStrategyFormation(
    data=data, signals=SIGNALS,
    holding_period=HP, num_portfolios=NPORT,
    turnover=True, rating=RATING,
    columns={'ID': 'cusip', 'ret': 'ret_vw', 'VW': 'mcap_e', 'RATING_NUM': 'spc_rat'},
    n_jobs=-2, verbose=False,
)
results = batch.fit()

report_path = ResultsReporter(results, mnemonic=MNEMONIC, script_text=SCRIPT).generate()

## Print summary table
rows = []
for sig in SIGNALS:
    r = results[sig]
    ew_ls, vw_ls = r.get_long_short()
    for label, ls in [('EW', ew_ls), ('VW', vw_ls)]:
        valid = ls.dropna()
        ann_mean = valid.mean() * 12 * 100
        sr = valid.mean() / valid.std() * np.sqrt(12) if valid.std() > 0 else 0
        nw_lag = int(len(valid) ** 0.25)
        t = compute_nw_tstat(valid, nw_lag=nw_lag)[0]
        turn = '—'
        try:
            ew_t, vw_t = r.get_turnover()
            td = ew_t if label == 'EW' else vw_t
            if td is not None and not td.empty:
                turn = f'{td.mean().mean() * 100:.1f}'
        except: pass
        rows.append((sig, label, ann_mean, t, sr, turn))

print('|Signal|Weight|Mean(%)|t-stat|SR|Turn(%)|')
print('|---|---|---|---|---|---|')
for sig, wt, m, t, sr, turn in rows:
    print(f'|{sig}|{wt}|{m:.2f}|{t:.2f}|{sr:.3f}|{turn}|')
first_sig = SIGNALS[0]
r0 = results[first_sig]
ew0, _ = r0.get_long_short()
print(f'Sample: {ew0.dropna().index[0].strftime("%Y-%m")} to {ew0.dropna().index[-1].strftime("%Y-%m")}')
print(f'Report: {report_path}')

Template: Batch WithinFirmSort

Same as above but replace BatchStrategyFormation with:

Python
batch = pbl.BatchWithinFirmSortFormation(
    data=data, signals=SIGNALS,
    firm_id_col='permno',
    min_bonds_per_firm=2,
    turnover=True, rating=RATING,
    columns={'ID': 'cusip', 'ret': 'ret_vw', 'VW': 'mcap_e', 'RATING_NUM': 'spc_rat'},
    n_jobs=-2, verbose=False,
)

Mnemonic: batchwfs_{n}s instead of batch_{n}s.

Template: Single Sort

For mode=single, run one signal at a time with StrategyFormation:

Python
strategy = pbl.SingleSort(holding_period=HP, sort_var=SIGNAL, num_portfolios=NPORT)
sf = pbl.StrategyFormation(data, strategy=strategy, turnover=True, rating=RATING, verbose=False)
result = sf.fit(IDvar='cusip', RETvar='ret_vw', VWvar='mcap_e', RATINGvar='spc_rat')
report_path = ResultsReporter(result, mnemonic=f'{SIGNAL}_single_{NPORT}', script_text=SCRIPT).generate()

Mnemonic Convention

Mode Pattern Example
batch batch_{n}s batch_3s
batch --wfs batchwfs_{n}s batchwfs_3s
single {signal}_single_{nport} cs_single_5

When NOT to Use /run

Use the pybondlab-orchestrator agent instead when: - Working with new/unfamiliar data (needs column discovery and mapping) - Running DoubleSort (needs sort_var/sort_var2 ordering decisions) - Running DataUncertaintyAnalysis or AssayAnomaly (complex config) - Debugging errors from a previous run