TradeForge is a local paper trading and historical backtesting lab. It imports price data, runs simulated strategies, stores results in SQLite, values simulated positions with optional live quotes, and automates local maintenance.
TradeForge does not connect to a brokerage account and does not place live trades.
Warning
TradeForge is research software. It is not financial advice. Backtest results are not a promise of future performance. API authentication is disabled by default, so keep the API on your own computer unless you deliberately enable API keys.
- What TradeForge does
- Before you start
- Windows installation
- First backtest
- Portfolio and research workflows
- Understanding results
- Files and folders
- Configuration
- Importing your own price data
- Using live quotes
- Using the local API
- Automated maintenance
- Health checks and failure recovery
- Backups and database restoration
- Corporate actions
- Docker setup
- Updating, resetting, and removing TradeForge
- Troubleshooting
- Command reference
- Developer validation
- Further documentation
TradeForge currently provides:
- a Windows friendly command line interface
- historical OHLCV CSV import with data quality checks
- bundled AAPL sample data for a no credential first run
- a moving average crossover strategy
- allocated multi symbol portfolio backtests
- event ordered bars, ticks, news, and system messages
- rolling risk, beta, factor, and market regime analytics
- immutable experiment records with dataset and report hashes
- allowlisted strategy, broker, indicator, and report plugins
- market, limit, stop, and stop limit order simulation
- configurable commissions, slippage, volume limits, and quantity increments
- position, exposure, drawdown, order size, and kill switch risk controls
- SQLite storage with Alembic schema migrations
- Markdown backtest reports
- optional Alpaca live quote retrieval for local valuation
- a read only FastAPI service and local dashboard with optional tenant API keys
- unattended import processing, backups, restore drills, reports, retries, and alerts
- a Windows Task Scheduler installer for daily maintenance
The current limitations matter:
- only one built in strategy is available
- portfolio runs isolate capital into one single symbol engine per allocation
- execution uses completed OHLCV bars, not an exchange order book
- money is stored as floating point values
- live quote retrieval supports Alpaca only
- API authentication is opt in and the API has no pagination or versioning
See ASSESSMENT.md for the current one minute project status.
- Repository means the
paper-trade-local-labproject folder. - PowerShell means the blue or black command window where you type commands.
- Project root means the repository folder containing
README.mdandpyproject.toml. - Virtual environment means the private
.venvPython installation used only by this project. - CLI means the
tradeforgecommand line program. - OHLCV means open, high, low, close, and volume price data.
- Paper trading means simulated trading with no real orders or money.
Unless a step says otherwise, run every command from the project root with the virtual environment activated.
Install these programs before continuing:
- Git for Windows.
- Python 3.11, 3.12, 3.13, or 3.14. Python 3.13 is a good default.
- Node.js with npm. The bootstrap process uses npm for Markdown validation tools.
- PowerShell. Windows PowerShell 5.1 works, but PowerShell 7 is recommended.
Docker Desktop is optional. It is needed only for the container setup.
Open PowerShell and verify the required tools:
git --version
py --version
node --version
npm --versionEach command must print a version. If a command is not recognized, install or repair that program, close PowerShell, open a new PowerShell window, and try again.
List the Python versions Windows can find:
py -0pUse one of the supported versions shown in that list.
- Never commit
.env. It can contain credentials. - Never commit files under
data/. They can contain market data and research results. - Never expose port 8000 to the internet without an authenticated reverse proxy.
- Stop the API and scheduled maintenance before manually replacing the database.
- Keep a backup before an upgrade, reset, or manual database restore.
- Do not use research results as the only basis for a financial decision.
Choose a folder where you keep source code. This example uses your Documents folder:
Set-Location "$env:USERPROFILE\Documents"
git clone https://github.com/mickpletcher/paper-trade-local-lab.git
Set-Location .\paper-trade-local-labIf you already cloned the repository, open PowerShell in that folder and confirm the location:
Get-Location
Get-ChildItem README.md, pyproject.tomlBoth files must be listed.
This example uses Python 3.13:
py -3.13 -m venv .venvIf Python 3.13 is not installed, replace 3.13 with another supported version from py -0p.
Activate the environment:
.\.venv\Scripts\Activate.ps1Your prompt should now start with (.venv).
You must activate this environment every time you open a new PowerShell window to work with TradeForge.
Run the repository bootstrap:
python scripts/bootstrap.pyThe bootstrap performs four jobs:
- installs the locked Pip version
- installs TradeForge and its development tools into
.venv - installs the locked Node validation tools
- runs
tradeforge doctorto compare the environment with the lock
Installation is successful when the command ends without a red traceback and the final doctor output reports a healthy environment and verified provenance.
Copy the safe example configuration:
Copy-Item .env.example .envThe sample backtest does not need an Alpaca account or API credentials. Leave the two Alpaca credential values blank until you want live quote refresh.
The .env file is ignored by Git. Do not override that protection.
Create or migrate the SQLite database:
tradeforge init-db
tradeforge db-currentThe first command should print:
Initialized TradeForge database.
The second command prints JSON. current_version and head_version should match. The database is stored at data\tradeforge.db unless .env changes its location.
Load the bundled AAPL dataset:
tradeforge seed-sample-dataExpected result:
Seeded 8 sample bars for AAPL.
It is safe to run this command again. Existing bars for the same timestamps are updated.
tradeforge --help
tradeforge doctorThe help command should show the TradeForge command list. Doctor exits with code 0 only when the active environment matches the lock and the lock provenance file is valid.
You can display the last exit code in PowerShell:
$LASTEXITCODE0 means success. Any other value means the command reported a problem.
Run this exact sample from the project root:
tradeforge run-backtest --strategy moving-average-cross --symbol AAPL --start 2023-01-01 --end 2023-01-08 --short-window 2 --long-window 3 --order-size 2The command prints a JSON object with four top level fields:
strategy_run_idis the unique ID for this backtest.experiment_idis the immutable input and artifact provenance record.metricscontains the calculated performance values.report_pathpoints to the generated Markdown report.
The report is written to data\reports\<strategy_run_id>.md.
Inspect the simulated activity:
tradeforge show-orders
tradeforge show-positions
tradeforge show-pnlOpen the newest Markdown report in Notepad:
$latestBacktestReport = Get-ChildItem .\data\reports\*.md |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
notepad $latestBacktestReport.FullNameThe sample is deliberately small. It proves that installation, migrations, data import, strategy execution, simulated orders, result persistence, and report generation work. It is not a meaningful strategy evaluation.
Load another sample symbol before trying the portfolio command:
tradeforge seed-sample-data --symbol MSFTRun the same strategy across two independently funded sleeves:
tradeforge run-portfolio-backtest --symbol AAPL --symbol MSFT --start 2023-01-01 --end 2023-01-08 --total-cash 100000 --short-window 2 --long-window 3 --order-size 2The default equal allocation gives each symbol half of the cash. Use fixed weights when you need an explicit allocation. The JSON must contain every requested symbol and the values must total exactly 1:
tradeforge run-portfolio-backtest --symbol AAPL --symbol MSFT --start 2023-01-01 --end 2023-01-08 --total-cash 100000 --allocation fixed --weights-json '{"AAPL":0.7,"MSFT":0.3}' --short-window 2 --long-window 3 --order-size 2Portfolio output includes total starting cash, ending equity, return, per symbol allocations, one strategy run and report per symbol, and the number of processed lifecycle events. Each sleeve has isolated cash. All sleeves commit together. If one symbol fails, TradeForge rolls back every sleeve and removes reports created by the failed portfolio. This is not yet a shared account where one symbol can consume another symbol's unused capital.
Every completed backtest automatically creates an immutable experiment record. It hashes the exact stored bars used by the run and the generated report. List the records without exposing report contents:
tradeforge show-experimentsCalculate rolling risk, beta, and market regimes from stored bars:
tradeforge analyze-symbol --symbol AAPL --benchmark-symbol MSFT --window 2Use a larger window, such as 20, with real daily history. TradeForge matches asset and benchmark bars by timestamp and ignores dates that are not present in both series. At least two matching timestamps are required. The result labels each price as insufficient, bull, bear, sideways, or high_volatility based on the configured rolling window.
Confirm the vectorized moving average path stays inside its runtime budget:
tradeforge benchmark-performance --rows 100000 --maximum-seconds 5List built in plugins and available connector adapters:
tradeforge list-plugins
tradeforge list-connectorsConnector entries describe request and normalization adapters only. live_order_routing is false for every connector. TradeForge never transmits the paper signals they produce.
The most important report fields are:
| Field | Meaning |
|---|---|
starting_cash |
Simulated cash available when the run began. |
ending_equity |
Final cash plus the last bar value of any open position. |
total_return |
Percentage change expressed as a decimal. 0.05 means 5 percent. |
cagr |
Annualized compound return. Very short tests can make this misleading. |
volatility |
Annualized variation in periodic returns. Higher is less stable. |
sharpe_ratio |
Return divided by total variability using a zero risk free rate. |
sortino_ratio |
Return divided by downside variability using a zero risk free rate. |
max_drawdown |
Largest peak to trough equity decline. -0.10 means a 10 percent decline. |
number_of_fills |
Individual simulated executions. One order can have several fills. |
number_of_trades |
Fully closed position lifecycles. |
open_trades |
Position lifecycles still open at the end. |
win_rate |
Winning closed trades divided by all closed trades. |
profit_factor |
Gross winning P/L divided by gross losing P/L. |
exposure |
Fraction of snapshots where capital was in a position. |
buy_and_hold_return |
Return from holding the symbol over the same dates. |
realized_pnl |
Profit or loss from closed simulated positions. |
unrealized_pnl |
Marked profit or loss still held at the final historical bar. |
A null value is intentional when a ratio cannot be calculated honestly. For example, profit factor is null when there are no losing trades.
Before trusting a comparison, use enough data, include realistic fees and slippage, inspect open trades, compare against buy and hold, and test periods that were not used to choose the parameters.
| Path | Purpose | Commit it? |
|---|---|---|
.env.example |
Safe inventory of supported settings. | Yes. |
.env |
Your local settings and optional secrets. | No. |
.venv\ |
Project specific Python environment. | No. |
data\tradeforge.db |
Default SQLite database. | No. |
data\reports\ |
Generated backtest reports. | No. |
data\imports\ |
Pending files for automated import. | No. |
data\imports\processed\ |
Timestamped successful imports. | No. |
data\imports\quarantine\ |
Failed imports and error details. | No. |
data\backups\ |
Integrity checked database backups. | No. |
data\automation\latest.json |
Latest maintenance result. | No. |
docs\ |
Detailed product and operator documentation. | Yes. |
src\tradeforge\ |
Application source code. | Yes. |
tests\ |
Automated tests. | Yes. |
Local data and secrets are excluded through .gitignore and .dockerignore. Check git status --short before every commit.
TradeForge reads .env when a CLI or API process starts. Stop and restart a long running process after changing .env.
The copied .env.example works for sample data, local backtesting, and maintenance when there are no open positions. The main defaults are:
Starting cash: $100,000
Fixed fee per order: $1.00
Slippage: 1 basis point
Bar fill limit: 25 percent of volume
Quantity increment: 1 whole share
Database: data/tradeforge.db
Backup retention: 7 files
Report retention: 30 timestamped reports
API metrics: disabled
Edit .env in VSCode or Notepad. These settings change simulated execution:
TRADEFORGE_STARTING_CASHsets initial simulated cash.TRADEFORGE_COMMISSION_MODELacceptsfixedorper_share.TRADEFORGE_FEE_PER_ORDERsets the fixed commission.TRADEFORGE_COMMISSION_PER_SHAREandTRADEFORGE_COMMISSION_MINIMUMconfigure per share commissions.TRADEFORGE_SLIPPAGE_BPSsets default adverse fill movement in basis points.TRADEFORGE_SYMBOL_SLIPPAGE_RULES_JSONsets ticker specific overrides such as{"AAPL": 2.5}.TRADEFORGE_MAX_BAR_FILL_RATIOcaps aggregate fills against one bar from0through1.TRADEFORGE_QUANTITY_INCREMENTdefaults to1for whole shares.
The following settings reject simulated orders outside the declared policy:
TRADEFORGE_RISK_MAX_ORDER_NOTIONALTRADEFORGE_RISK_MAX_POSITION_QUANTITYTRADEFORGE_RISK_MAX_GROSS_EXPOSURETRADEFORGE_RISK_MAX_DRAWDOWN_RATIOTRADEFORGE_RISK_KILL_SWITCH
Set TRADEFORGE_RISK_KILL_SWITCH=true to reject new simulated execution. This does not delete existing research data.
See the configuration reference for every setting and valid range.
The first line must contain these exact lowercase column names:
date,open,high,low,close,volume
2026-01-02,100.00,103.00,99.50,102.25,1250000
2026-01-03,102.20,104.10,101.80,103.75,1100000Each row must meet these rules:
datemust be parseable as a date or datetime- prices must be finite and greater than zero
highmust not be belowlowopenandclosemust be betweenlowandhighvolumemust be a nonnegative whole number- extreme returns beyond the configured quality threshold are rejected
TradeForge sorts rows by date, normalizes timestamps, repairs safe duplicate timestamp cases, records quality findings, and updates an existing symbol and timestamp instead of creating a duplicate bar.
Import a file immediately:
tradeforge import-csv --symbol MSFT --file "C:\MarketData\msft-daily.csv"Success looks like:
Imported 250 bars for MSFT.
Use your actual row count and path. Then run a backtest with dates covered by that file:
tradeforge run-backtest --strategy moving-average-cross --symbol MSFT --start 2025-01-01 --end 2025-12-31For unattended import, name each file after its ticker and copy it to data\imports:
Copy-Item "C:\MarketData\MSFT.csv" .\data\imports\MSFT.csv
tradeforge run-maintenanceSuccessful files move to data\imports\processed with a UTC timestamp and SHA-256 recorded in the maintenance report.
Failed files move to data\imports\quarantine. A matching .error.json sidecar explains the failure. Maintenance exits with code 1 so a scheduler or monitoring tool can detect it.
Live quotes are optional and are used only for local valuation. They do not send orders to Alpaca.
Open .env and set:
TRADEFORGE_ALPACA_API_KEY_ID=your_key_id
TRADEFORGE_ALPACA_API_SECRET_KEY=your_secret_key
TRADEFORGE_ALPACA_FEED=iex
Do not add quotes around the values. Do not paste the completed .env into an issue, chat, report, or commit.
The symbol must already exist through sample data or CSV import:
tradeforge refresh-quotes --symbol AAPL
tradeforge show-quotes
tradeforge show-valuationRefresh more than one symbol by repeating the option:
tradeforge refresh-quotes --symbol AAPL --symbol MSFTWithout --symbol, TradeForge refreshes symbols with open positions. It fails clearly when neither explicit symbols nor open positions exist.
Quote output distinguishes market timestamp age from retrieval age and marks stale data. Retries use bounded exponential backoff with jitter. Repeated provider failures open a persistent circuit breaker that closes itself after the configured reset period.
Start the API from an activated environment:
tradeforge start-apiKeep that PowerShell window open. Use Ctrl+C to stop the server.
Open these local pages in a browser:
- API documentation:
http://127.0.0.1:8000/docs - API health:
http://127.0.0.1:8000/health - local dashboard:
http://127.0.0.1:8000/dashboard
You can also test health from another PowerShell window:
Invoke-RestMethod http://127.0.0.1:8000/healthCurrent read only endpoints include:
/health/symbols/quotes/portfolio/positions/orders/strategy-runs/experiments/dashboard/metricswhen metrics are enabled
Use a different local port if 8000 is occupied:
tradeforge start-api --port 8001Authentication is disabled by default for the simplest loopback only first run. Enable it before allowing another device to reach the API.
Create a tenant and a least privilege API identity:
$tenant = tradeforge create-tenant --name personal | ConvertFrom-Json
$identity = tradeforge create-api-key --tenant-id $tenant.id --name local-dashboard --role viewer | ConvertFrom-Json
$identity.api_keyCopy the printed tf_... secret into a password manager. TradeForge stores only a per key salted PBKDF2-HMAC-SHA-256 verifier and cannot display the secret again. Do not place the secret in Git, screenshots, shell transcripts, URLs, or issue comments.
Set this value in .env, then restart the API:
TRADEFORGE_API_AUTH_ENABLED=true
The health and interactive documentation routes remain public. Other routes require the configured X-TradeForge-Key header. Test the new identity from PowerShell:
$headers = @{ "X-TradeForge-Key" = $identity.api_key }
Invoke-RestMethod http://127.0.0.1:8000/positions -Headers $headersRoles are cumulative:
viewerreads tenant scoped research data and the dashboard.operatoralso reads process metrics when metrics are enabled.admincurrently has the same read access and is reserved for future administrative endpoints.
Each key can see only positions, orders, runs, experiments, and valuation for its tenant. Symbols and quotes are shared market data. Give each automated process its own key so you can revoke it without interrupting another process.
Rotate a key before it expires:
$replacement = tradeforge rotate-api-key --api-key-id $identity.id | ConvertFrom-Json
$replacement.api_keyRotation revokes the old key immediately. Update the caller with the replacement secret before its next request. Revoke a compromised or retired key:
tradeforge revoke-api-key --api-key-id $replacement.id
tradeforge show-api-keys --tenant-id $tenant.idThe metadata listing never returns stored secrets. Keys expire after 90 days by default. Change TRADEFORGE_API_KEY_ROTATION_DAYS before issuing a key or pass --expires-in-days to the creation or rotation command.
Do not use --host 0.0.0.0 on a normal workstation. API keys do not provide TLS. If the service must cross a network, put it behind an HTTPS reverse proxy and retain the API key requirement. Compose uses 0.0.0.0 inside the container but binds the host side to loopback only.
One command performs the complete unattended workflow:
tradeforge run-maintenanceIt performs these actions in order:
- acquires an atomic maintenance lock
- creates or migrates the configured database
- imports every
data\imports\<TICKER>.csv - archives successful imports and quarantines failures
- refreshes live quotes for open positions
- checks SQLite integrity, lock response, and WAL state
- creates and integrity checks an online SQLite backup
- restores that backup into memory as a recovery drill
- applies backup and report retention
- writes a timestamped report and
data\automation\latest.json
A failed run exits with code 1 and still tries to write a detailed local report. Optional HTTPS webhook, Teams, and SMTP settings can report failures without exposing the full local report.
If open positions exist, configure Alpaca credentials before scheduling maintenance. Quote refresh is part of the same fail closed workflow.
$maintenance = Get-Content .\data\automation\latest.json -Raw | ConvertFrom-Json
$maintenance.status
$maintenance.backup_path
$maintenance.restore_drillstatus should be success. The restore drill should report verified.
First run maintenance manually and fix any error. Then install the task from an activated environment:
.\scripts\Install-TradeForgeScheduledTask.ps1 -DailyAt "02:00" -RunNowThis creates TradeForge Daily Maintenance. It runs the exact tradeforge.exe from the current virtual environment with the repository as its working directory.
The task:
- runs every day at the selected local time
- starts a missed run when the computer becomes available
- retries a failed run three times at five minute intervals
- stops a run after one hour
Do not move the repository or delete .venv while the task is installed. Reinstall the task after either path changes.
If registration returns Access Denied, open PowerShell as Administrator, activate the environment, return to the project root, and repeat the installer command.
Get-ScheduledTask -TaskName "TradeForge Daily Maintenance"
Get-ScheduledTaskInfo -TaskName "TradeForge Daily Maintenance"LastTaskResult equal to 0 means the last completed run succeeded. Also verify data\automation\latest.json because it contains the application level result.
Start a test run:
Start-ScheduledTask -TaskName "TradeForge Daily Maintenance"
Start-Sleep -Seconds 5
Get-ScheduledTaskInfo -TaskName "TradeForge Daily Maintenance"Disable-ScheduledTask -TaskName "TradeForge Daily Maintenance"
Enable-ScheduledTask -TaskName "TradeForge Daily Maintenance"
Unregister-ScheduledTask -TaskName "TradeForge Daily Maintenance" -Confirm:$falseThe removal command deletes only the scheduled task. It does not delete the repository, database, reports, or backups.
tradeforge health
$LASTEXITCODEHealth is healthy only when the database integrity check passes and the latest maintenance report says success. Before the first maintenance run, attention_required is expected even if installation is correct.
The health output also reports:
- database existence, integrity, and journal mode
- latest maintenance details
- backup count and newest backup
- pending and quarantined import counts
- whether the maintenance lock exists
Use the exit code in scripts and monitoring. 0 is healthy. 1 requires attention.
List failed files:
Get-ChildItem .\data\imports\quarantineRead the matching error sidecar:
Get-Content ".\data\imports\quarantine\<timestamp>-MSFT.csv.error.json"Replace <timestamp>-MSFT.csv with the actual filename. Correct the quarantined CSV, then return it to the pending queue:
tradeforge acknowledge-import --file "<timestamp>-MSFT.csv" --retry
tradeforge run-maintenanceTradeForge restores the original ticker filename from the error sidecar before retrying it.
If the file should not be retried, acknowledge and archive it:
tradeforge acknowledge-import --file "<timestamp>-MSFT.csv"- Run
tradeforge health. - Open
data\automation\latest.json. - Fix the specific reported problem.
- Check
data\imports\quarantineif an import failed. - Check Alpaca credentials and connectivity if quote refresh failed.
- Run
tradeforge run-maintenancemanually. - Confirm
tradeforge healthreturns exit code 0.
Do not silently delete data\automation\maintenance.lock. Another process may still own it. Stop duplicate TradeForge processes first. The lock automatically recovers after its configured stale interval when the recorded owner is no longer valid.
Every successful maintenance run creates data\backups\tradeforge-<UTC timestamp>.db. The backup is created with SQLite's online backup API and passes an integrity check before its temporary file is promoted.
Maintenance also restores the new backup into memory, checks integrity and application table presence, and records recovery time. This drill proves the file can be opened. It does not replace the active database.
The default retention is the newest seven backups. Change TRADEFORGE_BACKUP_RETENTION_COUNT in .env to keep from 1 through 365.
List backups newest first:
Get-ChildItem .\data\backups\tradeforge-*.db |
Sort-Object LastWriteTime -DescendingRun a separate recovery objective drill at any time:
tradeforge run-dr-drillThe command selects the newest backup, restores it into memory, verifies integrity and application tables, measures the backup age as recovery point age, measures restore duration, and writes data\automation\dr-latest.json. It returns exit code 1 when either target is missed. Defaults are a 24 hour recovery point objective and a 60 second recovery time objective:
TRADEFORGE_DR_RPO_TARGET_SECONDS=86400
TRADEFORGE_DR_RTO_TARGET_SECONDS=60
This is a local recoverability drill. It does not protect against loss of the entire workstation. Copy verified backups to encrypted off device storage using a separate approved process.
Use this only when the active database is damaged or you intentionally need an older state.
-
Disable the scheduled task if it exists.
Disable-ScheduledTask -TaskName "TradeForge Daily Maintenance"
-
Stop the API with
Ctrl+Cand close any other TradeForge processes. -
Select the newest backup and confirm the path.
$restoreSource = Get-ChildItem .\data\backups\tradeforge-*.db | Sort-Object LastWriteTime -Descending | Select-Object -First 1 $restoreSource.FullName
-
Move the current database and its SQLite companion files into a dated archive before restoring.
$restoreStamp = Get-Date -Format "yyyyMMdd-HHmmss" $preRestoreArchive = ".\data\before-restore-$restoreStamp" New-Item -ItemType Directory -Path $preRestoreArchive Get-ChildItem .\data\tradeforge.db* -ErrorAction SilentlyContinue | Move-Item -Destination $preRestoreArchive
-
Copy the selected backup into the active database path.
Copy-Item -LiteralPath $restoreSource.FullName -Destination .\data\tradeforge.db -Force
-
Upgrade the restored schema if needed and verify it.
tradeforge init-db tradeforge db-current tradeforge run-maintenance tradeforge health
-
Reenable scheduling after health returns exit code 0.
Enable-ScheduledTask -TaskName "TradeForge Daily Maintenance"
The before-restore archive is ignored by Git. Keep it until the restored database is proven good.
For off device protection, copy verified backup files to storage you control. TradeForge does not currently encrypt or upload backups.
Corporate actions are persistent research inputs. Record them only when the symbol already exists and the effective date belongs in the historical dataset.
Record a two for one split:
tradeforge record-corporate-action --symbol AAPL --type split --effective-at 2025-06-01 --ratio 2Record a per share cash dividend:
tradeforge record-corporate-action --symbol AAPL --type dividend --effective-at 2025-08-15 --cash-amount 0.25Record a ticker change:
tradeforge record-corporate-action --symbol ABC --type symbol_change --effective-at 2025-09-01 --new-ticker XYZRecord a delisting with a simulated liquidation value per share:
tradeforge record-corporate-action --symbol XYZ --type delisting --effective-at 2025-10-01 --cash-amount 5.00Backtests apply actions in timestamp order. Splits adjust positions and pending orders. Dividends credit simulated cash. Symbol changes update the ticker. Delistings liquidate the position, cancel pending orders, close the trade, and stop later strategy execution for that symbol.
Back up the database before experimenting with corporate action records. There is no public delete command.
Use Docker when you want an isolated API service instead of a local virtual environment. Docker Desktop must be installed and running.
Copy-Item .env.example .env -ErrorAction SilentlyContinue
docker compose up --build --detach
docker compose psCompose builds the image, starts the API, binds it to 127.0.0.1:8000, uses a named volume for /app/data, restarts after failure or reboot, runs as a nonroot user, and uses a read only root filesystem.
Open http://127.0.0.1:8000/docs or verify health:
Invoke-RestMethod http://127.0.0.1:8000/healthdocker compose exec tradeforge tradeforge seed-sample-data
docker compose exec tradeforge tradeforge run-backtest --strategy moving-average-cross --symbol AAPL --start 2023-01-01 --end 2023-01-08 --short-window 2 --long-window 3 --order-size 2
docker compose exec tradeforge tradeforge run-maintenance
docker compose exec tradeforge tradeforge healthdocker compose logs --tail 100 tradeforge
docker compose downdocker compose down stops and removes the container but preserves the named data volume.
This command also deletes the named volume and all databases, reports, imports, and backups inside it:
docker compose down --volumesDo not run the volume deletion command unless you have intentionally exported anything you need.
Check for local changes first:
git status --shortDo not overwrite work shown by that command. Stop the API, allow maintenance to finish, activate .venv, and create a current backup:
tradeforge run-maintenanceThen update and validate:
git pull --ff-only
python scripts/bootstrap.py
tradeforge init-db
tradeforge db-current
tradeforge doctor
tradeforge run-maintenance
tradeforge healthIf the scheduled task points to a moved repository or recreated virtual environment, reinstall it.
Stop the API and disable scheduled maintenance first. Move the current database and its SQLite companion files into a dated archive:
$resetStamp = Get-Date -Format "yyyyMMdd-HHmmss"
$resetArchive = ".\data\reset-$resetStamp"
New-Item -ItemType Directory -Path $resetArchive
Get-ChildItem .\data\tradeforge.db* -ErrorAction SilentlyContinue |
Move-Item -Destination $resetArchive
tradeforge init-db
tradeforge seed-sample-dataThis keeps the previous database files under data\reset-<timestamp> so the reset can be reversed.
- Unregister the scheduled task if you installed it.
- Stop the API and deactivate the environment with
deactivate. - Copy any database, report, or backup you need outside the repository.
- Delete the repository folder through File Explorer.
TradeForge has no cloud account or hosted service to cancel. Alpaca credentials remain managed in your Alpaca account.
Activate the environment and retry:
Set-Location C:\path\to\paper-trade-local-lab
.\.venv\Scripts\Activate.ps1
tradeforge --helpIf the command is still unavailable, run:
python -m tradeforge.cli --help
python scripts/bootstrap.pyAllow scripts only for the current PowerShell process, then activate again:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\.venv\Scripts\Activate.ps1This does not change the machine wide execution policy.
Install Node.js with npm, close PowerShell, open a new window, activate .venv, and rerun python scripts/bootstrap.py.
First rerun the bootstrap. If drift remains, preserve the old environment and create a clean one:
deactivate
Move-Item .venv .venv-old
py -3.13 -m venv .venv
.\.venv\Scripts\Activate.ps1
python scripts/bootstrap.pyDelete .venv-old through File Explorer only after the clean environment passes tradeforge doctor.
tradeforge init-db
tradeforge db-currentThe current and head migration versions should match.
Load the sample or import your file before running the backtest:
tradeforge seed-sample-data --symbol AAPL
tradeforge import-csv --symbol MSFT --file "C:\MarketData\msft-daily.csv"Use ISO dates, make the start earlier than the end, and choose dates covered by the imported file. The bundled sample covers 2023-01-01 through 2023-01-08.
Set both credential values in .env, save the file, and run the command again in a new process. Never print the values while troubleshooting.
The provider failed repeatedly. Read data\automation\quote-circuit.json, fix the credential or connectivity problem, and wait for TRADEFORGE_QUOTE_CIRCUIT_RESET_SECONDS before retrying. Do not create an aggressive retry loop.
This is expected before the first maintenance run. Otherwise read data\automation\latest.json, resolve the reported error, run maintenance again, and repeat health.
Check Task Manager and Task Scheduler for another TradeForge process. Wait for it to finish. A valid lock prevents overlapping imports and backups. Stale lock recovery is automatic when the owner is gone and the configured stale interval has passed.
Stop duplicate API, CLI, and scheduled processes. Wait for the current operation to finish, then retry. TRADEFORGE_SQLITE_BUSY_TIMEOUT_MS controls the bounded wait and defaults to 5000 milliseconds.
Start on another loopback port:
tradeforge start-api --host 127.0.0.1 --port 8001Copy-Item .env.example .env
docker compose up --build --detachActivate the same virtual environment, run tradeforge run-maintenance manually, and read data\automation\latest.json. The manual run exposes the same failure without waiting for Task Scheduler.
Run the full bootstrap or install the declared Node packages:
npm ci --ignore-scripts
.\scripts\Test-Markdown.ps1If the problem is not covered here, read SUPPORT.md before opening an issue. Remove credentials, private data, and local paths from any shared logs.
| Command | Purpose |
|---|---|
tradeforge init-db |
Create or migrate the configured database. |
tradeforge db-current |
Show current and head database revisions. |
tradeforge db-revision |
Developer command to create an Alembic revision. |
tradeforge import-csv |
Import one OHLCV CSV immediately. |
tradeforge seed-sample-data |
Load the bundled eight row AAPL sample. |
tradeforge run-backtest |
Run one historical strategy simulation. |
tradeforge run-portfolio-backtest |
Run allocated backtests across repeated symbols. |
tradeforge analyze-symbol |
Calculate rolling risk, beta, factor, and regime analytics. |
tradeforge benchmark-performance |
Enforce the vectorized signal runtime budget. |
tradeforge list-plugins |
List built in and explicitly allowlisted plugins. |
tradeforge list-connectors |
List connector capabilities and routing safety state. |
tradeforge refresh-quotes |
Retrieve and store Alpaca quotes. |
tradeforge run-maintenance |
Run imports, quotes, health checks, backup, restore drill, and reporting. |
tradeforge run-dr-drill |
Measure latest backup recovery point and recovery time objectives. |
tradeforge create-tenant |
Create an isolated research and API tenant. |
tradeforge create-api-key |
Issue a time limited least privilege API identity secret once. |
tradeforge rotate-api-key |
Revoke one key and issue its replacement. |
tradeforge revoke-api-key |
Revoke an API identity immediately. |
tradeforge show-api-keys |
List identity metadata without secrets. |
tradeforge show-experiments |
List immutable backtest provenance records. |
tradeforge record-corporate-action |
Store a split, dividend, symbol change, or delisting. |
tradeforge acknowledge-import |
Archive or retry one quarantined import. |
tradeforge health |
Return exit coded database and automation health. |
tradeforge doctor |
Compare the active environment with the lock and provenance. |
tradeforge start-api |
Start the local read only FastAPI service. |
tradeforge show-quotes |
Print stored live quotes. |
tradeforge show-valuation |
Value one strategy run from stored quotes. |
tradeforge show-positions |
Print simulated positions. |
tradeforge show-orders |
Print simulated orders. |
tradeforge show-pnl |
Print strategy run profit and loss summaries. |
Get exact options for any command:
tradeforge run-backtest --help
tradeforge run-maintenance --helpThe bootstrap installs all declared validation tools. Before handing off a change, run:
python -m ruff check .
python -m ruff format --check .
python -m mypy
python -m pytest -q --cov=tradeforge --cov-report=term-missing
.\scripts\Test-Markdown.ps1
.\scripts\Test-ProjectGovernance.ps1 -CheckWorkingTreeTests treat warnings as errors and enforce at least 88 percent statement coverage. The governance check requires every repository change to update the four living root files:
See CONTRIBUTING.md before changing source code, migrations, workflows, or project policy.
Use these entry points after completing the first run:
- Documentation hub
- Installation details
- Configuration reference
- Automation operations
- Backtesting rules
- Market data behavior
- Database behavior
- Architecture
- Security policy
- Support guide
- Roadmap
TradeForge is licensed under the MIT License.