From f015d73e8778c8d949be8bc9f93dbffbcc56c120 Mon Sep 17 00:00:00 2001 From: yenru0 Date: Sat, 9 May 2026 14:04:07 +0900 Subject: [PATCH] add many change --- bot split/ETC.py | 97 --------------------------------------- bot split/order.py | 52 --------------------- bot.py | 99 ++++++++++++++++++++-------------------- order.py | 110 ++++++++++++++++++++++++++++++++++++++++++--- state.py | 7 +-- 5 files changed, 156 insertions(+), 209 deletions(-) delete mode 100644 bot split/ETC.py delete mode 100644 bot split/order.py diff --git a/bot split/ETC.py b/bot split/ETC.py deleted file mode 100644 index 6e28dad..0000000 --- a/bot split/ETC.py +++ /dev/null @@ -1,97 +0,0 @@ -from collections import deque -from enum import Enum -import time -import socket -import json -import order - -team_name = "HanyangFloorFunction" - -class Dir(str, Enum): - BUY = "BUY" - SELL = "SELL" - -class ExchangeConnection: - def __init__(self, args): - self.message_timestamps = deque(maxlen=500) - self.exchange_hostname = args.exchange_hostname - self.port = args.port - exchange_socket = self._connect(add_socket_timeout=args.add_socket_timeout) - self.reader = exchange_socket.makefile("r", 1) - self.writer = exchange_socket - - self._write_message({"type": "hello", "team": team_name.upper()}) - - def read_message(self): - """Read a single message from the exchange""" - message = json.loads(self.reader.readline()) - if "dir" in message: - message["dir"] = Dir(message["dir"]) - return message - - def send_add_message( - self, order_id: int, symbol: str, dir: Dir, price: int, size: int - ): - """Add a new order""" - self._write_message( - { - "type": "add", - "order_id": order_id, - "symbol": symbol, - "dir": dir, - "price": price, - "size": size, - } - ) - - def send_convert_message(self, order_id: int, symbol: str, dir: Dir, size: int): - """Convert between related symbols""" - self._write_message( - { - "type": "convert", - "order_id": order_id, - "symbol": symbol, - "dir": dir, - "size": size, - } - ) - - def send_cancel_message(self, order_id: int): - """Cancel an existing order""" - self._write_message({"type": "cancel", "order_id": order_id}) - - def _connect(self, add_socket_timeout): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - - if add_socket_timeout: - # Automatically raise an exception if no data has been recieved for - # multiple seconds. This should not be enabled on an "empty" test - # exchange. - s.settimeout(5) - s.connect((self.exchange_hostname, self.port)) - return s - - def _write_message(self, message): - what_to_write = json.dumps(message) - if not what_to_write.endswith("\n"): - what_to_write = what_to_write + "\n" - - length_to_send = len(what_to_write) - total_sent = 0 - while total_sent < length_to_send: - sent_this_time = self.writer.send( - what_to_write[total_sent:].encode("utf-8") - ) - if sent_this_time == 0: - raise Exception("Unable to send data to exchange") - total_sent += sent_this_time - - now = time.time() - self.message_timestamps.append(now) - if len( - self.message_timestamps - ) == self.message_timestamps.maxlen and self.message_timestamps[0] > (now - 1): - print( - "WARNING: You are sending messages too frequently. The exchange will start ignoring your messages. Make sure you are not sending a message in response to every exchange message." - ) - diff --git a/bot split/order.py b/bot split/order.py deleted file mode 100644 index c6108ac..0000000 --- a/bot split/order.py +++ /dev/null @@ -1,52 +0,0 @@ -import time -from collections import deque -from ETC import ExchangeConnection, Dir - -class OrderManager: - def __init__(self, exchange: ExchangeConnection): - self.exchange = exchange - self._order_size = 0 - - # 최근 500개의 전송 시간만 기록하는 데크 - self._send_timestamps = deque(maxlen=500) - - def next_order(self): - self._order_size += 1 - return self._order_size - - def _attempt_send(self, action_type: str, **kwargs) -> bool: - """ - 주문 전송을 시도합니다. - 제한에 걸려 무시되면 False를, 정상 전송되면 True를 반환합니다. - """ - now = time.time() - - # 최근 500개를 이미 보냈다면, 가장 오래된 주문(0번 인덱스)의 시간을 확인 - if len(self._send_timestamps) == 500: - elapsed = now - self._send_timestamps[0] - if elapsed < 1.01: - # 1.01초 내에 500개를 이미 보냈으므로 이 요청은 무시합니다. - # (print문은 필요시 주석 해제하여 로깅 용도로 사용하세요) - # print("전송 제한 초과! 주문이 무시되었습니다.") - return False - - # 전송 조건 통과 시 실제 통신 수행 - if action_type == "add": - self.exchange.send_add_message(**kwargs) - elif action_type == "cancel": - self.exchange.send_cancel_message(**kwargs) - elif action_type == "convert": - self.exchange.send_convert_message(**kwargs) - - # 전송한 시간 기록 - self._send_timestamps.append(now) - return True - - def sell(self, symbol: str, dir: Dir, price: int, size: int): - return self._attempt_send("add", order_id=self.next_order(), symbol=symbol, dir=dir, price=price, size=size) - - def buy(self, symbol: str, dir: Dir, price: int, size: int): - return self._attempt_send("add", order_id=self.next_order(), symbol=symbol, dir=dir, price=price, size=size) - - def cancel(self, order_id: int): - return self._attempt_send("cancel", order_id=order_id) \ No newline at end of file diff --git a/bot.py b/bot.py index 4653f2c..02e0806 100644 --- a/bot.py +++ b/bot.py @@ -12,7 +12,7 @@ import socket import json from state import StateManager - +from order import OrderManager # ~~~~~============== CONFIGURATION ==============~~~~~ # Replace "REPLACEME" with your team name! team_name = "HanyangFloorFunction" @@ -32,48 +32,18 @@ team_name = "HanyangFloorFunction" def main(): args = parse_arguments() state = StateManager() - exchange = ExchangeConnection(args=args) + orderman = OrderManager(exchange) - # Store and print the "hello" message received from the exchange. This - # contains useful information about your positions. Normally you start with - # all positions at zero, but if you reconnect during a round, you might - # have already bought/sold symbols and have non-zero positions. hello_message = exchange.read_message() print("First message from exchange:", hello_message) - # Send an order for BOND at a good price, but it is low enough that it is - # unlikely it will be traded against. Maybe there is a better price to - # pick? Also, you will need to send more orders over time. - exchange.send_add_message(order_id=1, symbol="BOND", dir=Dir.BUY, price=990, size=1) + orderman.sell("BOND", 1001, 99) + orderman.buy("BOND", 999, 99) - # Set up some variables to track the bid and ask price of a symbol. Right - # now this doesn't track much information, but it's enough to get a sense - # of the VALE market. - vale_bid_price, vale_ask_price = None, None - vale_last_print_time = time.time() - - # Here is the main loop of the program. It will continue to read and - # process messages in a loop until a "close" message is received. You - # should write to code handle more types of messages (and not just print - # the message). Feel free to modify any of the starter code below. - # - # Note: a common mistake people make is to call write_message() at least - # once for every read_message() response. - # - # Every message sent to the exchange generates at least one response - # message. Sending a message in response to every exchange message will - # cause a feedback loop where your bot's messages will quickly be - # rate-limited and ignored. Please, don't do that! while True: message = exchange.read_message() - # Some of the message types below happen infrequently and contain - # important information to help you understand what your bot is doing, - # so they are printed in full. We recommend not always printing every - # message because it can be a lot of information to read. Instead, let - # your code handle the messages and just print the information - # important for you! if message["type"] == "close": print("The round has ended") break @@ -81,28 +51,55 @@ def main(): print(message) elif message["type"] == "reject": print(message) - elif message["type"] == "fill": - print(message) elif message["type"] == "book": - if message["symbol"] == "VALE": + on_book(message, state) + elif message["type"] == "trade": + on_trade(message, state) + elif message["type"] == "fill": + on_fill(message, orderman, state) - def best_price(side): - if message[side]: - return message[side][0][0] - vale_bid_price = best_price("buy") - vale_ask_price = best_price("sell") +def on_book(message: dict, state: StateManager): + symbol = message["symbol"] - now = time.time() + def best_price(side) -> int: + if message[side]: + return message[side][0][0] - if now > vale_last_print_time + 1: - vale_last_print_time = now - print( - { - "vale_bid_price": vale_bid_price, - "vale_ask_price": vale_ask_price, - } - ) + bid_price = int(best_price("buy")) + ask_price = int(best_price("sell")) + + state.update_bid_ask_price(symbol, bid_price, ask_price) + +def on_fill(message: dict, orderman: OrderManager, state: StateManager): + symbol = message["symbol"] + dir = message["dir"] + size = message["size"] + + quantity = size if dir == Dir.BUY else -size + orderman.update_position(symbol, message["order_id"], quantity) + + +def on_trade(message: dict, orderman: OrderManager, state: StateManager): + symbol = message["symbol"] + price = message["price"] + + state.set_last_price(symbol, price) + + # 거래도 + p_valbz = state.get_fair_value("VALBZ") + p_vale = state.get_fair_value("VALE") + + if state.get_last_price("VALBZ") is None or state.get_last_price("VALE") is None: + return + + FEE = 10 + SZ = 10 + if p_vale < p_valbz: + if p_vale * SZ + FEE < p_valbz * SZ: + orderman.buy("VALE", p_vale, SZ) + orderman.sell("VALBZ", p_valbz, SZ) + # ~~~~~============== PROVIDED CODE ==============~~~~~ diff --git a/order.py b/order.py index 739a391..6c82236 100644 --- a/order.py +++ b/order.py @@ -1,10 +1,108 @@ +import time +from collections import deque + + class OrderManager: - _order_size = 0 - - def __init__(self): + def __init__(self, exchange: "ExchangeConnection"): + symbols = ["BOND", "VALBZ", "VALE", "GS", "MS", "WFC", "XLF"] + + self.POSITIONS_LIMIT = { + "BOND": 100, + "VALBZ": 10, + "VALE": 10, + "GS": 100, + "MS": 100, + "WFC": 100, + "XLF": 100, + + } + + self.exchange = exchange self._order_size = 0 - - def next_order(self): + + # 최근 500개의 전송 시간만 기록하는 데크 + self._send_timestamps = deque(maxlen=500) + + self.orders = {} + + self.positions = {s: 0 for s in symbols} + self.future_positions = {s: 0 for s in symbols} + + def add_order(self, order_id: int, symbol: str, size: int): + self.orders[order_id] = size + + def get_order(self, order_id: int): + return self.orders.get(order_id) + + def _next_order(self): self._order_size += 1 return self._order_size - \ No newline at end of file + + def _attempt_send(self, action_type: str, **kwargs) -> bool: + """ + 주문 전송을 시도합니다. + 제한에 걸려 무시되면 False를, 정상 전송되면 True를 반환합니다. + """ + now = time.time() + + # 최근 500개를 이미 보냈다면, 가장 오래된 주문(0번 인덱스)의 시간을 확인 + if len(self._send_timestamps) == 500: + elapsed = now - self._send_timestamps[0] + if elapsed < 1.01: + # 1.01초 내에 500개를 이미 보냈으므로 이 요청은 무시합니다. + # (print문은 필요시 주석 해제하여 로깅 용도로 사용하세요) + # print("전송 제한 초과! 주문이 무시되었습니다.") + return False + + # 전송 조건 통과 시 실제 통신 수행 + if action_type == "add": + size = kwargs["size"] + if self.future_positions[kwargs["symbol"]] + size > self.POSITIONS_LIMIT[kwargs["symbol"]]: + return False + + self.future_positions[kwargs["symbol"]] += size + self.exchange.send_add_message(**kwargs) + + elif action_type == "cancel": + self.exchange.send_cancel_message(**kwargs) + elif action_type == "convert": + self.exchange.send_convert_message(**kwargs) + + # 전송한 시간 기록 + self._send_timestamps.append(now) + return True + + def sell(self, symbol: str, price: int, size: int): + return self._attempt_send( + "add", + order_id=self._next_order(), + symbol=symbol, + dir="SELL", + price=price, + size=size, + ) + + def buy(self, symbol: str, price: int, size: int): + return self._attempt_send( + "add", + order_id=self._next_order(), + symbol=symbol, + dir="BUY", + price=price, + size=size, + ) + + def cancel(self, order_id: int): + return self._attempt_send("cancel", order_id=order_id) + + def update_position(self, symbol: str, order_id: int, quantity: int): + self.positions[symbol] = self.positions.get(symbol, 0) + quantity + if order_id in self.orders: + new_size = self.orders[order_id]["quantity"] + quantity + if new_size <= 0: + del self.orders[order_id] + else: + self.orders[order_id]["quantity"] = new_size + + def check_pos_limit(self, symbol: str) -> bool: + return abs(self.positions[symbol]) < self.POSITIONS_LIMIT[symbol] diff --git a/state.py b/state.py index 211187e..e223741 100644 --- a/state.py +++ b/state.py @@ -4,8 +4,6 @@ CONVERSION_FEE = 5 class StateManager: def __init__(self): symbols = ["BOND", "VALBZ", "VALE", "GS", "MS", "WFC", "XLF"] - self.positions = {s: 0 for s in symbols} - self.orders = {s: {} for s in symbols} self.bid_prices = {s: None for s in symbols} self.ask_prices = {s: None for s in symbols} self.bid_depths = {s: 0 for s in symbols} @@ -26,6 +24,9 @@ class StateManager: } self.etf_shares = {"XLF": 10} + def set_last_price(self, symbol: str, price: int): + self.last_prices[symbol] = price + def get_last_price(self, symbol: str): return self.last_prices.get(symbol) @@ -71,7 +72,7 @@ class StateManager: self.predicted_prices[symbol] = mid def update_fair_value(self): - valbz_price = self.get_predicted_price("VALBZ") + valbz_price = self.last_prices.get("VALBZ") if valbz_price is not None: self.fair_values["VALBZ"] = valbz_price self.fair_values["VALE"] = valbz_price