initial: alpaclaudia paper-trading bot + dashboard

Python bot (bot/alpaclaudia): alpaca-py client, wheel strategy (CSP + covered
calls) plus equity trailing stops, risk gates (cash buffer, cost-basis guard,
per-symbol concentration cap), SQLite state log, Typer CLI (tick/loop/status/
report/dump-state), Discord daily report, pytest suite.

Next.js 14 dashboard (dashboard/): read-only — reads the bot's SQLite directly
and pulls live account/positions/orders from Alpaca. KPIs, equity chart,
positions, bot-intents audit table, and orders table. Dark UI with Tailwind.

systemd/: user-unit templates for the polling loop and the post-close report
timer.

docs/STRATEGY.md: wheel mechanics, risk invariants, later candidates.

Defaults to BOT_MODE=dry — nothing is submitted to Alpaca until explicitly
enabled in .env. ALPACA_ENV=paper by default; flipping to live requires an
explicit second guard.
This commit is contained in:
2026-04-16 21:38:25 +02:00
commit 39875112a0
46 changed files with 5195 additions and 0 deletions
View File
+111
View File
@@ -0,0 +1,111 @@
from alpaclaudia.config import RiskConfig
from alpaclaudia.risk import (
AccountSnapshot,
check_covered_call,
check_csp,
check_trailing_stop,
)
RISK = RiskConfig(
max_position_pct=0.25,
min_cash_buffer_pct=0.05,
trailing_stop_enabled=True,
trailing_stop_pct=0.08,
)
ACCT = AccountSnapshot(equity=100_000, cash=50_000, buying_power=50_000)
def test_csp_ok():
r = check_csp(
underlying="TSLA",
strike=200.0,
qty=1,
account=ACCT,
existing_short_puts=0,
max_short_puts_per_symbol=1,
risk_cfg=RISK,
)
assert r.ok, r.reason
def test_csp_blocked_by_cash_buffer():
r = check_csp(
underlying="TSLA",
strike=600.0, # 60k collateral, only 50k cash
qty=1,
account=ACCT,
existing_short_puts=0,
max_short_puts_per_symbol=1,
risk_cfg=RISK,
)
assert not r.ok
def test_csp_blocked_by_position_cap():
# 25% of 100k equity = 25k; strike 300 * 100 = 30k collateral
r = check_csp(
underlying="TSLA",
strike=300.0,
qty=1,
account=ACCT,
existing_short_puts=0,
max_short_puts_per_symbol=1,
risk_cfg=RISK,
)
assert not r.ok
assert "cap" in r.reason or "exceeds" in r.reason
def test_csp_blocked_by_concurrency():
r = check_csp(
underlying="TSLA",
strike=200.0,
qty=1,
account=ACCT,
existing_short_puts=1,
max_short_puts_per_symbol=1,
risk_cfg=RISK,
)
assert not r.ok
def test_covered_call_blocks_below_cost_basis():
positions = [
{"symbol": "TSLA", "qty": 200, "avg_entry_price": 250.0, "is_option": False}
]
r = check_covered_call(
underlying="TSLA",
strike=240.0,
qty=1,
positions=positions,
)
assert not r.ok
def test_covered_call_requires_shares():
r = check_covered_call(
underlying="TSLA",
strike=300.0,
qty=1,
positions=[],
)
assert not r.ok
def test_covered_call_ok():
positions = [
{"symbol": "TSLA", "qty": 200, "avg_entry_price": 250.0, "is_option": False}
]
r = check_covered_call(
underlying="TSLA",
strike=275.0,
qty=2,
positions=positions,
)
assert r.ok
def test_trailing_stop_needs_long_position():
r = check_trailing_stop(symbol="TSLA", qty=100, positions=[])
assert not r.ok