PAPER TRADING LIVE
--:--:-- ET
Build Log

Documenting the construction of CapitolEdge from the ground up — data infrastructure, scoring model, options strategy, deployment architecture, and results as they develop. 12 posts total.

Post 1
Congressional Trading Disclosures and the Edge Hidden in Plain Sight
Live
Post 2
Setting Up Paper Trading for Free
Live
Post 3
How the Conviction Scoring Model Works
Live
Post 4
Options Strategy: Why Deep OTM Contracts and How We Size Them
Live
Post 5
Macro Regime Filtering with FRED Data
Live
Post 6
Why Is My Bot's Balance Lower Than What I Put In?
Live
Post 7
Going Live on Railway
Live
Post 8
The Part of Risk We Weren't Tracking
Live
Post 9
Building the Dashboard
Live
Post 10
Month 1 Results
Live
Post 11
When Your Data Provider Goes Dark
Live
Post 12
How We Read Politician Disclosures
Live
Post 13
Timing the Trade: How the Earnings Surprise Signal Works
Live

Congressional Trading Disclosures and the Edge Hidden in Plain Sight

The STOCK Act requires every member of Congress to publicly disclose stock transactions within 45 days. This series documents what we built using that data.

The STOCK Act, passed in 2012, requires every member of the United States Congress to publicly disclose stock transactions within 45 days of execution. Any trade over $1,000 — equities, options, bonds — must be reported by name, ticker, size range, and date via a Periodic Transaction Report filed with the House or Senate clerk.

The legislation came out of a period of sustained scrutiny around congressional trading activity, specifically around members who held oversight responsibilities in sectors where they were also making significant personal investments. The response was a disclosure framework rather than a prohibition, which means the data is now public and updated continuously.

This series documents what we have built using that data.


The Signal

Congressional trading data contains a significant proportion of routine disclosures — small transactions, diversified holdings, inherited positions being wound down. Filtering that activity out before drawing any conclusions is essential to working with the dataset accurately.

The more relevant subset consists of trades where a member's committee assignment overlaps with the sector they are buying into. Members of the Armed Services Committee have early visibility into defence procurement decisions. Intelligence Committee members are briefed on which technology firms are being evaluated for government contracts before those evaluations become public. Finance Committee members develop a reading of incoming regulatory frameworks well ahead of their public announcement.

Congressional oversight activity does not constitute insider trading under current statute — that body of law addresses material non-public corporate information specifically, and committee briefings do not meet that definition. What committee membership does produce is an information environment where certain trades carry substantially more context than comparable trades made by members without relevant oversight responsibilities. Our analysis across 56 members of Congress and seven oversight committees shows that committee-aligned trades have historically outperformed trades made outside their area of jurisdiction, and the pattern is consistent across election cycles, market regimes, and party affiliation.

Nancy Pelosi's disclosure of an NVIDIA position in early 2023, filed during the period in which semiconductor legislation was active on the House floor, is the most frequently referenced example in this dataset. NVIDIA returned approximately 248% over the following twelve months and is one of several well-documented cases in the broader congressional trading record.


System Design

CapitolEdge pulls STOCK Act filings in near-real time and scores each trade against a conviction model before any position is taken. The scoring incorporates the politician's committee assignments and track record, the size of the disclosed transaction, the relationship between their oversight role and the sector in question, and the prevailing macroeconomic regime. Disclosures that do not clear a minimum conviction threshold are not executed.

Macro Regime

The macroeconomic layer is an integral component of the model. The same disclosure in a contracting credit environment carries different implications than it does during an expansionary period, and position sizing needs to reflect that. Our economic data module monitors seven Federal Reserve indicators and classifies the current environment across four regimes — bull, neutral, caution, and bear — adjusting position sizing accordingly.

Position Sizing

Position sizing runs on fractional Kelly Criterion at 25%. Kelly is a well-established framework in quantitative finance for scaling capital deployment proportionally to the estimated edge of a given signal. Running it at 25% of full Kelly keeps sizing conservative during the validation period.


Current Status

Both accounts are in paper trading — a $1,000 proof-of-concept and a $100,000 parallel account running the same strategy across a larger position set. Paper trading uses live market data and real execution conditions and is standard practice before committing capital to any systematic strategy.

Account Starting Capital Current Equity Positions Status
Trading Bot $1,000 $826 8/8 Active
CapitolEdge $100,000 $102,739 40/40 Active

The $1,000 account drawdown is almost entirely attributable to deep out-of-the-money options — call contracts 30–80% above current price taken on the highest-conviction signals. These carry a high probability of expiring worthless, with the expected value derived from the occasions where the underlying moves significantly. The stock side and the current NVIDIA option position reflect the model performing as intended.

Ticker Type Entry Signal P&L
NVDA Call option Pelosi disclosure +103%
MU Stock Congressional purchase +16%
GLW Stock Congressional purchase +15%
SARO Stock Congressional purchase +7%
AMZN Call option Congressional purchase −29%

Both accounts have been running for several weeks and the dataset is still accumulating.


Series Overview

The remaining nine posts will document the build in full — the data infrastructure, the scoring model, the options strategy, the deployment architecture, and the results as they develop. The writing is aimed at readers who are interested in systematic trading but do not have a quantitative background, and technical components will be explained as they are introduced.

Post 2 covers setting up a paper trading account and connecting it to a live data source. The tools are free and the setup is straightforward.

Post 1 of 10 Post 2 →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Setting Up Paper Trading for Free

Paper trading is trading with simulated money against real market prices. This post walks through setting up the exact environment we use — a free Alpaca account connected to a Python script. No paid subscriptions. No real funds required.

Paper trading is trading with simulated money against real market prices. Orders execute at live bid/ask. Positions move with the actual underlying. The only thing that is not real is the capital at risk.

The purpose is to validate a strategy under real conditions before committing money to it. That is the phase CapitolEdge is currently in, and it is the phase you should be in before running any systematic strategy live.

This post walks through setting up the exact environment we use — a free Alpaca paper trading account connected to a Python script. By the end you will have a working connection and a live view of your paper portfolio.


Step 1 — Create an Alpaca Account

Go to app.alpaca.markets and sign up. Email and password — no financial information required for paper trading.

After verifying your email, log in. Alpaca creates a paper trading account automatically with $100,000 in simulated starting capital. You will see a blue banner across the top confirming: "You are on Paper Trading, no real money is being used."

That banner is your confirmation you are in the right environment.


Step 2 — Get Your API Keys

Your API keys are how the Python script authenticates with your account. Here is exactly where to find them.

Click your profile icon in the top right corner → Profile SettingsManage Accounts in the left sidebar → scroll down to the Paper Account section. On the right you will see an API Keys panel showing your endpoint and key.

The endpoint will read: https://paper-api.alpaca.markets

The key starts with PK and is partially visible in the panel. To reveal the secret key, click Regenerate. Alpaca will display both together. Copy both immediately and store them somewhere safe — a notes app, a text file, anywhere offline. The secret key will not be shown again until you regenerate.


Step 3 — Run the Connection Script

Download the script below and open it in any text editor. Replace the two placeholder values with your actual keys, then save and run.

# test_connection.py
API_KEY    = "PASTE_YOUR_KEY_HERE"       # starts with PK
SECRET_KEY = "PASTE_YOUR_SECRET_HERE"    # shown once when you regenerate

from alpaca.trading.client import TradingClient

client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=True)
account = client.get_account()

print("Connection successful")
print(f"Account:  {account.account_number}")
print(f"Equity:   ${float(account.equity):,.2f}")
print(f"Cash:     ${float(account.cash):,.2f}")
print(f"Status:   {account.status}")

Before running, install the Alpaca library if you have not already:

pip install alpaca-py

Then:

python test_connection.py

A working connection prints:

Connection successful
Account:  PA123456789X
Equity:   $100,000.00
Cash:     $100,000.00
Status:   ACTIVE

If you see an authentication error, the most common cause is using keys from the live account instead of the paper account. Go back to Manage Accounts, confirm you are looking at the Paper Account section, and regenerate from there.


Step 4 — Place a Test Order

Once the connection is confirmed, this script places a single paper trade:

# place_test_order.py
API_KEY    = "PASTE_YOUR_KEY_HERE"
SECRET_KEY = "PASTE_YOUR_SECRET_HERE"

from alpaca.trading.client import TradingClient
from alpaca.trading.requests import MarketOrderRequest
from alpaca.trading.enums import OrderSide, TimeInForce

client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=True)

order = client.submit_order(
    MarketOrderRequest(
        symbol="AAPL",
        qty=1,
        side=OrderSide.BUY,
        time_in_force=TimeInForce.DAY
    )
)

print(f"Order placed: {order.symbol} x{order.qty} — Status: {order.status}")

Run this during market hours (9:30 AM – 4:00 PM ET, Monday to Friday). Outside those hours the order will sit pending until the next session — that is expected.

After the order fills, check your position:

# check_positions.py
API_KEY    = "PASTE_YOUR_KEY_HERE"
SECRET_KEY = "PASTE_YOUR_SECRET_HERE"

from alpaca.trading.client import TradingClient

client = TradingClient(api_key=API_KEY, secret_key=SECRET_KEY, paper=True)
positions = client.get_all_positions()

if not positions:
    print("No open positions yet")
else:
    for p in positions:
        pl = float(p.unrealized_pl)
        print(f"{p.symbol}  qty={p.qty}  P&L=${pl:+.2f}")

If this prints your AAPL position with a P&L figure, the environment is fully working.


Why Paper Trading Before Live

A strategy that looks clean in theory behaves differently in real market conditions — gaps at open, wide spreads on options, orders that fill at worse prices than expected. Paper trading surfaces those issues before they cost anything real.

For CapitolEdge, the paper phase also builds the dataset we need to evaluate whether the conviction model is producing edge. We run it until the pattern is clear. The current paper accounts have been active for several weeks and both are showing positive equity. That data is in Post 1.


What Comes Next

Post 3 covers the conviction scoring model — how each congressional disclosure is evaluated and what determines whether it qualifies for a trade.

Post 2 of 10 Post 3 →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

How the Conviction Scoring Model Works

Not every congressional trade is worth following. This post walks through how each disclosure is evaluated and what determines whether the bot takes a position.

The raw STOCK Act feed contains thousands of disclosures. A significant proportion of that activity is routine — broad market ETF rebalances, mandatory household disclosures, small purchases with no apparent relationship to the member's committee work. Acting on every line in the dataset without filtering is not tracking congressional insight. It is tracking noise.

The conviction score is the filter. Before any position is taken, each qualifying disclosure is run through a multi-factor model that weights the politician, their committee assignment, the size of the transaction, and the current macroeconomic environment. Only disclosures that clear a minimum threshold enter the portfolio. This post walks through how that model is built.


The Whitelist

The model does not follow all 535 members of Congress. It follows 37 — a curated list of members selected for a combination of relevant committee assignments, active personal trading histories, and documented patterns of trading ahead of sector-relevant legislative events.

Restricting to a whitelist is a deliberate design choice. A larger universe produces more signals, but also more noise. The current 37 represents a calibrated balance — enough coverage to capture most of the high-conviction activity without diluting the signal quality.


Base Score

Every disclosure that passes the eligibility check — a purchase by a whitelist member in a non-blocked ticker — starts at 40 points. This baseline represents the floor value of any trade that clears the initial filter. The assumption is that a deliberate purchase by a tracked politician already carries some information content, even before any contextual weighting is applied.


Politician Weight

Not all members of the whitelist carry equal signal value. Each politician is assigned a weight expressed as a multiplier against a 20-point ceiling, based on their committee profile and trading history. Pelosi sits at the top at 20 points — the maximum. Members lower in the signal hierarchy contribute between 12 and 18 points depending on their profile.

This weighting is static — set in configuration and updated manually as the whitelist evolves. It is not recalculated dynamically based on recent performance.


Committee Bonus

Beyond the politician weight, the model applies a committee bonus based on the member's oversight role. The thesis is straightforward: members with jurisdiction over a sector have earlier access to information that moves stocks in that sector. The bonus quantifies how much structural weight we assign to that advantage.

Committee Bonus Why
Armed Services / Intelligence +15 pts Defence procurement, classified contract visibility
Finance +12 pts Regulatory framework, tax legislation, banking oversight
Energy & Commerce +10 pts Energy sector, tech regulation, healthcare
Judiciary / Foreign Relations +8 pts Antitrust, trade policy, sanctions
Other committees +0 pts No structural advantage assigned

The committee bonus operates independently of the politician weight. A member with a moderate individual weight who sits on Armed Services still earns the full 15-point bonus — the two factors do not compound each other at this stage.


Trade Size

Congressional disclosures report amounts in ranges rather than exact figures. Those ranges are precise enough to be informative. The model uses the disclosed range as a proxy for conviction — a $1 million purchase signals more deliberate intent than a $15,000 one.

Disclosed Range Points
$1,000,001 or more +20 pts
$500,001 – $1,000,000 +16 pts
$250,001 – $500,000 +12 pts
$100,001 – $250,000 +8 pts
Below $100,001 +5 pts

The Boost Signals

Congressional disclosure is the primary trigger, but three additional data sources can adjust the score before execution. These signals do not initiate positions on their own — they only modify the score of a disclosure already in the pipeline.

SEC Form 4 — Corporate Insider Activity

Form 4 filings track purchases and sales by corporate insiders — executives and directors of publicly traded companies. When the model is evaluating a congressional buy and insiders at the same company are also buying, that alignment is treated as confirmation. Each insider buy adds 5 points to the conviction score, capped at +20. Each insider sell deducts 2 points, capped at −10.

The practical effect: if a politician buys NVDA and the NVDA CFO files a Form 4 buy in the same window, the conviction score increases meaningfully before a position is taken. If insiders are selling into the same period, the score falls accordingly.

Government Contracts

USASpending.gov publishes federal contract awards in near-real time. New and expanded contracts to publicly traded companies add a boost to any matching ticker score. The signal is most relevant for defence and infrastructure — sectors where Armed Services committee members are simultaneously buying equities and sitting in procurement briefings.

Lobbying Growth

The Senate LDA API tracks registered lobbying spend by company and year. When a company materially increases its lobbying budget, it is typically trying to influence an upcoming regulatory event, legislative provision, or federal contract cycle — activity that often precedes a stock-moving outcome. Lobbying growth above 30% earns a moderate boost. Growth above 100% earns the maximum. NVDA, MSFT, and GOOGL have all shown confirmed boosts in the current dataset.


Committee Multiplier

After the raw score is assembled from all of the above components, a committee multiplier is applied to the total. This is a second committee-based adjustment — distinct from the additive bonus — that reflects the structural scale of certain oversight advantages. The two operate in sequence, not in isolation.

Committee Multiplier
Armed Services / Intelligence 1.5×
Finance 1.35×
Energy & Commerce 1.3×
Judiciary / Foreign Relations 1.2×
All others 1.0×

The result is capped at 100. An Armed Services member buying a defence stock at $1M+ with active government contract and lobbying signals would produce a score well above 100 before the cap — which is why the ceiling exists.


The Regime Multiplier

The macroeconomic environment is the final layer. The model classifies the current regime using seven Federal Reserve indicators and applies a corresponding multiplier to the post-committee score. The same disclosure in a contracting credit environment carries different implications than it does during an expansionary period, and position sizing needs to reflect that.

Regime Multiplier Conditions
BULL 1.2× Strong macro — buy more
NEUTRAL 1.0× Balanced — standard sizing
CAUTION 0.75× Headwinds — size down
BEAR 0.5× Risk-off — minimal sizing

The current regime is NEUTRAL. Post 5 covers the seven indicators and the classification logic in full.


The Threshold

A score below 55 does not trade. This minimum conviction threshold eliminates the majority of disclosures that reach the scoring stage — typically those from lower-weight politicians with small transaction sizes and no committee bonus or boost signal. In practice, the bot passes on most of the dataset and acts only on the subset where multiple factors align.


What the Scores Look Like in Practice

Scenario Score Result
Pelosi $1M+ buy, Armed Services context, insiders buying, lobbying growth confirmed ~100 (capped) Trades — maximum conviction
Pelosi $500K buy, Finance committee, NEUTRAL regime ~88 Trades — high conviction
Armed Services member, $250K defence buy, no SEC or lobbying boost ~72 Trades — solid conviction
Average whitelist member, $50K buy, no committee bonus, NEUTRAL regime ~55 Borderline — passes threshold
Non-whitelist member, any size, any committee Does not reach scoring stage

What Happens When a Score Clears

Once a disclosure scores above 55, the conviction score passes to the position sizing layer. Position size is calculated using fractional Kelly Criterion at 25% — a standard approach for scaling capital deployment proportionally to estimated edge. The regime multiplier feeds directly into the sizing calculation, meaning the same signal in a BULL regime produces a materially larger position than the same signal in a CAUTION regime.

Post 4 covers the options strategy for Pelosi-tracked tickers. Post 5 covers the FRED macro regime layer in detail.

Post 3 of 10 Post 4 →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Options Strategy: Why Deep OTM Contracts and How We Size Them

The bot allocates a small budget to deep out-of-the-money call contracts on the highest-conviction signals. Most will expire worthless. That is part of the design.

Options are not the primary instrument in this strategy. Stocks are. But for a small subset of disclosures — specifically purchases by Pelosi on tickers where the conviction score is highest — the model also buys call contracts, deliberately structured to expire worthless in most scenarios.

That framing matters. Understanding why a strategy that expects most of its options positions to fail can still produce positive expected value is the core of what this post covers.


What a Call Option Is

A call option gives the buyer the right — but not the obligation — to purchase a stock at a specific price (the strike price) before a specific date (the expiry). The cost of that right is the premium, which is also the maximum amount the buyer can lose.

If the stock never reaches the strike price before expiry, the option expires worthless and the buyer loses the premium. If the stock does reach and exceed the strike, the option gains value — in some cases substantially. The downside is fixed. The upside is not.


What Deep OTM Means

An option is out-of-the-money when its strike price is above the current price of the stock. How far above determines how deep out-of-the-money it is. In our case, the model targets strikes between 30% and 80% above the current price at the time of purchase.

A contract with a strike 50% above current price needs the underlying to move 50% just to break even on the premium. That is a high bar. The probability of any single contract finishing in the money is low — and the premium reflects that. Deep OTM contracts are cheap precisely because the market assigns them a low probability of paying off.

So why do it?


The Asymmetric Payoff

The maximum loss on a call option is always the premium paid. A $50 contract can lose $50 — nothing more. But when a deep OTM contract does hit, when the underlying stock makes the kind of move that a well-timed congressional buy can precede, the return on that premium can be thousands of percent.

NVDA is the clearest example in the current dataset. The contract bought on the back of a Pelosi disclosure returned +218% on the CE account before being closed. The underlying moved enough to turn a cheap contract into a material gain, at a cost that was a small fraction of a standard stock position.

The math of this strategy does not require every contract to pay off. It requires the wins, when they come, to be large enough relative to the accumulated premiums lost to produce positive expected value over time. Congressional disclosures — specifically from politicians with demonstrated committee alignment — represent one of the more credible catalysts for the kind of large, directional moves that make this payoff structure viable.


The Parameters

Parameter Value Detail
OTM range 30–80% above spot Contract selected closest to midpoint on live options chain
Expiry 180 days from trade date Fixed S42 — was 45 days, causing rapid theta decay
Budget $200 per account Maximum total allocated to options at any time
Tickers GOOGL, AMZN, NVDA, VST, TEM Pelosi's active disclosed positions — options triggered on these only
Stop loss −75% Auto-closes contract at 75% loss — frees slot without waiting for expiry

Why 180 Days

Options lose value over time through theta decay — the erosion of an option's time value as expiry approaches. All else equal, a contract expiring in 180 days is worth more today than the same contract expiring in 45, because there is more time for the underlying to move.

Deep OTM contracts on large-cap stocks need material price movements to become profitable. Congressional disclosures, when they play out, typically do so over weeks or months — not days. The original 45-day expiry was structurally incompatible with this time horizon. Contracts were expiring before the thesis had time to develop, producing losses that were more a function of the wrong expiry than the wrong signal.

The 180-day window aligns the contract duration with the realistic timeframe over which a Pelosi-driven position tends to move. The fix was applied in Session 42 and applies to all new options positions going forward.


Sizing

Each options position cost is estimated using max($50, spot × 0.005 × 100). This keeps individual contract costs small within the $200 budget and prevents the allocation from concentrating in a single ticker. The goal is to spread exposure across the five monitored tickers as signals arrive, rather than placing a single large bet on one contract.


Current Positions

The current and recent options book illustrates the full range of outcomes this structure produces.

Contract Account Signal Result
NVDA Jun '26 call CE Pelosi disclosure +218% — closed
NVDA Jun '26 call Bot2 Pelosi disclosure −14.8% — hold through May 20 earnings
TEM Jun '26 call CE Pelosi disclosure −9.0% — hold, RT watcher active
VST Jun '26 call CE Pelosi disclosure −45.8% — hold, −75% SL set
GOOGL Jun '26 call Both Pelosi disclosure −100% — close Monday
AMZN Jun '26 call CE Pelosi disclosure −98.7% — close Monday

One large winner, one partial winner, and four contracts in various stages of loss. That distribution is not a failure state — it is approximately what the model predicts. The NVDA CE return at +218% meaningfully offsets the accumulated premiums lost on the other contracts, and the remaining open positions (NVDA Bot2, TEM, VST) still have time to develop before expiry.


The Stop Loss

A 75% loss on a deep OTM option means the market has effectively priced that contract as having near-zero probability of recovery. Holding it to expiry produces no useful information and ties up a position slot that could be redeployed on the next signal. The 75% stop loss is a pragmatic exit — take the capped loss and move on.

The GTC fallback handles the edge case where a contract's bid is zero or the market is closed. In that situation the system submits a $0.01 GTC limit order, which ensures the position closes and the slot frees without manual intervention.


The Relationship to the Conviction Score

Options are not placed on every qualifying disclosure. They are triggered only when a Pelosi buy scores above the minimum conviction threshold, and only on the five tickers in the monitored set. Other whitelist politicians drive stock positions at lower conviction scores, but the options budget is reserved for the highest-signal events in the dataset — the ones where the combination of the discloser, the committee context, and the transaction size suggests the most deliberate positioning.


What Comes Next

Post 5 covers the FRED macro regime layer — how seven Federal Reserve indicators determine the current market environment and how that classification adjusts position sizing across both stock and options trades.

Post 4 of 10 Post 5 →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Macro Regime Filtering with FRED Data

Before the bot sizes a single position, it reads seven Federal Reserve economic indicators and classifies the current macro environment. That classification determines how aggressively the bot deploys capital.

Why Macro Regime Matters

Congressional disclosures provide the signal — the trigger that tells the bot a high-conviction politician has made a trade worth following. But the signal alone doesn't determine position size. A purchase by a member of the Armed Services Committee is more interesting in a bull market than in the middle of a credit crisis. The macro regime layer exists to make the bot aware of that difference.

Without it, the bot would size positions identically whether the yield curve is deeply inverted, inflation is running at 7%, or unemployment is rising sharply. The regime filter applies a multiplier to every conviction score before capital is deployed, scaling exposure up in favorable conditions and pulling it back when the macro environment is deteriorating.


The Seven FRED Indicators

The bot queries the Federal Reserve Economic Data (FRED) API — a free, authoritative data source maintained by the St. Louis Fed — to retrieve seven series on each trading cycle. Each indicator contributes to a composite score that determines the current regime classification.

The seven indicators are: the Federal Funds effective rate (short-term borrowing cost), the 10-year minus 2-year Treasury yield spread (yield curve shape), the Consumer Price Index year-over-year change (inflation), the unemployment rate, the ISM Manufacturing PMI (industrial activity), the University of Michigan Consumer Sentiment Index, and the CBOE Volatility Index (VIX) as a real-time fear gauge.

Each series is fetched with a one-hour cache. FRED imposes rate limits on free-tier access, so the cache prevents redundant calls during intraday polling cycles while keeping the data fresh enough to catch regime shifts as they develop.


The Four Regimes

The composite score from the seven indicators maps to one of four named regimes, each with a corresponding position-sizing multiplier applied to every new trade.

BULL (1.2×) — Yield curve positive, inflation contained, unemployment low, sentiment high, VIX below 18. The bot sizes positions 20% larger than the Kelly baseline. Favorable conditions for following congressional disclosures aggressively.

NEUTRAL (1.0×) — Mixed signals. Some indicators positive, some softening. The bot uses unmodified Kelly sizing. This is the default state; both accounts are currently running at NEUTRAL.

CAUTION (0.75×) — Yield curve flattening or mildly inverted, inflation elevated, VIX rising above 20. The bot cuts position sizes to 75% of the Kelly baseline. Disclosures still trigger trades but at reduced scale.

BEAR (0.5×) — Yield curve deeply inverted, recession indicators present, VIX above 30, unemployment rising. The bot halves every position. The model still trades — congressional insiders continue to file disclosures regardless of market conditions — but capital deployment is heavily restricted.


How the Multiplier Is Applied

The conviction scoring model produces a raw score for each disclosure — a number that reflects the discloser's committee position, trade size, and historical track record. That score is first adjusted by the committee multiplier (Armed Services and Intelligence committees receive the highest premium), then multiplied by the regime factor before the Kelly sizing formula calculates share count.

The result is a position sizing system that is sensitive to both the quality of the political signal and the macroeconomic backdrop. A Pelosi buy scoring 95 in a BULL regime produces a meaningfully larger position than the same disclosure scoring 95 in a CAUTION regime — and that difference is intentional.


What Comes Next

Post 6 covers a question that comes up every time someone looks at the account summary for the first time: why is the balance lower than the amount deposited? The answer involves options math, margin accounting, and why paper losses in some positions are actually part of the plan.

Post 5 of 10 Post 6: Why is my bot's balance lower? →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Why Is My Bot's Balance Lower Than What I Put In?

The account summary shows negative cash, options positions deep in the red, and an equity figure that doesn't match the starting deposit. Here is exactly what each number means and why some of the losses are part of the plan.

The Number That Confuses Everyone First

Open the CapitolEdge dashboard and switch to the CE Retail account. The equity reads just over $5,000 — roughly in line with the $5,000 starting balance. But the cash line reads negative four thousand dollars. How can a paper account that started with five thousand dollars show negative cash?

The short answer is that the bot operates on a margin-enabled paper account. When it places a stock buy, Alpaca does not wait for settled cash before executing. It allocates the position against available buying power, which includes a margin facility. The positions themselves are worth more than the deposited cash, so the cash balance appears negative while total equity — the value of all held positions minus any margin balance — remains positive.

Nothing has been lost in that gap. The positions in the account are worth real (simulated) money. The negative cash is a ledger artefact of how fractional share allocation works on margin accounts, not a sign that the strategy has failed.


The Options Budget Is Designed to Lose Most of the Time

The model allocates $200 per account to deep out-of-the-money call options. These are contracts with strike prices 30% to 80% above the current share price — highly speculative, cheap to buy, and statistically unlikely to expire in the money. That is not a design flaw. It is the design.

The thesis is asymmetric: the bot is paying $150–200 for a lottery ticket that, if a high-conviction congressional disclosure is followed by a strong earnings beat or a sector catalyst, can return 5× to 10× the premium. On the first NVDA call opened on this account, the bot paid $1.42 per share. That contract is now worth $1.64 — up 15.5% — ahead of NVDA's May 20 earnings report. The previous NVDA contract on the larger CE account was closed at +218%.

Against those, the current book also holds a TEM contract down 42.8% and a VST contract down 50.8%. Both are still above the automatic stop-loss level of −75%, which is the point at which the bot determines the contract is effectively worthless and closes it to free the position slot. The expected outcome for any individual option is a loss. The expected outcome for the options book as a whole — if it catches one or two large moves per year — is positive.

Reading the options P&L column in isolation and concluding the bot is failing is like reading only the losing trades from a blackjack card counter's session. The sizing and frequency are what determine the outcome, not any single hand.


The Stock Side Is Where the Compounding Happens

Separate from the options, the bot holds up to twelve stock positions on CE Retail and up to fifty on the larger CapitolEdge account. These are market buys sized by the Kelly Criterion — a formula that calibrates position size to the conviction score of the underlying congressional disclosure.

At time of writing, the CE Retail stock book shows RVTY up 9.2%, W up 8.6%, LLY up 1.5%, and no position at the −8% automatic stop-loss threshold. GPN is the closest at −6.4%, sitting 1.6 percentage points from an automated close. Everything else is flat to modestly positive. The larger CapitolEdge account holds TEL at +18%, SE at +14.3%, COHR at +13.2%, and MIAX at +10.8% — four positions approaching or inside the trailing stop activation window.

This is the part of the model that is working. Congressional disclosures in sectors where the disclosing politician sits on a relevant oversight committee have, in this sample, produced above-average returns in the weeks following the filing. The stock positions are the evidence of that edge. The options are the long-shot overlay that converts a single exceptional catalyst into a large return.


Equity vs Cash: The Number That Actually Matters

When evaluating whether the strategy is working, ignore the cash line. The relevant number is equity — the total market value of all held positions minus any outstanding margin balance. That is the figure that reflects whether the capital deployed has grown or shrunk.

CE Retail started at $5,000. Equity today is $5,032. The account is slightly ahead of its starting point despite holding options contracts with unrealised losses totalling several hundred dollars. The stock side has more than offset the options drag.

The larger CapitolEdge account started at $100,000 and currently shows equity of $107,149 — up 7.1% since inception. Both accounts are running on paper. No real capital is at risk. The purpose of this phase is to generate a verifiable track record before considering a live deployment.


Why Paper Trading First

The model has never traded real money. Before it does, it needs to demonstrate consistent positive returns across a meaningful sample of congressional disclosures — not just a few lucky picks. Paper trading on Alpaca replicates the mechanics of live trading exactly: the same order types, the same fills, the same position limits, the same API. The only difference is that the capital is simulated.

If the account equity curves upward over a 90-day period, with the stock side generating consistent returns and the options book occasionally delivering outsized wins, that is the evidence base required to consider moving real money into the strategy. Until then, the balance being slightly above or below the starting deposit is not the story. The shape of the curve over time is.


What Comes Next

Post 7 covers deploying the bot to Railway — moving from a local Python script to a continuously running cloud service that executes trades during market hours without requiring a local machine to stay online.

← Post 5: Macro Regime Filtering Post 6 of 10 Post 7: Going Live on Railway →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Going Live on Railway

A trading bot that only runs when your laptop is open isn't a trading bot — it's a script. Deploying to Railway turned CapitolEdge into a continuously running service. Here is exactly how that was done and what broke along the way.

The Problem with Running Locally

For the first few weeks of development, the bot ran on a local Windows machine. That worked fine for testing — you could start a cycle, watch the logs scroll, verify a trade fired correctly, and shut it down. But it broke down immediately as a trading setup. Market hours run from 9:30am to 4:00pm Eastern. If the laptop was asleep, the bot missed every signal that arrived while it was offline. If a position crossed its stop-loss threshold during the day, the bot had no way to act on it. The WebSocket feed that drives real-time SL/TP enforcement simply wasn't running.

The solution is a cloud service — a small virtual machine that runs continuously, executes the trading loop on schedule, and stays connected to the Finnhub WebSocket feed regardless of what is happening on the local machine. The remaining question was which platform to use.


Why Railway

Several options were evaluated. Heroku ended its free tier in 2022. Render offers a free tier but cold-starts services after 15 minutes of inactivity — acceptable for a web API, not acceptable for a trading bot that needs to respond to price crosses within seconds. DigitalOcean and Hetzner require provisioning and managing a VPS directly, which adds operational overhead for a project at this stage.

Railway sits at the right point on that spectrum. It runs a Python service continuously with no cold starts, connects directly to a GitHub repository, supports multiple services from the same repo, and handles environment variables cleanly through a web dashboard. Pricing is usage-based — for a lightweight Python process running one or two polling cycles per hour, monthly costs are minimal. For a project that needed to move from local script to cloud service quickly without introducing infrastructure complexity, it was the obvious choice.


Two Services, One Repository

CapitolEdge runs two independent accounts: CE Retail (the $5,000 proof-of-concept) and CapitolEdge (the $107,000 parallel account). Both bots run the same Python codebase from the same GitHub repository. The only difference between them is their environment variables — each service on Railway has its own set of secrets pointing to a different Alpaca API key, a different Finnhub key, and a different account label.

This setup avoids maintaining two separate codebases. A fix to the stop-loss logic, a new signal integration, or a change to the conviction scoring model gets pushed once to GitHub and deployed to both services. The Railway project contains two services — CE and CE Retail — both connected to the same main branch of the NeonArb/politician-trader-bot repository.


The Deployment Gotcha: git push Does Nothing

This is the most important operational lesson from the Railway setup, and it burned time early on. Railway does not automatically redeploy when you push to GitHub. It appears to, based on how the documentation describes the GitHub integration, but in practice the auto-deploy pipeline failed silently due to a mismatch between the commit author email and the account used to authorise the GitHub connection. Every push appeared to succeed. The running service on Railway remained on the previous version without warning.

The reliable deploy sequence is always manual. After pushing code to GitHub, you run railway link in the project root to associate the local terminal session with the target Railway service, then railway up to trigger the actual deployment. You repeat this for each service that needs updating. It takes an extra sixty seconds per service and it always works.

The practical consequence of missing this is that you believe you have deployed a fix, the fix is not running, and the next session starts with debugging behaviour that was supposed to be resolved. Running railway up explicitly after every code change is now a non-negotiable step in the session protocol.


Environment Variables

The .env file in the project root holds every secret the bot needs — Alpaca keys, Finnhub API keys, Quiver credentials, Supabase keys for the waitlist, and Stripe keys for the payment layer. That file is listed in .gitignore and has never been committed to the repository.

On Railway, environment variables are set through the service dashboard under the Variables tab. Each service has its own isolated variable set. When Railway builds and starts the service, those variables are injected into the running process as standard environment variables, identical to how python-dotenv loads the local .env file during development. The bot code itself doesn't distinguish between the two sources — it calls os.getenv() either way.

One variable that caused issues: QUIVER_API_KEY. It was present in the Railway CE service variables but missing from the local .env file, which meant the Quiver scanner script ran without error locally but was actually operating without a valid key. That class of discrepancy — where Railway and local environments have drifted — is worth auditing periodically.


What Breaks on Redeploy

Railway's filesystem is ephemeral. When a service redeploys — whether triggered manually via railway up or automatically after a crash — any files written to disk by the running process are wiped. For most services this is fine. For a trading bot with persistent state, it creates specific failure modes.

The first one discovered was the cooldown ledger. After closing a position, the bot writes the ticker and timestamp to cache/cooldowns.json, which prevents it from reopening the same position within 24 hours. On redeploy, that file is gone. The bot treats every ticker as fresh and can immediately reopen a position it just closed. VST was closed and reopened four times across separate sessions before the root cause was identified. The fix was not to persist the cooldown file — that would require an external store like Redis or Supabase — but to add VST to a permanent BLOCKED_TICKERS list in settings.py. A committed settings change survives every redeploy.

The second failure mode was more subtle. The trailing stop logic tracks a position's peak price in memory and closes the position if it drops more than 5% from that peak. After a Railway redeploy, the in-memory peak dictionary is empty. The bot rebuilds it using a startup seeding loop, but that loop only seeds positions that are currently above the +15% activation threshold. A position that peaked at +18% but was sitting at +9.7% at the time of the redeploy would not be seeded — its floor was gone.

That is what happened to SE. The position had peaked above +18%, established a trailing floor, and been drifting. A redeploy wiped the floor. The next trading cycle saw the position at +9.7% with no floor, treated it as a normal holding, and took no action. The fix was to persist the peak dictionary to cache/high_water.json after every update and load it back at startup before the seeding loop runs. The file now survives redeployes because it is written after every change rather than held only in memory, and the seeding loop restores floors for any position not already in the file. Railway logs show [S55] Trailing floor restored from file: SYMBOL for file-loaded entries and [S50] Trailing floor seeded (fallback): SYMBOL for new entries encountered at startup.


What Comes Next

Post 8 covers the risk management layer that was added after the first month of live paper trading — specifically portfolio-level Value at Risk (VaR), correlation monitoring, and sector concentration limits. These are the guardrails that prevent a single bad sector bet from causing structural damage to the account.

← Post 6: Why Is My Balance Lower? Post 7 of 10 Post 8: The Part of Risk We Weren't Tracking →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

The Part of Risk We Weren't Tracking

Stop losses, take profits, trailing stops — all position-level controls. After the first month of live paper trading, it became clear those were not enough. This post covers the three portfolio-level guardrails built to prevent a single bad sector bet from causing structural damage.

What Position-Level Risk Misses

The original risk framework was built around individual positions. Every stock held a −8% stop loss and a +25% take profit. Options carried a −75% stop. Trailing stops activated at +15% unrealised gain and followed the price down, closing if it fell more than 5% from peak. That framework correctly limits how much any single holding can damage the account.

What it does not address is what happens when the whole book moves together. If ten positions are all in the same sector and that sector drops 12% in a week, each individual stop loss activates in sequence — but by then the damage is already structural. The account has not had ten independent bad outcomes. It has had one concentrated bet that lost.

Portfolio-level risk management asks a different question: not whether each position is under control individually, but whether the combined portfolio has become fragile in ways the position-level view cannot see.


Value at Risk — What It Actually Measures

Value at Risk (VaR) is an estimate of the maximum loss a portfolio could experience over a defined time period at a given confidence level. The CapitolEdge implementation runs at two confidence thresholds: 95% and 99%.

The 95% VaR figure means that, based on the portfolio's historical price behaviour over the past 252 trading days, there is a 95% probability that a single-day loss will not exceed that amount. The 1-in-20 scenario. The 99% figure covers the 1-in-100 day — the tail event that does not happen often but is worth knowing the size of when it does.

The calculation is historical simulation, not parametric. Rather than assuming returns follow a normal distribution, the model replays actual historical daily returns for each held ticker and constructs an empirical distribution of portfolio-level outcomes. The tail of that distribution is where the VaR numbers come from. This approach captures real-world skew and fat tails that a Gaussian model would underestimate.


Building the VaR Calculator

The implementation lives in core/var_calculator.py. On each run, it fetches 252 days of daily closing prices for every ticker currently held across both accounts, weights each position by its current market value, and simulates 252 historical portfolio return scenarios by applying each day's actual return weights to the current holdings. The resulting distribution is sorted and the 5th and 1st percentile values are extracted as the 95% and 99% VaR estimates respectively.

The check script — python check_var.py — runs against both accounts and labels each result as NORMAL, ELEVATED, or CRITICAL based on the ratio of the VaR figure to total portfolio equity. Both accounts currently read NORMAL. The recommended cadence is weekly, or immediately after adding several new positions in the same sector.


The Correlation Monitor

VaR tells you the size of the potential loss. The correlation monitor tells you why the portfolio might be more exposed than it looks.

If ten of the twelve CE Retail positions are highly correlated — moving up and down together — then twelve positions is not really twelve independent bets. It is closer to one concentrated bet held twelve ways. The diversification benefit that position limits are supposed to provide disappears when the underlying holdings behave as a single factor.

The correlation monitor in core/correlation_monitor.py runs 30-day Pearson correlations across all held tickers and uses a breadth-first search to identify clusters — groups of positions where pairwise correlation exceeds 0.75. A cluster of five or more positions above that threshold is flagged. The output also surfaces the single highest-correlation pair in the book, which is often where a new sector concentration is developing before it becomes visible in the position list.

Both accounts have passed every correlation check run to date. The highest-correlation pairs on the CE account sit around 0.6 — below the cluster threshold and in line with expected co-movement among large-cap US equities in a NEUTRAL macro regime.


The Sector Concentration Cap

The third guardrail is the most directly actionable. The bot tracks the sector exposure of every held position and blocks new buys in any sector that exceeds 30% of total portfolio equity.

Technology hit 40.8% on the CE account before the cap was implemented. That figure made sense on paper — the conviction scoring model correctly identified several high-scoring Technology sector disclosures in rapid succession, each of which qualified individually. But the net effect was that more than four in ten dollars of portfolio value were moving with the same macro factor. A single adverse event for large-cap tech would have affected NVDA, MSFT, PYPL, PLTR, FICO, and IT simultaneously.

The cap does not close existing positions. It blocks new ones. Once the Technology allocation crossed 30%, the bot stopped accepting new Technology buys until sector rebalancing brings the figure back below the threshold. The sector breakdown is recalculated on every trading cycle. python check_sector.py shows the current breakdown.

Tool What It Measures Cadence Script
Portfolio VaR Max 1-day loss at 95% / 99% confidence Weekly check_var.py
Correlation monitor Cluster detection — positions moving as one Weekly check_correlation.py
Sector cap Sector allocation as % of equity — blocks buys above 30% Every cycle (automatic) check_sector.py

What the Checks Found

The first VaR run on the CE account — after 18 stock positions had accumulated — returned a 95% VaR of approximately $1,200 against a $107,000 equity base. That represents roughly 1.1% of portfolio value as the daily tail risk figure. Within normal parameters for a diversified equity portfolio and well within the range the account could absorb without approaching its stop-loss thresholds.

The correlation check on the same account found no clusters above the 0.75 threshold. The highest single-pair correlation was between MSFT and PLTR at approximately 0.62 — both large-cap technology, expected co-movement, not a concentration problem.

The sector check is what flagged the actual issue. Technology sat at 40.8% of the CE account — 10 percentage points over the cap. The bot blocked all new technology buys from that point forward. Several high-scoring technology disclosures arrived in the following two weeks and were correctly filtered out. COHR, MIAX, and positions in other sectors absorbed the new capital instead, pulling the technology allocation back toward the cap threshold over time.


Risk Management Is Not a Single Number

The instinct when adding risk tools is to find one metric that captures everything. VaR is often presented that way — as a single number that summarises portfolio risk. It does not. It measures tail loss under historical conditions. It has no opinion on sector concentration or correlation structure. Those require separate checks.

The three tools together cover different failure modes: VaR for the magnitude of a bad day, correlation for hidden concentration within the position list, and the sector cap for the structural allocation problem that develops slowly as new signals arrive. None of them replaces the position-level stop losses. All of them are necessary alongside them.


What Comes Next

Post 9 covers building the live dashboard — how the account data, signal intelligence, and macro indicators are surfaced in a public-facing interface and what that required technically.

← Post 7: Going Live on Railway Post 8 of 10 Post 9: Building the Dashboard →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Building the Dashboard

The trading bot generates thousands of lines of log output across two Railway services. None of that is visible to anyone who didn't build it. This post covers how CapitolEdge went from terminal output to a live public dashboard — the stack, the data pipeline, and the decisions made along the way.

The Problem With Terminal Output

When both bots went live on Railway, the state of the system existed only in two places: the Railway log stream and the Alpaca paper account pages. To check whether a stop loss had fired, whether the macro regime had changed, or what the current sector allocation looked like, the answer was to either open the Railway logs or navigate to Alpaca's interface directly — neither of which tells you the full picture in one place.

The goal was a single page that surfaces everything the bot knows: current positions with live P&L, account equity, the current macro regime, recent signal boosts from the secondary intelligence layer, and the sector concentration breakdown. Readable without context. Refreshable without effort.


The Stack

The dashboard is a single-page HTML/CSS/JavaScript application hosted on Vercel. There is no frontend framework — no React, no build step, no node_modules in the dashboard directory. The reasoning was simple: the complexity lives in the bot, not the UI. A clean HTML file with vanilla JavaScript loads fast, deploys in seconds, and has no dependency surface to maintain.

Typography uses Inter for headings and DM Mono for body text and data — the monospace font makes position tables and P&L figures easier to scan at a glance. The colour palette is a custom dark theme built around deep navy backgrounds and a muted gold accent, which became the CapitolEdge visual identity across all pages.

Chart.js handles the equity curve visualisation — a lightweight library that renders clean time-series charts without pulling in a full data visualisation framework.


Why Vercel Serverless Functions

The obvious approach — calling the Alpaca REST API directly from the browser — does not work. API keys cannot live in client-side JavaScript where they are visible in page source, and Alpaca's endpoints do not permit cross-origin requests from arbitrary browser sessions.

Vercel serverless functions solve both problems. Each function runs on Vercel's infrastructure on every request, reads the Alpaca keys from Vercel environment variables (never exposed to the client), makes the API call server-side, and returns the result as JSON. The browser calls /api/portfolio — a safe relative URL — and receives the formatted data without ever seeing the credentials.

The dashboard currently runs five endpoints:

Endpoint Data source What it returns
api/portfolio.js Alpaca REST Positions, equity, cash, buying power
api/config.js Static config Position caps, account labels, bot metadata
api/signals.js USASpending + LDA API Gov contracts and lobbying growth boosts
api/insider.js SEC EDGAR Form 4 insider buy/sell activity
api/macro.js FRED API 7 macro indicators and current regime

The Hobby tier on Vercel allows 12 serverless function slots. All 12 are currently in use. Any new API endpoint requires either consolidating existing ones behind a single function with query parameter routing, or upgrading to Vercel Pro.


The Account Switcher

Running two separate bots — CE Retail ($5K paper) and CapitolEdge ($107K paper) — meant the dashboard needed to show data for either account without deploying two separate sites.

The solution is a query parameter: ?account=bot2 switches the portfolio endpoint to pull from CE Retail instead of the main CE account. The api/portfolio.js function reads the parameter server-side and selects the corresponding Alpaca key pair from environment variables. The switch button in the dashboard UI rewrites the URL and reloads the data. Both accounts share the same deployment, the same domain, and the same codebase.


The Hardcoded Values Problem

The first version of the dashboard had a problem that took several sessions to fully diagnose: numbers like position caps, account names, and starting balances were written directly into the HTML. When the CE Retail account was reset — new account ID, new starting balance — the dashboard continued to display the old values until each one was manually hunted down and updated.

The fix was api/config.js — a single endpoint that serves all configurable dashboard values from one place. The HTML now reads these values on page load rather than embedding them statically. When an account parameter changes, updating the config endpoint updates every panel that depends on it simultaneously. The audit that identified this issue found twelve separate places in the original HTML where static values had been used where dynamic ones should have been.


The Sector Concentration Widget

After the sector concentration cap was built into the bot, the natural next step was surfacing it in the dashboard. The sector panel shows the current allocation breakdown for the active account — each broad sector group as a percentage of total equity, with a visual indicator when any sector approaches or exceeds the 30% cap.

This panel was the first place the Technology overexposure became visually obvious. Seeing 40.8% rendered against a 30% limit makes the problem concrete in a way that a log line does not. Dashboards are useful not because they add new information, but because they make existing information impossible to ignore.


Deployment

The dashboard lives in dashboard/ — a subfolder of the main bot repository. Vercel is configured to treat that subfolder as the project root, so only the dashboard files are deployed, not the Python bot code.

The deploy command is python deploy_dashboard.py from the project root. This script runs vercel deploy --prod directly via the Vercel CLI rather than relying on a git push trigger, which had intermittent issues with commit author verification during early sessions. The CLI route is reliable and adds about 30 seconds to any dashboard change.

One edge case worth documenting: the dashboard folder was accidentally initialised as a git submodule in an early session, which caused Vercel to treat it as an external dependency rather than project files. The fix was removing the nested .git folder and re-staging everything under the main repository. The deployed site has been stable since.


What the Dashboard Shows Now

The current dashboard has four main views. The Portfolio page shows equity, cash, buying power, and a full positions table with entry price, market price, and unrealised P&L for both accounts. The Signal Intel page surfaces the secondary signal layer — government contracts, lobbying growth, and SEC Form 4 insider activity for held tickers. The Macro page renders all seven FRED indicators and the current regime label with multiplier. The Build Log is this series.

The equity curve, sector breakdown widget, and account switcher all sit on the Portfolio page. The dashboard is publicly accessible at capitoledge.app — no login required to view the portfolio data.


What Comes Next

Post 10 covers the Month 1 results — what the combined performance of both accounts looks like after a full month of paper trading, what the scoring model got right, what it missed, and what the backtest projected versus what actually happened.

← Post 8: The Part of Risk We Weren't Tracking Post 9 of 11 Post 10: Month 1 Results →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Month 1 Results

Two weeks of live paper trading across two accounts. Here is what the numbers look like — what the scoring model got right, what broke in production, and what the Quiver API outage revealed about building a system that depends on third-party data.

The Accounts at Two Weeks

CapitolEdge runs two paper accounts simultaneously. The CE account — the primary account — started May 1 and has been running continuously since. The CE Retail account was reset to a clean $5,000 on May 12 to model a more realistic starting capital. Both are Alpaca paper accounts. No real money is at risk.

Account Start Current Equity Positions Cash
CE (CapitolEdge) $100,000 $107,837 15 / 50 +$14,409
CE Retail $5,000 (reset May 12) $5,008 12 / 12 −$2,933 (margin)

The CE account is up approximately 7.8% from its starting equity. The CE Retail account is essentially flat — by design. The small account runs 12 positions including deep out-of-the-money options contracts that are expected to lose value before they expire. The stock side of CE Retail is consistently green. The options drag the overall figure down.


What the Scoring Model Got Right

The clearest validation of the conviction scoring model is the NVDA position on the CE account. A congressional disclosure from an Armed Services committee member triggered a score near the maximum — lobbying boost, committee multiplier, and trade size all stacking together. The bot placed a deep out-of-the-money call option at 30% above spot. That contract is currently up 185%.

COHR and MIAX are the two strongest stock positions on the CE account, both with trailing stops active. COHR entered trailing stop mode above +15% unrealised gain and has held a floor above $381 through several pullbacks. MIAX has followed a similar pattern, currently sitting at +20%. Both positions demonstrate the trailing stop doing its job — not closing too early, but protecting the bulk of the gain if the price reverses sharply.

A GOOGL November 2026 LEAPS call entered at 30% out of the money is up 23%. It was placed on a Pelosi disclosure — the highest-weighted politician in the scoring model — and benefits from a long time horizon that reduces the urgency of short-term price moves.

The secondary signal layer has also contributed. The short interest signal, insider clustering bonus, and unusual options flow scanner each add incremental points to scores that would otherwise sit at borderline levels. No single secondary signal fires a trade on its own — they push qualified disclosures over the threshold or hold them below it.


The Bugs That Made It Into Production

Honest accounting requires logging the failures alongside the wins.

The most costly bug was the after-hours phantom stop-loss. The Finnhub WebSocket delivers price ticks continuously — including ticks outside market hours when spreads are wide and prices are unreliable. The real-time watcher was checking stop-loss thresholds against these ticks without first checking whether the market was actually open. Two positions — PLTR and MSFT — had their stop losses triggered by after-hours ticks and were filled at market open the following morning at approximately −1.5% each. The positions were not in danger of hitting their stops during regular hours.

The fix was a single guard call: is_market_open() at the top of the stop-loss watcher. If the market is closed, after-hours ticks are discarded. The symbol re-evaluates on the first genuine market-hours tick. This is live as of Session 59.

A second bug affected early sessions: the bot was sizing positions against total account equity rather than available cash. On a margin account, equity and available cash are different numbers. The result was that the bot attempted to place positions it did not have the cash to support, leading to negative cash balances on CE Retail. A cash guard now fetches available buying power before every position is sized and skips the trade if insufficient funds exist. CE Retail's current negative cash balance is a legacy of positions placed before the guard was active — it resolves as those positions hit their stop losses or take profits.

The trailing stop persistence bug was more subtle. Trailing stop floors were stored in memory on the Railway server. When the service redeployed — which happens on every code push — the floors were lost. A position that had peaked at +18% and pulled back to +9% would lose its floor entirely because the +15% seeding threshold was no longer met. The fix persists all peak prices to a cache file on disk, loaded before the seeding loop runs at startup.


The Quiver Outage — Building on Third-Party Data

The primary signal source for CapitolEdge was the Quiver Quantitative API, which aggregates congressional STOCK Act disclosures. Around May 4–5, the free-tier unauthenticated endpoint began returning 401 errors. The free tier had ended. Every alternative tested — Finnhub (premium-only), FMP (paywalled), House Stock Watcher (403), Lambda Finance (50 calls/month) — was unusable.

Quiver’s paid Hobbyist plan was subscribed briefly, but the portal login was broken and no API key was ever delivered. The subscription was cancelled after we built a replacement pipeline directly against Capitol Trades. The full story of how that was built is in Post 11. The bot resumed receiving live STOCK Act disclosures on May 15, 2026 — free, no API key, no subscription required.


What the Numbers Actually Mean

Two weeks is not long enough to draw statistically meaningful conclusions. A handful of positions going well does not validate a model. A handful going badly does not invalidate one. The value of this early period is not in the returns — it is in the bugs that surface when the system runs continuously against real market conditions.

Every bug found and fixed in paper trading is a bug that does not cost real money later. The after-hours phantom stop-loss would have triggered unnecessary sells on a live account. The cash sizing error would have resulted in margin calls. The trailing stop persistence gap would have exposed gains that the model correctly identified and correctly protected — until a server restart wiped the floor.

The stock side of the strategy is performing as expected. The congressional disclosure signal, filtered through the conviction scoring model, has identified positions that have moved significantly in the right direction. The options side is more volatile and that is by design — deep out-of-the-money contracts either go to zero or return multiples. The NVDA call at +185% and the TEM call at −54% are both outcomes that were anticipated at the time of entry.


What Comes Next

The congressional signal pipeline has since been rebuilt from scratch — our own scraper against Capitol Trades replaced Quiver entirely. The bot is back to receiving live STOCK Act disclosures with no API key and no subscription. That story is in Post 11.

Longer term, the infrastructure roadmap includes migrating from Railway to a dedicated VPS for always-on uptime without cold-start delays, adding real-time WebSocket data for faster signal detection, and building a machine learning layer to rank incoming signals by expected price impact. Live trading with real capital is the end goal — after a full three-month profitable paper track record and a legal review of the LLC structure.

The build continues. Follow along at capitoledge.app.

← Post 9: Building the Dashboard Post 10 of 11 Post 11: When Your Data Provider Goes Dark →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

When Your Data Provider Goes Dark

Quiver Quantitative ended its free tier. Every alternative failed. So we reverse-engineered Capitol Trades, built our own congressional scraper, and cancelled the subscription we never needed.

The Dependency Problem

Around May 4–5, the Quiver Quantitative API began returning 401 errors on every request. The free-tier unauthenticated endpoint had been silently discontinued. The bot had been running for two weeks, was managing 25+ positions across two accounts, and suddenly had no congressional signal data at all. Existing positions continued to be managed by the Finnhub WebSocket — stop losses, take profits, trailing stops all functioned. But new entries stopped entirely.

This is the infrastructure risk that every system built on third-party data inherits. When Quiver changed its pricing model, we did not get a warning. The endpoint just started returning 401. If the bot had been live trading real capital, it would have continued managing existing positions but gone dark on new signals for an indeterminate period.


Everything We Tried First

Before building anything new, every plausible free alternative was tested. The results were not encouraging.

Source Result Reason
Finnhub congressional endpointFailedPremium tier only — not available on free plan
Financial Modeling Prep (FMP)Failed402 error — legacy endpoint restricted to legacy accounts
House Stock Watcher APIFailed403 Forbidden on all endpoints tested
Capitol Trades (first attempt)FailedDNS resolution failure — could not reach the domain
Lambda FinanceFailed50 calls per month — insufficient for hourly polling
Quiver paid planSubscribed~$30/mo Hobbyist — portal login broken, no key delivered

After two weeks without a key from Quiver support and a broken portal login, the decision was made to build the pipeline ourselves. The paid subscription was cancelled the same day the scraper went live.


How Capitol Trades Works Under the Hood

Capitol Trades (capitoltrades.com) is a Next.js application. Like most modern Next.js sites, it embeds its server-rendered data directly in the page HTML rather than exposing it through a separate public API. The data is not hidden — it is sitting in a <script> tag in every page response. It is just not obvious where to look.

The key is the Next.js RSC (React Server Components) payload format. Every page includes one or more script blocks of the form:

self.__next_f.push([1, "...serialised JSON payload..."])

That payload contains the full page data — including every trade record on the page — encoded as a JSON string. The data structure includes issuerTicker, txDate, txType, value, and a nested politician object with firstName, lastName, and chamber.

Each page returns 96 records. We fetch 10 pages in parallel using asyncio.gather(), giving approximately 817 raw records per cycle — covering the most recent 5–10 days of STOCK Act disclosures from both House and Senate.


The Whitelist Name Bug

The first live test found that Rohit Khanna — a high-value whitelist member — was matching zero records. The whitelist had him as “Ro Khanna” — his preferred name and the one used in the original Quiver dataset. Capitol Trades uses full legal names. One field update in settings.py fixed it. His 300+ April disclosures then flowed through the scoring pipeline.


What the April Data Actually Shows

The first full cycle produced 817 raw records and 364 whitelist signals. Here is what stands out.

April McClain Delaney — TDG (TransDigm Group): Five purchases across April 15–29, including one $15K–$50K tranche. TransDigm makes aerospace components for defence exclusively. Repeated accumulation over two weeks is the highest-conviction pattern the model looks for.

Gilbert Cisneros — DASH (DoorDash): Five purchases from April 6 through May 1. Cisneros sits on Armed Services. Repeated buying from a committee member, even on a consumer name, stacks conviction through the score accumulation logic.

Michael McCaul — SPGI (S&P Global): Five purchases between April 1 and April 28. McCaul chairs House Foreign Affairs. Steady accumulation with no single large transaction suggests a systematic buying programme.

Mark Warner — Finance sector, April 13: Warner (Senate Banking and Intelligence) bought WFC, BX (Blackstone), CEG, TOST, and TEAM in the same session. A Finance committee senator buying financial names — BX in particular — hits Warner’s 1.35x committee multiplier directly.


The Result

The Capitol Trades pipeline went live May 15, 2026. No API key, no subscription, no rate limits. Both chambers covered. The Quiver subscription — which we were paying for without ever receiving a working key — was cancelled the same day.

The lesson: a system whose core signal depends on a single external endpoint is always one pricing change away from going dark. Where data is publicly available and scrapeable, building the pipeline yourself is the more resilient choice.

The build continues. Follow along at capitoledge.app.

← Post 10: Month 1 Results Post 11 of 12 Post 12: How We Read Disclosures →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

How We Read Politician Disclosures

The STOCK Act created the data. But it doesn’t come with an API. Here is every step of the pipeline — from raw HTTP request to a conviction score.

The Data Exists. Getting It Is the Problem.

Every member of Congress must disclose stock transactions within 45 days under the STOCK Act. The filings are public. But “public” does not mean “accessible” — it means the data exists somewhere on the internet in whatever format the House and Senate clerks choose to publish it.

The government’s own disclosure portals are PDFs, flat XML dumps, and HTML tables with no consistent schema. Capitol Trades, a third-party aggregator, does the hard work of parsing those filings and presenting them in a coherent interface. We use their site — not the government’s — as the data source for every trade signal this bot produces.


How Capitol Trades Serves Its Data

Capitol Trades is built on Next.js, a React framework that server-renders pages and ships data as part of the HTML. The trade records are not loaded by a separate API call after the page opens — they are embedded directly in the HTML source as a React Server Component (RSC) payload.

This payload appears in the page as a script tag like:

self.__next_f.push([1,"...escaped JSON string..."])

The JSON inside is the full page state — navigation, metadata, and somewhere within it, a data array containing the trade records. The content is JSON-encoded twice: first as the RSC payload, then as a standard string literal inside the script tag.


Step 1 — Fetch All 10 Pages in Parallel

The endpoint accepts pageSize and page query parameters. We use pageSize=96 — the maximum that returns a full set of results — and we fetch 10 pages simultaneously using asyncio.gather():

tasks = [
    asyncio.create_task(self._fetch_page(session, page))
    for page in range(1, CAPITOL_PAGES + 1)
]
results = await asyncio.gather(*tasks, return_exceptions=True)

Ten concurrent HTTP requests take roughly the same wall-clock time as one sequential request. The result is ~960 raw records covering the most recent 5–10 days of filed disclosures, retrieved in a single cycle.


Step 2 — Extract the RSC Payload

For each page’s HTML, we scan the <script> tags and look for any that contain issuerTicker — the field that identifies a trading disclosure. When we find one:

m = re.search(r'self\.__next_f\.push\(\[1,"(.*?)"\]\)', s, re.DOTALL)
content = json.loads('"' + m.group(1) + '"')

The json.loads trick unwraps the outer string escaping: passing '"' + payload + '"' lets the JSON parser interpret the escape sequences and return the actual content string, fully decoded.


Step 3 — Walk the JSON to Find the Data Array

The RSC payload is not clean JSON — it is a large serialised React state object. We do not try to parse the whole thing. Instead, we find the literal string "data":[{ and then walk character-by-character, tracking bracket depth, until we find the matching closing ]:

idx = content.find('"data":[{')
start = idx + len('"data":')
depth, end = 0, start
for j, ch in enumerate(content[start:], start):
    if ch == '[': depth += 1
    elif ch == ']':
        depth -= 1
        if depth == 0:
            end = j + 1
            break
raw_trades = json.loads(content[start:end])

Bracket walking is more fragile than a full JSON parse, but it is dramatically faster and does not fail when the surrounding payload contains unexpected structure. On a 10-page parallel fetch, parse time is under 200ms total.


Step 4 — Normalise to a Consistent Schema

Each raw trade from Capitol Trades has a different shape from what our strategy module expects. We map it to a stable schema — the same one originally used by Quiver, so that strategy.py required zero changes when we switched data sources:

Representative, Ticker, TransactionDate,
Transaction, Amount, Range, House

Two quirks worth noting. First, Capitol Trades formats tickers as "AAPL:US" with an exchange suffix — we strip everything after the colon. Second, politician names must match exactly. "Ro Khanna" is listed on Capitol Trades as "Rohit Khanna" (his legal name). The whitelist in settings.py reflects the legal form. The first version of the scraper missed every Khanna trade until this was corrected.


Step 5 — The Mass-Buy Filter

Congressional disclosures can arrive in batches. On April 13, Rohit Khanna disclosed over 150 individual stock purchases in a single session. Most were small — broad index rebalancing rather than directional bets. Without a filter, 150 new tickers would flood the portfolio in one cycle, each with an inflated base score.

The fix: if any politician buys more than 20 unique tickers on a single calendar date, every trade from that day is flagged _mass_buy_day=True in the fetcher. strategy.py then halves the conviction score for flagged trades before applying the regime multiplier.

High-conviction signals are unaffected — a Pelosi GOOGL buy or an Armed Services senator accumulating a defence name would still score above the 55-point threshold even at half value. Index sweeps are discounted without being discarded entirely.


The Result: 817 Records → 364 Signals Per Cycle

After fetching, parsing, normalising, and applying the whitelist, date, and mass-buy filters, a typical cycle produces:

StageCount
Raw records fetched (10 pages × 96)~817
After buy-only filter~540
After 90-day lookback window~420
After whitelist filter (44 politicians)~364
After mass-buy penalty applied364 (scores adjusted)

The pipeline costs nothing to run, carries no rate limits, and has no third-party API dependency. If Capitol Trades ever changes its page structure, the RSC parser breaks — but fixing a regex is a 10-minute job, not a cancelled subscription.

The build continues. Follow along at capitoledge.app.

← Post 11: When Your Data Provider Goes Dark Post 12 of 13 Post 13: Timing the Trade →
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.

Timing the Trade: How the Earnings Surprise Signal Works

Congressional disclosures tell us what to buy. The earnings surprise signal tells us when it matters most — and adds up to 10 conviction points when the timing lines up.

The Setup

A politician buying a stock is a signal. A politician buying a stock four days before that company reports earnings — and that company has beaten analyst estimates three of the last four quarters — is a much stronger one.

The earnings surprise signal is built around that observation. It asks two questions about every ticker the bot is considering:

  1. Is there an earnings date within the next 7 days?
  2. Has this company consistently beaten analyst EPS estimates in recent quarters?

If both answers are yes, the base conviction score gets a boost. If the timing is off or the earnings history is inconsistent, nothing changes — the signal simply doesn't fire.

The Scoring Logic

The signal checks the last four quarterly earnings reports and counts how many times the company beat the analyst consensus EPS estimate. The scoring is tiered:

Positive Surprises (last 4Q) Boost Applied Reasoning
3 or 4 of 4 +10 pts Strong consistent beater — pre-earnings positioning likely intentional
2 of 4 +5 pts Moderate history — worth a partial boost but not full confidence
0 or 1 of 4 0 pts Inconsistent beater — the earnings window doesn't add conviction

The boost only fires when earnings are within the configured window (default 7 calendar days). Outside that window, the signal is silent — it doesn't penalise a stock just because earnings are far away.

A Real Example: NVDA

NVDA reported earnings on May 20, 2026. The bot ran a cycle on May 19. The signal checked NVDA's last four quarters and found four positive EPS surprises out of four. With earnings one day away and a perfect surprise history, NVDA received the full +10 point boost. That pushed any fresh NVDA congressional disclosure well above the 55-point minimum threshold.

Quarters checked
4 / 4
All positive surprises
Days to earnings
1
Within 7-day window
Boost applied
+10 pts
Added to raw score

Why Earnings Timing Matters for Congressional Signals

The standard critique of congressional trading signals is that disclosures are filed up to 45 days after the trade. By the time the data is public, the move may have already happened. The earnings surprise signal partially addresses this by identifying a specific near-term catalyst that the market is already pricing in — and that a congressman buying a week before earnings may also be pricing in.

It's not a claim that politicians have inside knowledge of earnings. It's a simpler observation: a company that has consistently beaten estimates for four straight quarters is more likely to beat again, and the market often runs into earnings for exactly these names. The congressional signal and the earnings setup are independent reasons to be long the same stock at the same time.

The Data Source

The signal uses yfinance to fetch the earnings calendar and historical EPS surprise data. No API key is required. Results are cached for four hours per ticker to avoid redundant fetches across cycles. The signal adds no ongoing cost and introduces no new external dependency beyond what was already in the stack for the short interest and unusual options signals.

The check runs as step 3f in the trading cycle, after SEC insider, government contracts, lobbying, unusual options, and short interest are evaluated — but before the final score is computed and trades are placed.

Where It Sits in the Scoring Formula

The full conviction score formula now includes nine inputs:

raw_score = base(40) + politician_weight(0–20) + committee_bonus(0–15)
           + trade_size(5–20) + sec_insider(−10 to +20)
           + gov_contracts(0–10) + lobbying(0–10)
           + unusual_options(0–15) + short_interest(−5 to +15)
           + earnings_surprise(0 to +10)

The earnings boost is applied before the committee multiplier and regime multiplier, so a +10 point boost on a high-multiplier position (e.g. Armed Services senator, 1.5×) is worth more than +10 in the final score.

The build continues. Follow along at capitoledge.app.

← Post 12: How We Read Disclosures Post 13 of 13
CapitolEdge is operating in paper trading mode. Nothing in this series constitutes financial advice. All performance figures are from simulated accounts.