131 lines
3.9 KiB
Python
131 lines
3.9 KiB
Python
import MetaTrader5 as mt5
|
|
import logging
|
|
import time
|
|
|
|
# -----------------------------
|
|
# Logging
|
|
# -----------------------------
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
|
log = logging.getLogger()
|
|
|
|
# -----------------------------
|
|
# Initialize MT5
|
|
# -----------------------------
|
|
if not mt5.initialize():
|
|
log.error(f"MT5 initialize failed: {mt5.last_error()}")
|
|
quit()
|
|
log.info("Connected to MT5 terminal")
|
|
|
|
# -----------------------------
|
|
# Parameters
|
|
# -----------------------------
|
|
SYMBOLS = ["XAUUSD", "USDJPY", "US30", "USTEC"]
|
|
LOT_SIZE = 0.05 # Full trade lot size
|
|
PARTIAL_RATIO = 0.5 # Fraction for partial close
|
|
DEVIATION = 20
|
|
|
|
# -----------------------------
|
|
# Helpers
|
|
# -----------------------------
|
|
def get_tick(symbol):
|
|
tick = mt5.symbol_info_tick(symbol)
|
|
if tick is None:
|
|
log.warning(f"No tick data for {symbol}")
|
|
return tick
|
|
|
|
def get_min_lot(symbol):
|
|
info = mt5.symbol_info(symbol)
|
|
if info is None:
|
|
return 0.01
|
|
return info.volume_min, info.volume_step
|
|
|
|
# -----------------------------
|
|
# Open trade function
|
|
# -----------------------------
|
|
def open_trade(symbol, order_type, lot_size):
|
|
tick = get_tick(symbol)
|
|
if not tick:
|
|
return None
|
|
|
|
price = tick.ask if order_type == mt5.ORDER_TYPE_BUY else tick.bid
|
|
request = {
|
|
"action": mt5.TRADE_ACTION_DEAL,
|
|
"symbol": symbol,
|
|
"volume": lot_size,
|
|
"type": order_type,
|
|
"price": price,
|
|
"deviation": DEVIATION,
|
|
"magic": 234000,
|
|
"comment": "HFT open",
|
|
"type_time": mt5.ORDER_TIME_GTC,
|
|
"type_filling": mt5.ORDER_FILLING_IOC
|
|
}
|
|
|
|
result = mt5.order_send(request)
|
|
if result.retcode != mt5.TRADE_RETCODE_DONE:
|
|
log.error(f"Trade failed for {symbol}: {result.retcode}")
|
|
return None
|
|
log.info(f"Trade opened: {symbol} {order_type} {lot_size} lots, ticket {result.order}")
|
|
return result.order
|
|
|
|
# -----------------------------
|
|
# Partial close function
|
|
# -----------------------------
|
|
def partial_close(symbol, position_ticket, fraction=0.5):
|
|
info = mt5.symbol_info(symbol)
|
|
if not info:
|
|
log.warning(f"{symbol} info unavailable for partial close")
|
|
return
|
|
|
|
pos = mt5.positions_get(ticket=position_ticket)
|
|
if not pos:
|
|
log.warning(f"Position {position_ticket} not found")
|
|
return
|
|
|
|
pos = pos[0]
|
|
min_lot, step = info.volume_min, info.volume_step
|
|
partial_volume = max(min_lot, round(pos.volume * fraction / step) * step)
|
|
if partial_volume <= 0:
|
|
log.warning(f"Partial volume too small for {symbol}")
|
|
return
|
|
|
|
tick = get_tick(symbol)
|
|
if not tick:
|
|
return
|
|
|
|
price = tick.bid if pos.type == mt5.ORDER_TYPE_BUY else tick.ask
|
|
request = {
|
|
"action": mt5.TRADE_ACTION_DEAL,
|
|
"symbol": symbol,
|
|
"volume": partial_volume,
|
|
"type": mt5.ORDER_TYPE_SELL if pos.type == mt5.ORDER_TYPE_BUY else mt5.ORDER_TYPE_BUY,
|
|
"position": position_ticket,
|
|
"price": price,
|
|
"deviation": DEVIATION,
|
|
"magic": 234000,
|
|
"comment": "HFT partial close",
|
|
"type_time": mt5.ORDER_TIME_GTC,
|
|
"type_filling": mt5.ORDER_FILLING_IOC
|
|
}
|
|
|
|
result = mt5.order_send(request)
|
|
if result.retcode != mt5.TRADE_RETCODE_DONE:
|
|
log.error(f"Partial close failed for {symbol}: {result.retcode}")
|
|
else:
|
|
log.info(f"Partial close executed: {symbol} {partial_volume} lots, ticket {position_ticket}")
|
|
|
|
# -----------------------------
|
|
# Example HFT loop
|
|
# -----------------------------
|
|
try:
|
|
for sym in SYMBOLS:
|
|
# Randomly open buy or sell
|
|
import random
|
|
order_type = random.choice([mt5.ORDER_TYPE_BUY, mt5.ORDER_TYPE_SELL])
|
|
ticket = open_trade(sym, order_type, LOT_SIZE)
|
|
if ticket:
|
|
time.sleep(1) # simulate holding
|
|
partial_close(sym, ticket, PARTIAL_RATIO)
|
|
finally:
|
|
mt5.shutdown()
|
|
log.info("MT5 shutdown complete")
|