From d3b6e7cbbb0c1215ac8138711b3036846b4a9f79 Mon Sep 17 00:00:00 2001 From: perror_MacBookPro Date: Sat, 9 May 2026 15:32:14 +0900 Subject: [PATCH] bot split sex --- bot split/ETC.py | 135 +++++++++++++++++++++++++++++++++++++++++ bot split/bot sb.py | 129 +++++++++++++++++++++++++++++++++++++++ bot split/bot split.py | 100 +++++++++++++++--------------- bot split/order_new.py | 52 ++++++++++++++++ bot split/position.py | 28 +++++++++ bot split/value.py | 21 +++++++ 6 files changed, 413 insertions(+), 52 deletions(-) create mode 100644 bot split/ETC.py create mode 100644 bot split/bot sb.py create mode 100644 bot split/order_new.py create mode 100644 bot split/position.py create mode 100644 bot split/value.py diff --git a/bot split/ETC.py b/bot split/ETC.py new file mode 100644 index 0000000..d73db07 --- /dev/null +++ b/bot split/ETC.py @@ -0,0 +1,135 @@ +import argparse +from collections import deque +from enum import Enum +import time +import socket +import json +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, + "tif": "DAY", + } + ) + + 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." + ) + + +def parse_arguments(): + test_exchange_port_offsets = {"prod-like": 0, "slower": 1, "empty": 2} + + parser = argparse.ArgumentParser(description="Trade on an ETC exchange!") + exchange_address_group = parser.add_mutually_exclusive_group(required=True) + exchange_address_group.add_argument( + "--production", action="store_true", help="Connect to the production exchange." + ) + exchange_address_group.add_argument( + "--test", + type=str, + choices=test_exchange_port_offsets.keys(), + help="Connect to a test exchange.", + ) + + # Connect to a specific host. This is only intended to be used for debugging. + exchange_address_group.add_argument( + "--specific-address", type=str, metavar="HOST:PORT", help=argparse.SUPPRESS + ) + + args = parser.parse_args() + args.add_socket_timeout = True + + if args.production: + args.exchange_hostname = "production" + args.port = 25000 + elif args.test: + args.exchange_hostname = "test-exch-" + team_name + args.port = 22000 + test_exchange_port_offsets[args.test] + if args.test == "empty": + args.add_socket_timeout = False + elif args.specific_address: + args.exchange_hostname, port = args.specific_address.split(":") + args.port = int(port) + + return args \ No newline at end of file diff --git a/bot split/bot sb.py b/bot split/bot sb.py new file mode 100644 index 0000000..81f58aa --- /dev/null +++ b/bot split/bot sb.py @@ -0,0 +1,129 @@ +#!/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 + +from state import StateManager + +# ~~~~~============== CONFIGURATION ==============~~~~~ +# Replace "REPLACEME" with your team name! +team_name = "HanyangFloorFunction" + +# ~~~~~============== MAIN LOOP ==============~~~~~ + +# You should put your code here! We provide some starter code as an example, +# but feel free to change/remove/edit/update any of it as you'd like. If you +# have any questions about the starter code, or what to do next, please ask us! +# +# To help you get started, the sample code below tries to buy BOND for a low +# price, and it prints the current prices for VALE every second. The sample +# code is intended to be a working example, but it needs some improvement +# before it will start making good trades! + + +def main(): + args = parse_arguments() + state = StateManager() + + exchange = ExchangeConnection(args=args) + + # 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) + + # 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 + elif message["type"] == "error": + print(message) + elif message["type"] == "reject": + print(message) + elif message["type"] == "fill": + on_fill(message) + elif message["type"] == "book": + on_book(message) + +def on_fill(message): + ; + +def on_book(message): + if message["symbol"] == "VALE": + def best_price(side): + if message[side]: + return message[side][0][0] + + vale_bid_price = best_price("buy") + vale_ask_price = best_price("sell") + + now = time.time() + + 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, + } + ) + elif message["symbol"] == "VALBZ": + + + + +# ~~~~~============== PROVIDED CODE ==============~~~~~ + +# You probably don't need to edit anything below this line, but feel free to +# ask if you have any questions about what it is doing or how it works. If you +# do need to change anything below this line, please feel free to + + +if __name__ == "__main__": + # Check that [team_name] has been updated. + assert team_name != "REPLAC" + "EME", ( + "Please put your team name in the variable [team_name]." + ) + + main() diff --git a/bot split/bot split.py b/bot split/bot split.py index 9654cdb..93debc7 100644 --- a/bot split/bot split.py +++ b/bot split/bot split.py @@ -5,9 +5,10 @@ # 3) Run in loop: while true; do ./bot.py --test prod-like; sleep 1; done import argparse import time -from ETC import ExchangeConnection -from ETC import Dir -from ETC import team_name +from ETC import ExchangeConnection, Dir, team_name +from order_new import OrderManager +from position import PositionManager +from value import ValueManager # ~~~~~============== CONFIGURATION ==============~~~~~ # Replace "REPLACEME" with your team name! @@ -25,49 +26,26 @@ from ETC import team_name def main(): + global args, exchange, positionMan, orderMan, valueMan + args = parse_arguments() - exchange = ExchangeConnection(args=args) - - # 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. + positionMan = PositionManager() + orderMan = OrderManager(exchange, positionMan) + valueMan = ValueManager() + 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) - # 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 @@ -76,27 +54,45 @@ def main(): elif message["type"] == "reject": print(message) elif message["type"] == "fill": - print(message) + on_fill(message) elif message["type"] == "book": - if message["symbol"] == "VALE": + on_book(message) + +def on_fill(message): + if (message["dir"] == Dir.BUY): + positionMan.update_position(message["symbol"], message["size"]) + elif (message["dir"] == Dir.SELL): + positionMan.update_position(message["symbol"], -message["size"]) + +def on_book(message): + valueMan.set_value(message["symbol"], message["buy"][0][0], message["sell"][0][0]) + + if message["symbol"] == "VALE" or message["symbol"] == "VALBZ": + def best_price(side): + if message[side]: + return message[side][0][0] + # best_price("buy") + # best_price("sell") - def best_price(side): - if message[side]: - return message[side][0][0] + now = time.time() - vale_bid_price = best_price("buy") - vale_ask_price = best_price("sell") - - now = time.time() - - 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, - } - ) + if now > vale_last_print_time: + vale_last_print_time = now + + valePos = positionMan.get_position("VALE") + valeBid = valueMan.get_bid("VALE") + valeAsk = valueMan.get_ask("VALE") + valbzPos = positionMan.get_position("VALBZ") + valbzBid = valueMan.get_bid("VALBZ") + valbzAsk = valueMan.get_ask("VALBZ") + FEE = 10 + if (valePos * positionMan.get_position["VALE"] + FEE < + valbzPos * positionMan.get_position["VALBZ"]): + orderMan.convert("VALE", Dir.SELL, valePos) + if (valePos * positionMan.get_position["VALE"] > + valbzPos * positionMan.get_position["VALBZ"] + FEE): + orderMan.convert("VALBZ", Dir.SELL, valbzPos) + # ~~~~~============== PROVIDED CODE ==============~~~~~ diff --git a/bot split/order_new.py b/bot split/order_new.py new file mode 100644 index 0000000..43caa36 --- /dev/null +++ b/bot split/order_new.py @@ -0,0 +1,52 @@ +import time +from collections import deque +from ETC import ExchangeConnection, Dir +from position import PositionManager + +class OrderManager: + def __init__(self, exchange: ExchangeConnection, positionMan: PositionManager): + self.exchange = exchange + self._order_size = 0 + + self._send_timestamps = deque(maxlen=500) + + self.positionMan = positionMan + + def _next_order(self): + self._order_size += 1 + return self._order_size + + def _attempt_send(self, action_type: str, symbol: str, dir: Dir, price: int, size: int) -> bool: + """ + 주문 전송을 시도합니다. + 제한에 걸려 무시되면 False를, 정상 전송되면 True를 반환합니다. + """ + now = time.time() + + if len(self._send_timestamps) == 500: + elapsed = now - self._send_timestamps[0] + if elapsed < 1.01: + return False + + # 전송 조건 통과 시 실제 통신 수행 + if action_type == "add": + self.exchange.send_add_message(symbol=symbol, dir=dir, price=price, size=size) + elif action_type == "cancel": + self.exchange.send_cancel_message(symbol=symbol, dir=dir, price=price, size=size) + elif action_type == "convert": + self.exchange.send_convert_message(symbol=symbol, dir=dir, price=price, size=size) + + 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 convert(self, symbol: str, dir: Dir, size: int): + return self._attempt_send("convert", order_id=self._next_order(), symbol=symbol, dir=dir, 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 split/position.py b/bot split/position.py new file mode 100644 index 0000000..7aeebf7 --- /dev/null +++ b/bot split/position.py @@ -0,0 +1,28 @@ + + +class PositionManager: + def __init__(self): + self.POSITION_LIMIT = { + "BOND": 100, + "GS": 10, + "MS": 10, + "VALBZ": 100, + "VALE": 100, + "WFC": 100, + "XLF": 100 + } + self.position = { + "BOND": 0, + "GS": 0, + "MS": 0, + "VALBZ": 0, + "VALE": 0, + "WFC": 0, + "XLF": 0 + } + + def update_position(self, symbol: str, quantity: int): + self.position[symbol] += quantity + + def get_position(self, symbol: str): + return self.position[symbol] \ No newline at end of file diff --git a/bot split/value.py b/bot split/value.py new file mode 100644 index 0000000..2933d1c --- /dev/null +++ b/bot split/value.py @@ -0,0 +1,21 @@ +class ValueManager: + def __init__(self): + self.value = { + # symbol: [bid, ask] + "BOND": [0, 0], + "GS": [0, 0], + "MS": [0, 0], + "VALBZ": [0, 0], + "VALE": [0, 0], + "WFC": [0, 0], + "XLF": [0, 0] + } + + def set_value(self, symbol: str, bid: int, ask: int): + self.value[symbol] = [bid, ask] + + def get_bid(self, symbol: str): + return self.value[symbol][0] + + def get_ask(self, symbol: str): + return self.value[symbol][1] \ No newline at end of file