← All Articles

How I Set Up My Pricer and Execution Engine for Kalshi 15 Minute BTC Markets

Part 5 of the hyhy.app series. The architecture behind my pricer and execution engine, a message-based pipeline on Redis, and the tradeoffs that come with it.

By HYHY.APP ·

Why not one script?

In my previous posts, I covered my approach to pricing the 15 minute prediction markets as options. A few people asked how the system is actually put together, so here is the architecture and why I landed on it.

From the get-go, I knew I did not want a single script running everything. One bad data point or one connectivity drop should not take the whole system down with it. Likewise, one null exception should not crash the whole process.

Once you split it up, you are looking at a pipeline with at least nine distinct steps:

  • Market discovery
  • Market price ingestion
  • Market data ingestion
  • Data aggregation
  • Pricer
  • Gates
  • Execution
  • Position management
  • Logging to a database, running in parallel

That raises the first problem. How do you keep the state of each step in sync?

How do the steps stay in sync?

I looked at a few options and settled on a message-based design using Redis as the transport layer. Every step is an input and output mailbox. It waits for messages on one or more input streams, processes them, and publishes its result. Each step gets its own independent behavior.

For example, market discovery listens to the exchange for market openings and closings. Since Kalshi's 15 minute markets are constantly rolling over, it publishes a state update on every event. The pricer waits until all the inputs it needs are present and fresh, checks timestamps for staleness, and only then runs and publishes a price for execution to evaluate.

Example Pipeline

Where does the logger sit?

The logger sits beside the pipeline, not inside it. The logger and database writer is one more subscriber, running in parallel to the whole pipeline. It listens to the same streams and writes everything to the database. It never sits in the path between a price and an order. If it falls behind or dies, trading carries on untouched.

A nice side effect is that once the data is in the database, presenting it as a live chart and a dashboard is straightforward. It is just reads on top of a table.

hyhy.app dashboard with live streaming prices

How do you scale beyond one of everything?

Having a clean pipeline for one market and one asset is only half the job. The next question was how to extend it to different assets, markets, pricing models, and execution strategies without rewriting each step every time.

I ended up with a factory-worker setup. Each step stays the same, and I spin up multiple instances with different parameters. The best example is spot price ingestion. Instead of one script subscribed to five exchanges, I run five instances, each pointed at a different exchange. The same idea applies to the pricer, with one instance per asset (BTC and ETH), each subscribed to its own inputs.

What did I get out of it?

  • Horizontal scaling. Adding an exchange or an asset means starting another instance, not touching the code.
  • Fast recovery from failure. Because each process is small and independent, one crash only takes out one step. A simple cron job that health-checks each process and restarts it is trivial to set up, and recovery is quick. Compare that to untangling a single monolith that died halfway through something.
  • Isolation. A bad feed from one exchange only affects the instance listening to it.

What are the downsides?

None of this is free.

  • Memory. Running the same process many times over adds up.
  • Latency. Every hop through Redis adds time. Keeping everything local helps.
  • Stale quotes. If a consumer falls behind, old messages can reach the pricer. That is exactly why every consumer checks timestamps before acting.
  • Reasoning about state. With many independent processes, answering "what state is the system in right now?" is harder than with a single script.

I think the tradeoff is worth it, but I would not claim it is the right call for everyone.