Environment variables
Declared but not wired up
.env.example documents these three. Each is a reasonable idea; none is implemented.
| Variable | Intended effect | Actual status |
|---|---|---|
LUXALGO_PROPFIRMS_URL | Override the prop-firm directory URL | Not read. propfirm/firms.py hardcodes DEFAULT_URL. The module imports os but never uses it |
BACKTEST_MAX_WORKERS | Cap concurrent pinets-cli subprocesses | Not read. Concurrency comes from max_workers in the sweep YAML config |
BACKTEST_RESULTS_DIR | Choose where sweep artifacts go | Not read. orchestrator/sweep.py writes to a results/<run-id>/ path relative to the project root |
Why a declared-but-unread variable is worse than an absent one
Setting one of these changes nothing, which is the mild half. The expensive half is that each one looks like it should work, so you end up debugging the variable instead of the code that would have to read it.
Each verdict in the table above comes down to one question asked of the module that would have to consume the value:
LUXALGO_PROPFIRMS_URLwould be read inpropfirm/firms.py. That module importsosand never uses it, and the URL is the hardcodedDEFAULT_URLconstant.BACKTEST_MAX_WORKERSwould cap concurrency where the sweep is driven. Concurrency comes frommax_workersin the sweep YAML instead, so a run you believe you have capped still executes at the default of 1.BACKTEST_RESULTS_DIRwould move the output path.orchestrator/sweep.pywrites toresults/<run-id>/relative to the project root, and that is where artifacts land.
The values that do take effect are on the next section.
What actually configures the system
Python — the sweep config file
orchestrator/sweep.py reads its settings from the YAML passed to --config. Worker
concurrency is the relevant one here:
max_workers: 1 # default; >1 switches to a ThreadPoolExecutorThe default is 1, i.e. sequential execution. Setting a value above 1 is what actually
enables parallel subprocess execution, and it changes the results/<run-id>/config.yaml
that gets written alongside the run. Full schema in CLI reference.
The precedence chain for sweep configuration has exactly one link
There is no chain to reason about. The YAML passed to --config is the only source
orchestrator/sweep.py reads settings from, and the environment is not consulted at all — so
an environment variable cannot override a value, and there is nothing for a setting to
out-rank.
What makes that checkable is that the value is recorded with the run. The max_workers you
set is the max_workers written into results/<run-id>/config.yaml beside the artifacts, so
a finished run reports what it actually used rather than what you believe you asked for.
Python — the prop-firm directory
The URL is a module constant, overridable in code rather than by environment:
# propfirm/firms.py
DEFAULT_URL = "https://app.luxalgo.com/api/propfirms/list"To point it elsewhere, change the constant or monkey-patch it. Cache behaviour is a function argument, not an environment variable:
list_firms(force_refresh=False, ttl_s=86_400) # 24-hour TTLPython — locating the Node toolchain
pinets_cli/runner.py resolves the Node installation by probing
%LOCALAPPDATA%\Tools\node-v20 before falling back to whatever is on PATH. This is what
lets the project work with the portable Node that scripts/install.ps1 installs without any
configuration.
The probe order is fixed, and the portable Node wins
pinets_cli/runner.py checks %LOCALAPPDATA%\Tools\node-v20 first and only then falls back to
whatever is on PATH. The order is the point: a portable Node installed by
scripts/install.ps1 is found with no configuration at all, and a PATH Node is the fallback
for the machines that never ran the installer.
If both are present the portable copy is the one that runs. That is also why LOCALAPPDATA and
PATH are the only non-test variables the codebase reads.
JavaScript — browser smoke tests
The Node smoke tests accept a base URL so a harness can point them at a non-default server:
| Variable | Default | Used by |
|---|---|---|
SMOKE_BASE | http://127.0.0.1:8080 | Most __smoke_optimizer_*.cjs suites |
TRAD_BASE | http://localhost:8080/ | The Phase-F smoke suites |
SMOKE_ARTIFACTS | unset | Directory for captured browser artifacts |
Example:
SMOKE_BASE=http://127.0.0.2:8765/ node app/__smoke_optimizer_workspace.cjs.env handling
python-dotenv is a declared dependency, but the codebase does not currently call
load_dotenv(). The .gitignore policy is nonetheless real and is the thing that matters
if you add a credential later:
| Pattern | Ignored? |
|---|---|
.env | yes |
.env.local | yes |
.env.*.local | yes |
.env.example | no — explicitly un-ignored so the template stays tracked |
Setting a real variable
Because nothing calls load_dotenv(), a value must be present in the process environment
rather than only in a .env file:
# PowerShell — current session
$env:MY_VAR = "value"
# PowerShell — persisted for your user account
[Environment]::SetEnvironmentVariable("MY_VAR", "value", "User")Why a `.env` file on its own changes nothing
python-dotenv is a declared dependency, but no entry point calls load_dotenv(). A .env
file is therefore never parsed, and a value has to be in the process environment before the
process starts.
The two commands above differ only in lifetime. The session-scoped one dies with the window it
was typed in. The user-scoped one is written to the account and is there for every new process
— which is the behaviour a .env file would have given you.
The .gitignore policy is independent of all this and holds either way: .env, .env.local
and .env.*.local are ignored, while .env.example is explicitly un-ignored so the template
stays tracked.
Summary
.env.exampleis ahead of the code. Treat it as a wish list.- Python configuration lives in the sweep YAML config, not the environment.
- The only environment variables the code reads today are
LOCALAPPDATA/PATH(Node discovery) and the threeSMOKE_*/TRAD_BASEtest harness variables. .gitignorealready protects.env, so adding a real credential later is safe by default.