A small Rust CLI that answers trade queries over a local dataset (trades.csv).
The interesting part is the caching: queries are at most 1 hour wide, so the program caches trades per-hour in an LRU to avoid repeating the same "expensive" fetch.
Note: get_fills_api in src/server.rs simulates an expensive API call (sleep + filter over the CSV-backed dataset). There's no HTTP server here.
Files expected in the repo root:
trades.csv(trade data)- a query file like
test_input.txtorinput.txt
Run:
cargo run --quiet < test_input.txtOr:
cargo run --quiet < input.txtEach line on stdin is:
<TYPE> <START_TIME> <END_TIME>
START_TIME/END_TIME: Unix timestamps (seconds)- Time window semantics: trades with
time > START_TIMEandtime <= END_TIME
Example:
C 1700000000 1700003600
V 1700000000 1700003600
Output is one line per query (a number for C/B/S, a Decimal for V).
C: count of taker trades (unique by sequence number)B: count of market buys (direction1)S: count of market sells (direction-1)V: total traded volume in USD (quantity * pricesummed over fills)
END_TIME - START_TIMEmust be <= 3600 seconds- Input timestamps are assumed to be within the dataset's time range
- A "taker trade" is uniquely identified by
sequence_number(duplicate sequence numbers are treated as the same taker trade forC/B/S)
- Rounds
START_TIMEandEND_TIMEdown to hour boundaries. - Fetches fills for the start hour, and (if different) the end hour.
- Uses an LRU cache keyed by hour timestamp (seconds) with capacity 168 hours.
- Filters fills to the exact (> start, <= end) window and answers the query.
Implementation entry point: src/main.rs.
Uses env_logger. Set RUST_LOG to control verbosity:
RUST_LOG=info cargo run --quiet < test_input.txt
RUST_LOG=debug cargo run --quiet < test_input.txtAt the end it prints cache stats + hit rate.
Performance + design notes
- Cache capacity: 168 hours (one week)
- Key: hour timestamp (rounded down to hour boundary)
- Value:
Vec<Fill>(all fills for that hour)
- Round timestamps to hour boundaries.
- Read required hours from cache, fetching missing hours via
get_fills_apiand inserting into cache. - Merge hour buckets (at most 2 hours given the <= 3600s constraint).
- Filter to the exact (> start, <= end) range.
- Answer the query.
- Environment: MacBook Pro (16GB RAM, M2 Pro)
- Dataset: 235,834 trades over ~165 hours
- Query Set: 1000 random queries
- Results:
- Memory usage: 13.2MB (normal), ~47MB (worst case estimate)
- API calls: reduced by 83.6% (from 1000 to 164)
- Total processing time: reduced by 64% (from 31.8s to 11.5s)
- Trade volume varies; average ~1,438 trades/hour, peak 4,211 trades/hour.
- Dataset is static during execution.
- Each fill has direction
1(buy) or-1(sell). - Single-threaded is sufficient.
- Total fills in dataset: 235,834
- Cache size if it held the entire dataset: 13,212,016 bytes (~13.2MB)
- Approx bytes/fill: 13,212,016 / 235,834 ~= 56 bytes
- Peak-hour assumption: 5,000 fills/hour
- One-week peak cache:
5000 * 56 * 168~= 47MB
- Cache raw
Fillvs. cache precomputed aggregates:- raw keeps query flexibility; costs memory and per-query processing.
- Cache full hours vs. cache exact ranges:
- hours simplify cache keys and improve hit rate; may store unused fills.
- LRU cache vs. HashMap:
- LRU gives a fixed cap + eviction; slightly more overhead.
rust_decimal::Decimalinstead off64to avoid floating point surprises for money.anyhowfor error messages and ergonomics.