now
Open to work
early stage · proof of concept → v1.0.0
Backend, data layer, and the product calls that come with owning both. Prototype through to the releases after v1.0.0.
20252026
Software Engineer · social crypto app, then prediction markets
Owned the actions service and stayed its main maintainer. It turns raw chain activity into the feed the app renders, and serves the API the iOS client reads. When the product turned to prediction markets I built that side too, from the Polymarket data model to the eighteen endpoints.
What that involved
- Prediction markets. The app had to carry live Polymarket state: events, markets and series, sports leagues and schedules, odds over time, open and redeemable positions, and the bettors behind a market. Data model, storage layer and eighteen endpoints, over seven weeks and 97 commits.
- Live trades. A WebSocket stream carries every Polymarket trade, and over 95% belong to traders this app does not follow. A proxy address resolves through an on-chain owner lookup, then Gamma, then a fallback, and every miss is remembered so none is asked twice.
- Block crawlers. A PostgreSQL function locked the cursor row for each chain and returned the next block number. Parallel tasks shared one cursor without claiming the same block.
- Replay. Each action row went in on a five-column key, so a repeat crawl replaced it. A repair service found stuck and missing blocks, then crawled them again.
- Wallet queue. The crawler indexed only the wallets held in a PostgreSQL queue. It refreshed that set every block, on a three minute lookback.
- Head agreement. Two providers reported the chain head. The crawler used the lower number, and fell back to one provider when they drifted more than five minutes apart.
- Actions and assets. Go services for the action pipeline and the asset store, behind the API that the iOS client called.
- The service. 1,603 of its 1,981 commits are mine, across the crawlers, the store and the API.
PythonGoPostgreSQLFastAPIPrediction marketsAWSRedis
20232025
Senior Data Engineer · Data Scientist · Analytics Engineer
Owned the real time half of the platform: twelve chains in PostgreSQL at the tip, with balances and query routing on top. I raised the decode rate on traces and logs by keying decoding to the signature, not the contract. Inputs and return data both, and a contract with no published ABI was no longer a wall.
What that involved
- Decoding without an ABI. Most contracts never publish one, so a trace or a log from an unverified contract stays hex. One table keyed by selector and topic carried the ABI fragment and the output types, so a signature learned once decoded that call anywhere. Nested tuples are expanded first, because
tuple[] decodes nothing.
- The panel on this page. That idea, running. A baked table of 144 signatures answers first, a remote lookup closes the rest, and the footer prints the share it named for the block on screen.
- Ingestor. Streamed blocks, transactions, logs and traces into PostgreSQL with asyncio, across twelve chains from Ethereum to Sui, behind 335 table definitions. A healer found the blocks that were missed or left unfinished, and crawled them again.
- Balances. A chain records transfers, not balances, so a balance is a walk over history unless something keeps it. Signed deltas from decoded transfer logs and traces kept each one current instead of recomputing on read, checked address by address against
balanceOf at the same block.
- Query routing. PostgreSQL answers a point read in milliseconds and labours on a full scan. Presto is the other way about. I specified the complexity heuristic that dispatched each query between Presto, Athena and PostgreSQL, so nobody had to choose one.
- Consensus layer. Beacon chain crawlers and data models for mainnet and testnet, beside the execution layer ones. zkSync had a crawler of its own.
- Data lake. Pipelines into a Presto, Athena and SparkSQL compatible lake on AWS. dbt for the transforms, MySQL for user management.
PostgreSQLasyncioPrestoIcebergdbtAWS
20222023
Data Lead · Data Scientist · Analytics Engineer
Owned the Network, DeFi, NFT and Gaming data: collected it, decoded it, automated the pipeline, and set the metric definitions. 140 dbt models and 25 DAGs replaced metrics computed by hand. A figure in a deck and the same figure on a dashboard finally agreed.
What that involved
- Wallet balances. No source would hand us a token balance for an address, and calling the chain per address does not reach a dashboard. Signed deltas from decoded transfer events and gas fees accumulate per address and token in BigQuery, so the models never call the chain.
- Event decoding. A raw log is hex, and no analyst can read one. Topic0 filters pull each event out of the raw logs table, and fifteen persistent BigQuery functions unpack the payloads into numbers and structs, so a model reads an event as columns.
- dbt models. 140 models across Ethereum, Polygon PoS and Polygon zkEVM, one table or view per file. Most are incremental and overwrite day partitions. Many repeat a two day window, so late blocks still land.
- Dataproc. The heavy Python jobs had outgrown one machine. They run as PySpark on a standing cluster and import pyspark.pandas, so most of the pandas code needed no change.
- Airflow DAGs. 25 DAGs, 21 of them on daily crons. Task factories emit one operator for each chain and time period, then route it to BigQuery, Flipside or GraphQL.
- Who read it. The same models fed Looker, the dashboards for network, DeFi, NFT, gaming, dapp coverage and retention, and the tables that went to business development, marketing and partners including Starbucks and Reddit.
dbtBigQueryPySparkDataprocAirflowLooker
2022
Bounty hunter · part time
SQL analytics and dashboards across Terra, Thorchain, Maker and Aave, written for open bounties while working full time elsewhere. Reached Elite tier. This is where the turn from risk modelling to blockchain data happened.
SQLDashboards
20212022
Data Scientist · credit risk
Built the first credit risk scorecard for private individuals: the model that decides whether accepting an applicant is worth the risk. I led the feature discovery, chose the inputs with the Finance and Credit Risk analysts, and delivered version one.
What that involved
- Credit scoring. The decision needed a number behind it, not a judgement call. I took the problem from feature discovery through analysis to a scored model, and delivered the first version.
- Data sources. The Finance and Credit Risk analysts and I chose the inputs, from standard credit data and from sources a scorecard does not usually use.
- Contract extraction. Text extraction pipelines read PDF contracts and found the inconsistencies between two documents.
PythonRisk modellingPDF extraction
20202021
Junior Data Scientist · crashmatics (acquired by Toyota)
Built the models that read a crash out of vehicle telematics: who was liable, and how severe it was. An accelerometer trace is not a description of a crash. The work was in the features, and in shipping the result as a deployed API.
What that involved
- Features. Physics signals from raw accelerometer traces: acceleration, jerk and impact direction, not only learned representations.
- Models. Driver liability and accident severity from crash telematics, framed as a CRISP-DM cycle from the business problem down to the features.
- Deployment. One workflow from preprocessing to a deployed API. MLflow logged and registered each model, and Docker kept the environment the same.
PythonMLflowAWSDocker
20162020
University
Physics, then data science
Theoretical physics first, then machine learning and reinforcement learning. Evolutionary methods are what stuck: search that is allowed to wander finds things that search aimed at a target does not.
Degrees
- Data Science MSc, University of Surrey, 2019 to 2020.
Machine Learning and Data Mining, and Computational Intelligence, taken from the
mathematical foundations up. Reinforcement learning came from the first and deep
learning from the second. Image Processing and Deep Learning covered convolutional
architectures. Cloud Computing on AWS and GCP was assessed on a trading algorithm.
Database Systems is where the SQL started.
- Theoretical and Mathematical Physics BSc, University of Nottingham, 2017 to 2019.
Quantum Dynamics, Classical Fields, Thermal and Statistical Physics, and Symmetry and
Action Principles. Scientific Computing is the module that carried into the work:
numerical methods applied to a physical system. Deriving jerk from an accelerometer
trace is the same habit. Peer mentor for first year physicists, and captain and coach
of the Physsoc basketball team.
- Physics BSc, University of Leicester, 2016 to 2017.
First year foundations: Dynamics, Electricity and Magnetism, Waves and Quanta, and two
semesters of mathematical physics. Transferred to Nottingham for the theoretical route.
Why that idea stayed
Influence, not experience. Every model I was paid for was supervised, trained against a fixed objective. NEAT is not something I shipped. It is something I keep going back to, and it changed how I choose an objective rather than which library I import.
NEAT grows a network's structure instead of fixing it up front. Kenneth O. Stanley and Joel Lehman's Why Greatness Cannot Be Planned makes the harder claim: an ambitious objective creates a deceptive gradient, so the objective itself becomes the trap.
Their result, from the paper behind the book: on a deceptive maze, searching for the goal reached it 3 times in 40. Random selection reached it 4 times. Searching only for novel behaviour, with no knowledge of where the goal was, reached it 39 times.
Both machine learning jobs were feature discovery, sweeping physics signals for things that turned out to matter. The book is why I now ask whether the objective is the deceptive part before optimising it.
PhysicsRLEvolutionary search
Most recent pipeline and product work sits in private repositories, so it is described here in general terms and without links. The public ones are on GitHub.