#!/usr/bin/env python3
import time
import math
import asyncio
import numpy as np
from datetime import datetime
from datetime import date
import random
import websockets
import MetaTrader5 as mt5
import json
import pytz


autoexit = False;
nbpositions = 0
oside = -1
omax = 0
average = 0
xtick = 0
dico = []
ticksize = 0
aticket = ''

print("websocket server by niokoz v4.2  - (2023/09/10)")

if not mt5.initialize():
    print("initialize() failed")
    mt5.shutdown()

async def order_objet(ws, action, symbol, volume, ttype, price, sl, tp):
    global average, nbpositions, oside, autoexit, omax, xtick, ticksize, aticket

    if ((autoexit == True) and (oside >= 0)):
        if ((ttype == 'BUY') and (oside == 1)):
            return
        if ((ttype == 'SELL') and (oside == 0)):
            return
  
    nbpositions = 0
    positions=mt5.positions_get(symbol=symbol)
    if positions!=None:
        if len(positions)>0:
            for position in positions:
                average = position.price_open
                aticket = position.ticket
                trades = mt5.history_deals_get(position=aticket)
                for trade in trades:
                    if ((oside == 0) and (trade.type == 0)):
                        nbpositions += 1
                    elif ((oside == 0) and (trade.type == 1)):
                        nbpositions -= 1
                    elif ((oside == 1) and (trade.type == 1)):
                        nbpositions += 1
                    elif ((oside == 1) and (trade.type == 0)):
                        nbpositions -= 1  

    if (nbpositions == 0):
        if (ttype == 'BUY'):
            oside = 0
        elif (ttype == 'SELL'):
            oside = 1
    sidecours = 0
    if (ttype == 'BUY'):
        sidecours = 0
    elif (ttype == 'SELL'):
        sidecours = 1
    print("omax= ", omax)
    if (oside == sidecours):
        if (nbpositions >= omax):
            return

    request = ''
    if action == 'DEAL':
        action = mt5.TRADE_ACTION_DEAL
    elif action == 'PENDING':
        action = mt5.TRADE_ACTION_PENDING
    elif action == 'FLATTEN':
        action = mt5.ORDER_TYPE_CLOSE_BY
    
    if symbol == '':
        try:
            await ws.send('INFO=no symbol')
            await dispatch(ws)
        except Exception as e:
            print(f'error 1: {e}')
        return 'Error: no symbol'
    
    if volume == '':
        try:
            await ws.send('INFO=no volume')
            await dispatch(ws)
        except Exception as e:
            print(f'error 2: {e}')
        return 'Error: no volume'
 
    if ttype == 'BUY':
        ttype = mt5.ORDER_TYPE_BUY
        request = {
            "action": action,
            "symbol": symbol,
            "volume": float(volume),
            "type": ttype,
            "deviation": 100,
            "magic": 234000,
            "comment": "metaquantuniverse.com",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_FOK,
        }
    elif ttype == 'SELL':
        ttype = mt5.ORDER_TYPE_SELL
        request = {
            "action": action,
            "symbol": symbol,
            "volume": float(volume),
            "type": ttype,
            "deviation": 100,
            "magic": 234000,
            "comment": "metaquantuniverse.com",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_FOK,
        }

    elif ttype == 'BUY_LIMIT':
        ttype = mt5.ORDER_TYPE_BUY_LIMIT
        if price == '':
            try:
                await ws.send('INFO=no price')
                await dispatch(ws)
            except Exception as e:
                print(f'error 3: {e}')
            return 'Error: no price'
        request = {
            "action": action,
            "symbol": symbol,
            "volume": float(volume),
            "type": ttype,
            "price": float(price),
            "magic": 234000,
            "comment": "metaquantuniverse.com",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_FOK,
        }
    
            
    elif ttype == 'SELL_LIMIT':
        ttype = mt5.ORDER_TYPE_SELL_LIMIT
        
        if price == '':
            try:
                await ws.send('INFO=no price')
                await dispatch(ws)
            except Exception as e:
                print(f'error 4: {e}')
            return 'Error: no price'
        request = {
            "action": action,
            "symbol": symbol,
            "volume": float(volume),
            "type": ttype,
            "price": float(price),

            "magic": 234000,
            "comment": "metaquantuniverse.com",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_FOK,
        }
  
    elif ttype == 'FLATTEN':
        ttype = mt5.ORDER_TYPE_SELL_LIMIT
        request = {
            "action": action,
            "symbol": symbol,
            "volume": float(volume),
            "type": ttype,
            "magic": 234000,
            "comment": "metaquantuniverse.com",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_FOK,
        }
    #if xtick > 0:
    #    if nbpositions > 0:
    #        if sidecours == 1:
    #            if (float(price) < (average - (xtick * ticksize))):
    #                return
    #        elif sidecours == 0:
    #            if (float(price) > (average + (xtick * ticksize))):
    #               return
 
    result = mt5.order_send(request) 
    if result.retcode != mt5.TRADE_RETCODE_DONE:
        try:
            print("refused sl/tp= ", result.comment)
            await ws.send('INFO=' + result.comment)
            await dispatch(ws)
        except Exception as e:
            print(f'error 5: {e}')
        return 'Error: ' + result.comment
    else:
        print('request: order sent')
        nbpositions = 0
        aticket = ''
        positions=mt5.positions_get(symbol=symbol)
        if positions!=None:
            if len(positions)>0:
                for position in positions:
                    average = position.price_open
                    aticket = position.ticket
                    trades = mt5.history_deals_get(position=aticket)
                    for trade in trades:
                        if ((oside == 0) and (trade.type == 0)):
                            nbpositions += 1
                        elif ((oside == 0) and (trade.type == 1)):
                            nbpositions -= 1
                        elif ((oside == 1) and (trade.type == 1)):
                            nbpositions += 1
                        elif ((oside == 1) and (trade.type == 0)):
                            nbpositions -= 1
        if aticket == '':
            return
        point=mt5.symbol_info(symbol).point
        if ttype == 0:
            if average == 0:
                average = mt5.symbol_info_tick(symbol).ask
            if (sl != '') and (tp != ''):
                temp_sl = average - float(sl) * point
                temp_tp = average + float(tp) * point
                request = {
                    "action": mt5.TRADE_ACTION_SLTP,
                    "position": aticket,
                    "symbol" : symbol,
                    "sl": float(temp_sl),
                    "tp": float(temp_tp)
                }
                result = mt5.order_send(request)
                if result.retcode == mt5.TRADE_RETCODE_DONE:
                    print("BUY SL/TP OK")
                else:
                    print('refused sl/tp= ', result.comment)
            elif (sl != '') and (tp == ''):
                temp_sl = average - float(sl) * point
                request = {
                    "action": mt5.TRADE_ACTION_SLTP,
                    "position": aticket,
                    "symbol" : symbol,
                    "sl": float(temp_sl)
                }
                result = mt5.order_send(request)
                if result.retcode == mt5.TRADE_RETCODE_DONE:
                    print("BUY SL OK")
                else:
                    print('refused sl/tp= ', result.comment)
            elif (sl == '') and (tp != ''):
                temp_tp = average + float(tp) * point
                request = {
                    "action": mt5.TRADE_ACTION_SLTP,
                    "position": aticket,
                    "symbol" : symbol,
                    "tp": float(temp_tp)
                }
                result = mt5.order_send(request)
                if result.retcode == mt5.TRADE_RETCODE_DONE:
                    print("BUY TP OK")
                else:
                    print('refused sl/tp= ', result.comment)
        elif ttype == 1:
            if average == 0:
                average = mt5.symbol_info_tick(symbol).bid
            if (sl != '') and (tp != ''):
                temp_sl = average + float(sl) * point
                temp_tp = average - float(tp) * point
                request = {
                    "action": mt5.TRADE_ACTION_SLTP,
                    "position": aticket,
                    "symbol" : symbol,
                    "sl": float(temp_sl),
                    "tp": float(temp_tp)
                }
                result = mt5.order_send(request)
                if result.retcode == mt5.TRADE_RETCODE_DONE:
                    print("SELL SL/TP OK")
                else:
                    print('refused sl/tp= ', result.comment)
            elif (sl != '') and (tp == ''):
                temp_sl = average + float(sl) * point
                request = {
                    "action": mt5.TRADE_ACTION_SLTP,
                    "position": aticket,
                    "symbol" : symbol,
                    "sl": float(temp_sl)
                }
                result = mt5.order_send(request)
                if result.retcode == mt5.TRADE_RETCODE_DONE:
                    print("SELL SL OK")
                else:
                    print('refused sl/tp= ', result.comment)
            elif (sl == '') and (tp != ''):
                temp_tp = average - float(tp) * point
                request = {
                    "action": mt5.TRADE_ACTION_SLTP,
                    "position": aticket,
                    "symbol" : symbol,
                    "tp": float(temp_tp)
                }
                result = mt5.order_send(request)
                if result.retcode == mt5.TRADE_RETCODE_DONE:
                    print("SELL TP OK") 
                else:
                    print('refused sl/tp= ', result.comment)                    
        
   
        try:
            f = open("requests.txt", "a")
            f.write(str(datetime.now()) + '\n')
            f.write(str(request) + '\n')
            f.write('-------------------------------\n')
            f.close()
            await ws.send('INFO=order sent')
            await dispatch(ws)
        except Exception as e:
            print(f'error 6: {e}')

async def envoi(ws, instrument):
    if mt5.market_book_add(instrument):
        anow = 0
        dnow = 0
        while True:
            now = math.floor(time.time())
            if (now % 60 == 0) and (anow != now):
                await task_rangebar(ws, instrument)
                anow = now
            items = mt5.market_book_get(instrument)
            jsonObj = json.dumps(items)
            ticks = mt5.copy_ticks_from(instrument, time.time() - 1, 100, mt5.COPY_TICKS_TRADE)
            tc = []
            if (now % 3 == 0) and (dnow != now):
                dnow = now
                for el in dico:
                    if (el > (now + 180)):
                        dico.remove(el)
            for el in ticks:
                if el[5] not in dico:
                    dico.append(el[5])
                    tc.append(el)
            jsonObj2 = json.dumps(np.array(tc).tolist())
            

            
            try:
                await ws.send("DATA=" + jsonObj + "+" + jsonObj2 + "+" + str(0) + "+" + str(0) + "+" + str(0) + "+" + str(0))
                await dispatch(ws)
            except Exception as e:
                print(f'metaquant disconnected x: {e}')
                break


async def dispatch(ws):
    global omax, autoexit, xtick
    try:
        datas = await ws.recv()
    except Exception as e:
        return
    infos = datas.split('=')
    if infos[0] == "ORDE":
        datas = infos[1].split('|')
        resp = await order_objet(ws, datas[0], datas[1], datas[2], datas[3], datas[4], datas[5], datas[6])
        if resp != None:
            print(resp)
    if infos[0] == "OMAX":
        omax = int(infos[1])
        print("max= ",omax)
    if infos[0] == "AEXI":
        if infos[1] == 'true':
            autoexit = True
        elif infos[1] == 'false':
            autoexit = False
        print("autoexit= ", autoexit)
    if infos[0] == "XTIC":
        xtick = int(infos[1])
        print('xtick= ', xtick)
    
    
    
async def task_rangebar(ws, instrument):
    timezone = pytz.timezone("Etc/UTC")
    utc_from = datetime.now()
    rates = mt5.copy_rates_from(instrument, mt5.TIMEFRAME_M1, utc_from, 1)
    for rate in rates:
        dailyrangeH = rate[2]
        dailyrangeL = rate[3]
    try:
        await ws.send('DAIL=' + str(dailyrangeL) + '|' + str(dailyrangeH))
        await dispatch(ws)
    except Exception as e:
        print(f'error 14: {e}')

async def task_profits(ws, instrument):
    account_currency=mt5.account_info().currency
    lot=1.0
    distance=300
    point=mt5.symbol_info(instrument).point
    symbol_tick=mt5.symbol_info_tick(instrument)
    ask=symbol_tick.ask
    bid=symbol_tick.bid
    buy_profit=mt5.order_calc_profit(mt5.ORDER_TYPE_BUY,instrument,lot,ask,ask+distance*point)
    sell_profit=mt5.order_calc_profit(mt5.ORDER_TYPE_SELL,instrument,lot,bid,bid-distance*point)
    try:
        await ws.send('PROF=' + str(buy_profit) + '|' + str(sell_profit) + '|' + account_currency)
        await dispatch(ws)
    except Exception as e:
        print(f'error 16: {e}')

async def pub_sub(ws, path):
    global ticksize
    if path == '/' :
        try:
            print('-----------------------------------------')
            print('metaquant connected')
            response = await ws.recv()
            instrument = response[5:]
            print('instrument= ',instrument)
            #symbols = mt5.symbols_get("*FUT*")
            #jsonObj = json.dumps(np.array(symbols).tolist())
            #try:
            #    await ws.send('SLST=' + jsonObj)
            #    await dispatch(ws)
            #except Exception as e:
            #    print(f'error 20: {e}')
            symbol_info = mt5.symbol_info(instrument)
            if symbol_info is not None:
                selected = mt5.symbol_select(instrument,True)
                if selected:
                    print(instrument, 'selected in MarketWatch window')
                ticksize = symbol_info.trade_tick_size
                print('ticksize= ',ticksize)
                print('description= ',symbol_info.description)
                try:
                    await ws.send('TICK=' + str(ticksize))
                    await dispatch(ws)
                    await envoi(ws, instrument)
                except Exception as e:
                    print(f'ref 17: metaquant disconnected: {mt5.last_error()} ')
            else:
                print(f'ref 18: metaquant diconnected: {mt5.last_error()}')
        except Exception as e:
            print(f'ref 19: metaquant disconnected: {mt5.last_error()}')
            
async def main():
    async with websockets.serve(pub_sub, '', 8000):
        await asyncio.Future() 

asyncio.run(main())