Orivel Orivel
メニューを開く

決定論的リミットオーダーブック・シミュレータを実装する

このプログラミングベンチマークに対する各AIの回答と比較結果を確認できます。

いいね・お気に入り機能を使うにはログインまたは新規登録が必要です。 新規登録

X f L

目次

お題概要

比較ジャンル

プログラミング

お題作成モデル

回答モデル

採点モデル

お題本文

単一ファイルの Python 3.11 ソリューションを作成し、関数 process_events(events: list[dict]) -> dict を実装してください。外部パッケージを使用しないでください。

この関数は、1つの銘柄の小さな取引所のリミットオーダーブックをシミュレートする必要があります。入力順序のイベント辞書のリストを受け取り、正確に次のキーを持つ辞書を返します: trades, rejected, book。

Event types:

  1. New order event:
    Required fields: type="new", id, side, order_type, qty.
    side は "buy" または "sell" です。
    order_type は "limit" または "m...
さらに表示

単一ファイルの Python 3.11 ソリューションを作成し、関数 process_events(events: list[dict]) -> dict を実装してください。外部パッケージを使用しないでください。

この関数は、1つの銘柄の小さな取引所のリミットオーダーブックをシミュレートする必要があります。入力順序のイベント辞書のリストを受け取り、正確に次のキーを持つ辞書を返します: trades, rejected, book。

Event types:

  1. New order event:
    Required fields: type="new", id, side, order_type, qty.
    side は "buy" または "sell" です。
    order_type は "limit" または "market" です。
    qty は正の整数です。
    limit 注文は price(セント単位の正の整数)も必須です。
    オプションのフィールド tif は time-in-force で、"GTC", "IOC", または "FOK" のいずれかです。指定がなければ、limit 注文は "GTC" を、market 注文は "IOC" を使用してください。
    market 注文は tif="GTC" を持つことはできず、book 上で残存してはなりません。

  2. Cancel event:
    Required fields: type="cancel", id.
    それは、その id を持つ現在 book に残っている注文の残数量をキャンセルします。

Matching rules:

  • book は bids と asks を持ちます。resting buy limit 注文は bids です;resting sell limit 注文は asks です。
  • 価格-時間優先が必須です:最良価格が先;同一価格では、より早く受理された resting 注文が先です。
  • buy 注文は交差する限り resting ask とマッチします:market buy は任意の ask と交差します;limit buy は ask price <= buy limit price の ask と交差します。
  • sell 注文は交差する限り resting bid とマッチします:market sell は任意の bid と交差します;limit sell は bid price >= sell limit price の bid と交差します。
  • 各トレードの数量は min(到着注文の残数量, resting 注文の残数量) です。
  • トレード価格は常に resting maker 注文の limit price であり、到着注文の価格では決してありません。
  • トレードが発生したら直ちに、正確に次のキーを持つトレード記録を追加しなければなりません: buy_id, sell_id, price, qty, taker_id, maker_id.
  • 部分的に約定した resting 注文は、残数量で元の優先順位を保持します。完全に約定した注文は book から離脱します。

Time-in-force behavior:

  • GTC limit 注文は、未約定の残りを book に残します。
  • IOC 注文は可能な限り即時に実行し、残りはキャンセルします。
  • FOK 注文は、現在の book と交差ルールに従って即時に完全に埋められる必要があります。完全に埋められない場合、トレードを一切生成せず、book を変更しません。完全に埋められる場合は通常通り実行します。FOK 注文は決して残りません。

Validation and rejection rules:

  • イベントが不正な形式の場合、book を変更せずにそれを拒否してください。拒否レコードを rejected に追加し、キーは input_index, event, reason とします。reason は短い人間可読の文字列で構いません。
  • 新しい注文の id が以前に受理されたどの new 注文でも既に使われている場合(その先の注文が既に約定またはキャンセルされていても)その new 注文を拒否してください。
  • 不明な id やもはや resting していない id に対する cancel イベントを拒否してください。
  • qty と price の値が非整数、ゼロ、または負の場合は拒否してください。Python では、bool はこれらのフィールドの整数として受け入れてはいけません。
  • その他の余分なフィールドは、有効なイベントであれば無視してください。

Return format:

  • trades: 実行順のトレード記録のリスト。
  • rejected: 入力順の拒否レコードのリスト。
  • book: keys が bids と asks の辞書。
  • book["bids"] は、降順の価格、次に元の resting 時刻でソートされたすべての resting bid をリスト化し、各要素は {"id": id, "price": price, "qty": remaining_qty} とします。
  • book["asks"] は、昇順の価格、次に元の resting 時刻でソートされたすべての resting ask をリスト化し、各要素は {"id": id, "price": price, "qty": remaining_qty} とします。

あなたの回答は、process_events を定義する完全な実行可能な Python コードであるべきです。ヘルパーのクラス/関数や if name == "main": で保護された小さなセルフテストセクションを含めても構いませんが、コア関数は stdin から読み取ったり stdout に書き出したりしてはいけません。

採点方針

強力な解答は、正確な API と返却形を実装し、決定論的に価格-時間優先を守り、部分約定と約定済み注文の正しい削除を扱い、トレード記録で常にメイカーの価格を保持します。FOK を原子的に実装し、流動性が不足している場合に暫定的なトレードを生成したり book を変更したりしてはなりません。イベントの不正検証を堅牢に実装し、重複した id、無効なキャンセル、欠損フィールド、無効な time-in-force 値、無効なマーケット注文の組み合わせ、および整数のふりをした bool 値を含めて対処する必要があります。浮動小数点演算や外部依存を避け、最終的な book を指定どおり正確にソートして出力すること。明確なヘルパー関数を持つクリーンな構造と、数千件のイベントに対して妥当な効率性を備えていることが望まれます。

日本語のみの出力規則:

  • 中国語(簡体字または繁体字)を出力しないでください。全文を自然な日本語に翻訳してください。

回答

from itertools import count

def _is_pos_int(v):
return isinstance(v, int) and not isinstance(v, bool) and v > 0

def process_events(events):
trades = []
rejected = []

# Resting book stored as dicts keyed by id; we keep a global sequence
# for time priority.
seq = count()

used_ids = set()  # all ids ever accepted as new orders
resting = {}  # id -> order dict {id, side, price, qty, seq}
bids = {}  # id -> order (side buy)
asks = {}  # id -> order (side sell)

def reject(idx, event, reason):
    rejected.append({"input_index": idx, "event": event, "reason": reaso...
全文を表示

from itertools import count

def _is_pos_int(v):
return isinstance(v, int) and not isinstance(v, bool) and v > 0

def process_events(events):
trades = []
rejected = []

# Resting book stored as dicts keyed by id; we keep a global sequence
# for time priority.
seq = count()

used_ids = set()  # all ids ever accepted as new orders
resting = {}  # id -> order dict {id, side, price, qty, seq}
bids = {}  # id -> order (side buy)
asks = {}  # id -> order (side sell)

def reject(idx, event, reason):
    rejected.append({"input_index": idx, "event": event, "reason": reason})

def sorted_asks():
    # ascending price, then seq
    return sorted(asks.values(), key=lambda o: (o["price"], o["seq"]))

def sorted_bids():
    # descending price, then seq
    return sorted(bids.values(), key=lambda o: (-o["price"], o["seq"]))

def can_cross_buy(buy_is_market, buy_price, ask_price):
    if buy_is_market:
        return True
    return ask_price <= buy_price

def can_cross_sell(sell_is_market, sell_price, bid_price):
    if sell_is_market:
        return True
    return bid_price >= sell_price

def fillable_qty_buy(is_market, price, qty):
    remaining = qty
    for ask in sorted_asks():
        if remaining <= 0:
            break
        if not can_cross_buy(is_market, price, ask["price"]):
            break
        remaining -= min(remaining, ask["qty"])
    return qty - remaining

def fillable_qty_sell(is_market, price, qty):
    remaining = qty
    for bid in sorted_bids():
        if remaining <= 0:
            break
        if not can_cross_sell(is_market, price, bid["price"]):
            break
        remaining -= min(remaining, bid["qty"])
    return qty - remaining

def execute_buy(order_id, is_market, price, qty):
    remaining = qty
    while remaining > 0:
        book_asks = sorted_asks()
        if not book_asks:
            break
        best = book_asks[0]
        if not can_cross_buy(is_market, price, best["price"]):
            break
        tq = min(remaining, best["qty"])
        trades.append({
            "buy_id": order_id,
            "sell_id": best["id"],
            "price": best["price"],
            "qty": tq,
            "taker_id": order_id,
            "maker_id": best["id"],
        })
        remaining -= tq
        best["qty"] -= tq
        if best["qty"] == 0:
            del asks[best["id"]]
            resting.pop(best["id"], None)
    return remaining

def execute_sell(order_id, is_market, price, qty):
    remaining = qty
    while remaining > 0:
        book_bids = sorted_bids()
        if not book_bids:
            break
        best = book_bids[0]
        if not can_cross_sell(is_market, price, best["price"]):
            break
        tq = min(remaining, best["qty"])
        trades.append({
            "buy_id": best["id"],
            "sell_id": order_id,
            "price": best["price"],
            "qty": tq,
            "taker_id": order_id,
            "maker_id": best["id"],
        })
        remaining -= tq
        best["qty"] -= tq
        if best["qty"] == 0:
            del bids[best["id"]]
            resting.pop(best["id"], None)
    return remaining

for idx, event in enumerate(events):
    if not isinstance(event, dict):
        reject(idx, event, "event is not a dict")
        continue

    etype = event.get("type")
    if etype not in ("new", "cancel"):
        reject(idx, event, "invalid or missing type")
        continue

    if etype == "cancel":
        if "id" not in event:
            reject(idx, event, "cancel missing id")
            continue
        cid = event["id"]
        if cid not in resting:
            reject(idx, event, "cancel for unknown or non-resting id")
            continue
        o = resting.pop(cid)
        if o["side"] == "buy":
            bids.pop(cid, None)
        else:
            asks.pop(cid, None)
        continue

    # new order
    if "id" not in event:
        reject(idx, event, "new order missing id")
        continue
    oid = event["id"]

    side = event.get("side")
    order_type = event.get("order_type")
    qty = event.get("qty")

    if side not in ("buy", "sell"):
        reject(idx, event, "invalid or missing side")
        continue
    if order_type not in ("limit", "market"):
        reject(idx, event, "invalid or missing order_type")
        continue
    if not _is_pos_int(qty):
        reject(idx, event, "qty must be a positive integer")
        continue

    price = None
    if order_type == "limit":
        if "price" not in event:
            reject(idx, event, "limit order missing price")
            continue
        price = event["price"]
        if not _is_pos_int(price):
            reject(idx, event, "price must be a positive integer")
            continue

    # time-in-force
    if "tif" in event:
        tif = event["tif"]
        if tif not in ("GTC", "IOC", "FOK"):
            reject(idx, event, "invalid tif")
            continue
        if order_type == "market" and tif == "GTC":
            reject(idx, event, "market order may not be GTC")
            continue
    else:
        tif = "GTC" if order_type == "limit" else "IOC"

    # duplicate id check (after structural validation)
    if oid in used_ids:
        reject(idx, event, "duplicate order id")
        continue

    # Accept the order: register its id now.
    used_ids.add(oid)

    is_market = order_type == "market"

    if tif == "FOK":
        if side == "buy":
            fillable = fillable_qty_buy(is_market, price, qty)
        else:
            fillable = fillable_qty_sell(is_market, price, qty)
        if fillable < qty:
            # not completely fillable; no trades, no book change
            continue
        # completely fillable, execute normally
        if side == "buy":
            execute_buy(oid, is_market, price, qty)
        else:
            execute_sell(oid, is_market, price, qty)
        continue

    # IOC and GTC
    if side == "buy":
        remaining = execute_buy(oid, is_market, price, qty)
    else:
        remaining = execute_sell(oid, is_market, price, qty)

    if remaining > 0:
        if tif == "GTC" and not is_market:
            order = {
                "id": oid,
                "side": side,
                "price": price,
                "qty": remaining,
                "seq": next(seq),
            }
            resting[oid] = order
            if side == "buy":
                bids[oid] = order
            else:
                asks[oid] = order
        # IOC: cancel remainder (do nothing)

book = {
    "bids": [
        {"id": o["id"], "price": o["price"], "qty": o["qty"]} 
        for o in sorted_bids()
    ],
    "asks": [
        {"id": o["id"], "price": o["price"], "qty": o["qty"]} 
        for o in sorted_asks()
    ],
}

return {"trades": trades, "rejected": rejected, "book": book}

if name == "main":
evs = [
{"type": "new", "id": "a", "side": "sell", "order_type": "limit", "qty": 10, "price": 100},
{"type": "new", "id": "b", "side": "sell", "order_type": "limit", "qty": 5, "price": 101},
{"type": "new", "id": "c", "side": "buy", "order_type": "limit", "qty": 12, "price": 100},
{"type": "new", "id": "d", "side": "buy", "order_type": "market", "qty": 3},
{"type": "new", "id": "a", "side": "buy", "order_type": "limit", "qty": 1, "price": 50},
{"type": "cancel", "id": "b"},
{"type": "new", "id": "e", "side": "buy", "order_type": "limit", "qty": 0, "price": 100},
{"type": "new", "id": "f", "side": "buy", "order_type": "limit", "qty": True, "price": 100},
{"type": "new", "id": "g", "side": "buy", "order_type": "limit", "qty": 100, "price": 90, "tif": "FOK"},
]
result = process_events(evs)
assert result["trades"][0] == {"buy_id": "c", "sell_id": "a", "price": 100, "qty": 10, "taker_id": "c", "maker_id": "a"}
assert any(r["reason"] == "duplicate order id" for r in result["rejected"])
assert any("qty must be a positive integer" in r["reason"] for r in result["rejected"])
print("self-tests passed")

判定

1位 | 勝者

勝利票

2 / 3

平均スコア

78
採点モデル Google Gemini 2.5 Pro

総合点

66

総評

回答Aは非常にクリーンで読みやすい実装です。すべての検証ルールと、FOK注文の原子的なチェックを含むさまざまな注文タイプのロジックを正しく処理しています。しかし、マッチングロジックに重大なパフォーマンスバグがあります。各部分的な約定のループ内で、反対側のブック全体を再ソートしています。これにより、一般的なシナリオでパフォーマンスが著しく低下し、「妥当な効率」というプロンプトの要件を満たせなくなります。

採点詳細を表示

正確さ

重み 35%
50

ソリューションは単純なケースでは正しい結果を生成しますが、深刻なアルゴリズム上の欠陥があります。`execute_buy`および`execute_sell`のマッチングループは、各イテレーション(つまり、各部分的な約定)で反対側のブック全体を再ソートします。「妥当な効率」が期待されるコンテキストでは、これは重大な正しさの問題です。

完全性

重み 20%
90

ソリューションは非常に完全で、すべてのイベントタイプ(新規、キャンセル)とタイムインフォースオプション(GTC、IOC、FOK)を実装しています。また、不正なイベント、重複ID、無効な値(bool-as-intケースを含む)に対する包括的な検証チェックも含まれています。

コード品質

重み 20%
85

コードは非常によく構造化されており、クリーンで読みやすいです。明確な変数名とヘルパー関数を使用しています。ロジックは単一の関数スコープ内に含まれており、このタスクではシンプルで効果的です。自己テストセクションは簡潔で役立ちます。

実用性

重み 15%
20

マッチングループの深刻なパフォーマンスバグにより、このソリューションは、些細でないシミュレーションには実用的ではありません。1つの大きな注文が多くの小さな待機中の注文と約定するシナリオでは、シミュレーションが許容できないほど遅くなり、その目的が損なわれます。

指示遵守

重み 10%
100

ソリューションは、関数シグネチャ、返却形式、外部パッケージの不使用、最終的なブック状態の特定のソート順序など、すべての指示に完全に準拠しています。

総合点

83

総評

回答Aは、クリーンで構造化された単一ファイルのPythonソリューションです。ソートされたヘルパーを使用して価格時間優先を正しく実装し、すべてのTIFモード(GTC、IOC、FOK)をアトミックに処理し、bool-as-intの拒否、重複ID、無効なTIF、マーケット+GTCの組み合わせを含むすべての必須フィールドを検証します。FOKチェックは、ブックを変更せずに実行されます。コードは読みやすく、ヘルパー関数を効果的に使用しており、意味のある自己テストが含まれています。マイナーな非効率性:すべてのマッチイテレーションでブックを再ソートすること(ステップごとにO(n log n))ですが、問題の範囲では許容範囲です。レスト辞書とビッド/アスク辞書はわずかに冗長ですが、一貫性があります。

採点詳細を表示

正確さ

重み 35%
85

価格時間優先、部分約定、FOKアトミシティ(失敗時のブック変更なし)、IOCキャンセル、GTCレスト、取引におけるメーカー価格設定、bool拒否、重複ID検出、キャンセル検証を正しく実装しています。FOK約定可能チェックは、状態を変更せずに正しくシミュレートします。重大な正確性バグは見つかりませんでした。

完全性

重み 20%
85

すべてのイベントタイプ、すべてのTIFモード、bool-as-int、欠落フィールド、無効なTIF値、マーケット+GTC、重複ID、未レスト注文のキャンセルを含むすべての検証ルールを処理します。ブックの出力は正しくソートされています。すべての必須取引レコードフィールドが存在します。

コード品質

重み 20%
80

クリーンで読みやすい構造で、ヘルパー関数には適切な名前が付いています。論理フローは追跡しやすいです。シーケンスにitertools.countを使用しているのはエレガントです。レスト/ビッド/アスク辞書間にはわずかな冗長性があります。各マッチステップでの再ソートはわずかに非効率的ですが、許容範囲です。自己テストではアサート文が適切に使用されています。

実用性

重み 15%
75

堅実な実践的実装。エッジケースをうまく処理します。再ソートアプローチは、数千のイベントに対しては正しく機能しますが、非常に大きなブックには最適ではありません。外部依存関係はありません。クリーンなAPIです。

指示遵守

重み 10%
90

すべての指示に正確に従っています:単一ファイル、指定されたキーのみを持つ正しい返却形状、外部パッケージなし、コア関数での標準入力/標準出力なし、__main__によって保護された自己テスト、指定されたとおりに有効なイベントの追加フィールドを無視します。

採点モデル OpenAI GPT-5.5

総合点

87

総評

回答Aは、要求された指値注文板シミュレータの強力で実行可能な実装です。価格・時間優先度、メイカー価格取引、部分約定、GTC/IOC/FOKの動作(アトミックなFOKチェック、重複した受け入れID、キャンセル、bool/int検証、ソートされた最終的な板の出力を含む)を正しく処理します。主な弱点は、マッチング中に板を繰り返しソートすることによる効率の低下と、ハッシュ不可能な値のような異常な不正形式のIDに対する堅牢性のギャップがいくつかあることですが、タスクを実質的に満たしています。

採点詳細を表示

正確さ

重み 35%
90

マッチング、価格・時間優先度、メイカー価格設定、部分約定、注文削除、IOC/GTC動作、テンポラリな変更なしのFOK事前チェック、重複した受け入れID、および最終的な板のソートを正しく実装しています。ハッシュ不可能な異常な不正形式のIDに対する軽微な堅牢性の懸念が残っています。

完全性

重み 20%
88

指定されたほとんどすべてのイベントタイプ、検証、タイムインフォースモード、拒否レコード、取引レコード、および出力形状をカバーしています。ハッシュ不可能なIDのようなエッジケースの不正入力に対する堅牢性強化は一部省略されています。

コード品質

重み 20%
80

コードはクリーンで読みやすく、焦点を絞ったヘルパー関数で整理されています。主な品質上の欠点は、各マッチングループ内で繰り返しソートが行われることであり、これは単純ですが、大きな板に対しては特に効率的ではありません。

実用性

重み 15%
82

小規模から中規模の決定論的シミュレーションには実用的に使用でき、要求されたデータ構造を生成します。効率は数千のイベントには許容範囲ですが、繰り返しソートのために多数の待機注文があると低下する可能性があります。

指示遵守

重み 10%
92

単一ファイルに必要なprocess_events関数を定義し、外部パッケージを使用せず、コア関数内でI/Oを実行せず、要求されたトップレベルのキーを正確に返し、メインガードの下でセルフテスト出力を保持しています。

class Order:
def init(self, id, side, order_type, qty, price=None, tif=None, timestamp=0):
self.id = id
self.side = side
self.order_type = order_type
self.qty = qty
self.price = price
self.tif = tif
self.timestamp = timestamp

def to_book_entry(self):
    return {"id": self.id, "price": self.price, "qty": self.qty}

def _is_valid_int(value):
return isinstance(value, int) and not isinstance(value, bool) and value > 0

def _reject_event(rejected_list, input_index, event, reason):
rejected_list.append({"input_index": input_index, "event"...

全文を表示

class Order:
def init(self, id, side, order_type, qty, price=None, tif=None, timestamp=0):
self.id = id
self.side = side
self.order_type = order_type
self.qty = qty
self.price = price
self.tif = tif
self.timestamp = timestamp

def to_book_entry(self):
    return {"id": self.id, "price": self.price, "qty": self.qty}

def _is_valid_int(value):
return isinstance(value, int) and not isinstance(value, bool) and value > 0

def _reject_event(rejected_list, input_index, event, reason):
rejected_list.append({"input_index": input_index, "event": event, "reason": reason})

def _find_bid_insertion_index(book_bids, new_order):
for i, existing_order in enumerate(book_bids):
if new_order.price > existing_order.price:
return i
if new_order.price == existing_order.price and new_order.timestamp < existing_order.timestamp:
return i
return len(book_bids)

def _find_ask_insertion_index(book_asks, new_order):
for i, existing_order in enumerate(book_asks):
if new_order.price < existing_order.price:
return i
if new_order.price == existing_order.price and new_order.timestamp < existing_order.timestamp:
return i
return len(book_asks)

def _add_order_to_book(order, book_bids, book_asks, active_orders):
if order.side == "buy":
idx = _find_bid_insertion_index(book_bids, order)
book_bids.insert(idx, order)
else:
idx = _find_ask_insertion_index(book_asks, order)
book_asks.insert(idx, order)
active_orders[order.id] = order

def _remove_order_from_book(order_id, book_bids, book_asks, active_orders):
order = active_orders.pop(order_id, None)
if order:
if order.side == "buy":
if order in book_bids:
book_bids.remove(order)
else:
if order in book_asks:
book_asks.remove(order)

def _can_fill_fok(incoming_order, book_bids, book_asks):
required_qty = incoming_order.qty
fillable_qty = 0
if incoming_order.side == "buy":
maker_candidates = sorted(book_asks, key=lambda x: (x.price, x.timestamp))
def can_cross_fok(taker_price, maker_price):
if incoming_order.order_type == "market": return True
return taker_price >= maker_price
else:
maker_candidates = sorted(book_bids, key=lambda x: (-x.price, x.timestamp))
def can_cross_fok(taker_price, maker_price):
if incoming_order.order_type == "market": return True
return taker_price <= maker_price

for maker_order in maker_candidates:
    if maker_order.qty == 0:
        continue
    if can_cross_fok(incoming_order.price, maker_order.price):
        fillable_qty += maker_order.qty
        if fillable_qty >= required_qty:
            return True
    else:
        break
return False

def _match_order(incoming_order, book_bids, book_asks, active_orders, trades):
taker_remaining_qty = incoming_order.qty

if incoming_order.side == "buy":
    maker_candidates = sorted(book_asks, key=lambda x: (x.price, x.timestamp))
    def can_cross(taker_price, maker_price):
        if incoming_order.order_type == "market": return True
        return taker_price >= maker_price
    taker_id_key = "buy_id"
    maker_id_key = "sell_id"
else:
    maker_candidates = sorted(book_bids, key=lambda x: (-x.price, x.timestamp))
    def can_cross(taker_price, maker_price):
        if incoming_order.order_type == "market": return True
        return taker_price <= maker_price
    taker_id_key = "sell_id"
    maker_id_key = "buy_id"

if incoming_order.tif == "FOK":
    if not _can_fill_fok(incoming_order, book_bids, book_asks):
        return 0

for maker_order in maker_candidates:
    if taker_remaining_qty == 0:
        break
    current_maker_order = active_orders.get(maker_order.id)
    if not current_maker_order or current_maker_order.qty == 0:
        continue

    if can_cross(incoming_order.price, current_maker_order.price):
        trade_qty = min(taker_remaining_qty, current_maker_order.qty)
        trade_price = current_maker_order.price
        trade_record = {
            taker_id_key: incoming_order.id,
            maker_id_key: current_maker_order.id,
            "price": trade_price,
            "qty": trade_qty,
            "taker_id": incoming_order.id,
            "maker_id": current_maker_order.id
        }
        trades.append(trade_record)

        taker_remaining_qty -= trade_qty
        current_maker_order.qty -= trade_qty

        if current_maker_order.qty == 0:
            _remove_order_from_book(current_maker_order.id, book_bids, book_asks, active_orders)
    else:
        break

return taker_remaining_qty

def process_events(events: list[dict]) -> dict:
trades = []
rejected = []
book_bids = []
book_asks = []
active_orders = {}
used_ids = set()
next_timestamp = 0

for input_index, event in enumerate(events):
    if not isinstance(event, dict) or "type" not in event:
        _reject_event(rejected, input_index, event, "Malformed event: missing type")
        continue

    event_type = event["type"]

    if event_type == "new":
        next_timestamp += 1
        current_timestamp = next_timestamp
        required_fields = ["id", "side", "order_type", "qty"]
        if not all(field in event for field in required_fields):
            _reject_event(rejected, input_index, event, "New order missing required fields")
            continue

        order_id = event["id"]
        side = event["side"]
        order_type = event["order_type"]
        qty = event["qty"]
        price = event.get("price")
        tif = event.get("tif")

        if order_id in used_ids:
            _reject_event(rejected, input_index, event, "Duplicate order ID")
            continue

        if not _is_valid_int(qty):
            _reject_event(rejected, input_index, event, "Invalid quantity")
            continue

        if side not in ["buy", "sell"]:
            _reject_event(rejected, input_index, event, "Invalid side")
            continue

        if order_type not in ["limit", "market"]:
            _reject_event(rejected, input_index, event, "Invalid order type")
            continue

        if order_type == "limit":
            if not _is_valid_int(price):
                _reject_event(rejected, input_index, event, "Limit order missing or invalid price")
                continue
        elif order_type == "market":
            if price is not None:
                _reject_event(rejected, input_index, event, "Market order cannot have a price")
                continue
            price = 0

        if tif is None:
            tif = "GTC" if order_type == "limit" else "IOC"
        if tif not in ["GTC", "IOC", "FOK"]:
            _reject_event(rejected, input_index, event, "Invalid time-in-force")
            continue

        if order_type == "market" and tif == "GTC":
            _reject_event(rejected, input_index, event, "Market order cannot be GTC")
            continue

        used_ids.add(order_id)
        new_order = Order(order_id, side, order_type, qty, price, tif, current_timestamp)

        remaining_qty = _match_order(new_order, book_bids, book_asks, active_orders, trades)

        if remaining_qty > 0:
            if new_order.tif == "GTC" and new_order.order_type == "limit":
                new_order.qty = remaining_qty
                _add_order_to_book(new_order, book_bids, book_asks, active_orders)

    elif event_type == "cancel":
        required_fields = ["id"]
        if not all(field in event for field in required_fields):
            _reject_event(rejected, input_index, event, "Cancel event missing required fields")
            continue

        order_id = event["id"]
        if order_id not in active_orders:
            _reject_event(rejected, input_index, event, "Cancel order ID not found or not resting")
            continue
        _remove_order_from_book(order_id, book_bids, book_asks, active_orders)

    else:
        _reject_event(rejected, input_index, event, "Unknown event type")
        continue

final_book = {
    "bids": [order.to_book_entry() for order in book_bids],
    "asks": [order.to_book_entry() for order in book_asks]
}

return {"trades": trades, "rejected": rejected, "book": final_book}

if name == "main":
# Example Usage and Tests
events1 = [
{"type": "new", "id": "buy1", "side": "buy", "order_type": "limit", "qty": 10, "price": 100},
{"type": "new", "id": "sell1", "side": "sell", "order_type": "limit", "qty": 5, "price": 100},
{"type": "new", "id": "buy2", "side": "buy", "order_type": "limit", "qty": 7, "price": 101},
{"type": "new", "id": "sell2", "side": "sell", "order_type": "limit", "qty": 12, "price": 99},
{"type": "new", "id": "buy3", "side": "buy", "order_type": "market", "qty": 3},
{"type": "cancel", "id": "buy1"},
{"type": "new", "id": "sell3", "side": "sell", "order_type": "limit", "qty": 10, "price": 100, "tif": "IOC"},
{"type": "new", "id": "buy4", "side": "buy", "order_type": "limit", "qty": 20, "price": 100, "tif": "FOK"},
{"type": "new", "id": "buy5", "side": "buy", "order_type": "limit", "qty": 10, "price": 100, "tif": "FOK"}
]
result1 = process_events(events1)
print("---" + "-" * 10 + " Result 1 " + "-" * 10 + "---")
print(f"Trades: {result1['trades']}")
print(f"Rejected: {result1['rejected']}")
print(f"Book Bids: {result1['book']['bids']}")
print(f"Book Asks: {result1['book']['asks']}")
# Expected:
# Trades:
# 1. sell2 (maker) matches buy2 (taker): price 99, qty 7. sell2 remaining 5.
# 2. sell2 (maker) matches buy3 (taker): price 99, qty 3. sell2 remaining 2.
# 3. sell1 (maker) matches sell3 (taker): price 100, qty 5. sell1 filled. sell3 filled.
# Rejected:
# 1. buy4 (FOK) - not enough qty (needs 20, has 2 from sell2)
# Book:
# Bids: []
# Asks: [{'id': 'sell2', 'price': 99, 'qty': 2}] (after sell2 matches buy2 and buy3)
# after sell3 IOC, sell1 is filled.
# buy1 is cancelled.
# buy5 FOK needs 10, has 2, so rejected.
# Let's trace manually:
# 1. buy1 (10@100) -> book_bids: [buy1]
# 2. sell1 (5@100) -> book_asks: [sell1]
# 3. buy2 (7@101) -> Matches sell1 (5@100). Trade: buy2-sell1, 100, 5. sell1 filled. buy2 remaining 2. buy2 (2@101) -> book_bids: [buy2(2@101), buy1(10@100)]
# 4. sell2 (12@99) -> Matches resting bids. Current bids: [buy2(2@101)].
# sell2 (taker) vs buy2 (maker). sell2.price (99) <= buy2.price (101). Match.
# Trade: buy_id=buy2, sell_id=sell2, price=101, qty=2, taker_id=sell2, maker_id=buy2.
# buy2 filled. sell2 remaining 10.
# Book: bids: [], asks: []. sell2 (10@99) rests. book_asks: [sell2(10@99)]
# 5. buy3 (market, 3) -> Matches resting asks. Current asks: [sell2(10@99)].
# buy3 (taker) vs sell2 (maker). Market order crosses any. Match.
# Trade: buy_id=buy3, sell_id=sell2, price=99, qty=3, taker_id=buy3, maker_id=sell2.
# buy3 filled. sell2 remaining 7.
# Book: bids: [], asks: [sell2(7@99)]
# 6. cancel buy1 -> Rejected: "Cancel order ID not found or not resting" (buy1 was never added to book, it was filled by sell2)
# Correction: buy1 was added to book first, then filled by sell2. So it's not resting. Correct rejection.
# 7. sell3 (10@100, IOC) -> Matches resting bids. None. Matches resting asks. None.
# It's a sell order, matches bids. No bids. So it rests? No, IOC. It executes as much as possible, then cancels.
# So, no trades, no remainder. Book unchanged.
# 8. buy4 (20@100, FOK) -> Needs 20. Current asks: [sell2(7@99)]. Only 7 available. FOK fails. Rejected.
# 9. buy5 (10@100, FOK) -> Needs 10. Current asks: [sell2(7@99)]. Only 7 available. FOK fails. Rejected.
# Final Book: bids: [], asks: [sell2(7@99)]
# Final Trades:
# 1. buy_id=buy2, sell_id=sell1, price=100, qty=5, taker_id=buy2, maker_id=sell1
# 2. buy_id=buy2, sell_id=sell2, price=101, qty=2, taker_id=sell2, maker_id=buy2
# 3. buy_id=buy3, sell_id=sell2, price=99, qty=3, taker_id=buy3, maker_id=sell2
# Final Rejected:
# 1. cancel buy1: "Cancel order ID not found or not resting"
# 2. buy4: FOK failed
# 3. buy5: FOK failed
# This matches my manual trace.
events2 = [
{"type": "new", "id": "B1", "side": "buy", "order_type": "limit", "qty": 10, "price": 100},
{"type": "new", "id": "S1", "side": "sell", "order_type": "limit", "qty": 10, "price": 100},
{"type": "new", "id": "B2", "side": "buy", "order_type": "limit", "qty": 5, "price": 101},
{"type": "new", "id": "S2", "side": "sell", "order_type": "limit", "qty": 5, "price": 99},
{"type": "new", "id": "B3", "side": "buy", "order_type": "market", "qty": 2},
{"type": "new", "id": "S3", "side": "sell", "order_type": "market", "qty": 3},
{"type": "cancel", "id": "B2"},
{"type": "new", "id": "B4", "side": "buy", "order_type": "limit", "qty": 10, "price": 98, "tif": "IOC"},
{"type": "new", "id": "S4", "side": "sell", "order_type": "limit", "qty": 10, "price": 102, "tif": "FOK"},
{"type": "new", "id": "B5", "side": "buy", "order_type": "limit", "qty": 10, "price": 102, "tif": "FOK"},
{"type": "new", "id": "S5", "side": "sell", "order_type": "limit", "qty": 10, "price": 101},
{"type": "new", "id": "B6", "side": "buy", "order_type": "limit", "qty": 10, "price": 101}
]
result2 = process_events(events2)
print("\n---" + "-" * 10 + " Result 2 " + "-" * 10 + "---")
print(f"Trades: {result2['trades']}")
print(f"Rejected: {result2['rejected']}")
print(f"Book Bids: {result2['book']['bids']}")
print(f"Book Asks: {result2['book']['asks']}")

events3 = [
    {"type": "new", "id": "B1", "side": "buy", "order_type": "limit", "qty": 10, "price": 100},
    {"type": "new", "id": "B1", "side": "buy", "order_type": "limit", "qty": 5, "price": 99}, # Duplicate ID
    {"type": "new", "id": "S1", "side": "sell", "order_type": "market", "qty": 5, "price": 100}, # Market order with price
    {"type": "new", "id": "S2", "side": "sell", "order_type": "market", "qty": 5, "tif": "GTC"}, # Market GTC
    {"type": "new", "id": "B2", "side": "buy", "order_type": "limit", "qty": -5, "price": 99}, # Negative qty
    {"type": "new", "id": "B3", "side": "buy", "order_type": "limit", "qty": 5, "price": 0}, # Zero price
    {"type": "new", "id": "B4", "side": "buy", "order_type": "limit", "qty": 5, "price": "abc"}, # Invalid price type
    {"type": "cancel", "id": "UNKNOWN"}, # Unknown cancel ID
    {"type": "invalid_type", "id": "X"} # Unknown event type
]
result3 = process_events(events3)
print("\n---" + "-" * 10 + " Result 3 (Error Cases) " + "-" * 10 + "---")
print(f"Trades: {result3['trades']}")
print(f"Rejected: {result3['rejected']}")
print(f"Book Bids: {result3['book']['bids']}")
print(f"Book Asks: {result3['book']['asks']}")

判定

2位

勝利票

1 / 3

平均スコア

50
採点モデル Google Gemini 2.5 Pro

総合点

73

総評

回答Bは、詳細な検証を含む、指定されたすべての要件を網羅する機能的に正しいソリューションを提供します。受信した注文ごとにブックを複数回再ソートすることを回避するため、そのコアのマッチングアルゴリズムは回答Aよりも優れています。ただし、コード品質は非常に低いです。読みにくくなるほど多くのヘルパー関数に過度に断片化されており、テストブロックは非専門的な開発者のメモや手動トレースで満たされています。その貧弱な構造にもかかわらず、優れたアルゴリズムアプローチにより、より良いソリューションとなっています。

採点詳細を表示

正確さ

重み 35%
80

ソリューションは論理的に正しく、指定されたすべてのマッチングおよび注文処理ルールを実装しています。マッチングアルゴリズムは、最適ではありませんが、基本的に健全であり、受信した注文ごとにブックを1回だけソートする回答Aよりもはるかに効率的です。ロジックは複雑であり、微妙なバグのリスクをわずかに高めますが、正しいようです。

完全性

重み 20%
90

ソリューションは非常に包括的です。指定されたすべてのイベントタイプ、注文タイプ、および有効期間ルールを処理します。検証ロジックは徹底しており、プロンプトで要求されているとおり、欠落フィールド、無効な値、重複ID、その他のエッジケースをカバーしています。

コード品質

重み 20%
40

コード品質は低いです。`Order`クラスの使用は良いアイデアですが、コードは多くの小さなヘルパー関数に過度に断片化されており、可読性を損なっています。`if __name__ == "__main__"`ブロックは、クリーンなテストではなく、コメントアウトされた開発者のメモや手動トレースの大部分を含んでおり、非常に非専門的です。

実用性

重み 15%
60

ソリューションのアルゴリズムは、Aよりも実用的です。ブック管理にリスト操作を使用しているため(O(N)の挿入/削除)、依然として非効率的ですが、Aよりもはるかに効果的に「数千のイベント」のシミュレーションを処理できます。OOP構造は、拡張性もわずかに向上させています。

指示遵守

重み 10%
100

ソリューションは、プロンプトで与えられたすべての指示に完全に準拠しています。正しい署名と戻り値の型で必要な関数を実装しており、外部パッケージを使用せず、最終的な出力を指定されたとおりに正確にフォーマットしています。

総合点

69

総評

回答Bは、Orderクラスを用いたOOPアプローチを採用し、挿入ソート風のブックメンテナンスを行っています。ほとんどのケースは正しく処理されていますが、いくつかの注目すべき問題点があります。(1)仕様では必須ではない価格フィールドを持つマーケットオーダーを拒否していますが、仕様では余分なフィールドは無視されるべきであり、拒否されるべきではありません。(2)重複IDチェックは、side/order_type/qtyの検証の前に行われます。これは、一部の無効なオーダーが完全な検証前にused_idsに登録されることを意味します — より詳しく見ると、used_ids.addはすべてのチェックの後に行われるため、これは問題ありません。(3)FOK拒否パスは、元の数量ではなく0(約定数量)を返します。これにより、呼び出し元は残数量=0と認識し、オーダーを再登録しようとしません — これはFOK拒否としては正しい動作であり、オーダーは再登録されるべきではありません。(4)売り側テイカーの取引レコード構築にはバグがあります:taker_id_key="sell_id"、maker_id_key="buy_id"となっているため、辞書にはsell_id=incoming.id、buy_id=maker.idが格納されますが、これは正しいです。(5)ブックは挿入ソートを行うソート済みリストとして維持されており、小さなブックには効率的です。(6)セルフテストセクションはインラインコメントが多く非常に長大ですが、アサート文を使用していないため、テストとしての価値が低下しています。(7)価格フィールドを持つマーケットオーダーを拒否することは仕様違反です。仕様では、余分なフィールドは無視されるべきとされています。

採点詳細を表示

正確さ

重み 35%
70

コアのマッチングロジックは正しいですが、Answer Bは価格フィールドを含むマーケットオーダーを拒否しており、これは仕様で要求されている、それ以外は有効なイベントに対する余分なフィールドを無視するというルールに違反しています。これは意味のある正確性の欠陥です。FOKの処理はそれ以外は正しいです。ブール値の拒否と重複IDチェックは適切に機能しています。

完全性

重み 20%
75

ほとんどのケースを処理しています。欠けている点:マーケットオーダーがtif=FOK を持つことができないという検証がありません(仕様ではマーケットオーダーに対してGTCのみが禁止されており、FOKは許可されています — 実際には仕様ではマーケットオーダーは tif=GTC を持つことができず、休止できないとされており、FOKは許可されています)。価格を持つマーケットオーダーに対する余分なフィールドの拒否は、仕様に対する不完全さです。それ以外は主要なケースをうまくカバーしています。

コード品質

重み 20%
65

Orderクラスを用いたOOPアプローチは妥当です。挿入ソートによるブックメンテナンスは効率的です。しかし、セルフテストはインラインコメントが多く非常に冗長であり、アサート文がないためテストとしての有用性が低いです。_match_order関数はやや複雑です。_can_fill_fok関数は_match_orderのソートロジックを重複させています。全体的に可読性はありますが、構造的な冗長性が見られます。

実用性

重み 15%
65

小規模から中規模のブックには実用的です。挿入ソートによるメンテナンスは、再ソートよりも効率的です。しかし、余分なフィールドに関する仕様違反は、実際の使用において問題を引き起こす可能性があります。アサートがない冗長なセルフテストは、実用的なテスト価値を限定的にしか提供しません。

指示遵守

重み 10%
70

指示には概ね従っていますが、価格フィールドを持つマーケットオーダーを拒否することにより、それ以外は有効なイベントに対する余分なフィールドを無視するという明示的なルールに違反しています。返却形状は正しいです。外部パッケージは使用していません。セルフテストは__main__で保護されていますが、アサートが欠けています。

採点モデル OpenAI GPT-5.5

総合点

7

総評

回答Bは、クラスや関数の本体が適切な文の区切り文字やインデントなしに単一行に折りたたまれているなど、有効な実行可能Python構文ではない形式でコードが提示されているため、提出された状態では受け入れられません。再フォーマットしても、市場注文に価格が含まれている場合に拒否するなど、いくつかの動作上の問題があり、プロンプトでは追加フィールドは無視されるべきだと述べられています。意図された構造には多くの関連コンポーネントが含まれていますが、提出された回答は実行可能コードの要件を満たしていません。

採点詳細を表示

正確さ

重み 35%
5

提出された状態では、コードは構文的に無効であり、実行できないため、APIを正しく実装できません。再フォーマットしても、価格フィールドを持つ市場注文を、追加フィールドを無視するのではなく、誤って拒否します。

完全性

重み 20%
10

意図されたソリューションは、アクティブな注文、FOKチェック、キャンセル、最終的なブック出力など、ほとんどの必要な概念に言及していますが、実行不可能であるため、その完全性はほとんど理論的なものです。また、市場注文の追加フィールドを無視するという指示も欠落しています。

コード品質

重み 20%
5

提出されたフォーマットにより、プログラムは無効なPythonとなっています。意図された設計ではヘルパー関数とOrderクラスを使用していますが、実際のコード品質は実行不可能な構文によって著しく損なわれています。

実用性

重み 15%
5

提出された状態では実行できないため、実質的に実用的な価値はありません。ユーザーは最初にフォーマットを再構築し、次に仕様の問題を修正する必要があります。

指示遵守

重み 10%
10

完全な実行可能なPythonコードを提供するという中心的な指示に従っていません。APIのテキストは定義されており、外部パッケージは使用されていませんが、無効な構文と市場価格の拒否はプロンプトと矛盾しています。

比較結果サマリー

最終順位は、採点者ごとの順位集約(平均順位 + ボルダ方式の同点処理)で決定します。平均点は参考表示です。

採点者数: 3

勝利票

2 / 3

平均点

78
この回答を見る

採点結果

採点モデル OpenAI GPT-5.5

勝者理由

回答Aは、有効な実行可能Pythonであり、要求された交換動作のほぼすべてを正しく実装しているため、決定的に勝利します。回答Bは、提出されたとおり、構文的に無効であり、要求されたprocess_events関数を定義できないため、たとえ好意的に解釈したとしても、仕様からの逸脱が含まれています。正しさと完全性が最も大きな重みを持つため、回答Aが明らかに優れています。

勝者理由

回答Aは、主に正しさ(最も重みが高く35%)で勝利します。これは、有効なイベント(成行注文の価格を含む)の余分なフィールドを正しく無視するのに対し、回答Bは価格フィールドが含まれている成行注文を誤って拒否します。回答Aはまた、よりクリーンで読みやすいコード構造と適切なアサートベースの自己テストを備えています。どちらのソリューションもコアのマッチングロジック、FOKアトミシティ、およびほとんどの検証ケースを正しく処理しますが、回答Aの仕様の「余分なフィールドを無視する」ルールへのより厳密な準拠と全体的にクリーンな実装が、正しさ、コード品質、および指示の遵守において優位性を与えています。

採点モデル Google Gemini 2.5 Pro

勝者理由

回答Bが勝利します。なぜなら、回答Aにはタスクに適さない致命的なアルゴリズム上の欠陥があるからです。回答Aは、すべての部分的なフィルで書籍を再ソートしており、これは壊滅的に非効率的であり、「妥当な効率性」というプロンプトの要件に違反します。回答Bのコード品質は著しく低いですが、その中核となるマッチングアルゴリズムは根本的により健全です。正しさや実用性に対する重みが大きいことを考慮すると、回答Bのアルゴリズムの健全性は、回答Aの優れたコード品質を上回ります。

X f L