diff --git a/bot bond sell.py b/bot bond sell.py index 608d5a0..3371076 100644 --- a/bot bond sell.py +++ b/bot bond sell.py @@ -1,3 +1,32 @@ +#!/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 ==============~~~~~ +# 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() @@ -15,7 +44,7 @@ def main(): # pick? Also, you will need to send more orders over time. # --- BOND 마켓 메이킹 설정 --- FAIR_VALUE = 1000 # BOND fair value (고정) - ORDER_SIZE = 100 # 한 번에 최대한 많이 (포지션 한도 = 100) + ORDER_SIZE = 10 # 주문당 수량 MAX_POSITION = 100 # 최대 포지션 한도 REFRESH_INTERVAL = 5.0 # 주문 갱신 주기 (초) @@ -30,9 +59,10 @@ def main(): return order_id def cancel_all_bond_orders(): - """BOND 주문 전부 한 번에 취소 (mass_cancel 사용)""" - exchange.send_mass_cancel_message(symbol="BOND") - active_orders.clear() + """활성 BOND 주문 전부 취소""" + for oid in list(active_orders.keys()): + exchange.send_cancel_message(oid) + active_orders.pop(oid, None) def place_bond_orders(): """포지션 한도 안에서 bid/ask 양방향 주문""" @@ -68,6 +98,7 @@ def main(): active_orders[ask] = {"dir": Dir.SELL, "price": sell_price} print(f" BOND 주문 → 매수: {buy_price} x{buy_size}, 매도: {sell_price} x{sell_size}, 포지션: {position}") + # 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. @@ -146,4 +177,149 @@ def main(): now = time.time() if now - last_refresh > REFRESH_INTERVAL: last_refresh = now - place_bond_orders() \ No newline at end of file + place_bond_orders() + + +# ~~~~~============== 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 + + +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", # 설명서 필수 필드: DAY or IOC + } + ) + + 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 + + +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()