Compare commits

..

40 Commits

Author SHA1 Message Date
5f7e000de3 last 2026-05-10 16:08:46 +09:00
8a85cc7248 x22 2026-05-09 17:14:04 +09:00
a9ed42bbba commit 2026-05-09 17:11:56 +09:00
e12ae87be6 x2 2026-05-09 17:08:34 +09:00
01cd34058d fa dw 2026-05-09 17:02:05 +09:00
a8ad37d35c commit 2026-05-09 17:01:57 +09:00
97a11f8c5b commit 2026-05-09 16:57:32 +09:00
5d6e1103b3 addg 2026-05-09 16:54:46 +09:00
12ccb4f2fd commit 2026-05-09 16:53:44 +09:00
c16ce43a7e add team_name 2026-05-09 16:51:45 +09:00
23ee31c18f commit 2026-05-09 16:51:42 +09:00
c9f4ad93ee down arb 2026-05-09 16:49:59 +09:00
05f6f007e1 fix 2026-05-09 16:49:02 +09:00
2160cc0e2a fix 2026-05-09 16:47:19 +09:00
b2aa766f25 commit 2026-05-09 16:46:34 +09:00
9c33cae2ce commit 2026-05-09 16:41:09 +09:00
7742a759f7 add bot_x 2026-05-09 16:39:44 +09:00
c81c344560 commit 2026-05-09 16:35:42 +09:00
30e63c9fc8 sad 2026-05-09 16:27:09 +09:00
7a8704c1d4 add cross + imb 2026-05-09 16:25:08 +09:00
5bd1588e09 asd 2026-05-09 16:23:10 +09:00
d161c0b6f1 va 2 2 2026-05-09 16:22:18 +09:00
d5c06c6b1a val 22 e2 2026-05-09 16:21:56 +09:00
e8886cebc4 commit 2026-05-09 16:18:49 +09:00
a411a31feb valsd2 12 2026-05-09 16:16:45 +09:00
e179de53ab add pos_limit 2026-05-09 16:16:07 +09:00
44325c398a value 1232131 2026-05-09 16:15:24 +09:00
d075831a55 add new 2026-05-09 16:12:25 +09:00
718cafb0ff add hello 2026-05-09 16:12:25 +09:00
7668287f5d value 22123 2026-05-09 16:11:27 +09:00
52f66ddba7 value 31 2026-05-09 16:06:44 +09:00
cef21e8bbe add: value 2026-05-09 15:59:01 +09:00
1b35f00923 commit 2026-05-09 15:58:33 +09:00
13159014fe hot fix 2026-05-09 15:56:05 +09:00
e40f457ecf botx 2026-05-09 15:54:41 +09:00
8b2444e418 bot split real 1 2026-05-09 15:54:26 +09:00
e826defc15 commit 2026-05-09 15:53:04 +09:00
ad49153cfc fix 2026-05-09 15:50:21 +09:00
99f9832390 debug frag 2026-05-09 15:49:54 +09:00
19096268f7 sex54 2026-05-09 15:49:23 +09:00
17 changed files with 3452 additions and 247 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -46,7 +46,7 @@ def main():
# pick? Also, you will need to send more orders over time.
# --- 설정 ---
BOND_FAIR_VALUE = 1000 # BOND fair value (고정)
BOND_ORDER_SIZE = 80 # BOND 주문당 수량 (크게 설정해서 체결 기회 극대화)
BOND_ORDER_SIZE = 50 # BOND 주문당 수량
XLF_CONVERSION_FEE = 100 # XLF 변환 비용
VALE_CONVERSION_FEE = 10 # VALE 변환 비용
VALE_ARB_SIZE = 10 # VALE 차익거래 단위
@@ -58,7 +58,7 @@ def main():
xlf_state = "IDLE"
xlf_pending = {}
xlf_direction = None
xlf_arb_size = 0 # 현재 차익거래 수량 추적
xlf_arb_size = 0
# VALE state machine
# IDLE → BUYING_VALBZ → CONVERTING → SELLING_VALE → IDLE
@@ -66,7 +66,7 @@ def main():
vale_state = "IDLE"
vale_pending = {}
vale_direction = None
vale_arb_size = 0 # 현재 차익거래 수량 추적
vale_arb_size = 0
state = StateManager()
om = OrderManager(exchange)
@@ -74,7 +74,6 @@ def main():
active_orders = {} # BOND 전용 {order_id: {"dir": ..., "price": ...}}
last_refresh = time.time()
vale_last_print = time.time()
def next_id():
return om.next_order()
@@ -111,8 +110,8 @@ def main():
sell_size = base_size
# 포지션 한도 초과 방지
buy_size = min(buy_size, 100 - position)
sell_size = min(sell_size, 100 + position)
buy_size = max(0, min(buy_size, 100 - position))
sell_size = max(0, min(sell_size, 100 + position))
if buy_size > 0:
bid = next_id()
@@ -161,6 +160,8 @@ def main():
profit1 = xlf_bid * 10 - basket_ask - XLF_CONVERSION_FEE
if profit1 > 0 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
@@ -175,6 +176,7 @@ def main():
profit2 = basket_bid - xlf_ask * 10 - XLF_CONVERSION_FEE
if profit2 > 0 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
@@ -318,6 +320,7 @@ def main():
xlf_state = "IDLE"
xlf_pending.clear()
xlf_direction = None
place_bond_orders()
if oid in vale_pending:
print(" VALE 주문 reject → IDLE 복귀")
vale_state = "IDLE"
@@ -330,11 +333,11 @@ def main():
print(" XLF 변환 완료 → 매도 시작")
if xlf_direction == "BASKET_TO_XLF":
# 변환: BOND -3, GS -2, MS -3, WFC -2, XLF +10
om.update_position("BOND", -1, -3)
om.update_position("GS", -1, -2)
om.update_position("MS", -1, -3)
om.update_position("WFC", -1, -2)
om.update_position("XLF", -1, xlf_arb_size)
om.positions["BOND"] -= 3
om.positions["GS"] -= 2
om.positions["MS"] -= 3
om.positions["WFC"] -= 2
om.positions["XLF"] += xlf_arb_size
xlf_state = "SELLING_XLF"
oid = next_id()
exchange.send_add_message(
@@ -343,11 +346,11 @@ def main():
xlf_pending[oid] = xlf_arb_size
elif xlf_direction == "XLF_TO_BASKET":
# 변환: XLF -10, BOND +3, GS +2, MS +3, WFC +2
om.update_position("XLF", -1, -xlf_arb_size)
om.update_position("BOND", -1, 3)
om.update_position("GS", -1, 2)
om.update_position("MS", -1, 3)
om.update_position("WFC", -1, 2)
om.positions["XLF"] -= xlf_arb_size
om.positions["BOND"] += 3
om.positions["GS"] += 2
om.positions["MS"] += 3
om.positions["WFC"] += 2
xlf_state = "SELLING_BASKET"
for sym, qty in [("BOND", 3), ("GS", 2), ("MS", 3), ("WFC", 2)]:
oid = next_id()
@@ -361,8 +364,8 @@ def main():
print(" VALE 변환 완료 → 매도 시작")
if vale_direction == "VALBZ_TO_VALE":
# 변환: VALBZ -size, VALE +size
om.update_position("VALBZ", -1, -vale_arb_size)
om.update_position("VALE", -1, vale_arb_size)
om.positions["VALBZ"] -= vale_arb_size
om.positions["VALE"] += vale_arb_size
vale_state = "SELLING_VALE"
oid = next_id()
exchange.send_add_message(
@@ -371,8 +374,8 @@ def main():
vale_pending[oid] = vale_arb_size
elif vale_direction == "VALE_TO_VALBZ":
# 변환: VALE -size, VALBZ +size
om.update_position("VALE", -1, -vale_arb_size)
om.update_position("VALBZ", -1, vale_arb_size)
om.positions["VALE"] -= vale_arb_size
om.positions["VALBZ"] += vale_arb_size
vale_state = "SELLING_VALBZ"
oid = next_id()
exchange.send_add_message(
@@ -406,6 +409,8 @@ def main():
print(" XLF 차익거래 완료 → IDLE 복귀")
xlf_state = "IDLE"
xlf_direction = None
# XLF 완료 후 BOND 주문 재배치
place_bond_orders()
# VALE state machine 체결 처리
handle_vale_fill(oid, sym, dir_, qty)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -35,6 +35,9 @@ def main():
print("First message from exchange:", hello_message)
#
man.orderMan.buy("BOND", 999, 99)
man.orderMan.sell("BOND", 1001, 99)
last_print_time = time.time()
while True:
@@ -61,7 +64,10 @@ def on_fill(message, man: Manager):
def on_book(message, man: Manager):
global last_print_time
man.valueMan.set_value(message["symbol"], message["buy"][0][0], message["sell"][0][0])
if (message["buy"]):
man.valueMan.set_bid(message["symbol"], message["buy"][0][0])
if (message["sell"]):
man.valueMan.set_ask(message["symbol"], message["sell"][0][0])
if message["symbol"] == "VALE" or message["symbol"] == "VALBZ":
def best_price(side):
@@ -72,7 +78,7 @@ def on_book(message, man: Manager):
now = time.time()
if now > last_print_time:
if now >= last_print_time+1:
last_print_time = now
valePos = man.positionMan.get_position("VALE")
@@ -82,12 +88,35 @@ def on_book(message, man: Manager):
valbzBid = man.valueMan.get_bid("VALBZ")
valbzAsk = man.valueMan.get_ask("VALBZ")
FEE = 10
if (valePos * valeBid + FEE <
valbzPos * valeBid):
if ((valeBid > 0 and valbzBid) and
valePos * valeBid + FEE <
valbzPos * valbzBid):
man.orderMan.convert("VALE", Dir.SELL, valePos)
if (valePos * valeBid >
valbzPos * valeBid + FEE):
if ((valeBid > 0 and valbzBid) and
valePos * valeBid >
valbzPos * valbzBid + FEE):
man.orderMan.convert("VALBZ", Dir.SELL, valbzPos)
elif message["symbol"] == "MS" or message["symbol"] == "GS":
now = time.time()
if now >= last_print_time+1:
last_print_time = now
if (man.valueMan.get_bid(message["symbol"])+10 < man.valueMan.get_ask(message["symbol"])):
man.orderMan.buy(message["symbol"], man.valueMan.get_bid(message["symbol"])-1, 10)
else:
man.orderMan.sell(message["symbol"], man.valueMan.get_ask(message["symbol"])+1, 10)
elif message["symbol"] == "WLF":
now = time.time()
if now >= last_print_time+1:
last_print_time = now
if (man.positionMan.get_position("BOND") > 3 and
man.positionMan.get_position("GS") > 2 and
man.positionMan.get_position("MS") > 3 and
man.positionMan.get_position("WFC") > 2):

View File

@@ -1,5 +1,5 @@
from ETC import ExchangeConnection
from order_new import OrderManager
from order import OrderManager
from position import PositionManager
from value import ValueManager

View File

@@ -16,34 +16,34 @@ class OrderManager:
self._order_size += 1
return self._order_size
def _attempt_send(self, action_type: str, symbol: str, dir: Dir, price: int, size: int) -> bool:
def _attempt_send(self, action_type: str, order_id: int, 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 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)
self.exchange.send_add_message(symbol=symbol, order_id=order_id, dir=dir, price=price, size=size)
elif action_type == "cancel":
self.exchange.send_cancel_message(symbol=symbol, dir=dir, price=price, size=size)
self.exchange.send_cancel_message(order_id=order_id)
elif action_type == "convert":
self.exchange.send_convert_message(symbol=symbol, dir=dir, price=price, size=size)
self.exchange.send_convert_message(symbol=symbol, order_id=order_id, dir=dir, 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 sell(self, symbol: str, price: int, size: int):
return self._attempt_send("add", order_id=self._next_order(), symbol=symbol, dir=Dir.SELL, 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 buy(self, symbol: str, price: int, size: int):
return self._attempt_send("add", order_id=self._next_order(), symbol=symbol, dir=Dir.BUY, 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)

View File

@@ -10,9 +10,16 @@ class ValueManager:
"WFC": [0, 0],
"XLF": [0, 0]
}
def set_value(self, symbol: str, bid: int, ask: int):
self.value[symbol] = [bid, ask]
self.set_bid(symbol, bid)
self.set_ask(symbol, ask)
def set_bid(self, symbol: str, bid: int):
self.value[symbol][0] = bid
def set_ask(self, symbol: str, ask: int):
self.value[symbol][1] = ask
def get_bid(self, symbol: str):
return self.value[symbol][0]

20
bot.py
View File

@@ -39,8 +39,19 @@ def main():
hello_message = exchange.read_message()
print("First message from exchange:", hello_message)
orderman.sell("BOND", 1001, 99)
orderman.buy("BOND", 999, 99)
if "symbols" in hello_message:
for sym_data in hello_message["symbols"]:
sym = sym_data["symbol"]
pos = sym_data["position"]
if sym in orderman.positions:
orderman.positions[sym] = pos
orderman.future_positions[sym] = pos
bond_pos = orderman.positions["BOND"]
if bond_pos > 0:
orderman.sell("BOND", 1001, bond_pos)
elif bond_pos < 0:
orderman.buy("BOND", 999, bond_pos)
while True:
message = exchange.read_message()
@@ -86,8 +97,9 @@ def on_trade(message: dict, orderman: OrderManager, state: StateManager):
price = message["price"]
state.set_last_price(symbol, price)
execute_arb(orderman, state)
if orderman.positions["VALE"] - orderman.future_positions["VALE"] == 0 and orderman.positions["VALBZ"] - orderman.future_positions["VALBZ"] == 0:
execute_arb(orderman, state)
def execute_arb(orderman: OrderManager, state: StateManager):

433
bot_new.py Normal file
View File

@@ -0,0 +1,433 @@
#!/usr/bin/env python3
import argparse
from collections import deque
from enum import Enum
import time
import socket
import json
from itertools import chain
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):
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):
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):
self._write_message(
{
"type": "convert",
"order_id": order_id,
"symbol": symbol,
"dir": dir,
"size": size,
}
)
def send_cancel_message(self, order_id: int):
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:
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.")
class Order:
def __init__(self, symbol, size, price, dir):
self.symbol = symbol
self.size = size
self.price = price
self.dir = dir
class StateManager:
def __init__(self, exchange):
self.exchange = exchange
self.order_id_counter = -1
self.positions_by_symbol = {}
self.unacked_orders = {}
self.open_orders = {}
self.pending_cancels = set()
self.average_vale_price = []
self.average_valbz_price = []
self.average_xlf_price = []
self.CONVERSION_FEE = 10
self.ETF_COMPONENTS = {"XLF": {"BOND": 3, "GS": 2, "MS": 3, "WFC": 2}}
self.ETF_SHARES = {"XLF": 10}
self.POSITION_LIMIT = {
"BOND": 100,
"VALBZ": 10,
"VALE": 10,
"GS": 100,
"MS": 100,
"WFC": 100,
"XLF": 100,
}
self.last_strategy_time = 0
self.strategy_interval = 0.1
self.ema_fast_period = 5
self.ema_slow_period = 20
self.ema_prices = {sym: [] for sym in ["VALBZ", "VALE", "XLF"]}
self.ema_fast = {sym: None for sym in ["VALBZ", "VALE", "XLF"]}
self.ema_slow = {sym: None for sym in ["VALBZ", "VALE", "XLF"]}
self.prev_ema_fast = {sym: None for sym in ["VALBZ", "VALE", "XLF"]}
self.position_signal = {sym: 0 for sym in ["VALBZ", "VALE", "XLF"]}
self.bid_prices = {sym: None for sym in ["BOND", "VALBZ", "VALE", "GS", "MS", "WFC", "XLF"]}
self.ask_prices = {sym: None for sym in ["BOND", "VALBZ", "VALE", "GS", "MS", "WFC", "XLF"]}
self.bid_depths = {sym: 0 for sym in ["BOND", "VALBZ", "VALE", "GS", "MS", "WFC", "XLF"]}
self.ask_depths = {sym: 0 for sym in ["BOND", "VALBZ", "VALE", "GS", "MS", "WFC", "XLF"]}
def position_for_symbol(self, symbol):
return self.positions_by_symbol.get(symbol, 0)
def next_order_id(self):
self.order_id_counter += 1
return self.order_id_counter
def on_ack(self, message):
order_id = message["order_id"]
if order_id in self.unacked_orders:
self.open_orders[order_id] = self.unacked_orders.pop(order_id)
def on_fill(self, message):
order_id = message["order_id"]
symbol = message["symbol"]
dir = message["dir"]
raw_size = message["size"]
size_multiplier = 1 if dir == Dir.BUY.value else -1
size = raw_size * size_multiplier
if order_id in self.open_orders:
self.open_orders[order_id].size -= raw_size
self.positions_by_symbol[symbol] = self.positions_by_symbol.get(symbol, 0) + size
def on_out(self, message):
order_id = int(message["order_id"])
if order_id in self.open_orders:
del self.open_orders[order_id]
self.pending_cancels.discard(order_id)
def on_hello(self, message):
symbol_positions = message["symbols"]
for symbol_position in symbol_positions:
symbol = symbol_position["symbol"]
position = symbol_position["position"]
self.positions_by_symbol[symbol] = position
def on_reject(self, message):
order_id = message["order_id"]
if order_id in self.unacked_orders:
del self.unacked_orders[order_id]
def send_order(self, symbol, dir, price, size):
order_id = self.next_order_id()
order = Order(symbol, size, price, Dir(dir))
self.unacked_orders[order_id] = order
self.exchange.send_add_message(order_id, symbol, dir, price, size)
return order_id
def cancel_order(self, order_id):
self.pending_cancels.add(order_id)
self.exchange.send_cancel_message(order_id)
def open_and_pending_orders_in_symbol_and_direction_by_price_level(self, symbol, dir):
output = {}
for order_id, order in chain(self.open_orders.items(), self.unacked_orders.items()):
if order.symbol == symbol and order.dir == dir and order_id not in self.pending_cancels:
price_level = order.price
if price_level not in output:
output[price_level] = {}
output[price_level][order_id] = order
return output
def set_orders_in_symbol_for_direction(self, symbol, dir, size_by_price_level):
current_orders = self.open_and_pending_orders_in_symbol_and_direction_by_price_level(symbol, dir)
for price_level in set(size_by_price_level.keys()) | set(current_orders.keys()):
current_orders_by_order_id = current_orders.get(price_level, {})
current_size_at_price_level = sum(order.size for order in current_orders_by_order_id.values())
desired_size_for_price_level = size_by_price_level.get(price_level, 0)
if current_size_at_price_level == desired_size_for_price_level:
pass
elif current_size_at_price_level < desired_size_for_price_level:
self.send_order(symbol, dir, price_level, desired_size_for_price_level - current_size_at_price_level)
else:
for order_id in current_orders_by_order_id:
if order_id not in self.pending_cancels:
self.cancel_order(order_id)
if desired_size_for_price_level != 0:
self.send_order(symbol, dir, price_level, desired_size_for_price_level)
def get_average_price(self, average_list):
if not average_list:
return 0
return int(sum(average_list) / len(average_list))
def convert(self, symbol, dir, size):
order_id = self.next_order_id()
self.exchange.send_convert_message(order_id, symbol, dir, size)
def calculate_ema(self, prices, period):
if len(prices) < period:
return None
alpha = 2 / (period + 1)
ema = prices[0]
for price in prices[1:]:
ema = alpha * price + (1 - alpha) * ema
return ema
def update_ema_prices(self, trade_message):
symbol = trade_message["symbol"]
price = trade_message["price"]
if symbol not in ["VALBZ", "VALE", "XLF"]:
return
self.ema_prices[symbol].append(price)
max_len = self.ema_slow_period
if len(self.ema_prices[symbol]) > max_len:
self.ema_prices[symbol].pop(0)
self.prev_ema_fast[symbol] = self.ema_fast[symbol]
self.ema_fast[symbol] = self.calculate_ema(self.ema_prices[symbol], self.ema_fast_period)
self.ema_slow[symbol] = self.calculate_ema(self.ema_prices[symbol], self.ema_slow_period)
def get_cross_signal(self, symbol):
if self.ema_fast[symbol] is None or self.ema_slow[symbol] is None:
return 0
if self.prev_ema_fast[symbol] is None:
return 0
prev_fast = self.prev_ema_fast[symbol]
prev_slow = self.ema_slow[symbol]
curr_fast = self.ema_fast[symbol]
curr_slow = self.ema_slow[symbol]
if prev_fast <= prev_slow and curr_fast > curr_slow:
return 1
elif prev_fast >= prev_slow and curr_fast < curr_slow:
return -1
return 0
def update_book(self, book_message):
symbol = book_message["symbol"]
if symbol not in self.bid_prices:
return
buy = book_message.get("buy", [])
sell = book_message.get("sell", [])
if buy:
self.bid_prices[symbol] = buy[0][0]
self.bid_depths[symbol] = sum(depth for _, depth in buy[:3])
if sell:
self.ask_prices[symbol] = sell[0][0]
self.ask_depths[symbol] = sum(depth for _, depth in sell[:3])
def get_book_imbalance(self, symbol):
bid = self.bid_depths.get(symbol, 0)
ask = self.ask_depths.get(symbol, 0)
if bid + ask == 0:
return 0
return (bid - ask) / (bid + ask)
def get_fair_value_from_book(self, symbol):
bid = self.bid_prices.get(symbol)
ask = self.ask_prices.get(symbol)
if bid is not None and ask is not None:
return (bid + ask) // 2
return None
def can_add_position(self, symbol, size, is_buy):
current = self.position_for_symbol(symbol)
if is_buy:
return current + size <= self.POSITION_LIMIT.get(symbol, 100)
else:
return current - size >= -self.POSITION_LIMIT.get(symbol, 100)
def execute_strategies(self):
now = time.time()
if now - self.last_strategy_time < self.strategy_interval:
return
self.last_strategy_time = now
for sym in ["VALBZ", "VALE", "XLF"]:
ema_signal = self.get_cross_signal(sym)
imbalance = self.get_book_imbalance(sym)
fair = self.get_fair_value_from_book(sym)
if fair is None:
continue
signal_strength = abs(ema_signal) + (1 if abs(imbalance) > 0.3 else 0)
if signal_strength == 0:
continue
if ema_signal == 1 and self.position_signal[sym] <= 0:
size = 1 if self.can_add_position(sym, 1, True) else 0
if size > 0:
price = fair + 1
self.set_orders_in_symbol_for_direction(sym, "BUY", {price: size})
self.position_signal[sym] = 1
elif ema_signal == -1 and self.position_signal[sym] >= 0:
size = 1 if self.can_add_position(sym, 1, False) else 0
if size > 0:
price = fair - 1
self.set_orders_in_symbol_for_direction(sym, "SELL", {price: size})
self.position_signal[sym] = -1
self.execute_arb()
def execute_arb(self):
vale_pos = self.position_for_symbol("VALE")
valbz_pos = self.position_for_symbol("VALBZ")
xlf_pos = self.position_for_symbol("XLF")
if len(self.average_valbz_price) >= 5 and len(self.average_vale_price) >= 5:
avg_valbz = self.get_average_price(self.average_valbz_price)
avg_vale = self.get_average_price(self.average_vale_price)
spread = avg_valbz - avg_vale
if spread > self.CONVERSION_FEE:
if self.can_add_position("VALE", 1, True) and self.can_add_position("VALBZ", 1, False):
self.send_order("VALE", "BUY", avg_vale, 1)
self.convert("VALE", Dir.SELL, 1)
self.send_order("VALBZ", "SELL", avg_valbz, 1)
elif spread < -self.CONVERSION_FEE:
if self.can_add_position("VALE", 1, False) and self.can_add_position("VALBZ", 1, True):
self.send_order("VALE", "SELL", avg_vale, 1)
self.convert("VALE", Dir.BUY, 1)
self.send_order("VALBZ", "BUY", avg_valbz, 1)
def on_startup(self):
bond_pos = self.position_for_symbol("BOND")
if bond_pos > 0:
self.send_order("BOND", "SELL", 1001, min(bond_pos, self.POSITION_LIMIT["BOND"]))
elif bond_pos < 0:
self.send_order("BOND", "BUY", 999, min(-bond_pos, self.POSITION_LIMIT["BOND"]))
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.")
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
def main():
args = parse_arguments()
exchange = ExchangeConnection(args=args)
state_manager = StateManager(exchange)
hello_message = exchange.read_message()
print("First message from exchange:", hello_message)
state_manager.on_hello(hello_message)
state_manager.on_startup()
while True:
message = exchange.read_message()
if message["type"] == "close":
print("The round has ended")
break
elif message["type"] == "error":
print(message)
elif message["type"] == "reject":
print(message)
state_manager.on_reject(message)
elif message["type"] == "fill":
print(message)
state_manager.on_fill(message)
elif message["type"] == "trade":
state_manager.update_ema_prices(message)
state_manager.execute_strategies()
elif message["type"] == "ack":
state_manager.on_ack(message)
elif message["type"] == "out":
state_manager.on_out(message)
elif message["type"] == "book":
state_manager.update_book(message)
if __name__ == "__main__":
assert team_name != "REPLACEME", "Please put your team name in the variable [team_name]."
main()

918
bot_x.py Normal file
View File

@@ -0,0 +1,918 @@
#!/usr/bin/env python3
import argparse
from collections import deque
from enum import Enum
import time
import socket
import json
from state import StateManager
from order import OrderManager
class PriceHistory:
def __init__(self, maxlen=100):
self.prices = deque(maxlen=maxlen)
def add(self, price):
if price is not None:
self.prices.append(price)
def get_all(self):
return list(self.prices)
def __len__(self):
return len(self.prices)
class TechnicalAnalyzer:
@staticmethod
def calculate_ema(prices, period):
if len(prices) < period:
return None
alpha = 2 / (period + 1)
ema = prices[0]
for price in prices[1:]:
ema = alpha * price + (1 - alpha) * ema
return ema
@staticmethod
def calculate_rsi(prices, period=14):
if len(prices) < period + 1:
return None
gains = []
losses = []
for i in range(1, len(prices)):
diff = prices[i] - prices[i - 1]
if diff > 0:
gains.append(diff)
losses.append(0)
else:
gains.append(0)
losses.append(abs(diff))
avg_gain = sum(gains[-period:]) / period
avg_loss = sum(losses[-period:]) / period
if avg_loss == 0:
return 100
rs = avg_gain / avg_loss
return 100 - (100 / (1 + rs))
@staticmethod
def calculate_sma(prices, period):
if len(prices) < period:
return None
return sum(prices[-period:]) / period
@staticmethod
def calculate_std(prices, period):
if len(prices) < period:
return None
sma = TechnicalAnalyzer.calculate_sma(prices, period)
variance = sum((p - sma) ** 2 for p in prices[-period:]) / period
return variance ** 0.5
@staticmethod
def calculate_zscore(prices, period=20):
if len(prices) < period:
return None
sma = TechnicalAnalyzer.calculate_sma(prices, period)
std = TechnicalAnalyzer.calculate_std(prices, period)
if std is None or std == 0:
return None
return (prices[-1] - sma) / std
@staticmethod
def calculate_vwap(prices, volumes=None):
if not prices:
return None
if volumes is None:
volumes = [1] * len(prices)
total_pv = sum(p * v for p, v in zip(prices, volumes))
return total_pv / sum(volumes)
class CrossEMAStrategy:
def __init__(self, fast_period=10, slow_period=30, rsi_period=14,
oversold=30, overbought=70, size=5):
self.fast_period = fast_period
self.slow_period = slow_period
self.rsi_period = rsi_period
self.oversold = oversold
self.overbought = overbought
self.size = size
self.price_histories = {}
self.last_signal = {}
def register_symbol(self, symbol):
if symbol not in self.price_histories:
maxlen = max(self.slow_period, self.rsi_period) + 10
self.price_histories[symbol] = PriceHistory(maxlen=maxlen)
self.last_signal[symbol] = None
def update(self, symbol, price):
self.register_symbol(symbol)
self.price_histories[symbol].add(price)
def get_signal(self, symbol):
history = self.price_histories[symbol]
prices = history.get_all()
if len(prices) < self.slow_period:
return None, None, None
fast_ema = TechnicalAnalyzer.calculate_ema(prices, self.fast_period)
slow_ema = TechnicalAnalyzer.calculate_ema(prices, self.slow_period)
rsi = TechnicalAnalyzer.calculate_rsi(prices, self.rsi_period)
if fast_ema is None or slow_ema is None:
return None, None, None
prev_fast = TechnicalAnalyzer.calculate_ema(prices[:-1], self.fast_period)
prev_slow = TechnicalAnalyzer.calculate_ema(prices[:-1], self.slow_period)
if prev_fast is None or prev_slow is None:
return None, None, None
signal = None
if prev_fast <= prev_slow and fast_ema > slow_ema:
signal = "LONG"
elif prev_fast >= prev_slow and fast_ema < slow_ema:
signal = "SHORT"
return signal, rsi, (fast_ema, slow_ema)
def should_trade(self, symbol, rsi):
if rsi is None:
return True
if self.last_signal.get(symbol) == "LONG" and rsi > self.overbought:
return True
if self.last_signal.get(symbol) == "SHORT" and rsi < self.oversold:
return True
return False
class MeanReversionStrategy:
def __init__(self, lookback_period=20, zscore_threshold=2.0,
size=5, exit_threshold=0.5):
self.lookback_period = lookback_period
self.zscore_threshold = zscore_threshold
self.size = size
self.exit_threshold = exit_threshold
self.price_histories = {}
self.position = {}
self.entry_zscore = {}
def register_symbol(self, symbol):
if symbol not in self.price_histories:
maxlen = self.lookback_period + 10
self.price_histories[symbol] = PriceHistory(maxlen=maxlen)
self.position[symbol] = None
self.entry_zscore[symbol] = None
def update(self, symbol, price):
self.register_symbol(symbol)
self.price_histories[symbol].add(price)
def get_signal(self, symbol):
history = self.price_histories[symbol]
prices = history.get_all()
if len(prices) < self.lookback_period:
return None, None
zscore = TechnicalAnalyzer.calculate_zscore(prices, self.lookback_period)
if zscore is None:
return None, None
current_pos = self.position[symbol]
if current_pos is None:
if zscore > self.zscore_threshold:
return "SHORT", zscore
elif zscore < -self.zscore_threshold:
return "LONG", zscore
else:
if current_pos == "LONG" and zscore >= -self.exit_threshold:
return "CLOSE_LONG", zscore
elif current_pos == "SHORT" and zscore <= self.exit_threshold:
return "CLOSE_SHORT", zscore
return None, zscore
def set_position(self, symbol, pos):
self.position[symbol] = pos
team_name = "HanyangFloorFunction"
class OrderRateLimiter:
def __init__(self, max_per_second=500):
self.max_per_second = max_per_second
self.timestamps = deque(maxlen=max_per_second + 100)
def can_send(self):
now = time.time()
self.timestamps.append(now)
recent = [t for t in self.timestamps if now - t < 1.0]
if len(recent) >= self.max_per_second:
return False
return True
def reset_if_needed(self):
now = time.time()
while self.timestamps and now - self.timestamps[0] >= 1.0:
self.timestamps.popleft()
def main():
args = parse_arguments()
exchange = ExchangeConnection(args=args)
hello_message = exchange.read_message()
print("First message from exchange:", hello_message)
BOND_FAIR_VALUE = 1000
BOND_ORDER_SIZE = 50
XLF_CONVERSION_FEE = 100
VALE_CONVERSION_FEE = 10
VALE_ARB_SIZE = 10
REFRESH_INTERVAL = 5.0
FAST_EMA_PERIOD = 10
SLOW_EMA_PERIOD = 30
EMA_SIZE = 5
MEAN_REV_LOOKBACK = 20
MEAN_REV_ZSCORE_THRESH = 2.0
MEAN_REV_SIZE = 5
MEAN_REV_EXIT_THRESH = 0.5
xlf_state = "IDLE"
xlf_pending = {}
xlf_direction = None
xlf_arb_size = 0
vale_state = "IDLE"
vale_pending = {}
vale_direction = None
vale_arb_size = 0
state = StateManager()
om = OrderManager(exchange)
rate_limiter = OrderRateLimiter(max_per_second=500)
market_open = False
active_orders = {}
last_refresh = time.time()
cross_ema = CrossEMAStrategy(
fast_period=FAST_EMA_PERIOD,
slow_period=SLOW_EMA_PERIOD,
size=EMA_SIZE
)
mean_rev = MeanReversionStrategy(
lookback_period=MEAN_REV_LOOKBACK,
zscore_threshold=MEAN_REV_ZSCORE_THRESH,
size=MEAN_REV_SIZE,
exit_threshold=MEAN_REV_EXIT_THRESH
)
symbols_for_ema = ["VALE", "VALBZ", "GS", "MS", "WFC", "XLF"]
symbols_for_mr = ["VALE", "VALBZ", "GS", "MS", "WFC", "XLF"]
for sym in symbols_for_ema:
cross_ema.register_symbol(sym)
mean_rev.register_symbol(sym)
active_ema_orders = {}
active_mr_orders = {}
def next_id():
return om.next_order()
def cancel_all_bond_orders():
for oid in list(active_orders.keys()):
om.cancel(oid)
active_orders.pop(oid, None)
def cancel_ema_orders_for_symbol(symbol):
to_cancel = [oid for oid, info in active_ema_orders.items() if info["symbol"] == symbol]
for oid in to_cancel:
om.cancel(oid)
active_ema_orders.pop(oid, None)
def cancel_mr_orders_for_symbol(symbol):
to_cancel = [oid for oid, info in active_mr_orders.items() if info["symbol"] == symbol]
for oid in to_cancel:
om.cancel(oid)
active_mr_orders.pop(oid, None)
def place_bond_orders():
if not market_open:
return
cancel_all_bond_orders()
buy_price = BOND_FAIR_VALUE - 1
sell_price = BOND_FAIR_VALUE + 1
position = om.positions["BOND"]
base_size = BOND_ORDER_SIZE
adjustment = abs(position) // 5
if position < 0:
buy_size = min(base_size + adjustment, 100 - position)
sell_size = max(base_size - adjustment, 1)
elif position > 0:
buy_size = max(base_size - adjustment, 1)
sell_size = min(base_size + adjustment, 100 + position)
else:
buy_size = base_size
sell_size = base_size
buy_size = max(0, min(buy_size, 100 - position))
sell_size = max(0, min(sell_size, 100 + position))
if buy_size > 0:
bid = next_id()
exchange.send_add_message(
order_id=bid, symbol="BOND",
dir=Dir.BUY, price=buy_price, size=buy_size
)
active_orders[bid] = {"dir": Dir.BUY, "price": buy_price}
if sell_size > 0:
ask = next_id()
exchange.send_add_message(
order_id=ask, symbol="BOND",
dir=Dir.SELL, price=sell_price, size=sell_size
)
active_orders[ask] = {"dir": Dir.SELL, "price": sell_price}
print(f" BOND 주문 → 매수:{buy_price} x{buy_size}, 매도:{sell_price} x{sell_size}, 포지션:{position}")
def try_xlf_arb():
nonlocal xlf_state, xlf_direction, xlf_pending, xlf_arb_size
if not market_open or xlf_state != "IDLE":
return
if any(mean_rev.position.get(s) is not None for s in symbols_for_mr):
return
if any(cross_ema.last_signal.get(s) is not None for s in symbols_for_ema):
return
rate_limiter.reset_if_needed()
if not rate_limiter.can_send():
return
bond_ask = state.ask_prices["BOND"]
gs_ask = state.ask_prices["GS"]
ms_ask = state.ask_prices["MS"]
wfc_ask = state.ask_prices["WFC"]
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,
bond_bid, gs_bid, ms_bid, wfc_bid, xlf_ask]:
return
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
profit1 = xlf_bid * 10 - basket_ask - XLF_CONVERSION_FEE
if profit1 > 0 and om.check_pos_limit("XLF"):
print(f" XLF 차익(바스켓→XLF) 시작, 예상수익:{profit1}")
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
profit2 = basket_bid - xlf_ask * 10 - XLF_CONVERSION_FEE
if profit2 > 0 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
def handle_xlf_fill(order_id, symbol, dir_, qty):
nonlocal xlf_state, xlf_pending
if order_id not in xlf_pending:
return
xlf_pending[order_id] -= qty
if xlf_pending[order_id] <= 0:
del xlf_pending[order_id]
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():
nonlocal vale_state, vale_direction, vale_pending, vale_arb_size
if not market_open or vale_state != "IDLE":
return
if any(mean_rev.position.get(s) is not None for s in symbols_for_mr):
return
if any(cross_ema.last_signal.get(s) is not None for s in symbols_for_ema):
return
rate_limiter.reset_if_needed()
if not rate_limiter.can_send():
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"]
profit1 = vale_bid - valbz_ask - VALE_CONVERSION_FEE
arb_size1 = min(VALE_ARB_SIZE, 10 - valbz_pos)
if profit1 > 0 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
profit2 = valbz_bid - vale_ask - VALE_CONVERSION_FEE
arb_size2 = min(VALE_ARB_SIZE, 10 - vale_pos)
if profit2 > 0 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):
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)
def try_ema_trade():
if not market_open:
return
if xlf_state != "IDLE" or vale_state != "IDLE":
return
if any(mean_rev.position.get(s) is not None for s in symbols_for_mr):
return
rate_limiter.reset_if_needed()
if not rate_limiter.can_send():
return
for symbol in symbols_for_ema:
mid_price = state.get_mid_price(symbol)
if mid_price is None:
continue
cross_ema.update(symbol, mid_price)
signal, rsi, ema_values = cross_ema.get_signal(symbol)
if signal is None:
continue
current_pos = om.positions[symbol]
limit = om.POSITIONS_LIMIT.get(symbol, 100)
remaining_capacity = limit - current_pos if signal == "LONG" else limit + current_pos
if remaining_capacity <= 0:
continue
size = min(cross_ema.size, remaining_capacity)
cancel_ema_orders_for_symbol(symbol)
if signal == "LONG":
bid_price = state.bid_prices[symbol]
if bid_price is not None and om.check_pos_limit(symbol):
oid = next_id()
exchange.send_add_message(oid, symbol, Dir.BUY, bid_price + 1, size)
active_ema_orders[oid] = {"symbol": symbol, "dir": "LONG"}
cross_ema.last_signal[symbol] = "LONG"
print(f" CrossEMA: {symbol} LONG (bid:{bid_price}, rsi:{rsi:.1f}, ema:{ema_values})")
elif signal == "SHORT":
ask_price = state.ask_prices[symbol]
if ask_price is not None and om.check_pos_limit(symbol):
oid = next_id()
exchange.send_add_message(oid, symbol, Dir.SELL, ask_price - 1, size)
active_ema_orders[oid] = {"symbol": symbol, "dir": "SHORT"}
cross_ema.last_signal[symbol] = "SHORT"
print(f" CrossEMA: {symbol} SHORT (ask:{ask_price}, rsi:{rsi:.1f}, ema:{ema_values})")
def try_mean_reversion_trade():
if not market_open:
return
if xlf_state != "IDLE" or vale_state != "IDLE":
return
rate_limiter.reset_if_needed()
if not rate_limiter.can_send():
return
for symbol in symbols_for_mr:
mid_price = state.get_mid_price(symbol)
if mid_price is None:
continue
mean_rev.update(symbol, mid_price)
signal, zscore = mean_rev.get_signal(symbol)
if signal is None:
continue
current_pos = om.positions[symbol]
limit = om.POSITIONS_LIMIT.get(symbol, 100)
if signal == "LONG":
remaining_capacity = limit - current_pos
if remaining_capacity <= 0:
continue
size = min(mean_rev.size, remaining_capacity)
cancel_mr_orders_for_symbol(symbol)
bid_price = state.bid_prices[symbol]
if bid_price is not None and om.check_pos_limit(symbol):
oid = next_id()
exchange.send_add_message(oid, symbol, Dir.BUY, bid_price + 1, size)
active_mr_orders[oid] = {"symbol": symbol, "dir": "LONG"}
mean_rev.set_position(symbol, "LONG")
mean_rev.entry_zscore[symbol] = zscore
print(f" MeanRev: {symbol} LONG (zscore:{zscore:.2f})")
elif signal == "SHORT":
remaining_capacity = limit + current_pos
if remaining_capacity <= 0:
continue
size = min(mean_rev.size, remaining_capacity)
cancel_mr_orders_for_symbol(symbol)
ask_price = state.ask_prices[symbol]
if ask_price is not None and om.check_pos_limit(symbol):
oid = next_id()
exchange.send_add_message(oid, symbol, Dir.SELL, ask_price - 1, size)
active_mr_orders[oid] = {"symbol": symbol, "dir": "SHORT"}
mean_rev.set_position(symbol, "SHORT")
mean_rev.entry_zscore[symbol] = zscore
print(f" MeanRev: {symbol} SHORT (zscore:{zscore:.2f})")
elif signal == "CLOSE_LONG":
if current_pos <= 0:
mean_rev.set_position(symbol, None)
continue
size = min(current_pos, mean_rev.size)
cancel_mr_orders_for_symbol(symbol)
ask_price = state.ask_prices[symbol]
if ask_price is not None:
oid = next_id()
exchange.send_add_message(oid, symbol, Dir.SELL, ask_price - 1, size)
active_mr_orders[oid] = {"symbol": symbol, "dir": "CLOSE_LONG"}
print(f" MeanRev: {symbol} CLOSE_LONG (zscore:{zscore:.2f})")
elif signal == "CLOSE_SHORT":
if current_pos >= 0:
mean_rev.set_position(symbol, None)
continue
size = min(abs(current_pos), mean_rev.size)
cancel_mr_orders_for_symbol(symbol)
bid_price = state.bid_prices[symbol]
if bid_price is not None:
oid = next_id()
exchange.send_add_message(oid, symbol, Dir.BUY, bid_price + 1, size)
active_mr_orders[oid] = {"symbol": symbol, "dir": "CLOSE_SHORT"}
print(f" MeanRev: {symbol} CLOSE_SHORT (zscore:{zscore:.2f})")
vale_last_print_time = time.time()
while True:
message = exchange.read_message()
if message["type"] == "close":
print("The round has ended")
break
elif message["type"] == "open":
print("Market opened:", message)
market_open = True
place_bond_orders()
elif message["type"] == "error":
print(message)
elif message["type"] == "reject":
print(message)
oid = message.get("order_id")
active_orders.pop(oid, None)
active_ema_orders.pop(oid, None)
active_mr_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
elif message["type"] == "ack":
if xlf_state == "CONVERTING":
print(" XLF 변환 완료 → 매도 시작")
if xlf_direction == "BASKET_TO_XLF":
om.positions["BOND"] -= 3
om.positions["GS"] -= 2
om.positions["MS"] -= 3
om.positions["WFC"] -= 2
om.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":
om.positions["XLF"] -= xlf_arb_size
om.positions["BOND"] += 3
om.positions["GS"] += 2
om.positions["MS"] += 3
om.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
elif vale_state == "CONVERTING":
print(" VALE 변환 완료 → 매도 시작")
if vale_direction == "VALBZ_TO_VALE":
om.positions["VALBZ"] -= vale_arb_size
om.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":
om.positions["VALE"] -= vale_arb_size
om.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
elif message["type"] == "fill":
print(message)
qty = message["size"]
sym = message["symbol"]
dir_ = message["dir"]
oid = message["order_id"]
if dir_ == Dir.BUY:
om.update_position(sym, oid, qty)
else:
om.update_position(sym, oid, -qty)
print(f" 포지션 → {om.positions}")
if sym == "BOND" and oid in active_orders:
active_orders.pop(oid, None)
place_bond_orders()
if oid in active_ema_orders:
active_ema_orders.pop(oid, None)
if oid in active_mr_orders:
info = active_mr_orders.pop(oid)
if info["dir"] in ("CLOSE_LONG", "CLOSE_SHORT"):
mean_rev.set_position(sym, None)
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
place_bond_orders()
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"],
})
if sym in ["BOND", "GS", "MS", "WFC", "XLF"]:
try_xlf_arb()
if sym in ["VALE", "VALBZ"]:
try_vale_arb()
if sym in symbols_for_ema:
try_mean_reversion_trade()
if sym in symbols_for_ema:
try_ema_trade()
now = time.time()
if now - last_refresh > REFRESH_INTERVAL:
last_refresh = now
place_bond_orders()
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):
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
):
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):
self._write_message(
{
"type": "convert",
"order_id": order_id,
"symbol": symbol,
"dir": dir,
"size": size,
}
)
def send_cancel_message(self, order_id: int):
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:
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.",
)
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__":
assert team_name != "REPLAC" + "EME", (
"Please put your team name in the variable [team_name]."
)
main()

1008
bot_x2.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +1,204 @@
#!/usr/bin/env python3
import argparse, socket, json
import argparse
from collections import deque
from enum import Enum
import time
import socket
import json
from state import StateManager
from order import OrderManager
team_name = "HanyangFloorFunction"
MAX_ARBS = 5
XLF_SIZE = 50
VALE_SIZE = 50
def main():
args = parse_arguments()
exchange = ExchangeConnection(args=args)
hello_message = exchange.read_message()
print("First message:", hello_message)
state = StateManager()
om = OrderManager(exchange)
market_open = False
xlf_pending = {}
def next_id():
return om.next_order()
# ---------------- BOND ----------------
def place_bond_orders():
if not market_open:
return
bid = state.bid_prices["BOND"]
ask = state.ask_prices["BOND"]
if bid is None or ask is None:
return
buy_price = bid + 1
sell_price = ask - 1
size = 100
exchange.send_add_message(next_id(), "BOND", Dir.BUY, buy_price, size)
exchange.send_add_message(next_id(), "BOND", Dir.SELL, sell_price, size)
# ---------------- XLF ----------------
def try_xlf_arb():
if not market_open:
return
if len(xlf_pending) >= MAX_ARBS:
return
bond_ask = state.ask_prices["BOND"]
gs_ask = state.ask_prices["GS"]
ms_ask = state.ask_prices["MS"]
wfc_ask = state.ask_prices["WFC"]
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,
bond_bid, gs_bid, ms_bid, wfc_bid, xlf_ask]:
return
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
profit1 = xlf_bid * 10 - basket_ask - 100
profit2 = basket_bid - xlf_ask * 10 - 100
# 🔥 공격 (손해도 감수)
if profit1 > -50:
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
elif profit2 > -50:
oid = next_id()
exchange.send_add_message(oid, "XLF", Dir.BUY, xlf_ask, XLF_SIZE)
xlf_pending[oid] = XLF_SIZE
# ---------------- VALE ----------------
def try_vale_arb():
if not market_open:
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
profit1 = vale_bid - valbz_ask - 10
profit2 = valbz_bid - vale_ask - 10
if profit1 > -5:
exchange.send_add_message(next_id(), "VALBZ", Dir.BUY, valbz_ask, VALE_SIZE)
elif profit2 > -5:
exchange.send_add_message(next_id(), "VALE", Dir.BUY, vale_ask, VALE_SIZE)
# ---------------- MAIN LOOP ----------------
while True:
message = exchange.read_message()
if message["type"] == "close":
print("Round ended")
break
elif message["type"] == "open":
print("Market opened")
market_open = True
place_bond_orders()
elif message["type"] == "fill":
sym = message["symbol"]
qty = message["size"]
dir_ = message["dir"]
if dir_ == Dir.BUY:
om.update_position(sym, message["order_id"], qty)
else:
om.update_position(sym, message["order_id"], -qty)
print("Position:", om.positions)
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
)
# 핵심 실행
try_xlf_arb()
try_vale_arb()
place_bond_orders()
class Dir(str, Enum):
BUY = "BUY"
SELL = "SELL"
def main():
class ExchangeConnection:
def __init__(self, args):
self.exchange_hostname = args.exchange_hostname
self.port = args.port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self.exchange_hostname, self.port))
self.reader = sock.makefile("r", 1)
self.writer = sock
self._write({"type": "hello", "team": team_name})
def read_message(self):
msg = json.loads(self.reader.readline())
if "dir" in msg:
msg["dir"] = Dir(msg["dir"])
return msg
def send_add_message(self, order_id, symbol, dir, price, size):
self._write({
"type": "add",
"order_id": order_id,
"symbol": symbol,
"dir": dir,
"price": price,
"size": size,
"tif": "DAY"
})
def _write(self, msg):
self.writer.send((json.dumps(msg) + "\n").encode())
def parse_arguments():
parser = argparse.ArgumentParser()
parser.add_argument("--test", default="prod-like")
parser.add_argument("--test", required=True)
args = parser.parse_args()
args.exchange_hostname = "test-exch-" + team_name
args.port = 22000
return args
s = socket.socket()
s.connect(("test-exch-"+team_name, 22000))
r = s.makefile("r",1)
def send(m):
s.send((json.dumps(m)+"\n").encode())
send({"type":"hello","team":team_name.upper()})
st = StateManager()
hello = json.loads(r.readline())
pos = {}
for sym in hello["symbols"]:
pos[sym["symbol"]] = sym["position"]
oid = 0
def nid():
nonlocal oid; oid+=1; return oid
while True:
m = json.loads(r.readline())
if m["type"] == "close":
break
if m["type"] == "book":
sym = m["symbol"]
if not m["buy"] or not m["sell"]:
continue
bid = m["buy"][0][0]
ask = m["sell"][0][0]
# 🔥 spread 충분할 때만
if ask - bid >= 3:
send({"type":"add","order_id":nid(),"symbol":sym,
"dir":"BUY","price":bid+1,"size":1})
send({"type":"add","order_id":nid(),"symbol":sym,
"dir":"SELL","price":ask-1,"size":1})
if m["type"] == "fill":
sym = m["symbol"]
q = m["size"]
pos[sym] = pos.get(sym,0)
if m["dir"] == "BUY":
pos[sym] += q
else:
pos[sym] -= q
print("POS:", pos)
if __name__ == "__main__":
main()

228
prac copy.py Normal file
View File

@@ -0,0 +1,228 @@
#!/usr/bin/env python3
import argparse
from enum import Enum
import socket
import json
from state import StateManager
team_name = "HanyangFloorFunction"
# ==================== 설정 ====================
ORDER_SIZE = 2
MAX_POS = 20
ARB_THRESHOLD = 40
# ====================
class Dir(str, Enum):
BUY = "BUY"
SELL = "SELL"
# ====================
def main():
args = parse_arguments()
exchange = ExchangeConnection(args)
state = StateManager()
hello = exchange.read_message()
# 포지션 직접 관리
positions = {}
for sym in hello["symbols"]:
positions[sym["symbol"]] = sym["position"]
order_id = 0
def next_id():
nonlocal order_id
order_id += 1
return order_id
active_orders = {}
# ==================== MARKET MAKING ====================
def market_make(sym):
bid = state.bid_prices.get(sym)
ask = state.ask_prices.get(sym)
if bid is None or ask is None:
return
if ask - bid <= 2:
return
pos = positions.get(sym, 0)
if abs(pos) > MAX_POS:
return
buy_price = bid + 1
sell_price = ask - 1
if buy_price >= sell_price:
return
# 포지션 조절
if pos > 0:
buy_size = 1
sell_size = ORDER_SIZE + 1
elif pos < 0:
buy_size = ORDER_SIZE + 1
sell_size = 1
else:
buy_size = sell_size = ORDER_SIZE
oid = next_id()
exchange.send_add_message(oid, sym, Dir.BUY, buy_price, buy_size)
active_orders[oid] = sym
oid = next_id()
exchange.send_add_message(oid, sym, Dir.SELL, sell_price, sell_size)
active_orders[oid] = sym
# ==================== BOND 안전 수익 ====================
def bond_arb():
bid = state.bid_prices.get("BOND")
ask = state.ask_prices.get("BOND")
if ask and ask < 999:
exchange.send_add_message_ioc(next_id(), "BOND", Dir.BUY, ask, 3)
if bid and bid > 1001:
exchange.send_add_message_ioc(next_id(), "BOND", Dir.SELL, bid, 3)
# ==================== XLF 차익거래 (초안전 버전) ====================
def xlf_arb():
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")
gs_bid = state.bid_prices.get("GS")
ms_bid = state.bid_prices.get("MS")
wfc_bid = state.bid_prices.get("WFC")
xlf_ask = state.ask_prices.get("XLF")
if None in [bond_ask, gs_ask, ms_ask, wfc_ask, xlf_bid,
bond_bid, gs_bid, ms_bid, wfc_bid, xlf_ask]:
return
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
pos = positions.get("XLF", 0)
if abs(pos) > 10:
return
profit1 = xlf_bid * 10 - basket_ask
if profit1 > ARB_THRESHOLD:
exchange.send_add_message_ioc(next_id(), "XLF", Dir.SELL, xlf_bid, 10)
profit2 = basket_bid - xlf_ask * 10
if profit2 > ARB_THRESHOLD:
exchange.send_add_message_ioc(next_id(), "XLF", Dir.BUY, xlf_ask, 10)
# ==================== 리스크 관리 ====================
def risk():
for sym in ["GS", "MS", "WFC", "XLF"]:
pos = positions.get(sym, 0)
if abs(pos) > MAX_POS:
if pos > 0:
exchange.send_add_message_ioc(
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 ====================
while True:
msg = exchange.read_message()
if msg["type"] == "close":
break
elif msg["type"] == "book":
sym = msg["symbol"]
bid = msg["buy"][0][0] if msg["buy"] else None
ask = msg["sell"][0][0] if msg["sell"] else None
state.update_bid_ask_price(sym, bid, ask)
for oid in list(active_orders.keys()):
exchange.send_cancel_message(oid)
del active_orders[oid]
# 전략 실행
bond_arb()
xlf_arb()
if sym in ["GS", "MS", "WFC"]:
market_make(sym)
risk()
elif msg["type"] == "fill":
qty = msg["size"]
sym = msg["symbol"]
if msg["dir"] == Dir.BUY:
positions[sym] = positions.get(sym, 0) + qty
else:
positions[sym] = positions.get(sym, 0) - qty
# ====================
class ExchangeConnection:
def __init__(self, args):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((args.exchange_hostname, args.port))
self.reader = s.makefile("r", 1)
self.writer = s
self._write({"type": "hello", "team": team_name.upper()})
def read_message(self):
msg = json.loads(self.reader.readline())
if "dir" in msg:
msg["dir"] = Dir(msg["dir"])
return msg
def send_add_message(self, oid, sym, dir, price, size):
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"DAY"})
def send_add_message_ioc(self, oid, sym, dir, price, size):
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"IOC"})
def send_cancel_message(self, oid):
self._write({"type": "cancel", "order_id": oid})
def _write(self, msg):
self.writer.send((json.dumps(msg)+"\n").encode())
# ====================
def parse_arguments():
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--production", action="store_true")
group.add_argument("--test", type=str, default="prod-like")
args = parser.parse_args()
if args.production:
args.exchange_hostname = "production"
args.port = 25000
else:
args.exchange_hostname = "test-exch-" + team_name
args.port = 22000
return args
if __name__ == "__main__":
main()

723
prac.py
View File

@@ -1,108 +1,190 @@
#!/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
from order import OrderManager
# ~~~~~============== CONFIGURATION ==============~~~~~
# Replace "REPLACEME" with your team name!
team_name = "HanyangFloorFunction"
# ==================== 설정 ====================
ORDER_SIZE = 2
MAX_POS = 20
ARB_THRESHOLD = 40
# ~~~~~============== 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!
# ====================
class Dir(str, Enum):
BUY = "BUY"
SELL = "SELL"
# ====================
def main():
args = parse_arguments()
exchange = ExchangeConnection(args)
state = StateManager()
hello = exchange.read_message()
exchange = ExchangeConnection(args=args)
# 포지션 직접 관리
positions = {}
for sym in hello["symbols"]:
positions[sym["symbol"]] = sym["position"]
# 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.
# --- 설정 ---
BOND_FAIR_VALUE = 1000 # BOND fair value (고정)
BOND_ORDER_SIZE = 60 # BOND 주문당 수량
XLF_CONVERSION_FEE = 100 # XLF 변환 비용
XLF_MIN_PROFIT = 5 # XLF 차익거래 최소 수익
XLF_COOLDOWN = 2.0 # XLF 실패 후 재시도 대기 시간 (초)
VALE_CONVERSION_FEE = 10 # VALE 변환 비용
VALE_MIN_PROFIT = 1 # 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
xlf_convert_oid = None
xlf_last_fail = 0.0 # 마지막 XLF 실패 시간
# VALE state machine (XLF와 독립적으로 동시 실행 가능)
# 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
vale_convert_oid = None
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():
nonlocal order_id
order_id += 1
return order_id
return om.next_order()
active_orders = {}
def cancel_bond_sell_orders():
"""BOND 매도 주문만 취소 (basket→XLF 시 매수 주문은 유지)"""
for oid in list(active_orders.keys()):
order_info = active_orders[oid]
if order_info["dir"] == Dir.SELL:
size = order_info.get("size", 0)
om.future_positions["BOND"] += size
om.cancel(oid)
active_orders.pop(oid, None)
# ==================== MARKET MAKING ====================
def market_make(sym):
bid = state.bid_prices.get(sym)
ask = state.ask_prices.get(sym)
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)
if bid is None or ask is None:
def place_bond_orders():
"""포지션 한도 안에서 bid/ask 양방향 주문"""
if not market_open:
return
if ask - bid <= 2:
return
cancel_all_bond_orders()
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:
return
# 포지션에 따라 size 비대칭 조정
base_size = BOND_ORDER_SIZE
adjustment = abs(position) // 5
buy_price = bid + 1
sell_price = ask - 1
if buy_price >= sell_price:
return
# 포지션 조절
if pos > 0:
buy_size = 1
sell_size = ORDER_SIZE + 1
elif pos < 0:
buy_size = ORDER_SIZE + 1
sell_size = 1
if position < 0:
buy_size = min(base_size + adjustment, 100 - position)
sell_size = max(base_size - adjustment, 1)
elif position > 0:
buy_size = max(base_size - adjustment, 1)
sell_size = min(base_size + adjustment, 100 + position)
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)
active_orders[oid] = sym
# 포지션 한도 초과 방지
buy_size = max(0, min(buy_size, 100 - position))
sell_size = max(0, min(sell_size, 100 + position))
oid = next_id()
exchange.send_add_message(oid, sym, Dir.SELL, sell_price, sell_size)
active_orders[oid] = sym
if buy_size > 0:
bid = next_id()
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 안전 수익 ====================
def bond_arb():
bid = state.bid_prices.get("BOND")
ask = state.ask_prices.get("BOND")
if sell_size > 0:
ask = next_id()
exchange.send_add_message(
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:
exchange.send_add_message_ioc(next_id(), "BOND", Dir.BUY, ask, 3)
print(f" BOND 주문 → 매수:{buy_price} x{buy_size}, 매도:{sell_price} x{sell_size}, 포지션:{position}")
if bid and bid > 1001:
exchange.send_add_message_ioc(next_id(), "BOND", Dir.SELL, bid, 3)
def try_xlf_arb():
"""XLF 차익거래 시도 - IDLE + 쿨다운 통과 시에만"""
nonlocal xlf_state, xlf_direction, xlf_pending, xlf_arb_size, xlf_convert_oid
# ==================== XLF 차익거래 (초안전 버전) ====================
def xlf_arb():
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")
if not market_open or xlf_state != "IDLE":
return
bond_bid = state.bid_prices.get("BOND")
gs_bid = state.bid_prices.get("GS")
ms_bid = state.bid_prices.get("MS")
wfc_bid = state.bid_prices.get("WFC")
xlf_ask = state.ask_prices.get("XLF")
# 실패 후 쿨다운 체크
if time.time() - xlf_last_fail < XLF_COOLDOWN:
return
bond_ask = state.ask_prices["BOND"]
gs_ask = state.ask_prices["GS"]
ms_ask = state.ask_prices["MS"]
wfc_ask = state.ask_prices["WFC"]
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,
bond_bid, gs_bid, ms_bid, wfc_bid, xlf_ask]:
@@ -111,118 +193,471 @@ def main():
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
pos = positions.get("XLF", 0)
# 바스켓 심볼 포지션 한도 체크
def basket_has_room():
return (
om.positions["BOND"] + 3 <= 100 and
om.positions["GS"] + 2 <= 100 and
om.positions["MS"] + 3 <= 100 and
om.positions["WFC"] + 2 <= 100
)
if abs(pos) > 10:
# 케이스 1: 바스켓 매수 → XLF 변환 → XLF 매도
profit1 = xlf_bid * 10 - basket_ask - XLF_CONVERSION_FEE
if profit1 > XLF_MIN_PROFIT and om.check_pos_limit("XLF") and basket_has_room():
print(f" XLF 차익(바스켓→XLF) 시작, 예상수익:{profit1}")
cancel_bond_sell_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
profit1 = xlf_bid * 10 - basket_ask
if profit1 > ARB_THRESHOLD:
exchange.send_add_message_ioc(next_id(), "XLF", Dir.SELL, xlf_bid, 10)
# 케이스 2: XLF 매수 → 바스켓 변환 → 각 종목 매도
profit2 = basket_bid - xlf_ask * 10 - XLF_CONVERSION_FEE
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
if profit2 > ARB_THRESHOLD:
exchange.send_add_message_ioc(next_id(), "XLF", Dir.BUY, xlf_ask, 10)
def handle_xlf_fill(order_id, symbol, dir_, qty):
"""XLF state machine 체결 처리 (부분 체결 추적)"""
nonlocal xlf_state, xlf_pending, xlf_convert_oid
# ==================== 리스크 관리 ====================
def risk():
for sym in ["GS", "MS", "WFC", "XLF"]:
pos = positions.get(sym, 0)
if order_id not in xlf_pending:
return
if abs(pos) > MAX_POS:
if pos > 0:
exchange.send_add_message_ioc(
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)
)
xlf_pending[order_id] -= qty
if xlf_pending[order_id] <= 0:
del xlf_pending[order_id]
# ==================== LOOP ====================
if xlf_state == "BUYING_BASKET" and not xlf_pending:
print(" 바스켓 매수 완료 → XLF 변환 시작")
xlf_state = "CONVERTING"
xlf_convert_oid = next_id()
exchange.send_convert_message(xlf_convert_oid, "XLF", Dir.BUY, xlf_arb_size)
elif xlf_state == "BUYING_XLF" and not xlf_pending:
print(" XLF 매수 완료 → 바스켓 변환 시작")
xlf_state = "CONVERTING"
xlf_convert_oid = next_id()
exchange.send_convert_message(xlf_convert_oid, "XLF", Dir.SELL, xlf_arb_size)
def try_vale_arb():
"""VALE/VALBZ 차익거래 시도 - XLF와 독립적으로 동시 실행"""
nonlocal vale_state, vale_direction, vale_pending, vale_arb_size, vale_convert_oid
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, vale_convert_oid
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"
vale_convert_oid = next_id()
exchange.send_convert_message(vale_convert_oid, "VALE", Dir.BUY, vale_arb_size)
elif vale_state == "BUYING_VALE" and not vale_pending:
print(" VALE 매수 완료 → VALBZ 변환 시작")
vale_state = "CONVERTING"
vale_convert_oid = next_id()
exchange.send_convert_message(vale_convert_oid, "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:
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
elif msg["type"] == "book":
sym = msg["symbol"]
elif message["type"] == "open":
# 시장이 열렸을 때 주문 시작 (open 전에 주문하면 reject됨)
print("Market opened:", message)
market_open = True
place_bond_orders()
bid = msg["buy"][0][0] if msg["buy"] else None
ask = msg["sell"][0][0] if msg["sell"] else None
elif message["type"] == "error":
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
xlf_last_fail = time.time() # 쿨다운 시작
place_bond_orders() # try_xlf_arb() 호출 제거
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()):
exchange.send_cancel_message(oid)
del active_orders[oid]
elif message["type"] == "ack":
ack_oid = message.get("order_id")
# 전략 실행
bond_arb()
xlf_arb()
# XLF 변환 ack 처리 (order_id로 구분)
if xlf_state == "CONVERTING" and ack_oid == xlf_convert_oid:
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
if sym in ["GS", "MS", "WFC"]:
market_make(sym)
# VALE 변환 ack 처리 (order_id로 구분)
elif vale_state == "CONVERTING" and ack_oid == vale_convert_oid:
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
risk()
elif message["type"] == "fill":
print(message)
qty = message["size"]
sym = message["symbol"]
dir_ = message["dir"]
oid = message["order_id"]
elif msg["type"] == "fill":
qty = msg["size"]
sym = msg["symbol"]
if msg["dir"] == Dir.BUY:
positions[sym] = positions.get(sym, 0) + qty
# 포지션 업데이트
if dir_ == Dir.BUY:
om.update_position(sym, oid, qty)
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
place_bond_orders()
# try_xlf_arb() 제거 → book 메시지에서 자동 시도
# 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
# try_vale_arb() 제거 → book 메시지에서 자동 시도
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:
def __init__(self, args):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((args.exchange_hostname, args.port))
self.reader = s.makefile("r", 1)
self.writer = s
self._write({"type": "hello", "team": team_name.upper()})
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):
msg = json.loads(self.reader.readline())
if "dir" in msg:
msg["dir"] = Dir(msg["dir"])
return msg
"""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, oid, sym, dir, price, size):
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"DAY"})
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_add_message_ioc(self, oid, sym, dir, price, size):
self._write({"type":"add","order_id":oid,"symbol":sym,"dir":dir,"price":price,"size":size,"tif":"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, oid):
self._write({"type": "cancel", "order_id": oid})
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 _write(self, msg):
self.writer.send((json.dumps(msg)+"\n").encode())
# ====================
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)
group.add_argument("--production", action="store_true")
group.add_argument("--test", type=str, default="prod-like")
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
else:
elif args.test:
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
if __name__ == "__main__":
main()
# Check that [team_name] has been updated.
assert team_name != "REPLAC" + "EME", (
"Please put your team name in the variable [team_name]."
)
main()