This commit is contained in:
2026-05-09 16:46:34 +09:00
parent 9c33cae2ce
commit b2aa766f25

107
prac.py
View File

@@ -46,36 +46,40 @@ def main():
# pick? Also, you will need to send more orders over time. # pick? Also, you will need to send more orders over time.
# --- 설정 --- # --- 설정 ---
BOND_FAIR_VALUE = 1000 # BOND fair value (고정) BOND_FAIR_VALUE = 1000 # BOND fair value (고정)
BOND_ORDER_SIZE = 50 # BOND 주문당 수량 BOND_ORDER_SIZE = 60 # BOND 주문당 수량
XLF_CONVERSION_FEE = 100 # XLF 변환 비용 XLF_CONVERSION_FEE = 100 # XLF 변환 비용
XLF_MIN_PROFIT = 10 # XLF 차익거래 최소 수익 (불확실한 거래 방지) XLF_MIN_PROFIT = 5 # XLF 차익거래 최소 수익
XLF_COOLDOWN = 2.0 # XLF 실패 후 재시도 대기 시간 (초)
VALE_CONVERSION_FEE = 10 # VALE 변환 비용 VALE_CONVERSION_FEE = 10 # VALE 변환 비용
VALE_MIN_PROFIT = 3 # VALE 차익거래 최소 수익 VALE_MIN_PROFIT = 1 # VALE 차익거래 최소 수익
VALE_ARB_SIZE = 10 # VALE 차익거래 단위 VALE_ARB_SIZE = 10 # VALE 차익거래 단위
REFRESH_INTERVAL = 5.0 # 주문 갱신 주기 (초) REFRESH_INTERVAL = 5.0 # 주문 갱신 주기 (초)
# XLF state machine # XLF state machine
# IDLE → BUYING_BASKET → CONVERTING → SELLING_XLF → IDLE # IDLE → BUYING_BASKET → CONVERTING → SELLING_XLF → IDLE
# IDLE → BUYING_XLF → CONVERTING → SELLING_BASKET → IDLE # IDLE → BUYING_XLF → CONVERTING → SELLING_BASKET → IDLE
xlf_state = "IDLE" xlf_state = "IDLE"
xlf_pending = {} xlf_pending = {}
xlf_direction = None xlf_direction = None
xlf_arb_size = 0 xlf_arb_size = 0
xlf_convert_oid = None
xlf_last_fail = 0.0 # 마지막 XLF 실패 시간
# VALE state machine # VALE state machine (XLF와 독립적으로 동시 실행 가능)
# IDLE → BUYING_VALBZ → CONVERTING → SELLING_VALE → IDLE # IDLE → BUYING_VALBZ → CONVERTING → SELLING_VALE → IDLE
# IDLE → BUYING_VALE → CONVERTING → SELLING_VALBZ → IDLE # IDLE → BUYING_VALE → CONVERTING → SELLING_VALBZ → IDLE
vale_state = "IDLE" vale_state = "IDLE"
vale_pending = {} vale_pending = {}
vale_direction = None vale_direction = None
vale_arb_size = 0 vale_arb_size = 0
vale_convert_oid = None
state = StateManager() state = StateManager()
om = OrderManager(exchange) om = OrderManager(exchange)
market_open = False market_open = False
active_orders = {} # BOND 전용 {order_id: {"dir": ..., "price": ..., "size": ...}} active_orders = {} # BOND 전용 {order_id: {"dir": ..., "price": ..., "size": ...}}
last_refresh = time.time() last_refresh = time.time()
# hello 메시지에서 기존 포지션 로드 (재접속 시 필수) # hello 메시지에서 기존 포지션 로드 (재접속 시 필수)
for sym_info in hello_message["symbols"]: for sym_info in hello_message["symbols"]:
@@ -89,6 +93,16 @@ def main():
def next_id(): def next_id():
return om.next_order() return om.next_order()
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)
def cancel_all_bond_orders(): def cancel_all_bond_orders():
"""활성 BOND 주문 전부 취소 + future_positions 롤백""" """활성 BOND 주문 전부 취소 + future_positions 롤백"""
for oid in list(active_orders.keys()): for oid in list(active_orders.keys()):
@@ -151,12 +165,16 @@ def main():
print(f" BOND 주문 → 매수:{buy_price} x{buy_size}, 매도:{sell_price} x{sell_size}, 포지션:{position}") print(f" BOND 주문 → 매수:{buy_price} x{buy_size}, 매도:{sell_price} x{sell_size}, 포지션:{position}")
def try_xlf_arb(): def try_xlf_arb():
"""XLF 차익거래 시도 - IDLE 상태일 때""" """XLF 차익거래 시도 - IDLE + 쿨다운 통과 시에"""
nonlocal xlf_state, xlf_direction, xlf_pending, xlf_arb_size nonlocal xlf_state, xlf_direction, xlf_pending, xlf_arb_size, xlf_convert_oid
if not market_open or xlf_state != "IDLE": if not market_open or xlf_state != "IDLE":
return return
# 실패 후 쿨다운 체크
if time.time() - xlf_last_fail < XLF_COOLDOWN:
return
bond_ask = state.ask_prices["BOND"] bond_ask = state.ask_prices["BOND"]
gs_ask = state.ask_prices["GS"] gs_ask = state.ask_prices["GS"]
ms_ask = state.ask_prices["MS"] ms_ask = state.ask_prices["MS"]
@@ -175,12 +193,20 @@ 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
# 바스켓 심볼 포지션 한도 체크
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
)
# 케이스 1: 바스켓 매수 → XLF 변환 → XLF 매도 # 케이스 1: 바스켓 매수 → XLF 변환 → XLF 매도
profit1 = xlf_bid * 10 - basket_ask - XLF_CONVERSION_FEE profit1 = xlf_bid * 10 - basket_ask - XLF_CONVERSION_FEE
if profit1 > XLF_MIN_PROFIT and om.check_pos_limit("XLF"): if profit1 > XLF_MIN_PROFIT and om.check_pos_limit("XLF") and basket_has_room():
print(f" XLF 차익(바스켓→XLF) 시작, 예상수익:{profit1}") print(f" XLF 차익(바스켓→XLF) 시작, 예상수익:{profit1}")
# BOND 주문 먼저 취소 (XLF용 BOND 매수가 기존 매도에 상쇄되지 않도록) cancel_bond_sell_orders()
cancel_all_bond_orders()
xlf_state = "BUYING_BASKET" xlf_state = "BUYING_BASKET"
xlf_direction = "BASKET_TO_XLF" xlf_direction = "BASKET_TO_XLF"
xlf_arb_size = 10 xlf_arb_size = 10
@@ -206,7 +232,7 @@ def main():
def handle_xlf_fill(order_id, symbol, dir_, qty): def handle_xlf_fill(order_id, symbol, dir_, qty):
"""XLF state machine 체결 처리 (부분 체결 추적)""" """XLF state machine 체결 처리 (부분 체결 추적)"""
nonlocal xlf_state, xlf_pending nonlocal xlf_state, xlf_pending, xlf_convert_oid
if order_id not in xlf_pending: if order_id not in xlf_pending:
return return
@@ -218,16 +244,18 @@ def main():
if xlf_state == "BUYING_BASKET" and not xlf_pending: if xlf_state == "BUYING_BASKET" and not xlf_pending:
print(" 바스켓 매수 완료 → XLF 변환 시작") print(" 바스켓 매수 완료 → XLF 변환 시작")
xlf_state = "CONVERTING" xlf_state = "CONVERTING"
exchange.send_convert_message(next_id(), "XLF", Dir.BUY, xlf_arb_size) 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: elif xlf_state == "BUYING_XLF" and not xlf_pending:
print(" XLF 매수 완료 → 바스켓 변환 시작") print(" XLF 매수 완료 → 바스켓 변환 시작")
xlf_state = "CONVERTING" xlf_state = "CONVERTING"
exchange.send_convert_message(next_id(), "XLF", Dir.SELL, xlf_arb_size) xlf_convert_oid = next_id()
exchange.send_convert_message(xlf_convert_oid, "XLF", Dir.SELL, xlf_arb_size)
def try_vale_arb(): def try_vale_arb():
"""VALE/VALBZ 차익거래 시도 - IDLE 상태일 때만""" """VALE/VALBZ 차익거래 시도 - XLF와 독립적으로 동시 실행"""
nonlocal vale_state, vale_direction, vale_pending, vale_arb_size nonlocal vale_state, vale_direction, vale_pending, vale_arb_size, vale_convert_oid
if not market_open or vale_state != "IDLE": if not market_open or vale_state != "IDLE":
return return
@@ -272,7 +300,7 @@ def main():
def handle_vale_fill(order_id, symbol, dir_, qty): def handle_vale_fill(order_id, symbol, dir_, qty):
"""VALE state machine 체결 처리 (부분 체결 추적)""" """VALE state machine 체결 처리 (부분 체결 추적)"""
nonlocal vale_state, vale_pending nonlocal vale_state, vale_pending, vale_convert_oid
if order_id not in vale_pending: if order_id not in vale_pending:
return return
@@ -284,12 +312,14 @@ def main():
if vale_state == "BUYING_VALBZ" and not vale_pending: if vale_state == "BUYING_VALBZ" and not vale_pending:
print(" VALBZ 매수 완료 → VALE 변환 시작") print(" VALBZ 매수 완료 → VALE 변환 시작")
vale_state = "CONVERTING" vale_state = "CONVERTING"
exchange.send_convert_message(next_id(), "VALE", Dir.BUY, vale_arb_size) 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: elif vale_state == "BUYING_VALE" and not vale_pending:
print(" VALE 매수 완료 → VALBZ 변환 시작") print(" VALE 매수 완료 → VALBZ 변환 시작")
vale_state = "CONVERTING" vale_state = "CONVERTING"
exchange.send_convert_message(next_id(), "VALE", Dir.SELL, vale_arb_size) 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 # 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 # now this doesn't track much information, but it's enough to get a sense
@@ -336,19 +366,22 @@ def main():
active_orders.pop(oid, None) active_orders.pop(oid, None)
if oid in xlf_pending: if oid in xlf_pending:
print(" XLF 주문 reject → IDLE 복귀") print(" XLF 주문 reject → IDLE 복귀")
xlf_state = "IDLE" xlf_state = "IDLE"
xlf_pending.clear() xlf_pending.clear()
xlf_direction = None xlf_direction = None
place_bond_orders() xlf_last_fail = time.time() # 쿨다운 시작
place_bond_orders() # try_xlf_arb() 호출 제거
if oid in vale_pending: if oid in vale_pending:
print(" VALE 주문 reject → IDLE 복귀") print(" VALE 주문 reject → IDLE 복귀")
vale_state = "IDLE" vale_state = "IDLE"
vale_pending.clear() vale_pending.clear()
vale_direction = None vale_direction = None
elif message["type"] == "ack": elif message["type"] == "ack":
# XLF 변환 ack 처리 ack_oid = message.get("order_id")
if xlf_state == "CONVERTING":
# XLF 변환 ack 처리 (order_id로 구분)
if xlf_state == "CONVERTING" and ack_oid == xlf_convert_oid:
print(" XLF 변환 완료 → 매도 시작") print(" XLF 변환 완료 → 매도 시작")
if xlf_direction == "BASKET_TO_XLF": if xlf_direction == "BASKET_TO_XLF":
# 변환: BOND -3, GS -2, MS -3, WFC -2, XLF +10 # 변환: BOND -3, GS -2, MS -3, WFC -2, XLF +10
@@ -388,8 +421,8 @@ def main():
) )
xlf_pending[oid] = qty xlf_pending[oid] = qty
# VALE 변환 ack 처리 # VALE 변환 ack 처리 (order_id로 구분)
elif vale_state == "CONVERTING": elif vale_state == "CONVERTING" and ack_oid == vale_convert_oid:
print(" VALE 변환 완료 → 매도 시작") print(" VALE 변환 완료 → 매도 시작")
if vale_direction == "VALBZ_TO_VALE": if vale_direction == "VALBZ_TO_VALE":
# 변환: VALBZ -size, VALE +size # 변환: VALBZ -size, VALE +size
@@ -423,6 +456,7 @@ def main():
dir_ = message["dir"] dir_ = message["dir"]
oid = message["order_id"] oid = message["order_id"]
# 포지션 업데이트
if dir_ == Dir.BUY: if dir_ == Dir.BUY:
om.update_position(sym, oid, qty) om.update_position(sym, oid, qty)
else: else:
@@ -430,22 +464,27 @@ def main():
print(f" 포지션 → {om.positions}") print(f" 포지션 → {om.positions}")
# BOND 체결 시 무조건 재주문
if sym == "BOND" and oid in active_orders: if sym == "BOND" and oid in active_orders:
active_orders.pop(oid, None) active_orders.pop(oid, None)
place_bond_orders() place_bond_orders()
# XLF state machine 체결 처리
handle_xlf_fill(oid, sym, dir_, qty) handle_xlf_fill(oid, sym, dir_, qty)
if xlf_state in ("SELLING_XLF", "SELLING_BASKET") and not xlf_pending: if xlf_state in ("SELLING_XLF", "SELLING_BASKET") and not xlf_pending:
print(" XLF 차익거래 완료 → IDLE 복귀") print(" XLF 차익거래 완료 → IDLE 복귀")
xlf_state = "IDLE" xlf_state = "IDLE"
xlf_direction = None xlf_direction = None
place_bond_orders() place_bond_orders()
# try_xlf_arb() 제거 → book 메시지에서 자동 시도
# VALE state machine 체결 처리
handle_vale_fill(oid, sym, dir_, qty) handle_vale_fill(oid, sym, dir_, qty)
if vale_state in ("SELLING_VALE", "SELLING_VALBZ") and not vale_pending: if vale_state in ("SELLING_VALE", "SELLING_VALBZ") and not vale_pending:
print(" VALE 차익거래 완료 → IDLE 복귀") print(" VALE 차익거래 완료 → IDLE 복귀")
vale_state = "IDLE" vale_state = "IDLE"
vale_direction = None vale_direction = None
# try_vale_arb() 제거 → book 메시지에서 자동 시도
elif message["type"] == "book": elif message["type"] == "book":
sym = message["symbol"] sym = message["symbol"]
@@ -621,4 +660,4 @@ if __name__ == "__main__":
"Please put your team name in the variable [team_name]." "Please put your team name in the variable [team_name]."
) )
main() main()