fix
This commit is contained in:
191
bot_x.py
191
bot_x.py
@@ -57,6 +57,39 @@ class TechnicalAnalyzer:
|
||||
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,
|
||||
@@ -119,7 +152,56 @@ class CrossEMAStrategy:
|
||||
return False
|
||||
|
||||
|
||||
team_name = "HanyangFloorFunction"
|
||||
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
|
||||
|
||||
|
||||
def main():
|
||||
@@ -141,6 +223,11 @@ def main():
|
||||
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
|
||||
@@ -164,11 +251,22 @@ def main():
|
||||
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()
|
||||
@@ -184,6 +282,12 @@ def main():
|
||||
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
|
||||
@@ -405,6 +509,82 @@ def main():
|
||||
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
|
||||
|
||||
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:
|
||||
@@ -427,6 +607,7 @@ def main():
|
||||
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"
|
||||
@@ -510,6 +691,11 @@ def main():
|
||||
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 복귀")
|
||||
@@ -549,6 +735,9 @@ def main():
|
||||
if sym in symbols_for_ema:
|
||||
try_ema_trade()
|
||||
|
||||
if sym in symbols_for_mr:
|
||||
try_mean_reversion_trade()
|
||||
|
||||
now = time.time()
|
||||
if now - last_refresh > REFRESH_INTERVAL:
|
||||
last_refresh = now
|
||||
|
||||
Reference in New Issue
Block a user