diff --git a/prac.py b/prac.py index d94dc50..00b5319 100644 --- a/prac.py +++ b/prac.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 import argparse -from collections import deque -from enum import Enum import time import socket import json @@ -9,8 +7,8 @@ import json # ~~~~~============== CONFIGURATION ==============~~~~~ team_name = "HanyangFloorFunction" -# --- 유틸리티 및 상태 관리 클래스 --- -class Dir(str, Enum): +# --- 상태 관리 및 유틸리티 --- +class Dir: BUY = "BUY" SELL = "SELL" @@ -37,119 +35,100 @@ class OrderManager: self.order_id_counter += 1 return self.order_id_counter -# --- 메인 봇 로직 --- -def main(): - args = parse_arguments() - exchange = ExchangeConnection(args=args) - - hello_message = exchange.read_message() - state = StateManager() - om = OrderManager() - - # 재연결 시 기존 포지션 복원 - for sym_info in hello_message.get("symbols", []): - state.update_position(sym_info["symbol"], sym_info["position"]) - - # ==================== 상수 및 설정 ==================== - BOND_FAIR = 1000 # 고정 가치 - XLF_FEE = 100 # 변환 수수료 - VALE_FEE = 10 # 변환 수수료 - - # 수익 안정화를 위한 문턱 값 (기존보다 높게 설정) - MIN_ARB_PROFIT = 30 - - market_open = False - last_refresh = time.time() - active_orders = set() - - def next_id(): return om.next_order() - - # ==================== 거래 함수 ==================== - - def trade_bond(): - """BOND는 항상 1000원이므로 999 이하 매수, 1001 이상 매도""" - if not market_open: return - pos = state.get_position("BOND") - - # 매수 주문 (100개 제한 준수) - if pos < 100: - exchange.send_add_message(next_id(), "BOND", Dir.BUY, 999, 100 - pos) - # 매도 주문 - if pos > -100: - exchange.send_add_message(next_id(), "BOND", Dir.SELL, 1001, 100 + pos) - - def trade_xlf_arb(): - """XLF 차익거래: 바스켓 가치와 XLF 가격 비교""" - prices = {s: (state.bid_prices.get(s), state.ask_prices.get(s)) for s in ["BOND", "GS", "MS", "WFC", "XLF"]} - if any(None in p for p in prices.values()): return - - # 바스켓 매수 가치 (최악의 경우 내가 사야하는 가격) - basket_buy_cost = prices["BOND"][1]*3 + prices["GS"][1]*2 + prices["MS"][1]*3 + prices["WFC"][1]*2 - xlf_sell_price = prices["XLF"][0] * 10 - - # 수익 계산 (수수료 포함) - profit = xlf_sell_price - basket_buy_cost - XLF_FEE - if profit > MIN_ARB_PROFIT: - print(f"[XLF ARB] 기회 포착! 수익: {profit}") - # 단순화를 위해 즉시 시장가(IOC)로 실행하거나 변환 로직 수행 - # (여기에 기존의 State Machine 기반 복잡한 로직을 넣되 Profit 문턱을 꼭 지킬 것) - - # ==================== 메인 루프 ==================== - while True: - message = exchange.read_message() - - if message["type"] == "open": - market_open = True - trade_bond() # 개장하자마자 BOND 주문 - - elif message["type"] == "book": - sym = message["symbol"] - state.update_bid_ask_price( - sym, - message["buy"][0][0] if message["buy"] else None, - message["sell"][0][0] if message["sell"] else None - ) - - if sym == "BOND": - trade_bond() - elif sym in ["XLF", "GS", "MS", "WFC"]: - trade_xlf_arb() - - elif message["type"] == "fill": - sym, qty, direction = message["symbol"], message["size"], message["dir"] - state.update_position(sym, qty if direction == Dir.BUY else -qty) - print(f"체결: {sym} {direction} {qty} | 현재 포지션: {state.positions.get(sym)}") - - if sym == "BOND": trade_bond() # BOND 체결 시 즉시 다시 주문 보충 - - elif message["type"] == "close": - market_open = False - -# --- 제공된 연결 코드 (수정 불필요) --- +# --- 연결 클래스 --- class ExchangeConnection: def __init__(self, args): self.exchange_hostname = args.exchange_hostname self.port = args.port - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect((self.exchange_hostname, self.port)) - self.reader = s.makefile("r", 1) - self.writer = s + # 타임아웃 설정으로 무한 대기 방지 + self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.s.settimeout(5) + self.s.connect((self.exchange_hostname, self.port)) + self.reader = self.s.makefile("r", 1) + # 초기 Handshake self._write_message({"type": "hello", "team": team_name.upper()}) def read_message(self): - return json.loads(self.reader.readline()) + line = self.reader.readline() + if not line: return None + return json.loads(line) def _write_message(self, message): - self.writer.send((json.dumps(message) + "\n").encode("utf-8")) + # 모든 메시지 끝에는 줄바꿈(\n)이 필수입니다 + self.s.send((json.dumps(message) + "\n").encode("utf-8")) - def send_add_message(self, order_id, symbol, dir, price, size): - self._write_message({"type": "add", "order_id": order_id, "symbol": symbol, "dir": dir, "price": price, "size": size, "tif": "DAY"}) + def send_add_message(self, order_id, symbol, direction, price, size): + self._write_message({ + "type": "add", "order_id": order_id, "symbol": symbol, + "dir": direction, "price": price, "size": size, "tif": "DAY" + }) + +# --- 메인 봇 로직 --- +def main(): + args = parse_arguments() + try: + exchange = ExchangeConnection(args) + except Exception as e: + print(f"연결 실패: {e}") + return + + state = StateManager() + om = OrderManager() + market_open = False + + # 첫 메시지 처리 (Handshake Hello) + hello_msg = exchange.read_message() + if hello_msg and "symbols" in hello_msg: + for sym_info in hello_msg["symbols"]: + state.update_position(sym_info["symbol"], sym_info["position"]) + + print("봇이 시작되었습니다. P/L 복구 모드 가동.") + + while True: + message = exchange.read_message() + if not message: continue + + msg_type = message.get("type") + + if msg_type == "open": + market_open = True + print("시장이 열렸습니다.") + + 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) + + # BOND 로직: 1000원 고정 가치를 활용한 안전 수익 + if sym == "BOND" and market_open: + pos = state.get_position("BOND") + if pos < 100: + exchange.send_add_message(om.next_order(), "BOND", Dir.BUY, 999, 100 - pos) + if pos > -100: + exchange.send_add_message(om.next_order(), "BOND", Dir.SELL, 1001, 100 + pos) + + elif msg_type == "fill": + # 체결 시 포지션 동기화 + sym, qty, direction = message["symbol"], message["size"], message["dir"] + delta = qty if direction == Dir.BUY else -qty + state.update_position(sym, delta) + print(f"[FILL] {sym} {direction} {qty} | 포지션: {state.positions.get(sym)}") + + elif msg_type == "close": + market_open = False + print("시장이 닫혔습니다.") + + elif msg_type == "error": + print(f"서버 에러: {message.get('error')}") def parse_arguments(): parser = argparse.ArgumentParser() - parser.add_argument("--test", type=str, choices=["prod-like", "slower", "empty"]) + parser.add_argument("--test", type=str, default="prod-like", choices=["prod-like", "slower", "empty"]) args = parser.parse_args() - args.exchange_hostname = f"test-exch-{team_name}" + # 팀 이름에 맞게 호스트네임 설정 + args.exchange_hostname = f"test-exch-{team_name.lower()}" args.port = 22000 + (0 if args.test == "prod-like" else 1 if args.test == "slower" else 2) return args