from datetime import datetime

import requests.exceptions
from http.client import HTTPException
import urllib3.exceptions
from colorama import Fore, init
import socket
import time

from helpers.fileManagement import FileManager
from helpers.klinesManager import TaManager
from helpers.binaceTrader import BinanceTrader
from helpers.mobileNotifications import MobileNotifications
from helpers.phonePushNotifier import PhonePushNotifier
import numpy as np
from binance.helpers import round_step_size
from helpers.discordHooks import DiscordPager
from helpers.calculators import Calculators
from backendDCA.dcaManager import BackendDca
from binance.exceptions import BinanceAPIException, BinanceRequestException

init(autoreset=True)
file_manager = FileManager()

backend_dca = BackendDca()
settings = file_manager.read_json_file(file_name="dcaBotSettings.json")
notify_mobile = MobileNotifications(activated=False)
binance_trader = BinanceTrader(binance_data=settings["binance"])
klines_manager = TaManager()
calculate = Calculators()
push = PhonePushNotifier(settings["pushNotifier"]["username"], settings["pushNotifier"]["password"],
                         settings["pushNotifier"]["appName"], settings["pushNotifier"]["token"],
                         activated=settings["pushNotifier"]["active"])
discord = DiscordPager(wh_url=settings["discordHooks"]["url"], active=settings["discordHooks"]["active"])


def check_symbol_integrity(symbol: dict, last_price, tick_size, step_size, lot_size):
    """
    Check the integrity of the symbol. If either buy limit ID or sell limit ID are missing, it will
    create new ones
    :param symbol: symbol track data from database
    :param last_price:
    :param tick_size:
    :param step_size:
    :param lot_size:
    :return: recheck boolean if recheck has been done
    """
    if not isinstance(symbol["pendingBuyLimitOrderId"], int):
        average_dca_entry, total_invested, total_coins_owned = calculate.dca(
            symbol["liveDca"])
        # Calculate next drop from the average of entries in DCA to make a next limit buy accroding to params
        next_price_target = float(average_dca_entry) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))  # Format it for binance

        # # Sometimes it happens that price dropped bellow before next limit buy could be initiate/processed
        # # Therefore a safety switch needs to be applied to check the level otherwise the program brakes
        # # We do so by getting the calculate next drop according to DCA, verify it with the price from the
        # # exchange and if the price of the next level is greater than deduct another 1% from last marked price
        # # Hardcoded level if true is 0.5 % drop to last processed price as a next buy limit order
        if next_price_level > last_price:
            next_price_target = float(last_price) * 0.99
            next_price_level = float(
                binance_trader.float_precision(next_price_target, tick_size))

        # Take the previous investment amount add N percent to get next USDT SUM to invest
        # Take the investment quantity from last buy purchase and append to it required increase
        # Also check the lot size to fit the needs of the exchange
        # Get the last order from the list of all processed to get the next step
        investment_size_next = float(total_invested) * (
                1 + symbol["increasePurchasePerc"])  # total invested dollars so far

        limit_purchase_qty = float(
            investment_size_next / float(next_price_level))  # Calculate qty in coin to purchase

        addition_coin_qty = binance_trader.get_limit_buy_info(coin_quantity=limit_purchase_qty,
                                                              step_size=step_size)  # Format the quantity to be correct

        addition_coin_qty = round_step_size(quantity=addition_coin_qty, step_size=lot_size)

        # Execute new limit buy order on Binance
        new_limit_buy_order = binance_trader.make_limit_buy(symbol=symbol["symbol"],
                                                            qty_str=f'{addition_coin_qty}',
                                                            value=f'{next_price_level:.8f}')

        if isinstance(new_limit_buy_order, dict):
            data = {"toSet": {"pendingBuyLimitOrderId": new_limit_buy_order["orderId"],
                              "recheck": False}}
            backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data)

    if not isinstance(symbol["pendingLimitSellOrderId"], int):
        average_dca_entry, total_invested, total_coins_owned = calculate.dca(
            symbol["liveDca"])
        # Calculate new sell limit
        # Take the executed quantity from limit buy and append the quantity tracked from database
        # Representing total share of the coin. The value is than used as total sum to sell if TP gets
        # a hit.
        new_order_quantity = total_coins_owned

        # Format data to fit exchange
        new_price_level_sell, new_coin_qty_sell = binance_trader.get_limit_sell_info(
            price=average_dca_entry,
            gain_perc=symbol[
                "dcaTarget"],
            coin_quantity=new_order_quantity,
            tick_size=tick_size,
            step_size=step_size)

        new_limit_sell_result = binance_trader.make_limit_sell(symbol=symbol["symbol"],
                                                               quantity=f'{new_coin_qty_sell}',
                                                               price=f'{new_price_level_sell:.8f}')

        # Checking limit sell result response from Binance
        if isinstance(new_limit_sell_result, dict):
            data = {"toSet": {"pendingLimitSellOrderId": new_limit_sell_result["orderId"],
                              "recheck": False}}
            backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data)


def update_current_DCA(symbol, tick_size, step_size, lot_size, last_price, buy_limit_order_data):
    recheck = False  # USed in case of network error

    # Get details on last pending limit buy order
    backend_dca.trader_tracker.push_to_completed_buys(symbol=symbol["symbol"], value=buy_limit_order_data)

    update_message = f' [SPOT-UPDATE] {symbol["symbol"]}\n' \
                     f'Buy limit hit @{datetime.now().strftime("%m/%d/%Y, %H:%M:%S")}\n'

    # Calculate average DCA entry price
    # Update current DCA values with additional one as limit buy was executed to be be
    # able to get average entry. At the end update database with DCA values
    live_dca = symbol["liveDca"]
    live_dca.append({"qty": float(buy_limit_order_data["executedQty"]),
                     "price": float(buy_limit_order_data["price"])})

    new_coin_qty_total = 0
    new_total_invested_so_far = 0
    for d in live_dca:
        new_coin_qty_total += float(d["qty"]) * float(d["price"])
        new_total_invested_so_far += float(d["qty"])

    # make new buy limit either by price drop from last buy or based on average entry
    average_purchase_price_so_far = (new_coin_qty_total / new_total_invested_so_far)
    if symbol["buyEntryDcaBased"]:
        # Calculate next drop from the average of entries in DCA to make a next limit buy accroding to params
        next_price_target = float(average_purchase_price_so_far) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))  # Format it for binance
    else:
        next_price_target = float(buy_limit_order_data["price"]) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))  # Format it for binance

    # # Sometimes it happens that price dropped bellow before next limit buy could be initiate/processed
    # # Therefore a safety switch needs to be applied to check the level otherwise the program brakes
    # # We do so by getting the calculate next drop according to DCA, verify it with the price from the
    # # exchange and if the price of the next level is greater than deduct another 1% from last marked price
    # # Hardcoded level if true is 0.5 % drop to last processed price as a next buy limit order
    if next_price_level > last_price:
        next_price_target = float(last_price) * (0.99)
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))

    # Take the previous investment amount add N percent to get next USDT SUM to invest
    # Take the investment quantity from last buy purchase and append to it required increase
    # Also check the lot size to fit the needs of the exchange
    investment_size_next = float(buy_limit_order_data["cummulativeQuoteQty"]) * (
            1 + symbol["increasePurchasePerc"])  # total Dollar spent on last filled buy limit

    limit_purchase_qty = float(
        investment_size_next / float(next_price_level))  # Calculate qty in coin to purchase
    addition_coin_qty = binance_trader.get_limit_buy_info(coin_quantity=limit_purchase_qty,
                                                          step_size=step_size)  # Format the quantity to be correct

    addition_coin_qty = round_step_size(quantity=addition_coin_qty, step_size=lot_size)

    # Execute new limit buy order on Binance
    new_limit_buy_order = binance_trader.make_limit_buy(symbol=symbol["symbol"],
                                                        qty_str=f'{addition_coin_qty}',
                                                        value=f'{next_price_level:.8f}')

    # Process binance response when trying to set limit buy order
    if not isinstance(new_limit_buy_order, dict):
        print(Fore.LIGHTRED_EX + f'Limit buy could not be set: {new_limit_buy_order}')
        update_message += f"Could not place new buy limit: {new_limit_buy_order}\n"
        limit_buy_id = None
        recheck = True
    else:
        update_message += "New buy limit placed!\n"
        limit_buy_id = new_limit_buy_order["orderId"]

    # Store executed limit buy into DB
    # Updating basic data in database according to last filled buy
    backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data={
        "toSet": {"liveDca": live_dca,
                  "lastPurchasePrice": buy_limit_order_data["price"],
                  "currentAverageDCA": f'{average_purchase_price_so_far:.8f}'},
        "toInc": {"ownedCoinQTY": float(buy_limit_order_data["executedQty"]),
                  "investedUsdt": float(buy_limit_order_data["cummulativeQuoteQty"]),
                  "availableMoney": float(buy_limit_order_data["cummulativeQuoteQty"]) * (-1),
                  "levelCounter": 1},

    })

    # MAKE NEXT LIMIT SELL
    # Cancel the previous limit sell order as the last buy limit has been processed due to different DCA
    removed_order_result = binance_trader.remove_order(symbol=symbol["symbol"],
                                                       order_id=symbol["pendingLimitSellOrderId"])

    if not isinstance(removed_order_result, dict):
        update_message += "Current TP Could not be removed!\n"

    # Calculate new sell limit
    # Take the executed quantity from limit buy and append the quantity tracked from database
    # Representing total share of the coin. The value is than used as total sum to sell if TP gets
    # a hit.
    new_order_quantity = float(buy_limit_order_data["executedQty"]) + float(
        symbol["ownedCoinQTY"])

    # Format data to fit exchange
    new_price_level_sell, new_coin_qty_sell = binance_trader.get_limit_sell_info(
        price=average_purchase_price_so_far,
        gain_perc=symbol[
            "dcaTarget"],
        coin_quantity=new_order_quantity,
        tick_size=tick_size,
        step_size=step_size)

    new_limit_sell_result = binance_trader.make_limit_sell(symbol=symbol["symbol"],
                                                           quantity=f'{new_coin_qty_sell}',
                                                           price=f'{new_price_level_sell:.8f}')

    # Checking limit sell result response from Binance
    if not isinstance(new_limit_sell_result, dict):
        print(Fore.RED + "Limit Sell order could not be placed")
        update_message += f" Limit Sell order could not be placed {new_limit_sell_result}\n"
        limit_sell_id = None
        recheck = True
    else:
        update_message += f"New TP placed successfully!\n"
        limit_sell_id = new_limit_sell_result["orderId"]

    # Update order IDs in database
    backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data={
        "toSet": {"pendingLimitSellOrderId": limit_sell_id,
                  "pendingBuyLimitOrderId": limit_buy_id,
                  "recheck": recheck}
    })

    notify_mobile.send_message(message=update_message)
    push.phone_ping(text=update_message)
    discord.push_discord_action(symbol=symbol["symbol"], text=update_message)


def process_dca_results(symbol, sell_limit_order_data):
    available_money = symbol["availableMoney"]  # Get total available money from DB for symbol
    total_invested = float(
        symbol["investedUsdt"])  # Total invested from all trades till the TP hit

    # Latest data
    usdt_from_tp = int(float(sell_limit_order_data["cummulativeQuoteQty"]) * (10 ** 2)) / (10 ** 2)
    new_balance = (int((float(available_money) + float(usdt_from_tp)) * (10 ** 2))) / (10 ** 2)
    net_gain = usdt_from_tp - total_invested

    # After the results at the end of DCA have been processed we need to reset the database for
    # next DCA run to happen from scratch.

    # Removing last buy limit as the target has been reached
    binance_trader.remove_order(symbol=symbol["symbol"], order_id=symbol["pendingBuyLimitOrderId"])

    data_to_reset = {"toSet": {"availableMoney": new_balance,
                               "liveDca": [],
                               "lastRoundEarned": round(net_gain, 3),
                               "levelCounter": 0,
                               "lastPurchasePrice": None,
                               "currentAverageDCA": None,
                               "allCompletedBuys": list(),
                               "pendingBuyLimitOrderId": None,
                               "pendingLimitSellOrderId": None,
                               "dcaInitOrderId": None,
                               "ownedCoinQTY": int(0),
                               "investedUsdt": int(0)},
                     "toInc": {"earned": round(net_gain, 3),
                               "completedDca": 1}
                     }
    backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data_to_reset)

    notfy = f'[SPOT-RESULT] {symbol["symbol"]}\n' \
            f'@{datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S")}\n' \
            f'Total Invested: {total_invested:2f} USDT\n' \
            f'NET gain: {net_gain:.2f} USDT'
    notify_mobile.send_message(message=notfy)
    push.phone_ping(text=notfy)
    discord.push_discord_tp_hit(symbol=symbol["symbol"], text=notfy)


def run_dca_for_symbol(symbol: dict, last_price):
    """
    Process the symbol which is active according to DCa status
    :param symbol:
    :param last_price:
    :return:
    """
    # Load the symbol profile to calculate tikck, step and lot sizes
    symbol_profile = backend_dca.trader_tracker.get_symbol_profile(symbol=symbol["symbol"])
    tick_size, lot_size = binance_trader.get_tick_and_step_size(symbol_profile)

    # Check  if DCA has been initiated already
    print(Fore.LIGHTBLUE_EX + f'Checking symbol {symbol["symbol"]}')
    if symbol["liveDca"]:
        # Check the symbol integrity  if buy or sell limit order are not created in the past it will

        sell_limit_order_data = binance_trader.get_order(symbol=symbol["symbol"],
                                                         order_id=symbol["pendingLimitSellOrderId"])
        if isinstance(sell_limit_order_data, dict):
            # Check if TP has been reached and re-initiate the DCA if set
            if sell_limit_order_data['status'] == 'FILLED':
                process_dca_results(symbol=symbol, sell_limit_order_data=sell_limit_order_data)
                if not symbol["finishAndStop"]:
                    # Re pull new coin data
                    new_symbol_data = backend_dca.trader_tracker.get_symbol_track(symbol=symbol["symbol"])
                    initiate_new_dca(new_symbol_data, symbol_profile, tick_size, lot_size=lot_size,
                                     last_price=last_price)
                else:
                    if backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"],
                                                                      data={"toSet": {"finishAndStop": False,
                                                                                      "trading": False}}):
                        push.phone_ping(text=f'[SPOT-STOP]{symbol["symbol"]}\n'
                                             f'Symbol has stop trading')
                        discord.push_discord_tp_hit(symbol=symbol["symbol"],
                                                    text=f'[SPOT-STOP] {symbol["symbol"]}\n'
                                                         f'Symbol has stop trading')
                    else:
                        print(Fore.RED + f"Could not stop the DCA for symbol {symbol['symbol']}")
            else:
                buy_limit_order = binance_trader.get_order(symbol=symbol["symbol"],
                                                           order_id=symbol["pendingBuyLimitOrderId"])
                if isinstance(buy_limit_order, dict):
                    if buy_limit_order["status"] == "FILLED":
                        update_current_DCA(symbol=symbol, tick_size=tick_size, step_size=lot_size,
                                           last_price=last_price,
                                           lot_size=lot_size, buy_limit_order_data=buy_limit_order)
                    else:
                        pass
                else:
                    discord.push_discord_error(symbol=symbol["symbol"], error=sell_limit_order_data,
                                               area='Buy limit check error')
        else:
            discord.push_discord_error(symbol=symbol["symbol"], error=sell_limit_order_data, area='Limit sell check')
    else:
        print(Fore.LIGHTMAGENTA_EX + f"Starting fresh DCA for {symbol['symbol']}")
        initiate_new_dca(symbol, symbol_profile, tick_size, lot_size=lot_size, last_price=last_price)  # This works


def initiate_new_dca(symbol, symbol_profile, tick_size, lot_size, last_price):
    """
    start the new DCA circle
    :param symbol:
    :param symbol_profile:
    :param tick_size:
    :return:
    """

    step_size = symbol_profile['filters'][2]['stepSize']

    # Get starting value in $
    starting_value = symbol["firstEntryValue"]

    # Check if dynamic start % from total available funds is activate and calculate dynamic start
    if symbol["dcaDynamicStart"] > 0:
        starting_value = round(symbol["availableMoney"] * symbol["dcaDynamicStart"],
                               2)  # calculate percantage share

    # Check if compounding is activated and append the compounding to the start
    if symbol["useCompound"]:
        starting_value += round(float(symbol["earned"]), 2)  # Append totla earned so far

    # Make market order
    market_result = binance_trader.market_quote_buy(symbol=symbol["symbol"], quote_qty=starting_value)
    if isinstance(market_result, dict) and market_result["status"] == 'FILLED':
        # Make limit orders
        initial_order_id = market_result["orderId"]
        fills_list = market_result["fills"]
        qty_purchased = 0
        avg_prices = []
        dca_data = []

        commission_bnb = float()
        # Process commision
        for x in fills_list:
            qty_purchased += float(x["qty"])
            avg_prices.append(float(x["price"]))
            # Append total commission in bnb
            if x["commissionAsset"] == "BNB":
                commission_bnb += float(x["commission"])

        array_prices = np.array(avg_prices)
        average_purchase_price = np.average(array_prices)

        # Append the info from the trade
        dict_to_store = {
            "qty": qty_purchased,
            "price": average_purchase_price
        }
        dca_data.append(dict_to_store)

        # Stuff to updated in Database
        data = {"toSet": {"lastPurchasePrice": f'{average_purchase_price:.8f}',
                          "liveDca": [dict_to_store],
                          "currentAverageDCA": f'{average_purchase_price:.8f}',
                          "dcaStart": market_result["transactTime"],
                          "dcaInitOrderId": initial_order_id},
                "toInc": {"ownedCoinQTY": qty_purchased,
                          "investedUsdt": float(market_result["cummulativeQuoteQty"]),
                          "availableMoney": float(market_result["cummulativeQuoteQty"]) * (-1),
                          "commissionBnb": float(commission_bnb)}
                }

        # Push completed buy data to db
        backend_dca.trader_tracker.push_to_completed_buys(symbol=symbol["symbol"], value=market_result)

        # Initiate Limit sell target order incase if no other levels
        order_quantity = float(market_result["executedQty"])
        order_quantity = round_step_size(quantity=order_quantity, step_size=lot_size)

        # Calculate price level to cash in profit
        limit_sell_price, limit_sell_qty = binance_trader.get_limit_sell_info(
            price=average_purchase_price,
            gain_perc=symbol["dcaTarget"],
            coin_quantity=order_quantity,
            tick_size=tick_size, step_size=step_size)

        # Create limit sell order
        limit_sell_result = binance_trader.make_limit_sell(symbol=symbol["symbol"],
                                                           quantity=limit_sell_qty,
                                                           price=f'{limit_sell_price:.8f}')
        # # Initiate limit buy/safety order
        investment_size = float(market_result["cummulativeQuoteQty"]) * (
                1 + symbol["increasePurchasePerc"])
        buy_limit_target = float(average_purchase_price) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(binance_trader.float_precision(buy_limit_target, tick_size))

        # Increase purchase value according to params
        limit_purchase_qty = float(investment_size / float(next_price_level))
        limit_buy_coin_qty = binance_trader.get_limit_buy_info(coin_quantity=limit_purchase_qty,
                                                               step_size=step_size)
        if lot_size <= 0:
            limit_buy_coin_qty = int(limit_buy_coin_qty)

        first_limit_buy = binance_trader.make_limit_buy(symbol=symbol["symbol"],
                                                        qty_str=f'{limit_buy_coin_qty}',
                                                        value=f'{next_price_level:.8f}')

        ### Check all limit orders and update in database accordingly
        if isinstance(limit_sell_result, dict) and limit_sell_result["status"] == "NEW":
            limit_sell_id = limit_sell_result["orderId"]
        elif isinstance(limit_sell_result, dict) and limit_sell_result["status"] != "NEW":
            print(Fore.RED + f'Limit Sell Error: {limit_sell_result}')
            push.phone_ping(text=f'NEW DCA Limit Sell error {symbol["symbol"]}\n'
                                 f'Could not set limit Sell: {limit_sell_result}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit sell error due to status : {limit_sell_result['status']}",
                                       area="DCA Start script")
            limit_sell_id = None

        elif not isinstance(limit_sell_result, dict):
            print(Fore.RED + f'Limit Sell Error: {limit_sell_result}')
            push.phone_ping(text=f'NEW DCA Limit Sell error {symbol["symbol"]}\n'
                                 f'Could not set limit Sell: {limit_sell_result}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit sell error: {limit_sell_result}",
                                       area="DCA Start script")
            limit_sell_id = None

        if isinstance(first_limit_buy, dict) and first_limit_buy['status'] == "NEW":
            limit_buy_id = first_limit_buy["orderId"]

        elif isinstance(first_limit_buy, dict) and first_limit_buy["status"] != "NEW":
            print(Fore.RED + f'Limit Buy Error status: {limit_sell_result["status"]}')
            push.phone_ping(text=f'NEW DCA Limit Buy error {symbol["symbol"]}\n'
                                 f'Could not set limit buy order due to status: {limit_sell_result["status"]}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit buy error status: {first_limit_buy['status']}",
                                       area="DCA start script")
            first_limit_buy = None
        if not isinstance(first_limit_buy, dict):
            print(Fore.RED + f'Limit Buy Error: {limit_sell_result}')
            push.phone_ping(text=f'NEW DCA Limit Buy error {symbol["symbol"]}\n'
                                 f'Could not set limit buy order: {limit_sell_result}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit buy error: {first_limit_buy}", area="DCA start script")
            limit_buy_id = None

        data["toSet"].update({"pendingBuyLimitOrderId": limit_buy_id,
                              "pendingLimitSellOrderId": limit_sell_id})

        print(Fore.LIGHTGREEN_EX + f'DCA FOR {symbol["symbol"]} started successfully...')
        backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data)
        message = f'[SPOT-DCA-START] {symbol["symbol"]}\n' \
                  f'Time: {datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S")}'
        notify_mobile.send_message(message=message)
        push.phone_ping(text=message)
        discord.push_discord_action(symbol=symbol["symbol"], text=message)
    else:
        if isinstance(market_result, dict):
            message = f'{datetime.now().strftime("%m/%d/%Y, %H:%M:%S")} Could not initiate DCA for ' \
                      f'{symbol["symbol"].upper()} with market order due to status:' \
                      f'{market_result["status"]}'
            print(Fore.RED + message)
            push.phone_ping(text=message)
            discord.push_discord_error(symbol=symbol["symbol"].upper(), error=message,
                                       area='DCA Start with market order')
        else:
            message = f'{datetime.now().strftime("%m/%d/%Y, %H:%M:%S")} Could not initiate DCA for ' \
                      f'{symbol["symbol"].upper()} with market order due to:' \
                      f'{market_result}'
            print(Fore.RED + message)
            push.phone_ping(text=message)
            discord.push_discord_error(symbol=symbol["symbol"].upper(), error=message,
                                       area='DCA Start with market order')


def check_ticker_db(all_symbols: list):
    """
    Check if all required DB entries are created to be used BY DCA system
    """
    # Check every coin from dcaBotSettings
    for s in all_symbols:
        # Check if profile is registered
        if not backend_dca.trader_tracker.get_symbol_profile(symbol=s["symbol"]):
            # Get symbol data from exchange
            symbol_data = binance_trader.get_symbol_data(symbol=s["symbol"])
            if backend_dca.trader_tracker.store_symbol_profile(symbol=symbol_data):
                pass
    return


def main():
    integrated_symbols = backend_dca.trader_tracker.get_all_symbols()
    check_ticker_db(integrated_symbols)
    activated = binance_trader.init_client()

    if activated:
        klines_manager.set_client(client=binance_trader.client)
        while True:
            time.sleep(settings["botProperties"]["dcaSleep"])
            try:
                binance_trader.client.ping()
                print(Fore.LIGHTCYAN_EX + f"Checking DCA symbols @ {datetime.utcnow().strftime('%m/%d/%Y, %H:%M:%S')}")
                live_symbols = backend_dca.trader_tracker.get_symbols_by_trading_state(active=True)
                all_prices = binance_trader.get_all_pricess()
                if all_prices:
                    for s in live_symbols:
                        last_price = float(next(x for x in all_prices if x["symbol"] == s["symbol"])["price"])
                        run_dca_for_symbol(s, last_price)
                else:
                    print(Fore.RED + 'Could not get live prices')
                    pass
                print(Fore.CYAN + "Checked all...sleeping!")
            except (BinanceAPIException, BinanceRequestException) as e:
                push.phone_ping(text=f'Binance exception on Main:\n{e.message}')
                discord.push_discord_error(symbol="Binance exception Main", error=e.message, area="DCA main")
                pass
            except (requests.exceptions.ConnectionError, urllib3.exceptions.ProtocolError, HTTPException, socket.error,
                    urllib3.exceptions.ReadTimeoutError, requests.exceptions.ReadTimeout) as e:
                push.phone_ping(text=f'Network Error:\n{e}')
                # discord.push_discord_error(symbol="Network error", error=e, area='DCA Main')
                pass
    else:
        print(Fore.RED + "Please check internet connection")


if __name__ == "__main__":
    main()

from datetime import datetime

import requests.exceptions
from http.client import HTTPException
import urllib3.exceptions
from colorama import Fore, init
import socket
import time

from helpers.fileManagement import FileManager
from helpers.klinesManager import TaManager
from helpers.binaceTrader import BinanceTrader
from helpers.mobileNotifications import MobileNotifications
from helpers.phonePushNotifier import PhonePushNotifier
import numpy as np
from binance.helpers import round_step_size
from helpers.discordHooks import DiscordPager
from helpers.calculators import Calculators
from backendDCA.dcaManager import BackendDca
from binance.exceptions import BinanceAPIException, BinanceRequestException

init(autoreset=True)
file_manager = FileManager()

backend_dca = BackendDca()
settings = file_manager.read_json_file(file_name="dcaBotSettings.json")
notify_mobile = MobileNotifications(activated=False)
binance_trader = BinanceTrader(binance_data=settings["binance"])
klines_manager = TaManager()
calculate = Calculators()
push = PhonePushNotifier(settings["pushNotifier"]["username"], settings["pushNotifier"]["password"],
                         settings["pushNotifier"]["appName"], settings["pushNotifier"]["token"],
                         activated=settings["pushNotifier"]["active"])
discord = DiscordPager(wh_url=settings["discordHooks"]["url"], active=settings["discordHooks"]["active"])


def check_symbol_integrity(symbol: dict, last_price, tick_size, step_size, lot_size):
    """
    Check the integrity of the symbol. If either buy limit ID or sell limit ID are missing, it will
    create new ones
    :param symbol: symbol track data from database
    :param last_price:
    :param tick_size:
    :param step_size:
    :param lot_size:
    :return: recheck boolean if recheck has been done
    """
    if not isinstance(symbol["pendingBuyLimitOrderId"], int):
        average_dca_entry, total_invested, total_coins_owned = calculate.dca(
            symbol["liveDca"])
        # Calculate next drop from the average of entries in DCA to make a next limit buy accroding to params
        next_price_target = float(average_dca_entry) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))  # Format it for binance

        # # Sometimes it happens that price dropped bellow before next limit buy could be initiate/processed
        # # Therefore a safety switch needs to be applied to check the level otherwise the program brakes
        # # We do so by getting the calculate next drop according to DCA, verify it with the price from the
        # # exchange and if the price of the next level is greater than deduct another 1% from last marked price
        # # Hardcoded level if true is 0.5 % drop to last processed price as a next buy limit order
        if next_price_level > last_price:
            next_price_target = float(last_price) * 0.99
            next_price_level = float(
                binance_trader.float_precision(next_price_target, tick_size))

        # Take the previous investment amount add N percent to get next USDT SUM to invest
        # Take the investment quantity from last buy purchase and append to it required increase
        # Also check the lot size to fit the needs of the exchange
        # Get the last order from the list of all processed to get the next step
        investment_size_next = float(total_invested) * (
                1 + symbol["increasePurchasePerc"])  # total invested dollars so far

        limit_purchase_qty = float(
            investment_size_next / float(next_price_level))  # Calculate qty in coin to purchase

        addition_coin_qty = binance_trader.get_limit_buy_info(coin_quantity=limit_purchase_qty,
                                                              step_size=step_size)  # Format the quantity to be correct

        addition_coin_qty = round_step_size(quantity=addition_coin_qty, step_size=lot_size)

        # Execute new limit buy order on Binance
        new_limit_buy_order = binance_trader.make_limit_buy(symbol=symbol["symbol"],
                                                            qty_str=f'{addition_coin_qty}',
                                                            value=f'{next_price_level:.8f}')

        if isinstance(new_limit_buy_order, dict):
            data = {"toSet": {"pendingBuyLimitOrderId": new_limit_buy_order["orderId"],
                              "recheck": False}}
            backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data)

    if not isinstance(symbol["pendingLimitSellOrderId"], int):
        average_dca_entry, total_invested, total_coins_owned = calculate.dca(
            symbol["liveDca"])
        # Calculate new sell limit
        # Take the executed quantity from limit buy and append the quantity tracked from database
        # Representing total share of the coin. The value is than used as total sum to sell if TP gets
        # a hit.
        new_order_quantity = total_coins_owned

        # Format data to fit exchange
        new_price_level_sell, new_coin_qty_sell = binance_trader.get_limit_sell_info(
            price=average_dca_entry,
            gain_perc=symbol[
                "dcaTarget"],
            coin_quantity=new_order_quantity,
            tick_size=tick_size,
            step_size=step_size)

        new_limit_sell_result = binance_trader.make_limit_sell(symbol=symbol["symbol"],
                                                               quantity=f'{new_coin_qty_sell}',
                                                               price=f'{new_price_level_sell:.8f}')

        # Checking limit sell result response from Binance
        if isinstance(new_limit_sell_result, dict):
            data = {"toSet": {"pendingLimitSellOrderId": new_limit_sell_result["orderId"],
                              "recheck": False}}
            backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data)


def update_current_DCA(symbol, tick_size, step_size, lot_size, last_price, buy_limit_order_data):
    recheck = False  # USed in case of network error

    # Get details on last pending limit buy order
    backend_dca.trader_tracker.push_to_completed_buys(symbol=symbol["symbol"], value=buy_limit_order_data)

    update_message = f' [SPOT-UPDATE] {symbol["symbol"]}\n' \
                     f'Buy limit hit @{datetime.now().strftime("%m/%d/%Y, %H:%M:%S")}\n'

    # Calculate average DCA entry price
    # Update current DCA values with additional one as limit buy was executed to be be
    # able to get average entry. At the end update database with DCA values
    live_dca = symbol["liveDca"]
    live_dca.append({"qty": float(buy_limit_order_data["executedQty"]),
                     "price": float(buy_limit_order_data["price"])})

    new_coin_qty_total = 0
    new_total_invested_so_far = 0
    for d in live_dca:
        new_coin_qty_total += float(d["qty"]) * float(d["price"])
        new_total_invested_so_far += float(d["qty"])

    # make new buy limit either by price drop from last buy or based on average entry
    average_purchase_price_so_far = (new_coin_qty_total / new_total_invested_so_far)
    if symbol["buyEntryDcaBased"]:
        # Calculate next drop from the average of entries in DCA to make a next limit buy accroding to params
        next_price_target = float(average_purchase_price_so_far) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))  # Format it for binance
    else:
        next_price_target = float(buy_limit_order_data["price"]) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))  # Format it for binance

    # # Sometimes it happens that price dropped bellow before next limit buy could be initiate/processed
    # # Therefore a safety switch needs to be applied to check the level otherwise the program brakes
    # # We do so by getting the calculate next drop according to DCA, verify it with the price from the
    # # exchange and if the price of the next level is greater than deduct another 1% from last marked price
    # # Hardcoded level if true is 0.5 % drop to last processed price as a next buy limit order
    if next_price_level > last_price:
        next_price_target = float(last_price) * (0.99)
        next_price_level = float(
            binance_trader.float_precision(next_price_target, tick_size))

    # Take the previous investment amount add N percent to get next USDT SUM to invest
    # Take the investment quantity from last buy purchase and append to it required increase
    # Also check the lot size to fit the needs of the exchange
    investment_size_next = float(buy_limit_order_data["cummulativeQuoteQty"]) * (
            1 + symbol["increasePurchasePerc"])  # total Dollar spent on last filled buy limit

    limit_purchase_qty = float(
        investment_size_next / float(next_price_level))  # Calculate qty in coin to purchase
    addition_coin_qty = binance_trader.get_limit_buy_info(coin_quantity=limit_purchase_qty,
                                                          step_size=step_size)  # Format the quantity to be correct

    addition_coin_qty = round_step_size(quantity=addition_coin_qty, step_size=lot_size)

    # Execute new limit buy order on Binance
    new_limit_buy_order = binance_trader.make_limit_buy(symbol=symbol["symbol"],
                                                        qty_str=f'{addition_coin_qty}',
                                                        value=f'{next_price_level:.8f}')

    # Process binance response when trying to set limit buy order
    if not isinstance(new_limit_buy_order, dict):
        print(Fore.LIGHTRED_EX + f'Limit buy could not be set: {new_limit_buy_order}')
        update_message += f"Could not place new buy limit: {new_limit_buy_order}\n"
        limit_buy_id = None
        recheck = True
    else:
        update_message += "New buy limit placed!\n"
        limit_buy_id = new_limit_buy_order["orderId"]

    # Store executed limit buy into DB
    # Updating basic data in database according to last filled buy
    backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data={
        "toSet": {"liveDca": live_dca,
                  "lastPurchasePrice": buy_limit_order_data["price"],
                  "currentAverageDCA": f'{average_purchase_price_so_far:.8f}'},
        "toInc": {"ownedCoinQTY": float(buy_limit_order_data["executedQty"]),
                  "investedUsdt": float(buy_limit_order_data["cummulativeQuoteQty"]),
                  "availableMoney": float(buy_limit_order_data["cummulativeQuoteQty"]) * (-1),
                  "levelCounter": 1},

    })

    # MAKE NEXT LIMIT SELL
    # Cancel the previous limit sell order as the last buy limit has been processed due to different DCA
    removed_order_result = binance_trader.remove_order(symbol=symbol["symbol"],
                                                       order_id=symbol["pendingLimitSellOrderId"])

    if not isinstance(removed_order_result, dict):
        update_message += "Current TP Could not be removed!\n"

    # Calculate new sell limit
    # Take the executed quantity from limit buy and append the quantity tracked from database
    # Representing total share of the coin. The value is than used as total sum to sell if TP gets
    # a hit.
    new_order_quantity = float(buy_limit_order_data["executedQty"]) + float(
        symbol["ownedCoinQTY"])

    # Format data to fit exchange
    new_price_level_sell, new_coin_qty_sell = binance_trader.get_limit_sell_info(
        price=average_purchase_price_so_far,
        gain_perc=symbol[
            "dcaTarget"],
        coin_quantity=new_order_quantity,
        tick_size=tick_size,
        step_size=step_size)

    new_limit_sell_result = binance_trader.make_limit_sell(symbol=symbol["symbol"],
                                                           quantity=f'{new_coin_qty_sell}',
                                                           price=f'{new_price_level_sell:.8f}')

    # Checking limit sell result response from Binance
    if not isinstance(new_limit_sell_result, dict):
        print(Fore.RED + "Limit Sell order could not be placed")
        update_message += f" Limit Sell order could not be placed {new_limit_sell_result}\n"
        limit_sell_id = None
        recheck = True
    else:
        update_message += f"New TP placed successfully!\n"
        limit_sell_id = new_limit_sell_result["orderId"]

    # Update order IDs in database
    backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data={
        "toSet": {"pendingLimitSellOrderId": limit_sell_id,
                  "pendingBuyLimitOrderId": limit_buy_id,
                  "recheck": recheck}
    })

    notify_mobile.send_message(message=update_message)
    push.phone_ping(text=update_message)
    discord.push_discord_action(symbol=symbol["symbol"], text=update_message)


def process_dca_results(symbol, sell_limit_order_data):
    available_money = symbol["availableMoney"]  # Get total available money from DB for symbol
    total_invested = float(
        symbol["investedUsdt"])  # Total invested from all trades till the TP hit

    # Latest data
    usdt_from_tp = int(float(sell_limit_order_data["cummulativeQuoteQty"]) * (10 ** 2)) / (10 ** 2)
    new_balance = (int((float(available_money) + float(usdt_from_tp)) * (10 ** 2))) / (10 ** 2)
    net_gain = usdt_from_tp - total_invested

    # After the results at the end of DCA have been processed we need to reset the database for
    # next DCA run to happen from scratch.

    # Removing last buy limit as the target has been reached
    binance_trader.remove_order(symbol=symbol["symbol"], order_id=symbol["pendingBuyLimitOrderId"])

    data_to_reset = {"toSet": {"availableMoney": new_balance,
                               "liveDca": [],
                               "lastRoundEarned": round(net_gain, 3),
                               "levelCounter": 0,
                               "lastPurchasePrice": None,
                               "currentAverageDCA": None,
                               "allCompletedBuys": list(),
                               "pendingBuyLimitOrderId": None,
                               "pendingLimitSellOrderId": None,
                               "dcaInitOrderId": None,
                               "ownedCoinQTY": int(0),
                               "investedUsdt": int(0)},
                     "toInc": {"earned": round(net_gain, 3),
                               "completedDca": 1}
                     }
    backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data_to_reset)

    notfy = f'[SPOT-RESULT] {symbol["symbol"]}\n' \
            f'@{datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S")}\n' \
            f'Total Invested: {total_invested:2f} USDT\n' \
            f'NET gain: {net_gain:.2f} USDT'
    notify_mobile.send_message(message=notfy)
    push.phone_ping(text=notfy)
    discord.push_discord_tp_hit(symbol=symbol["symbol"], text=notfy)


def run_dca_for_symbol(symbol: dict, last_price):
    """
    Process the symbol which is active according to DCa status
    :param symbol:
    :param last_price:
    :return:
    """
    # Load the symbol profile to calculate tikck, step and lot sizes
    symbol_profile = backend_dca.trader_tracker.get_symbol_profile(symbol=symbol["symbol"])
    tick_size, lot_size = binance_trader.get_tick_and_step_size(symbol_profile)

    # Check  if DCA has been initiated already
    print(Fore.LIGHTBLUE_EX + f'Checking symbol {symbol["symbol"]}')
    if symbol["liveDca"]:
        # Check the symbol integrity  if buy or sell limit order are not created in the past it will

        sell_limit_order_data = binance_trader.get_order(symbol=symbol["symbol"],
                                                         order_id=symbol["pendingLimitSellOrderId"])
        if isinstance(sell_limit_order_data, dict):
            # Check if TP has been reached and re-initiate the DCA if set
            if sell_limit_order_data['status'] == 'FILLED':
                process_dca_results(symbol=symbol, sell_limit_order_data=sell_limit_order_data)
                if not symbol["finishAndStop"]:
                    # Re pull new coin data
                    new_symbol_data = backend_dca.trader_tracker.get_symbol_track(symbol=symbol["symbol"])
                    initiate_new_dca(new_symbol_data, symbol_profile, tick_size, lot_size=lot_size,
                                     last_price=last_price)
                else:
                    if backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"],
                                                                      data={"toSet": {"finishAndStop": False,
                                                                                      "trading": False}}):
                        push.phone_ping(text=f'[SPOT-STOP]{symbol["symbol"]}\n'
                                             f'Symbol has stop trading')
                        discord.push_discord_tp_hit(symbol=symbol["symbol"],
                                                    text=f'[SPOT-STOP] {symbol["symbol"]}\n'
                                                         f'Symbol has stop trading')
                    else:
                        print(Fore.RED + f"Could not stop the DCA for symbol {symbol['symbol']}")
            else:
                buy_limit_order = binance_trader.get_order(symbol=symbol["symbol"],
                                                           order_id=symbol["pendingBuyLimitOrderId"])
                if isinstance(buy_limit_order, dict):
                    if buy_limit_order["status"] == "FILLED":
                        update_current_DCA(symbol=symbol, tick_size=tick_size, step_size=lot_size,
                                           last_price=last_price,
                                           lot_size=lot_size, buy_limit_order_data=buy_limit_order)
                    else:
                        pass
                else:
                    discord.push_discord_error(symbol=symbol["symbol"], error=sell_limit_order_data,
                                               area='Buy limit check error')
        else:
            discord.push_discord_error(symbol=symbol["symbol"], error=sell_limit_order_data, area='Limit sell check')
    else:
        print(Fore.LIGHTMAGENTA_EX + f"Starting fresh DCA for {symbol['symbol']}")
        initiate_new_dca(symbol, symbol_profile, tick_size, lot_size=lot_size, last_price=last_price)  # This works


def initiate_new_dca(symbol, symbol_profile, tick_size, lot_size, last_price):
    """
    start the new DCA circle
    :param symbol:
    :param symbol_profile:
    :param tick_size:
    :return:
    """

    step_size = symbol_profile['filters'][2]['stepSize']

    # Get starting value in $
    starting_value = symbol["firstEntryValue"]

    # Check if dynamic start % from total available funds is activate and calculate dynamic start
    if symbol["dcaDynamicStart"] > 0:
        starting_value = round(symbol["availableMoney"] * symbol["dcaDynamicStart"],
                               2)  # calculate percantage share

    # Check if compounding is activated and append the compounding to the start
    if symbol["useCompound"]:
        starting_value += round(float(symbol["earned"]), 2)  # Append totla earned so far

    # Make market order
    market_result = binance_trader.market_quote_buy(symbol=symbol["symbol"], quote_qty=starting_value)
    if isinstance(market_result, dict) and market_result["status"] == 'FILLED':
        # Make limit orders
        initial_order_id = market_result["orderId"]
        fills_list = market_result["fills"]
        qty_purchased = 0
        avg_prices = []
        dca_data = []

        commission_bnb = float()
        # Process commision
        for x in fills_list:
            qty_purchased += float(x["qty"])
            avg_prices.append(float(x["price"]))
            # Append total commission in bnb
            if x["commissionAsset"] == "BNB":
                commission_bnb += float(x["commission"])

        array_prices = np.array(avg_prices)
        average_purchase_price = np.average(array_prices)

        # Append the info from the trade
        dict_to_store = {
            "qty": qty_purchased,
            "price": average_purchase_price
        }
        dca_data.append(dict_to_store)

        # Stuff to updated in Database
        data = {"toSet": {"lastPurchasePrice": f'{average_purchase_price:.8f}',
                          "liveDca": [dict_to_store],
                          "currentAverageDCA": f'{average_purchase_price:.8f}',
                          "dcaStart": market_result["transactTime"],
                          "dcaInitOrderId": initial_order_id},
                "toInc": {"ownedCoinQTY": qty_purchased,
                          "investedUsdt": float(market_result["cummulativeQuoteQty"]),
                          "availableMoney": float(market_result["cummulativeQuoteQty"]) * (-1),
                          "commissionBnb": float(commission_bnb)}
                }

        # Push completed buy data to db
        backend_dca.trader_tracker.push_to_completed_buys(symbol=symbol["symbol"], value=market_result)

        # Initiate Limit sell target order incase if no other levels
        order_quantity = float(market_result["executedQty"])
        order_quantity = round_step_size(quantity=order_quantity, step_size=lot_size)

        # Calculate price level to cash in profit
        limit_sell_price, limit_sell_qty = binance_trader.get_limit_sell_info(
            price=average_purchase_price,
            gain_perc=symbol["dcaTarget"],
            coin_quantity=order_quantity,
            tick_size=tick_size, step_size=step_size)

        # Create limit sell order
        limit_sell_result = binance_trader.make_limit_sell(symbol=symbol["symbol"],
                                                           quantity=limit_sell_qty,
                                                           price=f'{limit_sell_price:.8f}')
        # # Initiate limit buy/safety order
        investment_size = float(market_result["cummulativeQuoteQty"]) * (
                1 + symbol["increasePurchasePerc"])
        buy_limit_target = float(average_purchase_price) * (
                1.00 - float(symbol["priceDropLevelPerc"]))
        next_price_level = float(binance_trader.float_precision(buy_limit_target, tick_size))

        # Increase purchase value according to params
        limit_purchase_qty = float(investment_size / float(next_price_level))
        limit_buy_coin_qty = binance_trader.get_limit_buy_info(coin_quantity=limit_purchase_qty,
                                                               step_size=step_size)
        if lot_size <= 0:
            limit_buy_coin_qty = int(limit_buy_coin_qty)

        first_limit_buy = binance_trader.make_limit_buy(symbol=symbol["symbol"],
                                                        qty_str=f'{limit_buy_coin_qty}',
                                                        value=f'{next_price_level:.8f}')

        ### Check all limit orders and update in database accordingly
        if isinstance(limit_sell_result, dict) and limit_sell_result["status"] == "NEW":
            limit_sell_id = limit_sell_result["orderId"]
        elif isinstance(limit_sell_result, dict) and limit_sell_result["status"] != "NEW":
            print(Fore.RED + f'Limit Sell Error: {limit_sell_result}')
            push.phone_ping(text=f'NEW DCA Limit Sell error {symbol["symbol"]}\n'
                                 f'Could not set limit Sell: {limit_sell_result}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit sell error due to status : {limit_sell_result['status']}",
                                       area="DCA Start script")
            limit_sell_id = None

        elif not isinstance(limit_sell_result, dict):
            print(Fore.RED + f'Limit Sell Error: {limit_sell_result}')
            push.phone_ping(text=f'NEW DCA Limit Sell error {symbol["symbol"]}\n'
                                 f'Could not set limit Sell: {limit_sell_result}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit sell error: {limit_sell_result}",
                                       area="DCA Start script")
            limit_sell_id = None

        if isinstance(first_limit_buy, dict) and first_limit_buy['status'] == "NEW":
            limit_buy_id = first_limit_buy["orderId"]

        elif isinstance(first_limit_buy, dict) and first_limit_buy["status"] != "NEW":
            print(Fore.RED + f'Limit Buy Error status: {limit_sell_result["status"]}')
            push.phone_ping(text=f'NEW DCA Limit Buy error {symbol["symbol"]}\n'
                                 f'Could not set limit buy order due to status: {limit_sell_result["status"]}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit buy error status: {first_limit_buy['status']}",
                                       area="DCA start script")
            first_limit_buy = None
        if not isinstance(first_limit_buy, dict):
            print(Fore.RED + f'Limit Buy Error: {limit_sell_result}')
            push.phone_ping(text=f'NEW DCA Limit Buy error {symbol["symbol"]}\n'
                                 f'Could not set limit buy order: {limit_sell_result}')
            discord.push_discord_error(symbol=symbol["symbol"].upper(),
                                       error=f"DCA start Limit buy error: {first_limit_buy}", area="DCA start script")
            limit_buy_id = None

        data["toSet"].update({"pendingBuyLimitOrderId": limit_buy_id,
                              "pendingLimitSellOrderId": limit_sell_id})

        print(Fore.LIGHTGREEN_EX + f'DCA FOR {symbol["symbol"]} started successfully...')
        backend_dca.trader_tracker.update_symbol_track(symbol=symbol["symbol"], data=data)
        message = f'[SPOT-DCA-START] {symbol["symbol"]}\n' \
                  f'Time: {datetime.utcnow().strftime("%m/%d/%Y, %H:%M:%S")}'
        notify_mobile.send_message(message=message)
        push.phone_ping(text=message)
        discord.push_discord_action(symbol=symbol["symbol"], text=message)
    else:
        if isinstance(market_result, dict):
            message = f'{datetime.now().strftime("%m/%d/%Y, %H:%M:%S")} Could not initiate DCA for ' \
                      f'{symbol["symbol"].upper()} with market order due to status:' \
                      f'{market_result["status"]}'
            print(Fore.RED + message)
            push.phone_ping(text=message)
            discord.push_discord_error(symbol=symbol["symbol"].upper(), error=message,
                                       area='DCA Start with market order')
        else:
            message = f'{datetime.now().strftime("%m/%d/%Y, %H:%M:%S")} Could not initiate DCA for ' \
                      f'{symbol["symbol"].upper()} with market order due to:' \
                      f'{market_result}'
            print(Fore.RED + message)
            push.phone_ping(text=message)
            discord.push_discord_error(symbol=symbol["symbol"].upper(), error=message,
                                       area='DCA Start with market order')


def check_ticker_db(all_symbols: list):
    """
    Check if all required DB entries are created to be used BY DCA system
    """
    # Check every coin from dcaBotSettings
    for s in all_symbols:
        # Check if profile is registered
        if not backend_dca.trader_tracker.get_symbol_profile(symbol=s["symbol"]):
            # Get symbol data from exchange
            symbol_data = binance_trader.get_symbol_data(symbol=s["symbol"])
            if backend_dca.trader_tracker.store_symbol_profile(symbol=symbol_data):
                pass
    return


def main():
    integrated_symbols = backend_dca.trader_tracker.get_all_symbols()
    check_ticker_db(integrated_symbols)
    activated = binance_trader.init_client()

    if activated:
        klines_manager.set_client(client=binance_trader.client)
        while True:
            time.sleep(settings["botProperties"]["dcaSleep"])
            try:
                binance_trader.client.ping()
                print(Fore.LIGHTCYAN_EX + f"Checking DCA symbols @ {datetime.utcnow().strftime('%m/%d/%Y, %H:%M:%S')}")
                live_symbols = backend_dca.trader_tracker.get_symbols_by_trading_state(active=True)
                all_prices = binance_trader.get_all_pricess()
                if all_prices:
                    for s in live_symbols:
                        last_price = float(next(x for x in all_prices if x["symbol"] == s["symbol"])["price"])
                        run_dca_for_symbol(s, last_price)
                else:
                    print(Fore.RED + 'Could not get live prices')
                    pass
                print(Fore.CYAN + "Checked all...sleeping!")
            except (BinanceAPIException, BinanceRequestException) as e:
                push.phone_ping(text=f'Binance exception on Main:\n{e.message}')
                discord.push_discord_error(symbol="Binance exception Main", error=e.message, area="DCA main")
                pass
            except (requests.exceptions.ConnectionError, urllib3.exceptions.ProtocolError, HTTPException, socket.error,
                    urllib3.exceptions.ReadTimeoutError, requests.exceptions.ReadTimeout) as e:
                push.phone_ping(text=f'Network Error:\n{e}')
                # discord.push_discord_error(symbol="Network error", error=e, area='DCA Main')
                pass
    else:
        print(Fore.RED + "Please check internet connection")


if __name__ == "__main__":
    main()

Check the integrity of the symbol. If either buy limit ID or sell limit ID are missing, it will     create new ones     :param symbol: symbol track data from database     :param last_price:     :param tick_size:     :param step_size:     :param lot_size:     :return: recheck boolean if recheck has been done # Calculate next drop from the average of entries in DCA to make a next limit buy accroding to params # Format it for binance # # Sometimes it happens that price dropped bellow before next limit buy could be initiate/processed # # Therefore a safety switch needs to be applied to check the level otherwise the program brakes # # We do so by getting the calculate next drop according to DCA, verify it with the price from the # # exchange and if the price of the next level is greater than deduct another 1% from last marked price # # Hardcoded level if true is 0.5 % drop to last processed price as a next buy limit order # Take the previous investment amount add N percent to get next USDT SUM to invest # Take the investment quantity from last buy purchase and append to it required increase # Also check the lot size to fit the needs of the exchange # Get the last order from the list of all processed to get the next step # total invested dollars so far # Calculate qty in coin to purchase # Format the quantity to be correct # Execute new limit buy order on Binance # Calculate new sell limit # Take the executed quantity from limit buy and append the quantity tracked from database # Representing total share of the coin. The value is than used as total sum to sell if TP gets # a hit. # Format data to fit exchange # Checking limit sell result response from Binance # USed in case of network error # Get details on last pending limit buy order # Calculate average DCA entry price # Update current DCA values with additional one as limit buy was executed to be be # able to get average entry. At the end update database with DCA values # make new buy limit either by price drop from last buy or based on average entry # Calculate next drop from the average of entries in DCA to make a next limit buy accroding to params # Format it for binance # Format it for binance # # Sometimes it happens that price dropped bellow before next limit buy could be initiate/processed # # Therefore a safety switch needs to be applied to check the level otherwise the program brakes # # We do so by getting the calculate next drop according to DCA, verify it with the price from the # # exchange and if the price of the next level is greater than deduct another 1% from last marked price # # Hardcoded level if true is 0.5 % drop to last processed price as a next buy limit order # Take the previous investment amount add N percent to get next USDT SUM to invest # Take the investment quantity from last buy purchase and append to it required increase # Also check the lot size to fit the needs of the exchange # total Dollar spent on last filled buy limit # Calculate qty in coin to purchase # Format the quantity to be correct # Execute new limit buy order on Binance # Process binance response when trying to set limit buy order # Store executed limit buy into DB # Updating basic data in database according to last filled buy # MAKE NEXT LIMIT SELL # Cancel the previous limit sell order as the last buy limit has been processed due to different DCA # Calculate new sell limit # Take the executed quantity from limit buy and append the quantity tracked from database # Representing total share of the coin. The value is than used as total sum to sell if TP gets # a hit. # Format data to fit exchange # Checking limit sell result response from Binance # Update order IDs in database # Get total available money from DB for symbol # Total invested from all trades till the TP hit # Latest data # After the results at the end of DCA have been processed we need to reset the database for # next DCA run to happen from scratch. # Removing last buy limit as the target has been reached Process the symbol which is active according to DCa status     :param symbol:     :param last_price:     :return: # Load the symbol profile to calculate tikck, step and lot sizes # Check  if DCA has been initiated already # Check the symbol integrity  if buy or sell limit order are not created in the past it will # Check if TP has been reached and re-initiate the DCA if set # Re pull new coin data # This works start the new DCA circle     :param symbol:     :param symbol_profile:     :param tick_size:     :return: # Get starting value in $ # Check if dynamic start % from total available funds is activate and calculate dynamic start # calculate percantage share # Check if compounding is activated and append the compounding to the start # Append totla earned so far # Make market order # Make limit orders # Process commision # Append total commission in bnb # Append the info from the trade # Stuff to updated in Database # Push completed buy data to db # Initiate Limit sell target order incase if no other levels # Calculate price level to cash in profit # Create limit sell order # # Initiate limit buy/safety order # Increase purchase value according to params ### Check all limit orders and update in database accordingly Check if all required DB entries are created to be used BY DCA system # Check every coin from dcaBotSettings # Check if profile is registered # Get symbol data from exchange # discord.push_discord_error(symbol="Network error", error=e, area='DCA Main')