commit
This commit is contained in:
107
prac.py
107
prac.py
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user