Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,10 @@ keel/_build_info.py

# operator's own working config (never the shipped template)
config.local.yaml
# operational paper-forward config (funded rehearsal; never the shipped template)
config.paperforward.yaml
paperforward-run.sh
com.keel.paperforward.plist
# supervised-live sandbox (tiny-cap confirm-mode turtle detector; operational, never shipped)
config.live-sandbox.yaml
keel-live-run.sh
com.keel.live.plist

# operator wrappers (config+db pinned) -- deployment artifacts, copied to ~/keel
keel-live
keel-paper

# The paper-forward and supervised-live deployment files (configs, run scripts, launchd plists,
# operator wrappers) were ignored here until 2026-08-03, on the reasoning that they are
# operational and never shipped. Both are still true -- they are excluded from the wheel and from
# the release assets by the packaging config, not by this file -- but ignoring them meant the only
# copy of the running deployment's configuration lived on one laptop, with no history and no
# offsite copy. They are now tracked. They contain no credentials; those stay in `.env` (line 2).
57 changes: 57 additions & 0 deletions com.keel.live.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.keel.live</string>

<!-- Daily SUPERVISED-LIVE detector. Runs one HEADLESS confirm-mode cycle (places nothing,
fails closed) to check whether a Turtle breakout fired, and notifies if so. Scheduled at
09:05 local -- just after the 09:00 paper-forward, so both see the fresh daily bar.

Fires hourly at :05 through 20:05, plus on load (= login/boot), because launchd re-runs a
missed calendar interval on wake from SLEEP but NOT when the trigger passed while the
machine was powered OFF -- so a shutdown over 09:05 lost the detector for the day
(observed 2026-07-28: booted 09:24, `launchctl print` reported runs = 0). ONCE PER DAY is
enforced by keel-live-run.sh's day-stamp, not by the trigger count: the first run at or
after 09:00 with no stamp for today does the cycle, later ones are no-ops, and a FAILED
cycle leaves no stamp so the next hour retries it.

This changes cadence only. The detector still places nothing: confirm mode fails closed
with no TTY, so approving remains a deliberate interactive `keel-live agent`. -->
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/elmehdiaitbrahim/keel/keel-live-run.sh</string>
</array>

<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>10</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>11</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>12</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>13</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>14</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>15</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>16</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>17</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>19</integer><key>Minute</key><integer>5</integer></dict>
<dict><key>Hour</key><integer>20</integer><key>Minute</key><integer>5</integer></dict>
</array>

<!-- Run on load, so a boot after the 09:05 trigger catches the day up immediately. The
runner's day-stamp makes a repeated load harmless. -->
<key>RunAtLoad</key>
<true/>

<key>WorkingDirectory</key>
<string>/Users/elmehdiaitbrahim/keel</string>

<key>StandardOutPath</key>
<string>/Users/elmehdiaitbrahim/keel/logs/keel-live.detector.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/elmehdiaitbrahim/keel/logs/keel-live.detector.err.log</string>
</dict>
</plist>
54 changes: 54 additions & 0 deletions com.keel.paperforward.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.keel.paperforward</string>

<!-- One paper cycle per day (daily Turtle). The ONCE-PER-DAY property is enforced by
paperforward-run.sh's day-stamp, NOT by having a single trigger here: launchd re-runs a
missed calendar interval when the machine wakes from SLEEP, but NOT when the trigger
passed while it was powered OFF. A shutdown over 09:00 therefore lost the day outright
(observed 2026-07-28: booted 09:24, `launchctl print` reported runs = 0).

So this fires often and the runner decides: 09:00 through 20:00 hourly, plus on load
(= login/boot). The first trigger at or after 09:00 that finds no stamp for today runs
the cycle; every later one is a no-op. A cycle that FAILS leaves no stamp, so the next
hour retries it -- which also covers waking with no network. -->
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/elmehdiaitbrahim/keel/paperforward-run.sh</string>
</array>

<key>StartCalendarInterval</key>
<array>
<dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>10</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>11</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>12</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>13</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>14</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>15</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>16</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>17</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>18</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>19</integer><key>Minute</key><integer>0</integer></dict>
<dict><key>Hour</key><integer>20</integer><key>Minute</key><integer>0</integer></dict>
</array>

<!-- Run on load, so a boot AFTER the 09:00 trigger catches the day up immediately instead of
waiting for the next hour. The runner's day-stamp makes a repeated load harmless, and its
before-09:00 guard stops an early-morning boot from consuming the day. -->
<key>RunAtLoad</key>
<true/>

<key>WorkingDirectory</key>
<string>/Users/elmehdiaitbrahim/keel</string>

<key>StandardOutPath</key>
<string>/Users/elmehdiaitbrahim/keel/logs/paperforward.out.log</string>
<key>StandardErrorPath</key>
<string>/Users/elmehdiaitbrahim/keel/logs/paperforward.err.log</string>
</dict>
</plist>
111 changes: 111 additions & 0 deletions config.live-sandbox.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# keel runtime configuration -- SUPERVISED-LIVE SANDBOX (confirm mode, tiny caps).
#
# Purpose: a small, human-approved live test of the 5-trend Turtle running IN PARALLEL with the
# paper-forward (which stays on config.paperforward.yaml + keel.db, untouched). Every order is
# PREVIEWED and requires a typed `y`. Caps are deliberately tiny so a bad fill can lose only a
# few dollars.
#
# Authored in the dev repo (gitignored). DEPLOYED to ~/keel at arm-time alongside its
# OWN database keel-live.db. There it runs as:
# .venv/bin/keel --config config.live-sandbox.yaml --db keel-live.db agent (interactive)

# The 5-trend Turtle set (same as the paper-forward + the walk-forward/PBO analysis).
# SOL/LTC/LINK are deliberately excluded -- they're the unvalidated skip set.
allowlist:
- BTC
- ETH
- PAXG
- ADA
- XLM

target_weights:
BTC: 0.30
ETH: 0.20
PAXG: 0.20
ADA: 0.15
XLM: 0.15

risk_pct: 0.01 # 1% of the (proxy) equity risked per trade

caps:
# NOTE: these are POSITION/SIZE caps, NOT a monthly spend cap. The MONTHLY spend limit you
# asked for (start $200/mo, scale to your Coinbase Basic $500/mo) is Rail 14, stored in the DB
# (keel-live.db) via `keel subscription set --free-volume-usd 200`, NOT here. To scale it later:
# keel --config config.live-sandbox.yaml --db keel-live.db subscription set --free-volume-usd 500
#
# max_exposure_usd = the TOTAL held-at-once ceiling AND the live sizing equity proxy, so it also
# bounds order size: at risk_pct 0.01 and a ~2xATR stop, a proposed order lands around $60-80.
# Think of it as "this sandbox is a $200 account." Raise it alongside the monthly cap as you scale.
max_exposure_usd: 200 # total open exposure across all positions AT ONCE (NOT per day, NOT per month)
max_per_order_usd: 100 # per-single-order ceiling
max_per_day_usd: 200 # per-rolling-24h ceiling
max_per_asset_pct: 0.50 # no single asset may exceed 50% of the $200 exposure ($100)

market_data:
granularities:
- ONE_DAY
- ONE_HOUR
- FIFTEEN_MINUTE
history_days: 365

auto_trade:
mode: confirm # LIVE, but every order asks for a typed `y` first (fails closed with no human)
enabled: false
interval_sec: 900

promotion:
min_trades: 100
min_expectancy: 0.0
min_rr: 1.5
min_win_rate: 0.55

money_mgmt:
profit_trigger_pct: 0.10
acceleration_pct: 0.05
max_total_dd_pct: 0.20
max_weekly_dd_pct: 0.08
max_consecutive_losses: 0
streak_cooloff_days: 0

dca:
budget_usd: 50
cadence_days: 7

# The settlement currency this deployment TRADES IN -- USD, matching the cached -USD history and
# the paper-forward. (each product spends USD, so the account needs a USD balance to fund a buy;
# rail 13 vetoes if there's no USD available -- see the note when you arm.)
quote_currency: USD

subscription:
assumed_free_volume_usd: 500
unsubscribed_allowance_usd: 0
pacing: opportunistic

tiers:
- name: Basic
free_volume_usd: 500
subscription_usd_month: 4.99
- name: Preferred
free_volume_usd: 10000
subscription_usd_month: 29.99
- name: Premium
free_volume_usd: null
subscription_usd_month: 299.99

fees:
taker_pct: 0.012
maker_pct: 0.006

# verbose: true since 2026-07-29, matching config.paperforward.yaml -- the detector's `signals=N`
# line says nothing about why a setup was or wasn't taken, and on the live side the rail vetoes
# are exactly what you want on record. Separate file from the paper deployment, so the two
# never interleave.
logging:
verbose: true
file: logs/keel-live.log
max_file_mb: 25
file_count: 5

research:
pbo_max: 0.05
slope_floor: -0.5
139 changes: 139 additions & 0 deletions config.paperforward.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# keel runtime configuration.
#
# These are Phase 1 placeholders (spec §21 open item) — no live orders are placed in Phase 1.
# `allowlist` and `caps` are required and validated by keel.config.load_config; missing or
# invalid values raise ConfigError naming the offending key rather than silently defaulting.

allowlist:
- BTC
- ETH
- PAXG
- SOL
- XLM
- LTC
- ADA
- LINK

target_weights:
BTC: 0.30
ETH: 0.20
PAXG: 0.20
SOL: 0.06
XLM: 0.06
LTC: 0.06
ADA: 0.06
LINK: 0.06

risk_pct: 0.01

caps:
# max_per_order_usd / max_per_day_usd are OPTIONAL internal RISK limits, not real Coinbase
# limits -- Coinbase One's only subscription constraint is monthly fee-free trading VOLUME
# (see `subscription:` below). They default to a non-binding $1B when omitted (Issue #85);
# set explicit values here only if you want an extra per-order/per-day risk ceiling tighter
# than the exposure/concentration caps below. Left at their non-binding default here so
# risk-sized rule orders ($400-24k typical) aren't silently rejected.
max_exposure_usd: 5000
max_per_asset_pct: 0.50

market_data:
granularities:
- ONE_DAY
- ONE_HOUR
- FIFTEEN_MINUTE
history_days: 365

auto_trade:
mode: paper
# NOTE: currently UNUSED by any code path -- it is NOT a kill-switch and setting it
# true or false changes nothing. Use `keel kill` to halt trading.
enabled: false
interval_sec: 900

promotion:
min_trades: 100
min_expectancy: 0.0
min_rr: 1.5
min_win_rate: 0.55

money_mgmt:
profit_trigger_pct: 0.10
acceleration_pct: 0.05
max_total_dd_pct: 0.20
max_weekly_dd_pct: 0.08
# Rail 16 (consecutive-loss breaker) — DISABLED by default (0 = off).
# Set from a backtest sweep, and set it ABOVE the strategy's tested max losing streak:
# turtle_breakout's max streak is 5, so a threshold of 3 would fire on normal variance.
max_consecutive_losses: 0
streak_cooloff_days: 0

dca:
budget_usd: 50
cadence_days: 7

paper:
starting_equity_usd: 10000 # FUNDED paper-forward: seed the synthetic account at $10k
monthly_contribution_usd: 500 # $500/mo contribution during the paper-forward

# The settlement currency this deployment TRADES IN. It must match the quote leg of the
# products you actually trade: everything here is `-USD` (see `_default_sim_products` /
# `_history_product`), so this is USD. It is NOT used to decide which balance funds a given
# order -- that comes from the product itself (`quote_currency_of`), because BTC-USD spends USD
# whatever this says. Used to screen candidates and to exclude the settlement balance from
# `keel assets holdings`.
quote_currency: USD

subscription:
# The SIMULATOR's assumed fee-free monthly volume. The LIVE rail-14 cap is not set here --
# it comes from the attested record: `keel subscription attest --venue coinbase --tier <t>`.
assumed_free_volume_usd: 500
# What rail 14 permits on a venue that is unattested, suspect, lapsed, or overdue.
# 0 means such a venue cannot buy at all until it is attested.
unsubscribed_allowance_usd: 0
pacing: opportunistic # opportunistic (monthly cap only) | even_daily (also paces per business day)

# Coinbase One subscription tiers (Issue #86) -- fee-free monthly TRADING VOLUME (buys + sells)
# allowance per tier, used by `keel simulate`'s tier/fee analysis matrix to compare staying
# within a tier's free volume (throttled, 0 trading fees, but you still pay the subscription)
# against trading freely and paying the taker fee on volume EXCEEDING it.
# free_volume_usd: null means unlimited (Premium -- always fee-free, no cap to exceed).
tiers:
- name: Basic
free_volume_usd: 500
subscription_usd_month: 4.99
- name: Preferred
free_volume_usd: 10000
subscription_usd_month: 29.99
- name: Premium
free_volume_usd: null
subscription_usd_month: 299.99

# Coinbase Advanced trading fees applied to volume beyond a tier's free allowance, for a
# <$1k-30d-volume account (Coinbase's published fee schedule). taker_pct is the sim's default --
# it fills market-style at next-bar open; maker_pct is exposed for a caller that wants to model
# limit-order fills instead.
fees:
taker_pct: 0.012
maker_pct: 0.006

# Engine-activity logging. verbose=false (default) means only errors/exceptions are ever logged
# (the "keel" logger stays at ERROR level); set verbose: true (or `keel -v`) to also log major
# operations/decisions (INFO level) -- cycle starts, signals, guard vetoes, order outcomes, etc.
# file_count is the TOTAL number of files kept (the active log + rotated backups), each capped
# at max_file_mb.
# verbose: true since 2026-07-29. With it off, a cycle reporting `signals=0` said nothing about
# WHY -- engine.no_signal and engine.setup_rejected are INFO events, so answering "why no order
# today?" meant replaying the rules against the database by hand. One cycle a day over 5 products
# is a trivial amount of INFO, and rotation is bounded at max_file_mb x file_count anyway.
logging:
verbose: true
file: logs/keel.log
max_file_mb: 25
file_count: 5

# G4 overfitting gate (KB §78). NEVER tune these to obtain a desired verdict -- doing so is
# the exact Strathern misuse the gate exists to prevent (§78.7). slope_floor is calibrated
# from §78.8's worked cases: real strategy -0.35, pure random walk -0.61, overfit -0.75.
research:
pbo_max: 0.05
slope_floor: -0.5
Loading
Loading