#import os,sys
#sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
"""
    Backtester component, allows to:

    pro lepší trasovatelnost máme následující razítka
    open orders
        - submitted_at
    trades
        - submitted_at
        - filled_at

    execute_orders_and_callbacks(time)
        - called before iteration
        - execute open orders before "time" and calls notification callbacks

    market buy
    limit buy
    market sell
    limit sell
    cancel order by id
    replace order by id
    get positions

    STATUSES supported:
     -  FILLED (including callbacks)

    not supported: NEW, ACCEPTED, CANCELED (currently no callback action will be backtestable on those)
        - případné nad těmito lze dát do synchronní části (api vrací rovnou zda objednávka neexistuje, pokud existuje tak předpokládáme vyplnění)
    
    supports standard validations and blocking of share and cash upon order submit
    supports only GTC order validity
    no partial fills
    RETURN
    1 - ok
    0 - ok, noaction
    - 1 - error

"""
from uuid import UUID, uuid4
from alpaca.trading.enums import OrderSide, OrderStatus, TradeEvent, OrderType
from v2realbot.common.model import TradeUpdate, Order
from rich import print as printanyway
import threading
import asyncio
from v2realbot.config import DATA_DIR
from v2realbot.utils.utils import AttributeDict, ltp, zoneNY, trunc, count_decimals, print
from v2realbot.utils.tlog import tlog
from v2realbot.enums.enums import FillCondition
from datetime import datetime, timedelta
import pandas as pd
#import matplotlib.pyplot as plt
#import seaborn; seaborn.set()
#import mplfinance as mpf
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
from bisect import bisect_left
from v2realbot.utils.dash_save_html import make_static
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
from dash import dcc, html, dash_table, Dash
import v2realbot.utils.config_handler as cfh
""""
LATENCY DELAYS
.000 trigger - last_trade_time (.4246266)
+.020 vstup do strategie a BUY (.444606)
+.023 submitted (.469198)
+.008    filled (.476695552)
+.023   fill not(.499888)
"""
lock = threading.Lock

#todo nejspis dat do classes, aby se mohlo backtestovat paralelne
#ted je globalni promena last_time_now a self.account a cash
class Backtester:
    def __init__(self, symbol: str, order_fill_callback: callable, btdata: list, bp_from: datetime, bp_to: datetime, cash: float = 100000):
        #this TIME value determines true time for submit, replace, cancel order should happen (allowing past)
        #it is set by every iteration of BT or before fill callback - allowing past events to happen
        self.time = None
        self.symbol = symbol
        self.order_fill_callback = order_fill_callback
        self.btdata = btdata
        self.backtest_start = None
        self.backtest_end = None
        self.cash_init = cash
        #backtesting period
        self.bp_from = bp_from
        self.bp_to = bp_to
        self.cash = cash
        self.cash_reserved_for_shorting = 0
        self.trades = []
        self.account = { "BAC": [0, 0] }
        # { "BAC": [avgp, size] }
        self.open_orders =[]
        # self.open_orders = [Order(id=uuid4(),
        #                         submitted_at = datetime(2023, 3, 17, 9, 30, 0, 0, tzinfo=zoneNY),
        #                         symbol = "BAC",
        #                         qty = 1,
        #                         status = OrderStatus.ACCEPTED,
        #                         order_type = OrderType.LIMIT,
        #                         side = OrderSide.BUY,
        #                         limit_price=22.4),
        #                     Order(id=uuid4(),
        #                         submitted_at = datetime(2023, 3, 17, 9, 30, 00, 0, tzinfo=zoneNY),
        #                         symbol = "BAC",
        #                         qty = 1,
        #                         order_type = OrderType.MARKET,
        #                         status = OrderStatus.ACCEPTED,
        #                         side = OrderSide.BUY)]
    
    #
    def execute_orders_and_callbacks(self, intime: float):
        """""
        Voláno ze strategie před každou iterací s časem T.
        Provede exekuci otevřených objednávek, které by se v reálu vyplnili do tohoto času.
        Pro vyplněné posílá fill notifikaci a volá callback s časem FILLu.

        - current time - state.time
        - actual prices - self.btdata
        - callback after order fill - self.order_fill_callback
        - set time for order fill callback - self.time
        """""

        print(10*"*",intime,"Exec OPEN ORDERS ",len(self.open_orders)," time", datetime.fromtimestamp(intime).astimezone(zoneNY),10*"*")
        # print("cash before", cash)
        # print("BT: executing pending orders")
        # print("list before execution", self.open_orders)

        if len(self.open_orders) == 0:
            #ic("no pend orders")
            return 0

        changes = 0

        #iterating while removing items - have to use todel list
        todel = []
        #with lock:
        """
        Pripravime si pracovni list
        btdata obsahuje vsechny aktualni timestampy tradu a jejich cenu.
        1) pracujeme vzdy OD zacatku listu DO indexu odpovidajici aktualnimu casu
        2) zjistime si index a pak iterujeme nad nim
        3) po skonceni pak tento pracovni kus umazeme
        """

        """
        Assumes myList is sorted. Returns first biggeer value to the number.
        """
        index_end = bisect_left(self.btdata, (intime,))

        # #find index of state.time in btdata - nahrazeno BISECT
        # for index_end in range(len(self.btdata)):
        #     #print(btdata[i][0])
        #     #print(i)
        #     if self.btdata[index_end][0] >= intime:
        #         break

        #pracovni list
        #ic("work_range 0:index_end")
        #ic(index_end)
        work_range = self.btdata[0:index_end]
        #ic(len(work_range))
        #print("index_end", i)
        #print("oriznuto",self.btdata[0:i+1])

        for order in self.open_orders:
            #pokud je vyplneny symbol, tak jedeme jen tyto, jinak vsechny
            print(order.id, datetime.timestamp(order.submitted_at), order.symbol, order.side, order.order_type, order.qty, order.limit_price, order.submitted_at)
            if order.canceled_at:
                #ic("deleting canceled order",order.id)
                todel.append(order)
            elif not self.symbol or order.symbol == self.symbol:
                #pricteme mininimalni latency od submittu k fillu
                if order.submitted_at.timestamp() + cfh.config_handler.get_val('BT_DELAYS','sub_to_fill') > float(intime):
                    print(f"too soon for {order.id}")
                #try to execute
                else:
                    #try to execute specific order
                    a = self._execute_order(o = order, intime=intime, work_range=work_range)
                    if a == 1:
                        #ic("EXECUTED")
                        todel.append(order)
                        changes = 1
                    else:
                        print(f"NOT EXECUTED {a}")
                        #ic("NOT EXECUTED",a)
        ##ic("istodel",todel)
        #vymazu z pending orderu vschny zprocesovane nebo ty na výmaz
        for i in todel:
            self.open_orders.remove(i)
        todel = {}

        #tady udelat pripadny ořez self.btdata - priste zaciname od zacatku
        ##ic("before delete", len(self.btdata))

        #TEST zkusime to nemazat, jak ovlivni performance
        #Mazeme, jinak je to hruza
        #nechavame na konci trady, které muzeme potrebovat pro consekutivni pravidlo
        #osetrujeme, kdy je malo tradu a oriznuti by slo do zaporu
        del_to_index = index_end-2-cfh.config_handler.get_val('BT_FILL_CONS_TRADES_REQUIRED')
        del_to_index = del_to_index if del_to_index > 0 else 0
        del self.btdata[0:del_to_index]
        ##ic("after delete",len(self.btdata[0:index_end]))
    
        if changes: return 1
        else: return 0 
            # print("pending orders after execution", self.open_orders)
            # print("trades after execution", trades)
            # print("self.account after execution", self.account)
            # print("cash after", cash)

    def _execute_order(self, o: Order, intime: float, work_range):
        """tries to execute order 

        o - specific Order
        time - intime
        work_range - aktualni slice of btdata pro tuto iteraci [(timestamp,price)] """

        fill_time = None
        fill_price = None
        order_min_fill_time = o.submitted_at.timestamp() + cfh.config_handler.get_val('BT_DELAYS','sub_to_fill')
        #ic(order_min_fill_time)
        #ic(len(work_range))

        if o.order_type == OrderType.LIMIT:
            if o.side == OrderSide.BUY:
                #counter for consecutive trades
                consec_cnt = 0
                for index, i in enumerate(work_range):
                #print(i)
                ##najde prvni nejvetsi čas vetsi nez minfill a majici odpovídající cenu
                ## pro LIMITku nejspíš přidat BT_DELAY.LIMIT_OFFSET, aby se nevyplnilo hned jako prvni s touto cenou
                ## offest by se pocital od nize nalezeneho casu, zvetsil by ho o LIMIT_OFFSET a zjistil, zda by
                ##v novem case doslo take k plneni a tam ho vyplnil. Uvidime az jestli bude aktualni prilis optimisticke.
                ## TBD zjistit na LIVE jaky je tento offset

                    #TODO pridat pokud je EXECUTION_DEBUG zalogování okolnich tradu (5 z kazde strany) od toho, který triggeroval plnění
                    #TODO pridat jako dalsi nastavovaci atribut pocet tradu po ktere musi byt cena zde (aby to nevyplnil knot high)

                    #NASTVENI PODMINEK PLNENI
                    fast_fill_condition = i[1] <= o.limit_price
                    slow_fill_condition = i[1] < o.limit_price
                    fill_cond_buy_limit = cfh.config_handler.get_val('BT_FILL_CONDITION_BUY_LIMIT')
                    if fill_cond_buy_limit == FillCondition.FAST:
                        fill_condition = fast_fill_condition
                    elif fill_cond_buy_limit == FillCondition.SLOW:
                        fill_condition = slow_fill_condition
                    else:
                        print("unknow fill condition")
                        return -1

                    if float(i[0]) > float(order_min_fill_time+cfh.config_handler.get_val('BT_DELAYS','limit_order_offset')) and fill_condition:
                        consec_cnt += 1
                        if consec_cnt == cfh.config_handler.get_val('BT_FILL_CONS_TRADES_REQUIRED'):

                            #(1679081919.381649, 27.88)
                            #ic(i)
                            fill_time = i[0]
                            
                            #v BT nikdy nekoupime za lepsi cenu nez LIMIT price (TBD mozna zmenit na parametr)
                            fill_price = o.limit_price
                            #fill_price = i[1]

                            print("FILL LIMIT BUY at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1])
                            if cfh.config_handler.get_val('BT_FILL_LOG_SURROUNDING_TRADES') != 0:
                                #TODO loguru
                                print("FILL SURR TRADES: before",work_range[index-cfh.config_handler.get_val('BT_FILL_LOG_SURROUNDING_TRADES'):index])
                                print("FILL SURR TRADES: fill and after",work_range[index:index+cfh.config_handler.get_val('BT_FILL_LOG_SURROUNDING_TRADES')])
                            break
                    else:
                        consec_cnt = 0
            else:
                consec_cnt = 0
                for index, i in enumerate(work_range):
                #print(i)
                    #NASTVENI PODMINEK PLNENI
                    fast_fill_condition = i[1] >= o.limit_price
                    slow_fill_condition = i[1] > o.limit_price
                    fill_conf_sell_cfg = cfh.config_handler.get_val('BT_FILL_CONDITION_SELL_LIMIT')
                    if fill_conf_sell_cfg == FillCondition.FAST:
                        fill_condition = fast_fill_condition
                    elif fill_conf_sell_cfg == FillCondition.SLOW:
                        fill_condition = slow_fill_condition
                    else:
                        print("unknown fill condition")
                        return -1

                    if float(i[0]) > float(order_min_fill_time+cfh.config_handler.get_val('BT_DELAYS','limit_order_offset')) and fill_condition:
                        consec_cnt += 1
                        if consec_cnt == cfh.config_handler.get_val('BT_FILL_CONS_TRADES_REQUIRED'):
                            #(1679081919.381649, 27.88)
                            #ic(i)
                            fill_time = i[0]

                            #support pomaleho plneni
                            #v BT nikdy neprodame za lepsi cenu nez LIMIT price (TBD mozna zmenit na parametr)
                            fill_price = o.limit_price


                            #fill_price = i[1]
                            print("FILL LIMIT SELL at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1])
                            surr_trades_cfg = cfh.config_handler.get_val('BT_FILL_LOG_SURROUNDING_TRADES')
                            if surr_trades_cfg != 0:
                                #TODO loguru
                                print("FILL SELL SURR TRADES: before",work_range[index-surr_trades_cfg:index])
                                print("FILL SELL SURR TRADES: fill and after",work_range[index:index+surr_trades_cfg])
                            break
                    else:
                        consec_cnt = 0
        
        elif o.order_type == OrderType.MARKET:
            for i in work_range:
                #print(i)
                #najde prvni nejvetsi čas vetsi nez minfill
                if i[0] > float(order_min_fill_time):
                    #(1679081919.381649, 27.88)
                    #ic(i)
                    fill_time = i[0]
                    fill_price = i[1]
                    #přičteme MARKET PREMIUM z konfigurace (je v pct nebo abs) (do budoucna mozna rozdilne pro BUY/SELL a nebo mozna z konfigurace pro dany titul)
                    cfg_premium = cfh.config_handler.get_val('BT_FILL_PRICE_MARKET_ORDER_PREMIUM')
                    if cfg_premium < 0: #configured as percentage
                        premium = abs(cfg_premium) * fill_price / 100.0
                    else: #configured as absolute value
                        premium = cfg_premium           
                    if o.side == OrderSide.BUY:
                        fill_price = fill_price + premium
                    elif o.side == OrderSide.SELL:
                        fill_price = fill_price - premium
                    
                    print("FILL ",o.side,"MARKET at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "cena", i[1])
                    break
        else:
            print("unknown order type")
            return -1
        
        if not fill_time:
            print("not FILLED")
            return 0
        else:

            #order FILLED - update trades and account and cash
            o.filled_at = datetime.fromtimestamp(float(fill_time))
            o.filled_qty = o.qty
            o.filled_avg_price = float(fill_price)
            o.status = OrderStatus.FILLED

            #ic(o.filled_at, o.filled_avg_price)

            a = self.update_account(o = o)
            if a < 0:
                tlog("update_account ERROR")
                return -1

            trade = TradeUpdate(order = o,
                                event = TradeEvent.FILL,
                                execution_id = str(uuid4()),
                                timestamp = datetime.fromtimestamp(fill_time),
                                position_qty= self.account[o.symbol][0],
                                price=float(fill_price),
                                qty = o.qty,
                                value = float(o.qty*fill_price),
                                cash = self.cash,
                                pos_avg_price = self.account[o.symbol][1])
            
            self.trades.append(trade)

            # do notification with callbacks
            #print("pred appendem",self.open_orders)
            self._do_notification_with_callbacks(tradeupdate=trade, time=float(fill_time))
            #print("po appendu",self.open_orders)
            #ohandlovat nejak chyby? v LIVE je to asynchronni a takze neni ohandlovano, takze jen print
            return 1

    def _do_notification_with_callbacks(self, tradeupdate: TradeUpdate, time: float):
    
        #do callbacku je třeba zpropagovat filltime čas (včetně latency pro notifikaci), aby se pripadne akce v callbacku udály s tímto časem
        self.time = time + float(cfh.config_handler.get_val('BT_DELAYS','fill_to_not'))
        print("current bt.time",self.time)
        #print("FILL NOTIFICATION: ", tradeupdate)
        res = asyncio.run(self.order_fill_callback(tradeupdate))
        return 0

    def update_account(self, o: Order):
        #updatujeme self.account
        #pokud neexistuje klic v accountu vytvorime si ho
        if o.symbol not in self.account:
            # { "BAC": [size, avgp] }
            self.account[o.symbol] = [0,0]

        if o.side == OrderSide.BUY:
            #[size, avgp]
            newsize = (self.account[o.symbol][0] + o.qty)
            #JPLNE UZAVRENI SHORT  (avgp 0)
            if newsize == 0: newavgp = 0
            #CASTECNE UZAVRENI SHORT (avgp puvodni)
            elif newsize < 0: newavgp = self.account[o.symbol][1]
            #JDE O LONG (avgp nove)
            else:
                newavgp = ((self.account[o.symbol][0] * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (self.account[o.symbol][0] + o.qty)
            
            self.account[o.symbol] = [newsize, newavgp]
            self.cash = self.cash - (o.qty * o.filled_avg_price)
            return 1
        #sell
        elif o.side == OrderSide.SELL:
            newsize = self.account[o.symbol][0]-o.qty
            #UPLNE UZAVRENI LONGU (avgp 0)
            if newsize == 0: newavgp = 0
            #CASTECNE UZAVRENI LONGU (avgp puvodni)
            elif newsize > 0: newavgp = self.account[o.symbol][1]
            #jde o SHORT (avgp nove)
            else:
                #pokud je predchozi 0 - tzn. jde o prvni short
                if self.account[o.symbol][1] == 0:
                    newavgp = o.filled_avg_price
                else:
                    newavgp = ((abs(self.account[o.symbol][0]) * self.account[o.symbol][1]) + (o.qty * o.filled_avg_price)) / (abs(self.account[o.symbol][0]) + o.qty)

            self.account[o.symbol] = [newsize, newavgp]

            #pokud jde o prodej longu(nova pozice je>=0) upravujeme cash
            if self.account[o.symbol][0] >= 0:
                self.cash = float(self.cash + (o.qty * o.filled_avg_price))
                print("uprava cashe, jde o prodej longu")
            else:
                self.cash = float(self.cash + (o.qty * o.filled_avg_price))
                #self.cash_reserved_for_shorting += float(o.qty * o.filled_avg_price)
                print("jde o short, upravujeme cash zatim stejne")    
            return 1
        else:
            print("neznaama side", o.side)
            return -1

    """""
    BACKEND PRO API
    
    TODO - možná toto předělat a brát si čas z bt.time - upravit také v BacktestInterface
    BUG: 
    """""

    def get_last_price(self, time: float, symbol: str = None):
        """""
        returns equity price in timestamp. Assumes myList is sorted. Returns first lower value to the number.
        optimalized as bisect left
        """""
        pos = bisect_left(self.btdata, (time,))
        #ic("vracime last price")
        #ic(time)
        if pos == 0:
            #ic(self.btdata[0][1])
            return self.btdata[0][1]
        afterTime, afterPrice = self.btdata[pos-1]
        #ic(afterPrice)
        return afterPrice


        #not optimalized:
        # for i in range(len(self.btdata)):
        #     #print(btdata[i][0])
        #     #print(i)
        #     if self.btdata[i][0] >= time:
        #         break
        # #ic(time, self.btdata[i-1][1])
        # #ic("get last price")
        # return self.btdata[i-1][1]

    def submit_order(self, time: float, symbol: str, side: OrderSide, size: int, order_type: OrderType, price: float = None):
        """submit order
        - zakladni validace
        - vloží do self.open_orders s daným časem
        - vrátí orderID

        status NEW se nenotifikuje

        TBD dotahovani aktualni ceny podle backtesteru
        """
        print("BT: submit order entry")

        if not time or time < 0:
            printanyway("time musi byt vyplneny")
            return -1

        if not size or int(size) < 0:
            printanyway("size musi byt vetsi nez 0")
            return -1

        if (order_type != OrderType.MARKET) and (order_type != OrderType.LIMIT):
            tlog("ordertype market and limit supported only", order_type)
            return -1

        if not side == OrderSide.BUY and not side == OrderSide.SELL:
            printanyway("side buy/sell required")
            return -1
        
        if order_type == OrderType.LIMIT and count_decimals(price) > 2:
            printanyway("only 2 decimals supported", price)
            return -1
    
        #pokud neexistuje klic v accountu vytvorime si ho
        if symbol not in self.account:
            # { "BAC": [size, avgp] }
            self.account[symbol] = [0,0]

        #check for available quantity
        if side == OrderSide.SELL:
            reserved = 0
            reserved_price = 0
            #with lock:
            for o in self.open_orders:
                if o.side == OrderSide.SELL and o.symbol == symbol and o.canceled_at is None:
                    reserved += o.qty
                    cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol)
                    reserved_price += o.qty * cena
                print("blokovano v open orders pro sell qty: ", reserved, "celkem:", reserved_price)
            
            actual_minus_reserved = int(self.account[symbol][0]) - reserved 
            if actual_minus_reserved > 0 and actual_minus_reserved - int(size) < 0:
                printanyway("not enough shares available to sell or shorting while long position",self.account[symbol][0],"reserved",reserved,"available",int(self.account[symbol][0]) - reserved,"selling",size)
                return -1
            
            #if is shorting - check available cash to short
            if actual_minus_reserved <= 0:
                cena = price if price else self.get_last_price(time, self.symbol)
                if (self.cash - reserved_price - float(int(size)*float(cena))) < 0:
                    printanyway("ERROR: not enough cash for shorting. cash",self.cash,"reserved",reserved,"available",self.cash-reserved,"needed",float(int(size)*float(cena)))
                    return -1               

        #check for available cash
        if side == OrderSide.BUY:
            reserved_qty = 0
            reserved_price = 0
            #with lock:
            for o in self.open_orders:
                if o.side == OrderSide.BUY and o.canceled_at is None:
                    cena = o.limit_price if o.limit_price else self.get_last_price(time, o.symbol)
                    reserved_price += o.qty * cena
                    reserved_qty += o.qty
                    print("blokovano v open orders for buy: qty, price", reserved_qty, reserved_price)

            actual_plus_reserved_qty = int(self.account[symbol][0]) + reserved_qty
            
            #jde o uzavreni shortu
            if actual_plus_reserved_qty < 0 and (actual_plus_reserved_qty + int(size)) > 0:
                printanyway("nejprve je treba uzavrit short pozici pro buy res_qty, size", actual_plus_reserved_qty, size)
                return -1

            #jde o standardni long, kontroluju cash
            if actual_plus_reserved_qty >= 0:
                cena = price if price else self.get_last_price(time, self.symbol)
                if (self.cash - reserved_price - float(int(size)*float(cena))) < 0:
                    printanyway("ERROR: not enough cash to buy long. cash",self.cash,"reserved_qty",reserved_qty,"reserved_price",reserved_price, "available",self.cash-reserved_price,"needed",float(int(size)*float(cena)))
                    return -1

        id = str(uuid4())
        order = Order(id=id,
                    submitted_at = datetime.fromtimestamp(float(time)),
                    symbol=symbol,
                    order_type = order_type,
                    status = OrderStatus.ACCEPTED,
                    side=side,
                    qty=int(size),
                    filled_qty=0,
                    limit_price=(float(price) if price else None))
        
        self.open_orders.append(order)
        #ic("order SUBMITTED", order)
        return id


    def replace_order(self, id: str, time: float, size: int = None, price: float = None):
        """replace order
        - zakladni validace vrací synchronně
        - vrací číslo nové objednávky
        """
        print("BT: replace order entry",id,size,price)

        if not price and not size:
            printanyway("size or price required")
            return -1

        if len(self.open_orders) == 0:
            printanyway("BT: order doesnt exist")
            return 0
        #with lock:
        for o in self.open_orders:
            print(o.id)
            if str(o.id) == str(id):
                newid = str(uuid4())
                o.id = newid
                o.submitted_at = datetime.fromtimestamp(time)
                o.qty = int((size if size else o.qty))
                o.limit_price = float(price if price else o.limit_price)
                print("order replaced")
                return newid
        print("BT: replacement order doesnt exist")
        return 0
    
    def cancel_order(self, time: float, id: str):
        """cancel order
        - základní validace vrací synchronně
        - vymaže objednávku z open orders
        - nastavuje v open orders  flag zrušeno, který pak exekuce ignoruje a odstraní
        (je tak podchycen stav, kdy se cancel volá z bt callbacku a z iterovaného listu by se odstraňovalo během iterace)

        TODO: teoreticky bych pred cancel order mohl zavolat execution, abych vyloucil, ze za 20ms zpozdeni, kdy se vola alpaca mi neprojde nejaka objednavka
        spise do budoucna
        """
        print("BT: cancel order entry",id)
        if len(self.open_orders) == 0:
            printanyway("BTC: order doesnt exist")
            return 0
        #with lock:
        for o in self.open_orders:
            if str(o.id) == id:
                o.canceled_at = time
                print("set as canceled in self.open_orders")
                return 1
        print("BTC: cantchange. open order doesnt exist")
        return 0
    
    def get_open_position(self, symbol: str):
        """get positions ->(avg,size)"""
        #print("BT:get open positions entry")
        try:
            return self.account[symbol][1], self.account[symbol][0]
        except:
            return (0,0)

    def get_open_orders(self, side: OrderSide, symbol: str):
        """get open orders ->list(OrderNotification)""" 
        print("BT:get open orders entry")
        if len(self.open_orders) == 0:
            print("BTC: order doesnt exist")
            return []
        res = []
        #with lock:
        for o in self.open_orders:
            #print(o)
            if o.symbol == symbol and o.canceled_at is None:
                if side is None or o.side == side:
                    res.append(o)
        return res

    ##toto bude predelano na display_results(ve variantach) umozni zobrazit result jak BT tak LIVE
    def display_backtest_result(self, state):
        """
        Displays backtest results chart, trades and orders with option to save the result as static HTML.
        """ 
        #open_orders to dataset
        oo_dict = AttributeDict(orderid=[],submitted_at=[],symbol=[],side=[],order_type=[],qty=[],limit_price=[],status=[])
        for t in self.open_orders:
            oo_dict.orderid.append(str(t.id))
            oo_dict.submitted_at.append(t.submitted_at)
            oo_dict.symbol.append(t.symbol)
            oo_dict.side.append(t.side)
            oo_dict.qty.append(t.qty)
            oo_dict.order_type.append(t.order_type)
            oo_dict.limit_price.append(t.limit_price)
            oo_dict.status.append(t.status)

        open_orders_df = pd.DataFrame(oo_dict)
        open_orders_df = open_orders_df.set_index('submitted_at', drop=False)

        #trades to dataset
        trade_dict = AttributeDict(orderid=[],timestamp=[],symbol=[],side=[],order_type=[],qty=[],price=[],position_qty=[],value=[],cash=[],pos_avg_price=[])
        for t in self.trades:
            trade_dict.orderid.append(str(t.order.id))
            trade_dict.timestamp.append(t.timestamp)
            trade_dict.symbol.append(t.order.symbol)
            trade_dict.side.append(t.order.side)
            trade_dict.qty.append(t.qty)
            trade_dict.price.append(t.price)
            trade_dict.position_qty.append(t.position_qty)
            trade_dict.value.append(t.value)
            trade_dict.cash.append(t.cash)
            trade_dict.order_type.append(t.order.order_type)
            trade_dict.pos_avg_price.append(t.pos_avg_price)

        trade_df = pd.DataFrame(trade_dict)
        trade_df = trade_df.set_index('timestamp',drop=False)

        #ohlcv dataset (TODO podporit i trady)
        hist_df = pd.DataFrame(state.bars)
        hist_df = hist_df.set_index('time', drop=False)

        #indicators
        #TODO vyresit if no indicators or no trades - pada na ValueError protoze pole obsahuje time, ale nikoliv indikatory
        #zatim jen takto workaround
        #print(state.indicators)
        try:
            ind_df = pd.DataFrame(state.indicators)
            ind_df = ind_df.set_index('time', drop=False)
        except ValueError as e:
            print("Value error", str(e))
            state.indicators = {'time': [] }
            ind_df = pd.DataFrame(state.indicators)
            ind_df = ind_df.set_index('time', drop=False)
            
        #print("Indicators", ind_df)
        #print(state.indicators)

        #KONEC přípravy dat
        fig = make_subplots(rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.02, row_heights=[0.7, 0.3], specs=[[{"secondary_y": True}],[{"secondary_y": True}]])


        # fig.add_trace(go.Scatter(x=trade_df.index,
        #                          y=trade_df.cash,
        #                          mode="lines+text",
        #                          name="Value"),
        #                 row = 1, col=1, secondary_y=True)

        #add_openorders 
        fig.add_trace(go.Scatter(x=open_orders_df.index,
                                    y=open_orders_df.limit_price,
                                    mode="markers+text",
                                    name="Open orders",
                                    customdata=open_orders_df.orderid,
                                    marker=dict(size=17, color='blue', symbol='arrow-bar-down'),
                                    text=open_orders_df.qty),
                        row = 1, col=1, secondary_y=False)
        
        #add trades
        fig.add_trace(go.Scatter(x=trade_df.loc[trade_df.side==OrderSide.BUY].index,
                                    y=trade_df.loc[trade_df.side==OrderSide.BUY].price,
                                    mode="markers+text",
                                    name="BUY Trades",
                                    customdata=trade_df.loc[trade_df.side==OrderSide.BUY].orderid,
                                    marker=dict(size=15, color='green', symbol='arrow-up'),
                                    text=trade_df.loc[trade_df.side==OrderSide.BUY].position_qty),
                        row = 1, col=1, secondary_y=False)

        fig.add_trace(go.Scatter(x=trade_df.loc[trade_df.side==OrderSide.SELL].index,
                                    y=trade_df.loc[trade_df.side==OrderSide.SELL].price,
                                    mode="markers+text",
                                    name="SELL Trades",
                                    customdata=trade_df.loc[trade_df.side==OrderSide.SELL].orderid,
                                    marker=dict(size=15, color='red', symbol='arrow-down'),
                                    text=trade_df.loc[trade_df.side==OrderSide.SELL].qty),
                        row = 1, col=1, secondary_y=False)
        
        #add avgprices of all buy trades
        
        fig.add_trace(go.Scatter(x=trade_df.loc[trade_df.side==OrderSide.BUY].index,
                    y=trade_df.loc[trade_df.side==OrderSide.BUY].pos_avg_price,
                    mode="markers+text",
                    name="BUY Trades avg prices",
                    customdata=trade_df.loc[trade_df.side==OrderSide.BUY].orderid,
                    marker=dict(size=15, color='blue', symbol='diamond-wide'),
                    text=trade_df.loc[trade_df.side==OrderSide.BUY].position_qty),
        row = 1, col=1, secondary_y=False)

        #display ohlcv
        fig.add_trace(go.Candlestick(x=hist_df.index,
                                    open=hist_df['open'],
                                    high=hist_df['high'],
                                    low=hist_df['low'],
                                    close=hist_df['close'],
                                    name = "OHLC"),
                        row = 1, col=1, secondary_y=False)

        #addvwap
        fig.add_trace(go.Scatter(x=hist_df.index,
                                    y=hist_df.vwap,
                                    mode="lines",
                                    opacity=1,
                                    name="VWAP"
                                    ),
                        row = 1, col=1,secondary_y=False)
        

        #display indicators from history
        for col in ind_df.columns:
            fig.add_trace(go.Scatter(x=ind_df.index, y = ind_df[col], mode = 'lines', name = col),
                          row = 1, col=1, secondary_y=False)

        fig.add_trace(go.Bar(x=hist_df.index, y=hist_df.volume, showlegend=True, marker_color='#ef5350', name='Volume'), row=2,
                 col=1)

        fig.update_layout(xaxis_rangeslider_visible=False)
        #fig.update_layout(title=f'Backtesting Results   '+str(Neco.vars), yaxis_title=f'Price')
        fig.update_layout(yaxis_title=f'Price')
        fig.update_yaxes(title_text=f'Price', secondary_y=False)
        fig.update_yaxes(title_text=f'Cash value', secondary_y=True)
        fig.update_yaxes(title_text=f'Volume', row=2, col=1)
        fig.update_xaxes(title_text='Date', row=2)


        # #remove gaps
        # alldays =set(hist_df.time[0]+timedelta(x) for x in range((hist_df.time[len(hist_df.time)-1]- hist_df.time[0]).days))
        # missing=sorted(set(alldays)-set(hist_df.time))

        
        rangebreaks=[
            # NOTE: Below values are bound (not single values), ie. hide x to y
            dict(bounds=["sat", "mon"]),  # hide weekends, eg. hide sat to before mon
            dict(bounds=[22, 15.5], pattern="hour"),  # hide hours outside of 9.30am-4pm
            # dict(values=["2020-12-25", "2021-01-01"])  # hide holidays (Christmas and New Year's, etc)
        ]

        fig.update_xaxes(rangebreaks=rangebreaks)


        ##START DASH
        app = Dash(__name__)

        ## Define the title for the app
        mytitle = dcc.Markdown('# Backtesting results')
        button = html.Button('save static', id='save', n_clicks=0)
        saved = html.Span('', id='saved')
        textik1 = html.Div('''
            Strategy:''' + state.name)
        textik2 = html.Div('''
            Tested period:'''+ self.bp_from.strftime("%d/%m/%Y, %H:%M:%S") + '-' + self.bp_to.strftime("%d/%m/%Y, %H:%M:%S"))
        textik3 = html.Div('''
            Stratvars:'''+ str(state.vars))
        textik35 = html.Div('''
            Resolution:'''+ str(state.resolution) + "s  rectype:" + str(state.rectype))
        textik4 = html.Div('''
            Started at:''' + self.backtest_start.strftime("%d/%m/%Y, %H:%M:%S") + " Duration:"+str(self.backtest_end-self.backtest_start))
        textik5 = html.Div('''
            Cash start:''' + str(self.cash_init)+ "  Cash final" + str(self.cash))
        textik55 = html.Div('''
            Positions:''' + str(self.account))
        textik6 = html.Div('''
            Open orders:''' + str(len(self.open_orders)))
        textik7 = html.Div('''
            Trades:''' + str(len(self.trades)))
        textik8 = html.Div('''
            Profit:''' + str(state.profit))
        textik9 = html.Div(f"{cfh.config_handler.get_val('BT_FILL_CONS_TRADES_REQUIRED')=}")
        textik10 = html.Div(f"{cfh.config_handler.get_val('BT_FILL_LOG_SURROUNDING_TRADES')=}")
        textik11 = html.Div(f"{cfh.config_handler.get_val('BT_FILL_CONDITION_BUY_LIMIT')=}")
        textik12 = html.Div(f"{cfh.config_handler.get_val('BT_FILL_CONDITION_SELL_LIMIT')=}")
        
        orders_title = dcc.Markdown('## Open orders')
        trades_title = dcc.Markdown('## Trades')
        ## Define the graph
        mygraph= dcc.Graph(id = "hlavni-graf", figure=fig)

        open_orders_table = dash_table.DataTable(
            id="orderstable",
            data=open_orders_df.to_dict('records'),
            columns=[{'id': c, 'name': c} for c in open_orders_df.columns],
            sort_action="native",
            row_selectable="single",
            column_selectable=False,
            fill_width = False,
            filter_action = "native",
            style_table={
                'height': 500,
                'overflowY': 'scroll'
            },
            style_header={
                'backgroundColor': 'lightgrey',
                'color': 'black'
            },
            style_data={
                'backgroundColor': 'white',
                'color': 'black'
            },
            style_cell={
                'overflow': 'hidden',
                'textOverflow': 'ellipsis',
                'maxWidth': 220,
                'minWidth': 5,
                'width': 5
            }
        )

        trades_table = dash_table.DataTable(
            id="tradestable",
            data=trade_df.to_dict('records'),
            columns=[{'id': c, 'name': c} for c in trade_df.columns],
            sort_action="native",
            row_selectable="single",
            column_selectable=False,
            fill_width = False,
            filter_action = "native",
            style_table={
                'height': 500,
                'overflowY': 'scroll'
            },
            style_header={
                'backgroundColor': 'lightgrey',
                'color': 'black'
            },
            style_data={
                'backgroundColor': 'white',
                'color': 'black'
            },
            style_cell={
                'overflow': 'hidden',
                'textOverflow': 'ellipsis',
                'maxWidth': 220,
                'minWidth': 5,
                'width': 5
            }
            # page_size=15
        )

        @app.callback(Output("tradestable", "style_data_conditional"),
                    Input("hlavni-graf", "hoverData"))
        def highlight(hoverData):
            #print(hoverData)
            if hoverData is None:
                return None
            try:
                row = hoverData["points"][0]["customdata"]
            except KeyError:
                return None
            #print(row)

            #print({"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"})
            return [
                {"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"}
            ]
        @app.callback(Output("orderstable", "style_data_conditional"),
                    Input("hlavni-graf", "hoverData"))
        def highlight(hoverData):
            #print(hoverData)
            if hoverData is None:
                return None
            try:
                row = hoverData["points"][0]["customdata"]
            except KeyError:
                return None
            #print(row)

            #print({"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"})
            return [
                {"if": {"filter_query": "{{orderid}}={}".format(row)}, "backgroundColor": "lightgrey"}
            ]

        @app.callback(
            Output('saved', 'children'),
            Input('save', 'n_clicks'),
        )
        def save_result(n_clicks):
            if n_clicks == 0:
                return 'not saved'
            else:
                bt_dir = DATA_DIR + "/backtestresults/" + self.symbol + self.bp_from.strftime("%d-%m-%y-%H-%M-%S") + ' ' + self.bp_to.strftime("%d-%m-%y-%H-%M-%S") + ' ' + str(datetime.now().microsecond)
                make_static(f'http://127.0.0.1:{port}/', bt_dir)
                return 'saved'

        ## Customize your layout
        app.layout = dbc.Container([mytitle,button,saved, textik1, textik2, textik3, textik35, textik4, textik5, textik55, textik6,textik7, textik8, textik9, textik10, textik11, textik12, mygraph, trades_title, trades_table, orders_title, open_orders_table])

        port = 9050
        print("Backtest FINSIHED"+str(self.backtest_end-self.backtest_start))
        threading.Thread(target=lambda: app.run(port=port, debug=False, use_reloader=False)).start()
        #app.run_server(debug=False, port = port)
        print("tady se spouští server")
        print("Jedeme dal?")
