Skip to content

CLI: keel rules update-params -- editing a live rule's params requires raw SQL #797

Description

@eaitbrahim

Problem

There is no supported way to change a stored rule's parameters. keel rules offers
add, seed, promote, demote, disable, enable, list, backtest, lookahead --
all lifecycle, none editing. add only inserts a fresh candidate.

So correcting a parameter on a live rule means hand-editing JSON inside SQLite:

.venv/bin/python -c "import json,sqlite3;d=sqlite3.connect('keel-live.db');\
p=json.loads(d.execute('select params from rules where id=6').fetchone()[0]);\
p['budget_usd']='50';d.execute('update rules set params=? where id=6',(json.dumps(p),));d.commit()"

That bypasses every validation the codebase has: the Rule.decimal_params /
granularity_param / tuple_params coercion tables (#447), parse_products_option's
product validation (which rules seed runs and names as the reason a bad id is refused
before anything is written), and build_rule_from_params, described in keel/agent.py:156
as "THE single boundary".

How this bit, concretely

keel-live.db rule 6 (dca, BTC-USD) carried budget_usd: "25" while
config.live-sandbox.yaml and deploy/live-rules.json both said "50". The divergence is
already documented at keel/execution/executor.py:673:

CAREFUL: the live path sizes DCA from the CONFIG's dca.budget_usd, and ignores the
RULE's own budget_usd [...] a rule row saying 25 while the config says 50 spends 50.
It surprised us once; do not assume the rule's number is what moves.

The consequence named two lines later is the real cost: sim/portfolio_sim.py:593 prefers
context["size_usd"], so the simulator and the live path modelled different position
sizes
-- the backtest was not measuring what the account was doing. Reconciling it
required the raw SQL above, on a live trading database, with no schema check and no audit
trail.

keel/commands/rules.py:1513 already documents a related failure in the same area: a
budget_usd stored as the string "Infinity" yields size_usd=Decimal('Infinity') "with
nothing raising anywhere".

Proposed

keel rules update-params <id> --param key=value [--param ...], which:

  • routes the merged params through build_rule_from_params and refuses if it raises,
    so the coercion tables and product validation apply exactly as they do on seed/add
  • refuses Infinity/NaN for any decimal_params member (the #1513 hole)
  • warns when the edited key is one the live path ignores in favour of config --
    dca.budget_usd being the known case -- naming both values, so an operator cannot
    "fix" a number that does not move
  • requires confirmation for a rule at status=live, like the other dangerous verbs
  • records the before/after in the audit chain

Also worth considering

A doctor check for the divergence itself: config dca.budget_usd, the stored rule row,
and deploy/live-rules.json disagreeing is exactly the class of silent skew doctor
exists to surface, and it would have caught this without anyone looking.

Found while diagnosing why the live loop had placed no order since 2026-08-21.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew capability (groups under Features)toolingDev/release tooling (Docs, CI & tooling)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions