This commit is contained in:
2026-05-09 16:18:46 +09:00
parent a411a31feb
commit e8886cebc4

693
prac.py
View File

@@ -1,108 +1,172 @@
#!/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 enum import Enum from enum import Enum
import time
import socket import socket
import json import json
from state import StateManager from state import StateManager
from order import OrderManager
# ~~~~~============== CONFIGURATION ==============~~~~~
# Replace "REPLACEME" with your team name!
team_name = "HanyangFloorFunction" team_name = "HanyangFloorFunction"
# ==================== 설정 ==================== # ~~~~~============== MAIN LOOP ==============~~~~~
ORDER_SIZE = 2
MAX_POS = 20 # You should put your code here! We provide some starter code as an example,
ARB_THRESHOLD = 40 # 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!
# ====================
class Dir(str, Enum):
BUY = "BUY"
SELL = "SELL"
# ====================
def main(): def main():
args = parse_arguments() args = parse_arguments()
exchange = ExchangeConnection(args)
state = StateManager() exchange = ExchangeConnection(args=args)
hello = exchange.read_message()
# 포지션 직접 관리 # Store and print the "hello" message received from the exchange. This
positions = {} # contains useful information about your positions. Normally you start with
for sym in hello["symbols"]: # all positions at zero, but if you reconnect during a round, you might
positions[sym["symbol"]] = sym["position"] # 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.
# --- 설정 ---
BOND_FAIR_VALUE = 1000 # BOND fair value (고정)
BOND_ORDER_SIZE = 50 # BOND 주문당 수량
XLF_CONVERSION_FEE = 100 # XLF 변환 비용
XLF_MIN_PROFIT = 10 # XLF 차익거래 최소 수익 (불확실한 거래 방지)
VALE_CONVERSION_FEE = 10 # VALE 변환 비용
VALE_MIN_PROFIT = 3 # VALE 차익거래 최소 수익
VALE_ARB_SIZE = 10 # VALE 차익거래 단위
REFRESH_INTERVAL = 5.0 # 주문 갱신 주기 (초)
# XLF state machine
# IDLE → BUYING_BASKET → CONVERTING → SELLING_XLF → IDLE
# IDLE → BUYING_XLF → CONVERTING → SELLING_BASKET → IDLE
xlf_state = "IDLE"
xlf_pending = {}
xlf_direction = None
xlf_arb_size = 0
# VALE state machine
# IDLE → BUYING_VALBZ → CONVERTING → SELLING_VALE → IDLE
# IDLE → BUYING_VALE → CONVERTING → SELLING_VALBZ → IDLE
vale_state = "IDLE"
vale_pending = {}
vale_direction = None
vale_arb_size = 0
state = StateManager()
om = OrderManager(exchange)
market_open = False
active_orders = {} # BOND 전용 {order_id: {"dir": ..., "price": ..., "size": ...}}
last_refresh = time.time()
# hello 메시지에서 기존 포지션 로드 (재접속 시 필수)
for sym_info in hello_message["symbols"]:
sym = sym_info["symbol"]
pos = sym_info["position"]
if sym in om.positions:
om.positions[sym] = pos
om.future_positions[sym] = pos
print(f" 초기 포지션 로드: {om.positions}")
order_id = 0
def next_id(): def next_id():
nonlocal order_id return om.next_order()
order_id += 1
return order_id
active_orders = {} def cancel_all_bond_orders():
"""활성 BOND 주문 전부 취소 + future_positions 롤백"""
for oid in list(active_orders.keys()):
order_info = active_orders[oid]
size = order_info.get("size", 0)
if order_info["dir"] == Dir.BUY:
om.future_positions["BOND"] -= size
else:
om.future_positions["BOND"] += size
om.cancel(oid)
active_orders.pop(oid, None)
# ==================== MARKET MAKING ==================== def place_bond_orders():
def market_make(sym): """포지션 한도 안에서 bid/ask 양방향 주문"""
bid = state.bid_prices.get(sym) if not market_open:
ask = state.ask_prices.get(sym)
if bid is None or ask is None:
return return
if ask - bid <= 2: cancel_all_bond_orders()
return
pos = positions.get(sym, 0) buy_price = BOND_FAIR_VALUE - 1 # 999
sell_price = BOND_FAIR_VALUE + 1 # 1001
position = om.positions["BOND"]
if abs(pos) > MAX_POS: # 포지션에 따라 size 비대칭 조정
return base_size = BOND_ORDER_SIZE
adjustment = abs(position) // 5
buy_price = bid + 1 if position < 0:
sell_price = ask - 1 buy_size = min(base_size + adjustment, 100 - position)
sell_size = max(base_size - adjustment, 1)
if buy_price >= sell_price: elif position > 0:
return buy_size = max(base_size - adjustment, 1)
sell_size = min(base_size + adjustment, 100 + position)
# 포지션 조절
if pos > 0:
buy_size = 1
sell_size = ORDER_SIZE + 1
elif pos < 0:
buy_size = ORDER_SIZE + 1
sell_size = 1
else: else:
buy_size = sell_size = ORDER_SIZE buy_size = base_size
sell_size = base_size
oid = next_id() # 포지션 한도 초과 방지
exchange.send_add_message(oid, sym, Dir.BUY, buy_price, buy_size) buy_size = max(0, min(buy_size, 100 - position))
active_orders[oid] = sym sell_size = max(0, min(sell_size, 100 + position))
oid = next_id() if buy_size > 0:
exchange.send_add_message(oid, sym, Dir.SELL, sell_price, sell_size) bid = next_id()
active_orders[oid] = sym exchange.send_add_message(
order_id=bid, symbol="BOND",
dir=Dir.BUY, price=buy_price, size=buy_size
)
om.future_positions["BOND"] += buy_size
active_orders[bid] = {"dir": Dir.BUY, "price": buy_price, "size": buy_size}
# ==================== BOND 안전 수익 ==================== if sell_size > 0:
def bond_arb(): ask = next_id()
bid = state.bid_prices.get("BOND") exchange.send_add_message(
ask = state.ask_prices.get("BOND") order_id=ask, symbol="BOND",
dir=Dir.SELL, price=sell_price, size=sell_size
)
om.future_positions["BOND"] -= sell_size
active_orders[ask] = {"dir": Dir.SELL, "price": sell_price, "size": sell_size}
if ask and ask < 999: print(f" BOND 주문 → 매수:{buy_price} x{buy_size}, 매도:{sell_price} x{sell_size}, 포지션:{position}")
exchange.send_add_message_ioc(next_id(), "BOND", Dir.BUY, ask, 3)
if bid and bid > 1001: def try_xlf_arb():
exchange.send_add_message_ioc(next_id(), "BOND", Dir.SELL, bid, 3) """XLF 차익거래 시도 - IDLE 상태일 때만"""
nonlocal xlf_state, xlf_direction, xlf_pending, xlf_arb_size
# ==================== XLF 차익거래 (초안전 버전) ==================== if not market_open or xlf_state != "IDLE":
def xlf_arb(): return
bond_ask = state.ask_prices.get("BOND")
gs_ask = state.ask_prices.get("GS")
ms_ask = state.ask_prices.get("MS")
wfc_ask = state.ask_prices.get("WFC")
xlf_bid = state.bid_prices.get("XLF")
bond_bid = state.bid_prices.get("BOND") bond_ask = state.ask_prices["BOND"]
gs_bid = state.bid_prices.get("GS") gs_ask = state.ask_prices["GS"]
ms_bid = state.bid_prices.get("MS") ms_ask = state.ask_prices["MS"]
wfc_bid = state.bid_prices.get("WFC") wfc_ask = state.ask_prices["WFC"]
xlf_ask = state.ask_prices.get("XLF") xlf_bid = state.bid_prices["XLF"]
bond_bid = state.bid_prices["BOND"]
gs_bid = state.bid_prices["GS"]
ms_bid = state.bid_prices["MS"]
wfc_bid = state.bid_prices["WFC"]
xlf_ask = state.ask_prices["XLF"]
if None in [bond_ask, gs_ask, ms_ask, wfc_ask, xlf_bid, if None in [bond_ask, gs_ask, ms_ask, wfc_ask, xlf_bid,
bond_bid, gs_bid, ms_bid, wfc_bid, xlf_ask]: bond_bid, gs_bid, ms_bid, wfc_bid, xlf_ask]:
@@ -111,118 +175,455 @@ def main():
basket_ask = bond_ask*3 + gs_ask*2 + ms_ask*3 + wfc_ask*2 basket_ask = bond_ask*3 + gs_ask*2 + ms_ask*3 + wfc_ask*2
basket_bid = bond_bid*3 + gs_bid*2 + ms_bid*3 + wfc_bid*2 basket_bid = bond_bid*3 + gs_bid*2 + ms_bid*3 + wfc_bid*2
pos = positions.get("XLF", 0) # 케이스 1: 바스켓 매수 → XLF 변환 → XLF 매도
profit1 = xlf_bid * 10 - basket_ask - XLF_CONVERSION_FEE
if abs(pos) > 10: if profit1 > XLF_MIN_PROFIT and om.check_pos_limit("XLF"):
print(f" XLF 차익(바스켓→XLF) 시작, 예상수익:{profit1}")
# BOND 주문 먼저 취소 (XLF용 BOND 매수가 기존 매도에 상쇄되지 않도록)
cancel_all_bond_orders()
xlf_state = "BUYING_BASKET"
xlf_direction = "BASKET_TO_XLF"
xlf_arb_size = 10
xlf_pending.clear()
for sym, qty in [("BOND", 3), ("GS", 2), ("MS", 3), ("WFC", 2)]:
oid = next_id()
exchange.send_add_message(oid, sym, Dir.BUY, state.ask_prices[sym], qty)
xlf_pending[oid] = qty
return return
profit1 = xlf_bid * 10 - basket_ask # 케이스 2: XLF 매수 → 바스켓 변환 → 각 종목 매도
if profit1 > ARB_THRESHOLD: profit2 = basket_bid - xlf_ask * 10 - XLF_CONVERSION_FEE
exchange.send_add_message_ioc(next_id(), "XLF", Dir.SELL, xlf_bid, 10) if profit2 > XLF_MIN_PROFIT and om.check_pos_limit("XLF"):
print(f" XLF 차익(XLF→바스켓) 시작, 예상수익:{profit2}")
cancel_all_bond_orders()
xlf_state = "BUYING_XLF"
xlf_direction = "XLF_TO_BASKET"
xlf_arb_size = 10
xlf_pending.clear()
oid = next_id()
exchange.send_add_message(oid, "XLF", Dir.BUY, xlf_ask, 10)
xlf_pending[oid] = 10
profit2 = basket_bid - xlf_ask * 10 def handle_xlf_fill(order_id, symbol, dir_, qty):
if profit2 > ARB_THRESHOLD: """XLF state machine 체결 처리 (부분 체결 추적)"""
exchange.send_add_message_ioc(next_id(), "XLF", Dir.BUY, xlf_ask, 10) nonlocal xlf_state, xlf_pending
# ==================== 리스크 관리 ==================== if order_id not in xlf_pending:
def risk(): return
for sym in ["GS", "MS", "WFC", "XLF"]:
pos = positions.get(sym, 0)
if abs(pos) > MAX_POS: xlf_pending[order_id] -= qty
if pos > 0: if xlf_pending[order_id] <= 0:
exchange.send_add_message_ioc( del xlf_pending[order_id]
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)
)
# ==================== LOOP ==================== if xlf_state == "BUYING_BASKET" and not xlf_pending:
print(" 바스켓 매수 완료 → XLF 변환 시작")
xlf_state = "CONVERTING"
exchange.send_convert_message(next_id(), "XLF", Dir.BUY, xlf_arb_size)
elif xlf_state == "BUYING_XLF" and not xlf_pending:
print(" XLF 매수 완료 → 바스켓 변환 시작")
xlf_state = "CONVERTING"
exchange.send_convert_message(next_id(), "XLF", Dir.SELL, xlf_arb_size)
def try_vale_arb():
"""VALE/VALBZ 차익거래 시도 - IDLE 상태일 때만"""
nonlocal vale_state, vale_direction, vale_pending, vale_arb_size
if not market_open or vale_state != "IDLE":
return
vale_bid = state.bid_prices["VALE"]
vale_ask = state.ask_prices["VALE"]
valbz_bid = state.bid_prices["VALBZ"]
valbz_ask = state.ask_prices["VALBZ"]
if None in [vale_bid, vale_ask, valbz_bid, valbz_ask]:
return
valbz_pos = om.positions["VALBZ"]
vale_pos = om.positions["VALE"]
# 케이스 1: VALE가 비쌀 때 → VALBZ 매수 → VALE 변환 → VALE 매도
profit1 = vale_bid - valbz_ask - VALE_CONVERSION_FEE
arb_size1 = min(VALE_ARB_SIZE, 10 - valbz_pos)
if profit1 > VALE_MIN_PROFIT and arb_size1 > 0:
print(f" VALE 차익(VALBZ→VALE) 시작, 예상수익:{profit1 * arb_size1}, size:{arb_size1}")
vale_state = "BUYING_VALBZ"
vale_direction = "VALBZ_TO_VALE"
vale_arb_size = arb_size1
vale_pending.clear()
oid = next_id()
exchange.send_add_message(oid, "VALBZ", Dir.BUY, valbz_ask, arb_size1)
vale_pending[oid] = arb_size1
return
# 케이스 2: VALBZ가 비쌀 때 → VALE 매수 → VALBZ 변환 → VALBZ 매도
profit2 = valbz_bid - vale_ask - VALE_CONVERSION_FEE
arb_size2 = min(VALE_ARB_SIZE, 10 - vale_pos)
if profit2 > VALE_MIN_PROFIT and arb_size2 > 0:
print(f" VALE 차익(VALE→VALBZ) 시작, 예상수익:{profit2 * arb_size2}, size:{arb_size2}")
vale_state = "BUYING_VALE"
vale_direction = "VALE_TO_VALBZ"
vale_arb_size = arb_size2
vale_pending.clear()
oid = next_id()
exchange.send_add_message(oid, "VALE", Dir.BUY, vale_ask, arb_size2)
vale_pending[oid] = arb_size2
def handle_vale_fill(order_id, symbol, dir_, qty):
"""VALE state machine 체결 처리 (부분 체결 추적)"""
nonlocal vale_state, vale_pending
if order_id not in vale_pending:
return
vale_pending[order_id] -= qty
if vale_pending[order_id] <= 0:
del vale_pending[order_id]
if vale_state == "BUYING_VALBZ" and not vale_pending:
print(" VALBZ 매수 완료 → VALE 변환 시작")
vale_state = "CONVERTING"
exchange.send_convert_message(next_id(), "VALE", Dir.BUY, vale_arb_size)
elif vale_state == "BUYING_VALE" and not vale_pending:
print(" VALE 매수 완료 → VALBZ 변환 시작")
vale_state = "CONVERTING"
exchange.send_convert_message(next_id(), "VALE", Dir.SELL, vale_arb_size)
# 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_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: while True:
msg = exchange.read_message() message = exchange.read_message()
if msg["type"] == "close": # 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 break
elif msg["type"] == "book": elif message["type"] == "open":
sym = msg["symbol"] # 시장이 열렸을 때 주문 시작 (open 전에 주문하면 reject됨)
print("Market opened:", message)
market_open = True
place_bond_orders()
bid = msg["buy"][0][0] if msg["buy"] else None elif message["type"] == "error":
ask = msg["sell"][0][0] if msg["sell"] else None print(message)
state.update_bid_ask_price(sym, bid, ask) elif message["type"] == "reject":
print(message)
oid = message.get("order_id")
active_orders.pop(oid, None)
if oid in xlf_pending:
print(" XLF 주문 reject → IDLE 복귀")
xlf_state = "IDLE"
xlf_pending.clear()
xlf_direction = None
place_bond_orders()
if oid in vale_pending:
print(" VALE 주문 reject → IDLE 복귀")
vale_state = "IDLE"
vale_pending.clear()
vale_direction = None
for oid in list(active_orders.keys()): elif message["type"] == "ack":
exchange.send_cancel_message(oid) # XLF 변환 ack 처리
del active_orders[oid] if xlf_state == "CONVERTING":
print(" XLF 변환 완료 → 매도 시작")
if xlf_direction == "BASKET_TO_XLF":
# 변환: BOND -3, GS -2, MS -3, WFC -2, XLF +10
om.positions["BOND"] -= 3
om.positions["GS"] -= 2
om.positions["MS"] -= 3
om.positions["WFC"] -= 2
om.positions["XLF"] += xlf_arb_size
om.future_positions["BOND"] -= 3
om.future_positions["GS"] -= 2
om.future_positions["MS"] -= 3
om.future_positions["WFC"] -= 2
om.future_positions["XLF"] += xlf_arb_size
xlf_state = "SELLING_XLF"
oid = next_id()
exchange.send_add_message(
oid, "XLF", Dir.SELL, state.bid_prices["XLF"], xlf_arb_size
)
xlf_pending[oid] = xlf_arb_size
elif xlf_direction == "XLF_TO_BASKET":
# 변환: XLF -10, BOND +3, GS +2, MS +3, WFC +2
om.positions["XLF"] -= xlf_arb_size
om.positions["BOND"] += 3
om.positions["GS"] += 2
om.positions["MS"] += 3
om.positions["WFC"] += 2
om.future_positions["XLF"] -= xlf_arb_size
om.future_positions["BOND"] += 3
om.future_positions["GS"] += 2
om.future_positions["MS"] += 3
om.future_positions["WFC"] += 2
xlf_state = "SELLING_BASKET"
for sym, qty in [("BOND", 3), ("GS", 2), ("MS", 3), ("WFC", 2)]:
oid = next_id()
exchange.send_add_message(
oid, sym, Dir.SELL, state.bid_prices[sym], qty
)
xlf_pending[oid] = qty
# 전략 실행 # VALE 변환 ack 처리
bond_arb() elif vale_state == "CONVERTING":
xlf_arb() print(" VALE 변환 완료 → 매도 시작")
if vale_direction == "VALBZ_TO_VALE":
# 변환: VALBZ -size, VALE +size
om.positions["VALBZ"] -= vale_arb_size
om.positions["VALE"] += vale_arb_size
om.future_positions["VALBZ"] -= vale_arb_size
om.future_positions["VALE"] += vale_arb_size
vale_state = "SELLING_VALE"
oid = next_id()
exchange.send_add_message(
oid, "VALE", Dir.SELL, state.bid_prices["VALE"], vale_arb_size
)
vale_pending[oid] = vale_arb_size
elif vale_direction == "VALE_TO_VALBZ":
# 변환: VALE -size, VALBZ +size
om.positions["VALE"] -= vale_arb_size
om.positions["VALBZ"] += vale_arb_size
om.future_positions["VALE"] -= vale_arb_size
om.future_positions["VALBZ"] += vale_arb_size
vale_state = "SELLING_VALBZ"
oid = next_id()
exchange.send_add_message(
oid, "VALBZ", Dir.SELL, state.bid_prices["VALBZ"], vale_arb_size
)
vale_pending[oid] = vale_arb_size
if sym in ["GS", "MS", "WFC"]: elif message["type"] == "fill":
market_make(sym) print(message)
qty = message["size"]
sym = message["symbol"]
dir_ = message["dir"]
oid = message["order_id"]
risk() # 포지션 업데이트
if dir_ == Dir.BUY:
elif msg["type"] == "fill": om.update_position(sym, oid, qty)
qty = msg["size"]
sym = msg["symbol"]
if msg["dir"] == Dir.BUY:
positions[sym] = positions.get(sym, 0) + qty
else: else:
positions[sym] = positions.get(sym, 0) - qty om.update_position(sym, oid, -qty)
print(f" 포지션 → {om.positions}")
# BOND 체결 시 무조건 재주문
if sym == "BOND" and oid in active_orders:
active_orders.pop(oid, None)
place_bond_orders()
# XLF state machine 체결 처리
handle_xlf_fill(oid, sym, dir_, qty)
if xlf_state in ("SELLING_XLF", "SELLING_BASKET") and not xlf_pending:
print(" XLF 차익거래 완료 → IDLE 복귀")
xlf_state = "IDLE"
xlf_direction = None
# XLF 완료 후 BOND 주문 재배치
place_bond_orders()
# VALE state machine 체결 처리
handle_vale_fill(oid, sym, dir_, qty)
if vale_state in ("SELLING_VALE", "SELLING_VALBZ") and not vale_pending:
print(" VALE 차익거래 완료 → IDLE 복귀")
vale_state = "IDLE"
vale_direction = None
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 == "VALE":
now = time.time()
if now > vale_last_print_time + 1:
vale_last_print_time = now
print({
"vale_bid_price": state.bid_prices["VALE"],
"vale_ask_price": state.ask_prices["VALE"],
})
# XLF 관련 심볼 호가 업데이트마다 차익거래 시도
if sym in ["BOND", "GS", "MS", "WFC", "XLF"]:
try_xlf_arb()
# VALE/VALBZ 호가 업데이트마다 차익거래 시도
if sym in ["VALE", "VALBZ"]:
try_vale_arb()
# 주기적으로 BOND 주문 갱신 (주문 만료 방지)
now = time.time()
if now - last_refresh > REFRESH_INTERVAL:
last_refresh = now
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: class ExchangeConnection:
def __init__(self, args): def __init__(self, args):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.message_timestamps = deque(maxlen=500)
s.connect((args.exchange_hostname, args.port)) self.exchange_hostname = args.exchange_hostname
self.reader = s.makefile("r", 1) self.port = args.port
self.writer = s exchange_socket = self._connect(add_socket_timeout=args.add_socket_timeout)
self._write({"type": "hello", "team": team_name.upper()}) self.reader = exchange_socket.makefile("r", 1)
self.writer = exchange_socket
self._write_message({"type": "hello", "team": team_name.upper()})
def read_message(self): def read_message(self):
msg = json.loads(self.reader.readline()) """Read a single message from the exchange"""
if "dir" in msg: message = json.loads(self.reader.readline())
msg["dir"] = Dir(msg["dir"]) if "dir" in message:
return msg message["dir"] = Dir(message["dir"])
return message
def send_add_message(self, oid, sym, dir, price, size): def send_add_message(
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"DAY"}) 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_add_message_ioc(self, oid, sym, dir, price, size): def send_convert_message(self, order_id: int, symbol: str, dir: Dir, size: int):
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"IOC"}) """Convert between related symbols"""
self._write_message(
{
"type": "convert",
"order_id": order_id,
"symbol": symbol,
"dir": dir,
"size": size,
}
)
def send_cancel_message(self, oid): def send_cancel_message(self, order_id: int):
self._write({"type": "cancel", "order_id": oid}) """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 _write(self, msg):
self.writer.send((json.dumps(msg)+"\n").encode())
# ====================
def parse_arguments(): def parse_arguments():
parser = argparse.ArgumentParser() test_exchange_port_offsets = {"prod-like": 0, "slower": 1, "empty": 2}
group = parser.add_mutually_exclusive_group(required=True) parser = argparse.ArgumentParser(description="Trade on an ETC exchange!")
group.add_argument("--production", action="store_true") exchange_address_group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--test", type=str, default="prod-like") 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 = parser.parse_args()
args.add_socket_timeout = True
if args.production: if args.production:
args.exchange_hostname = "production" args.exchange_hostname = "production"
args.port = 25000 args.port = 25000
else: elif args.test:
args.exchange_hostname = "test-exch-" + team_name args.exchange_hostname = "test-exch-" + team_name
args.port = 22000 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 return args
if __name__ == "__main__": 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() main()