Add answer key and fixed solution

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SirBlobandClaude Opus 5.5 committed 2026-10-31 08:53:30 -04:00
commit b51d29bdb5
3 files changed
+292

No files matched your search

+86
View File
@@ -0,0 +1,86 @@
from dataclasses import dataclass
SIDES = ("BUY", "SELL")
@dataclass
class Trade:
trade_id: str
symbol: str
side: str
qty: int
price: float
def signed_qty(self):
return -self.qty if self.side == "SELL" else self.qty
@dataclass
class Position:
symbol: str
qty: int = 0
avg_price: float = 0.0
realized: float = 0.0
def update(self, qty, price):
new_qty = self.qty + qty
if self.qty == 0 or (self.qty > 0) == (qty > 0):
self.avg_price = (self.avg_price * self.qty + price * qty) / new_qty
else:
closed = min(abs(qty), abs(self.qty))
direction = 1 if self.qty > 0 else -1
self.realized += closed * (price - self.avg_price) * direction
if new_qty == 0:
self.avg_price = 0.0
elif (new_qty > 0) != (self.qty > 0):
self.avg_price = price
self.qty = new_qty
class Ledger:
def __init__(self, logger):
self.log = logger
self.positions = {}
self.history = []
def _validate(self, trade):
if trade.side not in SIDES:
raise ValueError(f"bad side {trade.side!r}")
if trade.qty <= 0:
raise ValueError(f"bad qty {trade.qty!r}")
if trade.price <= 0:
raise ValueError(f"bad price {trade.price!r}")
def apply(self, trade):
self.log.push(trade_id=trade.trade_id, symbol=trade.symbol)
try:
self._validate(trade)
except ValueError as exc:
self.log.error(f"rejected: {exc}")
raise
pos = self.positions.setdefault(trade.symbol, Position(trade.symbol))
pos.update(trade.signed_qty(), trade.price)
self.history.append(trade)
self.log.info(f"applied {trade.side} {abs(trade.qty)}@{trade.price:.2f} -> {pos.qty}")
return pos
def position(self, symbol):
return self.positions.get(symbol, Position(symbol))
@classmethod
def rebuild(cls, trades, logger):
ledger = cls(logger)
for trade in trades:
try:
ledger.apply(trade)
except ValueError:
continue
return ledger
def reconcile(self, other):
breaks = []
for symbol in sorted(set(self.positions) | set(other.positions)):
mine, theirs = self.position(symbol), other.position(symbol)
if mine.qty != theirs.qty or abs(mine.avg_price - theirs.avg_price) > 1e-9:
breaks.append((symbol, mine.qty, theirs.qty))
return breaks
+68
View File
@@ -0,0 +1,68 @@
import sys
import time
LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR")
class Record:
__slots__ = ("ts", "level", "msg", "context")
def __init__(self, ts, level, msg, context):
self.ts = ts
self.level = level
self.msg = msg
self.context = context
def render(self):
ctx = " ".join(f"{k}={v}" for k, v in sorted(self.context.items()))
return f"{self.ts:.6f} {self.level:<7} {self.msg} {ctx}".rstrip()
class BufferedLogger:
def __init__(self, name, level="INFO", stream=None, capacity=64):
self.name = name
self.stream = stream if stream is not None else sys.stderr
self.capacity = capacity
self._context = {}
self._buffer = []
self.set_level(level)
def set_level(self, level):
level = level.upper()
if level not in LEVELS:
raise ValueError(f"unknown level {level!r}")
self.level = level
def push(self, **fields):
self._context.update(fields)
def clear(self):
self._context.clear()
def _enabled(self, level):
return LEVELS.index(level) >= LEVELS.index(self.level)
def _log(self, level, msg):
if not self._enabled(level):
return
self._buffer.append(Record(time.time(), level, msg, dict(self._context)))
if len(self._buffer) >= self.capacity:
self.flush()
def debug(self, msg):
self._log("DEBUG", msg)
def info(self, msg):
self._log("INFO", msg)
def warning(self, msg):
self._log("WARNING", msg)
def error(self, msg):
self._log("ERROR", msg)
def flush(self):
for rec in self._buffer:
self.stream.write(f"[{self.name}] {rec.render()}\n")
self.stream.flush()
self._buffer.clear()