$ whoami
> jake hoffman · computer engineering @ queen’s · class of 2027

$ summary
> i build production-grade software — trading agents, arbitrage daemons, and ML pipelines. paper-trading the s&p 500 on real infrastructure; running a 24/7 odds daemon across ten bookmakers.

$ contact
          

// 3 unattended systems 10 bookmakers ingested 6 scheduled agents dean’s scholar

What I work on

Computer engineering at Queen’s. I’m most at home building systems that run unattended — trading agents that paper-trade the S&P 500, an arbitrage daemon ingesting ten bookmakers every minute, and the automation glue that keeps both running unattended.

What I’m best at: turning a messy real-world domain (sports-book pricing, broker order flow) into clean abstractions and a service that doesn’t wake me up at 3am.

Looking for an internship or co-op where I can ship production code — ideally somewhere with hard correctness requirements: trading, infrastructure, financial systems, or developer tools.

Stack

// languages

python · java · c · c# · c++ · javascript · dart · sql

// tools & frameworks

git · fastapi · playwright · unity · sqlite · alembic · monday.com · google apps script

// domains

machine learning · web development · automation · trading systems · mobile apps

What I’m running

ACTIVE last commit: 2d ago

trader private

24/7 AI swing-trading agent for S&P 500 equities, driven by six scheduled, idempotent routines.

Paper-traded on IBKR + Finnhub news; graduates to live only after 30 paper days of documented outperformance. Designed the risk model, journaling, and kill-switch from day one.

// s&p 500 · 6 scheduled agents · ibkr paper

python · pytest · ib_async · finnhub

// example daemon log
[2026-05-12 09:31:02] scan_premarket: 14 candidates → 3 passed (volatility gate)
[2026-05-12 09:31:18] place_orders: limit buy 100 NVDA @ 132.40 (paper)
[2026-05-12 16:00:01] eod: pnl +1.23%, 1 fill, 0 reject

[ read case study ↓ ]

ACTIVE last commit: today

Odds Aggregator private

Production arbitrage daemon covering 10 bookmakers across 6 sports.

Ingest → normalize → detect cross-book arbs → push alerts to Telegram and Discord. Runs 24/7 on a Windows host with Alembic-managed schema and replay tooling for postmortems.

// 10 books · 6 sports · 24/7 uptime

python · playwright · sqlite · alembic · fastapi

// example alert payload
[ARB] NHL · Edmonton @ Vegas
  pinnacle  EDM -132   →  stake 0.567
  betmgm    VGK +145   →  stake 0.433
  edge      +0.84%      ttl 4m12s

[ read case study ↓ ]

ACTIVE last commit: 3w ago

tax-rebalance private

Python CLI that monitors a Canadian TFSA + RRSP portfolio for allocation drift and emails weekly digests with cost-aware rebalance verdicts.

Read-only — never places trades; the user takes the action in their broker. Async end-to-end: SQLAlchemy 2.0 + aiosqlite, httpx, aiosmtplib, jinja2, structlog. Spec-driven development — every feature traces to a written design spec and a 21-task TDD implementation plan executed task-by-task with a two-stage independent review (spec compliance, then code quality).

// 117 tests · ruff-clean · ci on py 3.11 + 3.13

python · async sqlalchemy 2 · httpx · aiosmtplib · jinja2 · pytest

// example weekly digest excerpt
[TFSA] VEQT 60.2% vs target 60.0%  → hold (drift <2%)
[TFSA] XBB  19.4% vs target 20.0%  → buy $312 (cost-aware)
[RRSP] VTI  39.8% vs target 40.0%  → hold (cost > expected benefit)
SHIPPED abandoned · 2026

trades-agency

AI receptionist + lead-gen service targeting Toronto trades — built the full stack, ran a real customer-discovery experiment, abandoned the venture on the data.

Three Python subsystems: lead-gen via Google Places API, an LLM-driven SDR generating cold outreach, and a conversational receptionist agent (“Riley”) with booking and message-taking logic. Ran a 32-contact cold-outreach experiment over four days (18 SMS, 14 email) and got 0 positive replies. Wrote a Bayesian post-mortem ranking six hypotheses; the leading explanation was ICP/channel mismatch — small Toronto trades simply don’t engage with cold outreach from strangers. Abandoned the venture rather than burning more credibility and time on a non-responsive niche.

// 32 contacts · 0 positive replies · 6 hypotheses ranked

python · google places api · llm · sdr · receptionist agent

// cold outreach batch 1 — 32 contacts, 0 positive replies
[H1] wrong icp for cold inbound      strong (0/32 across 2 channels)
[H2] send time wrong (sat 2pm)      weak   (wouldn’t explain 0%)
[H6] wrong offer (missed-call pmf)  strong (never validated demand)
// recommendation: warm-intro experiment before more cold outreach
SHIPPED v1.0 · 2026

equity-arbs

Async Python stat-arb research toolkit for TSX-listed equities and ETFs — documented a hard ceiling and retired at v1.0.

Engle-Granger cointegration + OLS hedge ratio + Ornstein-Uhlenbeck half-life scoring across an 80-name TSX universe; rolling-window regime-stability sweep; configurable backtest engine with z-score entry/exit, hold-time cap, and stop-loss buffer. Six analysis arcs, 76 unit tests. Decision-grade backtest proved the strategy works mathematically (+$216 net across 6 ETF pairs at zero commissions) but caps at ~0.6% annual return on $5K capital — below the passive TSX index alternative. Shipped a correct negative; retired the project at v1.0 with the toolkit as a reusable research asset.

// 76 tests · 80 equities + 39 etfs · 6 analysis arcs

python · asyncio · sqlalchemy 2 · statsmodels · pydantic · typer

// example backtest result — etf pairs, 7 years
[XIU/XIC] coint p=0.001  half-life 22d  → +$84 (entries 14, hits 9)
[XEG/XIT] coint p=0.024  half-life 18d  → -$31 (entries 8, hits 3)
// net 6 pairs over 7y: +$216 (zero fees) → ~$30/yr at retail capital → below tsx index
SHIPPED mar 2024

Mega Tic-Tac-Toe

Unity game on a 3×3 grid of linked Tic-Tac-Toe boards — each move dictates the opponent’s next board.

Local + online multiplayer; single player vs. a Minimax-based AI with multiple difficulty levels. Self-shipped indie title on itch.io.

// unity · c# · minimax ai · local + online mp

unity · c# · minimax ai · multiplayer

Mega Tic-Tac-Toe gameplay: a 3x3 grid of sub-boards with Xs and Os, one sub-board claimed by a large blue O

[ play on itch.io ↗ ]

SHIPPED 2024

Smart Shoe Navigation

Dart mobile app pairing via Bluetooth to a “Smart Shoe” for turn-by-turn directions.

Users enter a destination; the app delivers on-screen prompts plus directional haptic feedback in the shoe itself.

// dart · flutter · bluetooth · embedded haptics

dart · flutter · bluetooth · embedded

// example api call — turn right in 30m
shoe.pulse(side: Side.right,
           strength: 0.7,
           pattern: Pattern.doubleTap);
SHIPPED 2023

Walking / Jumping Classifier

Python ML pipeline classifying accelerometer time-series as walking vs. jumping.

End-to-end — data collection, feature engineering, model training, evaluation. Course capstone shipped with reproducible scripts.

// python · accelerometer · signal processing

python · numpy · scikit-learn · signal processing

Confusion matrix: 182 true walking, 156 true jumping, 7+5 misclassifications. Accuracy 0.965, F1 0.964.

$ ls ~/github →

trader — a 24/7 ai swing-trading agent

Six scheduled, idempotent routines on IBKR paper + Finnhub news. Paper-trading the S&P 500, graduating to live only after 30 paper days of documented outperformance.

   ┌─────────────────┐    ┌─────────────────┐
   │  scan_premarket  │─────────────────────►│      gate        │
   └──────────────────┘    └─────────────────┘
                                       │
                                       ▼
   ┌─────────────────┐    ┌─────────────────┐
   │     monitor      │◄────────────────────│   place_orders   │
   └──────────────────┘    └─────────────────┘
            │
            ▼
   ┌─────────────────┐    ┌─────────────────┐
   │       eod        │─────────────────────►│     journal      │
   └──────────────────┘    └─────────────────┘
          

The problem

A monolith trading bot is one place where every bug becomes a money bug. Splitting the work across six small, idempotent routines means each one can be tested in isolation, re-run safely on failure, and reasoned about on its own. scan_premarket only knows how to filter candidates; gate only knows the risk rules; place_orders only knows the IBKR API. They communicate through the journal, not in-process state.

The risk model

Default is paper. The kill-switch is a single YAML key (kill_switch.enabled: true) read at the top of every routine — flip it and the system stops opening positions at the next scheduled tick. Risk counters (per-trade, per-day, per-week) live in committed JSON so the state survives restarts and is reviewable in a diff. The 30-day paper gate is enforced by gate reading a graduation file written only by the EOD routine.

What I learned

Three things I’d repeat on the next agent like this: idempotent scheduled routines from day one (the monolith refactor I avoided would have eaten weeks); kill-switch and risk gates before any trading logic, so strategy iteration can’t corrupt state; observability turned out to be the hardest engineering problem, not the math — knowing why the agent made each call was harder than getting it to make calls at all.

What’s next

Graduation to live trading is contingent on documented outperformance vs. SPY across 30 paper days. The broker layer is already abstracted (broker.py — IBKR active, Alpaca preserved) so the live cutover is a config change. Building toward a backtester that replays the journal against historical Finnhub headlines to validate model changes before they touch a real fill.

odds aggregator — a 24/7 cross-book arbitrage daemon

Ten bookmakers, six sports, polled every minute. When prices on the same outcome diverge enough to clear the vig and transaction friction, an alert fires to Telegram and Discord within seconds of the gap opening. Runs unattended on a Windows host with replay-tooling for postmortems.

   ┌─────────────────┐   ┌─────────────────┐   ┌─────────────────┐
   │  scraper pool   │──►│   normalize     │──►│  arb detector   │
   │  (10 books)     │   │  (canonical     │   │  (cross-book,  │
   │                 │   │   markets)       │   │   per-event)    │
   └────────┬────────┘   └─────────────────┘   └────────┬────────┘
            │                                          │
            ▼                                          ▼
   ┌─────────────────┐                          ┌─────────────────┐
   │  SQLite +       │◄─────────────────────────│ alert pipeline  │
   │  Alembic        │                          │ (Telegram /     │
   │  (journal)      │                          │  Discord)       │
   └─────────────────┘                          └─────────────────┘
          

The problem

A real-time cross-book ingester sounds simple and isn’t. Each book formats markets differently, prices go stale within seconds, and books vary wildly in scrape-friendliness — FanDuel removed itself with bot detection; Pinnacle requires auth. An arb that’s real on paper is often gone by the time you see it; an arb that looks “available” is often a stale price. The hard part isn’t math, it’s fresh math.

The system

Per-book ingest workers feed a normalization layer that canonicalizes markets (so Moneyline on BetMGM resolves to the same key as h2h on Pinnacle). The arb detector walks canonical odds for each event, converts to implied probabilities, and flags any cross-book combination where Σ implied < 1 minus a configurable edge floor. SQLite with Alembic handles schema evolution — over ten migrations so far. A separate journal table records every raw scrape, which means failures and false-positives are replayable against the exact inputs that produced them.

What I learned / what’s next

Production scrapers fail in interesting ways. The most underestimated work was defensive ingestion — per-book health scoring, automatic backoff on bot-detection, intermittent-cluster tolerance. The math layer was small; the operations layer was most of it. Next step: a paper-trading recorder that attributes realized P&L to historical alerts without actually placing bets, so the alert pipeline can be calibrated against real outcomes.

The cheapest kill-switch is a YAML key

You’re building something that can blow up — money, data, state. You need a way to stop it. The instinct is to reach for “real” infrastructure: a feature flag service, an env var the operator sets at runtime, a row in a database the worker polls. I picked none of those for trader. I used a YAML key. Here’s why.

The instinct

Each of the “serious” options has real engineering behind it. A feature flag service like LaunchDarkly gives you per-user gates, scheduled rollouts, and audit logs — sophisticated and well-understood. An environment variable, set with KILL_SWITCH=1 and a restart, is the textbook twelve-factor move. A row in a kill_switches table that the worker polls on every iteration is composable, supports per-strategy flags, and trivially grows a UI later.

I have used all three in other contexts and I will use them again. None of them is right for this problem. The kill-switch on a trading agent is not a feature flag and not a deployment toggle. It is a stop button. The thing a stop button needs to do, more than anything else, is work when you press it.

Why those are wrong here

The asymmetry is the whole argument. For a kill-switch, the cost of failing to stop is catastrophic — money moves, positions open, the agent keeps trading through the failure. The cost of being too simple — one global switch, no per-strategy granularity, no UI — is zero. The decision should optimize for can I reliably stop? and nothing else.

Each “serious” option introduces a dependency between I want to stop and I can stop. The feature flag service needs the network; if the network is what’s broken, the kill-switch is what’s broken. The environment variable requires the operator to be present and the process to restart, and the restart is itself a risky action while a position is open. The database row requires the worker to be talking to the DB to read it, so a degraded DB connection is a degraded kill-switch.

A YAML key in the repo has no failure mode that isn’t also a failure mode of running the code at all.

The one-key approach

The implementation is six lines of config and four lines of code:

kill_switch:
  enabled: false
def main():
    cfg = yaml.safe_load(open("config.yaml"))
    if cfg["kill_switch"]["enabled"]:
        log.warning("kill_switch enabled; exiting")
        return
    # ... normal routine body

Flipping the switch is git commit -am 'kill' && git push. The next scheduled tick reads the new config and exits cleanly without opening a position. The state is in version control, the diff is the audit log, the rollback is git revert, and “who triggered the kill” is git blame.

What you give up

No per-strategy flags. No scheduled windows. No UI. No ability to disable opening positions while still allowing closes — that one stings a little, and if I ever need it I’ll add a second key, not a second system. All true tradeoffs. None of them answer the question the kill-switch exists to answer, which is: is the agent allowed to trade right now? That is the only question, and one global boolean answers it.

The shape of the principle

The cheapest tool that solves the actual problem beats the sophisticated tool that also solves problems you don’t have. The trap is reaching for infrastructure because it feels more engineered, when the asymmetry of the failure modes points hard at the simple option. Kill-switches are the clearest case I’ve worked through. The same shape shows up elsewhere — risk counters in committed JSON instead of SQLite, a 30-day paper-trade gate as a file write instead of a feature flag. Same instinct, same result.

Where I’ve worked

  1. IT Intern

    jun 2024 – aug 2025

    Lynx Equity Limited

    • Built a task-management system on Monday.com that streamlined communication between subsidiaries by auto-routing issues to the right workflow queue.
    • Designed an On/Off-Boarding workflow with standardized role-based tasks, handoffs, and approvals; delivered a stakeholder demo and shipped feedback into the final product.
    • Wrote Google Apps Script (JavaScript) to ingest multiple Excel exports into a single Google Sheet, apply pricing logic, and track unmatched keys for reconciliation; added a Sheets custom menu and logic for dynamic Excel file IDs.
    • Conducted DNS lookups across subsidiary domains to inventory email providers, SSL status, and other domain metadata.
  2. Independent Game Developer — Mega Tic-Tac-Toe

    jan – mar 2024

    Self-published, Unity

    • Designed and programmed a Unity game on a 3×3 grid of linked Tic-Tac-Toe boards where each move dictates the opponent’s next board.
    • Shipped local + online multiplayer plus single-player vs. a Minimax-based AI with multiple difficulty levels.
  3. Design Team Member

    oct 2023 – mar 2024

    QMIND — Queen’s AI Hub

    • Trained a reinforcement-learning agent to play Blackjack alongside the team.
    • Built a tabular Q-learning baseline with training loop and basic tests, then helped migrate the system to a Deep Q-learning version so the model could learn policies beyond a simple lookup table.
  4. Financial Analyst

    oct 2021 – jun 2023

    BMC Pharmacy

    • Scoped the animal-drug compounding market opportunity across SW Ontario.
    • Tracked monthly, quarterly, and YoY profitability and margin contribution from drug utilization, expanded services, and Rx reports.

Education

Queen’s University

2023 – expected 2027

BASc, Computer Engineering · GPA 3.85 · Dean’s Scholar

  • Built a Python ML classifier on accelerometer data for walking vs. jumping.
  • Developed a pathfinding algorithm in Python for autonomous rover operation.
  • Built a Dart mobile app paired with a Bluetooth “Smart Shoe” for directional haptic navigation.

Get in touch

Email is best. Open to internships, co-ops, and interesting side-project collaborations.