This commit is contained in:
2026-05-09 14:22:13 +09:00
parent 16d2649f86
commit 807918c311

354
prac.py
View File

@@ -1,8 +1,4 @@
#!/usr/bin/env python3 #!/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 import argparse
from collections import deque from collections import deque
@@ -10,218 +6,236 @@ from enum import Enum
import time import time
import socket import socket
import json import json
from state import StateManager
from order import OrderManager
# ~~~~~============== CONFIGURATION ==============~~~~~
team_name = "HanyangFloorFunction" team_name = "HanyangFloorFunction"
# ~~~~~============== CLASSES & UTILS ==============~~~~~ # ==================== 설정 ====================
BOND_FAIR_VALUE = 1000
BOND_SPREAD = 2
BOND_ORDER_SIZE = 20
BOND_MAX_POSITION = 100
STOCK_SPREAD = 6
STOCK_ORDER_SIZE = 5
STOCK_MAX_POSITION = 100
STOCK_REORDER_DELAY = 0.2
XLF_CONVERSION_FEE = 100
VALE_CONVERSION_FEE = 10
REFRESH_INTERVAL = 1.0
MIN_PROFIT_BUFFER = 10
MAX_POSITION_SOFT = 50
# ==================== 방향 ====================
class Dir(str, Enum): class Dir(str, Enum):
BUY = "BUY" BUY = "BUY"
SELL = "SELL" 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(): def main():
args = parse_arguments() args = parse_arguments()
exchange = ExchangeConnection(args=args) exchange = ExchangeConnection(args=args)
# 초기 메시지 처리 및 포지션 복원 hello = exchange.read_message()
hello_message = exchange.read_message()
print("First message from exchange:", hello_message)
state = StateManager() state = StateManager()
om = OrderManager() om = OrderManager()
if hello_message and "symbols" in hello_message: for sym_info in hello.get("symbols", []):
for sym_info in hello_message["symbols"]:
state.update_position(sym_info["symbol"], sym_info["position"]) state.update_position(sym_info["symbol"], sym_info["position"])
# --- 핵심 설정 값 --- implied_fairs = {"GS": None, "MS": None, "WFC": None}
BOND_FAIR = 1000 active_orders = {}
BOND_ORDER_SIZE = 30
BOND_MAX_POS = 100
XLF_FEE = 100 def next_id():
XLF_PROFIT_THRESHOLD = 50 # 보수적 진입 (수수료+슬리피지 방어) return om.next_order()
XLF_MAX_POS = 100
market_open = False # ==================== FAIR VALUE ====================
active_mm_orders = {} def update_fair():
last_refresh = time.time() try:
REFRESH_INTERVAL = 3.0 xlf_mid = (state.bid_prices["XLF"] + state.ask_prices["XLF"]) / 2
gs_mid = (state.bid_prices["GS"] + state.ask_prices["GS"]) / 2
def next_id(): return om.next_order() ms_mid = (state.bid_prices["MS"] + state.ask_prices["MS"]) / 2
wfc_mid = (state.bid_prices["WFC"] + state.ask_prices["WFC"]) / 2
# --- 전략 1: BOND 마켓 메이킹 (사용자 기존 로직) --- except:
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 return
# 1. 바스켓 매수 가치 계산 (우리가 사야 할 가격의 합) residual = xlf_mid * 10 - 3000
# 10 XLF = 3 BOND + 2 GS + 3 MS + 2 WFC total = gs_mid*2 + ms_mid*3 + wfc_mid*2
basket_buy_cost = (state.ask_prices["BOND"] * 3 + if total <= 0:
return
raw_gs = residual * (gs_mid*2 / total) / 2
raw_ms = residual * (ms_mid*3 / total) / 3
raw_wfc = residual * (wfc_mid*2 / total) / 2
implied_fairs["GS"] = int(0.7 * gs_mid + 0.3 * raw_gs)
implied_fairs["MS"] = int(0.7 * ms_mid + 0.3 * raw_ms)
implied_fairs["WFC"] = int(0.7 * wfc_mid + 0.3 * raw_wfc)
# ==================== BOND ARB ====================
def bond_arb():
bid = state.bid_prices.get("BOND")
ask = state.ask_prices.get("BOND")
if ask and ask < 1000:
exchange.send_add_message_ioc(next_id(), "BOND", Dir.BUY, ask, 10)
if bid and bid > 1000:
exchange.send_add_message_ioc(next_id(), "BOND", Dir.SELL, bid, 10)
# ==================== STOCK MM ====================
def market_make(sym):
bid = state.bid_prices.get(sym)
ask = state.ask_prices.get(sym)
fair = implied_fairs.get(sym)
if None in [bid, ask] or fair is None:
return
pos = state.get_position(sym)
buy_price = min(fair - STOCK_SPREAD, bid + 1)
sell_price = max(fair + STOCK_SPREAD, ask - 1)
if pos > 0:
buy_size, sell_size = 1, STOCK_ORDER_SIZE + 3
elif pos < 0:
buy_size, sell_size = STOCK_ORDER_SIZE + 3, 1
else:
buy_size = sell_size = STOCK_ORDER_SIZE
if buy_size > 0:
exchange.send_add_message(next_id(), sym, Dir.BUY, int(buy_price), buy_size)
if sell_size > 0:
exchange.send_add_message(next_id(), sym, Dir.SELL, int(sell_price), sell_size)
# ==================== XLF ARB ====================
def xlf_arb():
try:
basket_ask = (
state.ask_prices["BOND"]*3 +
state.ask_prices["GS"]*2 + state.ask_prices["GS"]*2 +
state.ask_prices["MS"]*3 + state.ask_prices["MS"]*3 +
state.ask_prices["WFC"] * 2) state.ask_prices["WFC"]*2
xlf_sell_revenue = state.bid_prices["XLF"] * 10 )
xlf_bid = state.bid_prices["XLF"]
except:
return
# 예상 수익 = (XLF 매도금액 - 바스켓 매수비용 - 수수료) profit = xlf_bid*10 - basket_ask - XLF_CONVERSION_FEE
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. 바스켓 매도 가치 계산 (우리가 팔 수 있는 가격의 합) if profit > MIN_PROFIT_BUFFER:
xlf_buy_cost = state.ask_prices["XLF"] * 10 exchange.send_add_message_ioc(next_id(), "XLF", Dir.SELL, xlf_bid, 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: # ==================== VALE ARB ====================
if state.get_position("XLF") < XLF_MAX_POS: def vale_arb():
print(f" [XLF ARB] BUY XLF Opportunity! Profit: {basket_sell_revenue - xlf_buy_cost - 100}") vale_bid = state.bid_prices.get("VALE")
exchange.send_add_message(next_id(), "XLF", Dir.BUY, state.ask_prices["XLF"], 10) valbz_ask = state.ask_prices.get("VALBZ")
# --- 메시지 처리 루프 --- if None in [vale_bid, valbz_ask]:
return
if vale_bid - valbz_ask - VALE_CONVERSION_FEE > 5:
exchange.send_add_message_ioc(next_id(), "VALBZ", Dir.BUY, valbz_ask, 1)
# ==================== 리스크 관리 ====================
def risk():
for sym in ["GS", "MS", "WFC"]:
pos = state.get_position(sym)
if abs(pos) > MAX_POSITION_SOFT:
if pos > 0:
exchange.send_add_message_ioc(
next_id(), sym, Dir.SELL,
state.bid_prices.get(sym, 1), abs(pos)
)
else:
exchange.send_add_message_ioc(
next_id(), sym, Dir.BUY,
state.ask_prices.get(sym, 99999), abs(pos)
)
last_refresh = time.time()
# ==================== LOOP ====================
while True: while True:
message = exchange.read_message() msg = exchange.read_message()
if not message: continue
msg_type = message["type"] if msg["type"] == "close":
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 break
# 주기적 리프레시 (주문 유실 방지) elif msg["type"] == "book":
now = time.time() sym = msg["symbol"]
if now - last_refresh > REFRESH_INTERVAL:
last_refresh = now
place_bond_orders()
# ~~~~~============== PROVIDED CONNECTION CODE ==============~~~~~ state.update_bid_ask_price(
sym,
msg["buy"][0][0] if msg["buy"] else None,
msg["sell"][0][0] if msg["sell"] else None
)
update_fair()
bond_arb()
xlf_arb()
vale_arb()
risk()
if sym in ["GS", "MS", "WFC"]:
market_make(sym)
if time.time() - last_refresh > REFRESH_INTERVAL:
last_refresh = time.time()
for s in ["GS", "MS", "WFC"]:
market_make(s)
elif msg["type"] == "fill":
qty = msg["size"]
sym = msg["symbol"]
if msg["dir"] == Dir.BUY:
state.update_position(sym, qty)
else:
state.update_position(sym, -qty)
# ==================== 연결 코드 ====================
class ExchangeConnection: class ExchangeConnection:
def __init__(self, args): def __init__(self, args):
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.exchange_hostname = args.exchange_hostname
self.s.settimeout(5) self.port = args.port
self.s.connect((args.exchange_hostname, args.port)) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.reader = self.s.makefile("r", 1) s.connect((self.exchange_hostname, self.port))
self.writer = self.s self.reader = s.makefile("r", 1)
self.writer = s
self._write({"type": "hello", "team": team_name.upper()}) self._write({"type": "hello", "team": team_name.upper()})
def read_message(self): def read_message(self):
line = self.reader.readline() msg = json.loads(self.reader.readline())
if not line: return None if "dir" in msg:
msg = json.loads(line) msg["dir"] = Dir(msg["dir"])
if "dir" in msg: msg["dir"] = Dir(msg["dir"])
return msg return msg
def send_add_message(self, oid, sym, dir, price, size):
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"DAY"})
def send_add_message_ioc(self, oid, sym, dir, price, size):
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"IOC"})
def _write(self, msg): def _write(self, msg):
self.writer.send((json.dumps(msg) + "\n").encode("utf-8")) data = json.dumps(msg) + "\n"
self.writer.send(data.encode())
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})
# ==================== args ====================
def parse_arguments(): def parse_arguments():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--test", type=str, default="prod-like") parser.add_argument("--test", type=str, default="prod-like")
args = parser.parse_args() args = parser.parse_args()
args.exchange_hostname = f"test-exch-{team_name.lower()}"
args.port = 22000 # JSON 프로토콜 포트 args.exchange_hostname = "test-exch-" + team_name
args.port = 22000
return args return args
if __name__ == "__main__": if __name__ == "__main__":