#!/usr/bin/env python3 # ~~~~~============== HOW TO RUN ==============~~~~~ # 1) Configure things in CONFIGURATION section # 2) Change permissions: chmod +x bot.py # 3) Run in loop: while true; do ./bot.py --test prod-like; sleep 1; done import argparse from collections import deque from enum import Enum import time import socket import json # ~~~~~============== CONFIGURATION ==============~~~~~ team_name = "HanyangFloorFunction" # ~~~~~============== CLASSES & UTILS ==============~~~~~ class Dir(str, Enum): BUY = "BUY" SELL = "SELL" class StateManager: """모든 종목의 포지션과 현재 호가 정보를 추적합니다.""" def __init__(self): self.positions = {s: 0 for s in ["BOND", "GS", "MS", "WFC", "XLF", "VALE", "VALBZ"]} self.bid_prices = {s: None for s in ["BOND", "GS", "MS", "WFC", "XLF", "VALE", "VALBZ"]} self.ask_prices = {s: None for s in ["BOND", "GS", "MS", "WFC", "XLF", "VALE", "VALBZ"]} def update_position(self, symbol, delta): self.positions[symbol] = self.positions.get(symbol, 0) + delta def get_position(self, symbol): return self.positions.get(symbol, 0) def update_bid_ask_price(self, symbol, bid, ask): self.bid_prices[symbol] = bid self.ask_prices[symbol] = ask class OrderManager: """단조 증가하는 주문 ID를 관리합니다.""" def __init__(self): self.order_id_counter = 0 def next_order(self): self.order_id_counter += 1 return self.order_id_counter # ~~~~~============== MAIN BOT LOGIC ==============~~~~~ def main(): args = parse_arguments() exchange = ExchangeConnection(args=args) # 초기 메시지 처리 및 포지션 복원 hello_message = exchange.read_message() print("First message from exchange:", hello_message) state = StateManager() om = OrderManager() if hello_message and "symbols" in hello_message: for sym_info in hello_message["symbols"]: state.update_position(sym_info["symbol"], sym_info["position"]) # --- 핵심 설정 값 --- BOND_FAIR = 1000 BOND_ORDER_SIZE = 30 BOND_MAX_POS = 100 XLF_FEE = 100 XLF_PROFIT_THRESHOLD = 50 # 보수적 진입 (수수료+슬리피지 방어) XLF_MAX_POS = 100 market_open = False active_mm_orders = {} last_refresh = time.time() REFRESH_INTERVAL = 3.0 def next_id(): return om.next_order() # --- 전략 1: BOND 마켓 메이킹 (사용자 기존 로직) --- def place_bond_orders(): if not market_open: return # 기존 BOND 주문 관리 (Active Order 추적) pos = state.get_position("BOND") adjustment = abs(pos) // 5 # 포지션 한도 내 수량 결정 if pos < 0: buy_size = min(BOND_ORDER_SIZE + adjustment, BOND_MAX_POS - pos) sell_size = max(BOND_ORDER_SIZE - adjustment, 1) elif pos > 0: buy_size = max(BOND_ORDER_SIZE - adjustment, 1) sell_size = min(BOND_ORDER_SIZE + adjustment, BOND_MAX_POS + pos) else: buy_size, sell_size = BOND_ORDER_SIZE, BOND_ORDER_SIZE # 999 매수 / 1001 매도 전략 if buy_size > 0: oid = next_id() exchange.send_add_message(oid, "BOND", Dir.BUY, 999, buy_size) active_mm_orders[oid] = {"sym": "BOND", "dir": Dir.BUY} if sell_size > 0: oid = next_id() exchange.send_add_message(oid, "BOND", Dir.SELL, 1001, sell_size) active_mm_orders[oid] = {"sym": "BOND", "dir": Dir.SELL} # --- 전략 2: XLF 차익거래 (ETF 바스켓 가치 계산) --- def try_xlf_arb(): """XLF와 바스켓(3:2:3:2) 간의 가격 괴리 이용""" if not market_open: return # 필요한 호가 정보 확인 needed = ["BOND", "GS", "MS", "WFC", "XLF"] if any(state.bid_prices[s] is None or state.ask_prices[s] is None for s in needed): return # 1. 바스켓 매수 가치 계산 (우리가 사야 할 가격의 합) # 10 XLF = 3 BOND + 2 GS + 3 MS + 2 WFC basket_buy_cost = (state.ask_prices["BOND"] * 3 + state.ask_prices["GS"] * 2 + state.ask_prices["MS"] * 3 + state.ask_prices["WFC"] * 2) xlf_sell_revenue = state.bid_prices["XLF"] * 10 # 예상 수익 = (XLF 매도금액 - 바스켓 매수비용 - 수수료) if xlf_sell_revenue - (basket_buy_cost + XLF_FEE) > XLF_PROFIT_THRESHOLD: if state.get_position("XLF") > -XLF_MAX_POS: print(f" [XLF ARB] SELL XLF Opportunity! Profit: {xlf_sell_revenue - basket_buy_cost - 100}") exchange.send_add_message(next_id(), "XLF", Dir.SELL, state.bid_prices["XLF"], 10) # 2. 바스켓 매도 가치 계산 (우리가 팔 수 있는 가격의 합) xlf_buy_cost = state.ask_prices["XLF"] * 10 basket_sell_revenue = (state.bid_prices["BOND"] * 3 + state.bid_prices["GS"] * 2 + state.bid_prices["MS"] * 3 + state.bid_prices["WFC"] * 2) if basket_sell_revenue - (xlf_buy_cost + XLF_FEE) > XLF_PROFIT_THRESHOLD: if state.get_position("XLF") < XLF_MAX_POS: print(f" [XLF ARB] BUY XLF Opportunity! Profit: {basket_sell_revenue - xlf_buy_cost - 100}") exchange.send_add_message(next_id(), "XLF", Dir.BUY, state.ask_prices["XLF"], 10) # --- 메시지 처리 루프 --- while True: message = exchange.read_message() if not message: continue msg_type = message["type"] if msg_type == "open": market_open = True print("Market is open. Starting trades...") place_bond_orders() elif msg_type == "book": sym = message["symbol"] bid = message["buy"][0][0] if message["buy"] else None ask = message["sell"][0][0] if message["sell"] else None state.update_bid_ask_price(sym, bid, ask) # 가격 업데이트 시마다 전략 체크 if sym == "BOND": place_bond_orders() elif sym in ["GS", "MS", "WFC", "XLF"]: try_xlf_arb() elif msg_type == "fill": sym, qty, direction = message["symbol"], message["size"], message["dir"] state.update_position(sym, qty if direction == Dir.BUY else -qty) print(f" [FILL] {sym} {direction} x{qty} | New Pos: {state.get_position(sym)}") if sym == "BOND": place_bond_orders() elif msg_type == "reject": print(f" [REJECT] Order {message.get('order_id')}: {message.get('error')}") active_mm_orders.pop(message.get("order_id"), None) elif msg_type == "close": print("The round has ended") break # 주기적 리프레시 (주문 유실 방지) now = time.time() if now - last_refresh > REFRESH_INTERVAL: last_refresh = now place_bond_orders() # ~~~~~============== PROVIDED CONNECTION CODE ==============~~~~~ class ExchangeConnection: def __init__(self, args): self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.s.settimeout(5) self.s.connect((args.exchange_hostname, args.port)) self.reader = self.s.makefile("r", 1) self.writer = self.s self._write({"type": "hello", "team": team_name.upper()}) def read_message(self): line = self.reader.readline() if not line: return None msg = json.loads(line) if "dir" in msg: msg["dir"] = Dir(msg["dir"]) return msg def _write(self, msg): self.writer.send((json.dumps(msg) + "\n").encode("utf-8")) def send_add_message(self, oid, sym, direction, price, size): self._write({"type": "add", "order_id": oid, "symbol": sym, "dir": direction, "price": price, "size": size, "tif": "DAY"}) def send_cancel_message(self, oid): self._write({"type": "cancel", "order_id": oid}) def send_convert_message(self, oid, sym, direction, size): self._write({"type": "convert", "order_id": oid, "symbol": sym, "dir": direction, "size": size}) def parse_arguments(): parser = argparse.ArgumentParser() parser.add_argument("--test", type=str, default="prod-like") args = parser.parse_args() args.exchange_hostname = f"test-exch-{team_name.lower()}" args.port = 22000 # JSON 프로토콜 포트 return args if __name__ == "__main__": main()