[
  {
    "path": ".gitignore",
    "content": "dist\ndist-*\ncabal-dev\n*.o\n*.hi\n*.chi\n*.chs.h\n*.dyn_o\n*.dyn_hi\n.hpc\n.hsenv\n.cabal-sandbox/\ncabal.sandbox.config\n*.prof\n*.aux\n*.hp\n*.eventlog\n.stack-work/\ncabal.project.local\ncabal.project.local~\n.HTF/\n.ghc.environment.*\nconfig_files/config.json"
  },
  {
    "path": "Actions/Actions.py",
    "content": "import inspect\nfrom abc import ABC\n\n\nclass Action(ABC):\n    name = 'abstract_action'\n\n    def __init__(self, exchange, original_event):\n        self.exchange = exchange\n        self.original_event = original_event\n\n    def __str__(self):\n        # print all attributes\n        attributes = {}\n        for i in inspect.getmembers(self):\n            # Ignores anything starting with underscore\n            # (that is, private and protected attributes)\n            if not i[0].startswith('_'):\n                # Ignores methods\n                if not inspect.ismethod(i[1]) and not i[0] == 'original_event':\n                    attributes[i[0]] = i[1]\n        return str(attributes)\n\n\nclass ActionNewOrder(Action):\n    name = 'new_order'\n\n    def __init__(self, order, exchange, original_event):\n        super().__init__(exchange, original_event)\n        self.order = order\n\n\nclass ActionCancel(Action):\n    name = 'cancel'\n\n    def __init__(self, symbol, price, order_id, exchange, original_event):\n        super().__init__(exchange, original_event)\n        self.symbol = symbol\n        self.price = price\n        self.order_id = order_id\n\n\nclass ActionClosePosition(Action):\n    name = 'close_position'\n\n    def __init__(self, symbol, order_type, price, order_id, exchange, original_event):\n        super().__init__(exchange, original_event)\n        self.symbol = symbol\n        self.price = price\n        self.order_id = order_id\n        self.order_type = order_type\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM phusion/baseimage:latest\n\n# Use baseimage-docker's init system.\nCMD [\"/sbin/my_init\"]\n\nRUN apt-get update -y && \\\n    apt-get install -y python3-pip python3-dev python-dev\n\nRUN apt-get install -y  build-essential autoconf libtool pkg-config python-opengl python-imaging python-pyrex python-pyside.qtopengl idle-python2.7 qt4-dev-tools qt4-designer libqtgui4 libqtcore4 libqt4-xml libqt4-test libqt4-script libqt4-network libqt4-dbus python-qt4 python-qt4-gl libgle3 python-dev libssl-dev\n\nRUN pip3 install --no-cache-dir Cython\n# We copy just the requirements.txt first to leverage Docker cache\nCOPY ./requirements.txt /app/requirements.txt\n\nWORKDIR /app\n\nRUN pip3 install --no-cache-dir Cython\n\nRUN pip3 install -r requirements.txt\n\n\nCOPY . /app\n\nEXPOSE 5000\n\nENTRYPOINT [ \"python3\" ]\n\nCMD [ \"api.py\" ]\n"
  },
  {
    "path": "ExchangeInterfaces/BinanceExchange.py",
    "content": "import math\nimport Actions.Actions as Actions\n\nfrom binance.exceptions import BinanceAPIException\n\nfrom .Exchange import Exchange\nfrom binance.client import Client\nfrom binance.websockets import BinanceSocketManager\nfrom Helpers.Order import Order\n\n\nclass BinanceExchange(Exchange):\n    exchange_name = \"Binance\"\n    isMargin = False\n\n    def __init__(self, apiKey, apiSecret, pairs, name):\n        super().__init__(apiKey, apiSecret, pairs, name)\n\n        self.connection = Client(self.api['key'], self.api['secret'])\n\n        symbol_info_arr = self.connection.get_exchange_info()\n        dict_symbols_info = {item['symbol']: item for item in symbol_info_arr[\"symbols\"]}\n        actual_symbols_info = {symbol: dict_symbols_info[symbol] for symbol in self.pairs}\n        self.symbols_info = actual_symbols_info\n\n        self.update_balance()\n        self.socket = BinanceSocketManager(self.connection)\n        self.socket.start_user_socket(self.on_balance_update)\n        self.socket.start()\n        self.is_last_order_event_completed = True\n        self.step_sizes = {}\n        self.balance_updated = True\n\n        for symbol_info in symbol_info_arr['symbols']:\n            if symbol_info['symbol'] in self.pairs:\n                self.step_sizes[symbol_info['symbol']] = \\\n                    [f['stepSize'] for f in symbol_info['filters'] if f['filterType'] == 'LOT_SIZE'][0]\n\n    def start(self, caller_callback):\n        self.socket.start_user_socket(caller_callback)\n\n    def update_balance(self):\n        account_information = self.connection.get_account()\n        self.set_balance(account_information['balances'])\n\n    def get_trading_symbols(self):\n        symbols = set()\n        if not self.symbols_info:\n            raise RuntimeError(\"Cant get exchange info\")\n        for key, value in self.symbols_info.items():\n            symbols.add(value[\"quoteAsset\"])\n            symbols.add(value[\"baseAsset\"])\n        return symbols\n\n    def set_balance(self, balances):\n        symbols = self.get_trading_symbols()\n        dict_balances = {item['asset']: item for item in balances}\n        actual_balance = {symbol: dict_balances[symbol] for symbol in symbols}\n        self.balance = actual_balance\n\n    def on_balance_update(self, upd_balance_ev):\n        if upd_balance_ev['e'] == 'outboundAccountPosition':\n            balance = []\n            for ev in upd_balance_ev['B']:\n                balance.append({'asset': ev['a'],\n                                'free': ev['f'],\n                                'locked': ev['l']})\n            self.balance.update({item['asset']: item for item in balance})\n\n    def get_open_orders(self):\n        orders = self.connection.get_open_orders()\n        general_orders = []\n        for o in orders:\n            quantityPart = self.get_part(o['symbol'], o[\"origQty\"], o['price'], o['side'])\n            general_orders.append(\n                Order(o['price'], o[\"origQty\"], quantityPart, o['orderId'], o['symbol'], o['side'], o['type'],\n                      self.exchange_name))\n        return general_orders\n\n    def _cancel_order(self, order_id, symbol):\n        self.connection.cancel_order(symbol=symbol, orderId=order_id)\n        self.logger.info(f'{self.name}: Order canceled')\n\n    async def on_cancel_handler(self, event: Actions.ActionCancel):\n        try:\n            slave_order_id = self._cancel_order_detector(event.price)\n            self._cancel_order(slave_order_id, event.symbol)\n        except BinanceAPIException as error:\n            self.logger.error(f'{self.name}: error {error.message}')\n        except:\n            self.logger.error(f\"{self.name}: error in action: {event.name} in slave {self.name}\")\n\n    def stop(self):\n        self.socket.close()\n\n    def _cancel_order_detector(self, price):\n        # detect order id which need to be canceled\n        slave_open_orders = self.connection.get_open_orders()\n        for ordr_open in slave_open_orders:\n            if float(ordr_open['price']) == float(price):\n                return ordr_open['orderId']\n\n    def process_event(self, event):\n        # return event in generic type from websocket\n\n        # if this event in general type it was send from start function and need call firs_copy\n        if 'exchange' in event:\n            return event\n\n        if event['e'] == 'outboundAccountPosition':\n            self.on_balance_update(event)\n\n        elif event['e'] == 'executionReport':\n            if event['X'] == 'FILLED':\n                return\n            elif event['x'] == 'CANCELED':\n                return Actions.ActionCancel(\n                    event['s'],\n                    event['p'],\n                    event['i'],\n                    self.exchange_name,\n                    event\n                )\n            elif event['X'] == 'NEW':\n                order_event = event\n\n                if order_event['s'] not in self.pairs:\n                    return\n\n                if order_event['o'] == 'MARKET':  # if market order, we haven't price and cant calculate quantity\n                    order_event['p'] = self.connection.get_ticker(symbol=order_event['s'])['lastPrice']\n\n                # part = self.get_part(order_event['s'], order_event['q'], order_event['p'], order_event['S'])\n\n                # shortcut mean https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#order-update\n                order = Order(order_event['p'],\n                              order_event['q'],\n                              self.get_part(order_event['s'], order_event['q'], order_event['p'], order_event['S']),\n                              order_event['i'],\n                              order_event['s'],\n                              order_event['S'],\n                              order_event['o'],\n                              self.exchange_name,\n                              order_event['P'])\n                return Actions.ActionNewOrder(order,\n                                              self.exchange_name,\n                                              event)\n            return\n\n    async def on_order_handler(self, event: Actions.ActionNewOrder):\n        self.create_order(event.order)\n\n    def create_order(self, order):\n        \"\"\"\n        :param order:\n        \"\"\"\n        quantity = self.calc_quantity_from_part(order.symbol, order.quantityPart, order.price, order.side)\n        self.logger.info('Slave ' + self.name + ' ' + str(self._get_quote_balance(order.symbol)) + ' '\n                         + str(self._get_base_balance(order.symbol)) +\n                         ', Create Order:' + ' amount: ' + str(quantity) + ', price: ' + str(order.price))\n        try:\n            if order.type == 'STOP_LOSS_LIMIT' or order.type == \"TAKE_PROFIT_LIMIT\":\n                self.connection.create_order(symbol=order.symbol,\n                                             side=order.side,\n                                             type=order.type,\n                                             price=order.price,\n                                             quantity=quantity,\n                                             timeInForce='GTC',\n                                             stopPrice=order.stop)\n            if order.type == 'MARKET':\n                self.connection.create_order(symbol=order.symbol,\n                                             side=order.side,\n                                             type=order.type,\n                                             quantity=quantity)\n            else:\n                self.connection.create_order(symbol=order.symbol,\n                                             side=order.side,\n                                             type=order.type,\n                                             quantity=quantity,\n                                             price=order.price,\n                                             timeInForce='GTC')\n            self.logger.info(f\"{self.name}: order created\")\n        except Exception as e:\n            self.logger.error(str(e))\n\n    def _get_quote_balance(self, symbol):\n        return self.balance[self.symbols_info[symbol]['quoteAsset']]\n\n    def _get_base_balance(self, symbol):\n        return self.balance[self.symbols_info[symbol]['baseAsset']]\n\n    def get_part(self, symbol: str, quantity: float, price: float, side: str):\n        # get part of the total balance of this coin\n\n        # if order[side] == sell: need obtain coin balance\n        if side == 'BUY':\n            get_context_balance = self._get_quote_balance\n            market_value = float(quantity) * float(price)\n        else:\n            get_context_balance = self._get_base_balance\n            market_value = float(quantity)\n\n        balance = float(get_context_balance(symbol)['free'])\n\n        # if first_copy the balance was update before\n        if self.balance_updated:\n            balance += float(get_context_balance(symbol)['locked'])\n        # else:\n        #     balance += market_value\n\n        part = market_value / balance\n        part = part * 0.99  # decrease part for 1% for avoid rounding errors in calculation\n        return part\n\n    def calc_quantity_from_part(self, symbol, quantityPart, price, side):\n        # calculate quantity from quantityPart\n\n        # if order[side] == sell: need obtain coin balance\n\n        if side == 'BUY':\n            get_context_balance = self._get_quote_balance\n            buy_koef = float(price)\n        else:\n            get_context_balance = self._get_base_balance\n            buy_koef = 1\n\n        cur_bal = float(get_context_balance(symbol)['free'])\n\n        if self.balance_updated:\n            cur_bal += float(get_context_balance(symbol)['locked'])\n\n        quantity = quantityPart * cur_bal / buy_koef\n\n        stepSize = float(self.step_sizes[symbol])\n        precision = int(round(-math.log(stepSize, 10), 0))\n        quantity = round(quantity, precision)\n        return quantity\n"
  },
  {
    "path": "ExchangeInterfaces/BitmexExchange.py",
    "content": "from .Exchange import Exchange\n# from bitmex_websocket import BitMEXWebsocket\nfrom Helpers.Bitmex_websocket_mod import BitMEXWebsocket_mod as BitMEXWebsocket\nimport bitmex\nfrom Helpers.Order import Order\nimport Actions.Actions as Actions\n\n\n# TEST_BITMEX_URL = \"wss://testnet.bitmex.com\"\n# BITMEX_URL = \"wss://www.bitmex.com\"\n\nclass BitmexExchange(Exchange):\n    exchange_name = \"Bitmex\"\n    isMargin = True\n    ENDPOINT = \"https://www.bitmex.com/api/v1\"\n    TEST = False\n\n    def __init__(self, apiKey, apiSecret, pairs, name):\n\n        super().__init__(apiKey, apiSecret, pairs, name)\n        self.pairs = list(map(lambda pair: self.translate(pair) if pair != self.translate(pair)\n        else self.logger.debug(f\"Can't translate word {pair} in {self.exchange_name}\"), self.pairs))\n        self.pairs = list(filter(None, self.pairs))\n        self.connection = bitmex.bitmex(api_key=apiKey, api_secret=apiSecret, test=self.TEST)\n        self.socket = {}\n        # self.firts_copy_flag = True\n        self.balance_updated = False\n        for pair in self.pairs:\n            if pair == 'XBTUSD':\n                self.socket['XBTUSD'] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol='XBTUSD',\n                                                        api_key=self.api['key'],\n                                                        api_secret=self.api['secret'],\n                                                        on_balance_update=self.on_balance_update)\n            else:\n                self.socket[pair] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol=pair,\n                                                api_key=self.api['key'],\n                                                api_secret=self.api['secret']\n                                                )\n\n    def start(self, caller_callback):\n        self.stop()\n        self.socket['XBTUSD'] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol='XBTUSD',\n                                                api_key=self.api['key'],\n                                                api_secret=self.api['secret'], on_balance_update=self.on_balance_update,\n                                                on_order_calback=caller_callback)\n        for pair in self.pairs:\n            if pair == 'XBTUSD':\n                continue\n            self.socket[pair] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol=pair,\n                                                api_key=self.api['key'],\n                                                api_secret=self.api['secret'], on_order_calback=caller_callback,\n                                                )\n\n    def stop(self):\n        for socket in self.socket:\n            self.socket[socket].exit()\n\n    def on_balance_update(self, event):\n        if 'availableMargin' in event:\n            self.balance = event['availableMargin'] / (10**8) * (self.socket['XBTUSD'].get_instrument()['midPrice']\\\n            if \"XBTUSD\" in self.socket else self.connection.Instrument.Instrument_get(symbol='XBTUSD', count=1, reverse=True).result()[0][0]['midPrice'])\n\n            self.balance_updated = True\n\n    def update_balance(self):\n        self.balance = self.socket['XBTUSD'].funds()['availableMargin'] / 10**8 *\\\n                       self.socket['XBTUSD'].get_instrument()['midPrice']\n\n    def get_open_orders(self):\n        open_orders = []\n        for pair in self.pairs:\n            open_orders += self.socket[pair].open_orders(clOrdIDPrefix=\"\")\n\n        general_orders = []\n        for o in open_orders:\n            general_orders.append(self._self_order_to_global(o))\n        return general_orders\n\n    def get_part(self, symbol,  quantity: float, price: float):\n        # btc = float(quantity) / float(price)\n        # btc_satoshi = btc * (10 ** 8)\n\n        usd_order_value = quantity\n\n        balance = self.get_balance()\n        # if self.balance_updated:\n        #     part = usd_order_value / float(balance + usd_order_value)\n        # else:\n        #     part = usd_order_value / balance\n        part = usd_order_value / balance\n        part = part * 0.99  # decrease part for 1% for avoid rounding errors in calculation\n        return part\n\n    def calc_quantity_from_part(self, symbol, quantityPart, price, **kwargs):\n        amount_usd = float(quantityPart) * float(self.get_balance())\n        # btc = btc_satoshi / (10 ** 8)\n        # amount_usd = float(btc) * float(price)\n        return amount_usd\n\n    def process_event(self, event):\n        # self.update_balance()\n        self.balance_updated = False\n\n        if event['action'] == \"insert\":\n\n            check_result = self.check_expected_order(event)\n            if check_result:\n                return check_result\n\n            data = event['data'][0]\n            if data['ordStatus'] == 'New' \\\n                    or (data['ordStatus'] == 'Filled' and 'ordType' in data):\n                if event['data'][0]['execInst'] == 'Close':\n                    # order to close position came\n                    close_order = event['data'][0]\n\n                    if close_order['ordType'] == 'Market':\n                        price = None\n                    else:\n                        price = close_order['price']\n\n                    return Actions.ActionClosePosition(\n                        self.translate(close_order['symbol']),\n                        self.translate(close_order['ordType']),\n                        price,\n                        close_order['orderID'],\n                        self.exchange_name,\n                        event\n                    )\n                else:\n                    order = self._self_order_to_global(event['data'][0])\n\n                    return Actions.ActionNewOrder(\n                        order,\n                        self.exchange_name,\n                        event)\n\n        elif event['action'] == 'update':\n            if 'ordStatus' not in event['data'][0]:\n                return\n            if event['data'][0]['ordStatus'] == 'Canceled':\n                orders = self.socket[event['data'][0]['symbol']].open_orders(clOrdIDPrefix=\"\")\n                order = list(filter(lambda o: o['orderID'] == event['data'][0]['orderID'],\n                                    orders))[0]\n                global_order = self._self_order_to_global(order)\n                return Actions.ActionCancel(\n                    global_order.symbol,\n                    global_order.price,\n                    global_order.id,\n                    self.exchange_name,\n                    event\n                )\n\n    async def on_order_handler(self, event: Actions.ActionNewOrder):\n        self.create_order(event.order)\n\n    async def on_cancel_handler(self, event: Actions.ActionCancel):\n        if self.is_program_order(event.order_id):\n            order_id = None\n            clOrderId = event.order_id\n        else:\n            order_id = self._cancel_order_detector(event.price)\n            clOrderId = None\n\n        if order_id or clOrderId:\n            self._cancel_order(order_id, clOrderId)\n        else:\n            self.logger.error(f'Cancel rejected: Cant find necessary order in slave {self.name}')\n\n    def _self_order_to_global(self, o) -> Order:\n        if 'stopPx' not in o:\n            o['stopPx'] = 0\n        if o['price'] is None:\n            o['price'] = self.socket[o['symbol']].get_instrument()['midPrice']\n\n        return Order(o['price'], o[\"orderQty\"],\n                     self.get_part(o['symbol'], o[\"orderQty\"], self.socket['XBTUSD'].get_instrument()['midPrice']),\n                     o['orderID'],\n                     self.translate(o['symbol']),\n                     o['side'].upper(),\n                     self.translate(o['ordType']),\n                     self.exchange_name,\n                     stop=o['stopPx'])\n\n    def _cancel_order_detector(self, price):\n        # detect order id which need to be canceled\n        open_orders = self.get_open_orders()\n        for ordr_open in open_orders:\n            if ordr_open.price == price:\n                return ordr_open.id\n\n    def _cancel_order(self, order_id, clOrderID=None):\n        try:\n            if clOrderID:\n                result = self.connection.Order.Order_cancel(clOrdID=clOrderID).result()\n            else:\n                result = self.connection.Order.Order_cancel(orderID=order_id).result()\n            self.logger.info(f'{self.name}: Cancel order request send. Response: {result}')\n            self.logger.info(f'{self.name}: Order canceled')\n        except:\n            self.logger.exception(f'{self.name}: Error cancel order')\n\n    def create_order(self, order: Order):\n        try:\n            quantity = self.calc_quantity_from_part(order.symbol, order.quantityPart,\n                                                    self.socket['XBTUSD'].get_instrument()['midPrice'])\n\n            self.logger.info(f\"Slave {self.name}, balance: {self.get_balance()}; \"\n                             f\"Create Order: amount {quantity}, price: {order.price}  \")\n            self.ids.append(order.id)\n            if order.type == 'MARKET' or order.type == 'Stop' or order.type == 'MarketIfTouched':\n                new_order = self.connection.Order.Order_new(symbol=self.translate(order.symbol),\n                                                            side=self.translate(order.side),\n                                                            orderQty=quantity,\n                                                            stopPx=order.stop,\n                                                            ordType=self.translate(order.type),\n                                                            clOrdID=order.id,\n                                                            timeInForce='GoodTillCancel')\n            else:\n                new_order = self.connection.Order.Order_new(symbol=self.translate(order.symbol),\n                                                            side=self.translate(order.side),\n                                                            orderQty=quantity,\n                                                            price=order.price,\n                                                            stopPx=order.stop,\n                                                            clOrdID=order.id,\n                                                            ordType=self.translate(order.type),\n                                                            timeInForce='GoodTillCancel'\n                                                            )\n            self.logger.info(f'{self.name} Create order request send')\n            self.logger.debug(f'Response: {new_order.result()} ')\n        except:\n            self.logger.exception(f'{self.name}: Error create order')\n\n    async def close_position(self, event: Actions.ActionClosePosition):\n        self.logger.info(f'{self.name}: close_position {event.symbol}')\n\n        if event.order_type == 'MARKET':\n            return self.connection.Order.Order_new(symbol=self.translate(event.symbol), ordType='Market',\n                                                   execInst='Close').result()\n        else:\n            self.ids.append(event.order_id)\n            return self.connection.Order.Order_new(symbol=self.translate(event.symbol), ordType='Limit',\n                                                   price=event.price,\n                                                   execInst='Close',\n                                                   clOrdID=event.order_id).result()\n\n    translate_dict = {\n        'BTCUSDT': 'XBTUSD',\n        'ETHUSDT': 'ETHUSD',\n        'LIMIT': 'Limit',\n        'STOP_LOSS_LIMIT': 'StopLimit',\n        'MARKET': 'Market',\n        'BUY': 'Buy',\n        'SELL': 'Sell'\n        # 'BCHUSDT': 'BCHUSD',\n        # 'TRXUSDT': 'TRXU20',\n        # 'XRPUSDT': 'XRPUSD'\n    }\n\n    @staticmethod\n    def translate(word) -> str:\n        translate_dict = BitmexExchange.translate_dict\n        if not word in translate_dict:\n            translate_dict = dict(zip(BitmexExchange.translate_dict.values(), BitmexExchange.translate_dict.keys()))\n            if not word in translate_dict:\n                return word\n        return translate_dict[word]\n"
  },
  {
    "path": "ExchangeInterfaces/BitmexTest.py",
    "content": "from ExchangeInterfaces.BitmexExchange import BitmexExchange\n\n\nclass BitmexTest(BitmexExchange):\n    ENDPOINT = \"https://testnet.bitmex.com/api/v1\"\n    TEST = True\n"
  },
  {
    "path": "ExchangeInterfaces/Exchange.py",
    "content": "from abc import ABC, abstractmethod\nimport logging\nimport Actions.Actions as Actions\n\nclass Exchange(ABC):\n    exchange_name = None\n    isMargin = None\n\n    def __init__(self, apiKey, apiSecret, pairs, name):\n        self.api = {'key': apiKey,\n                    'secret': apiSecret}\n        # delete '\\n' from symbols'\n        self.pairs = list(map(lambda pair: pair.replace('\\n', ''), pairs))\n        self.name = name\n        self.balance = None\n        self.expected_orders = list()\n        self.ids = []  # store here order which was created by program\n        self.logger = logging.getLogger('cct')\n\n    def get_balance(self) -> float:\n        return self.balance\n\n\n\n    @abstractmethod\n    def stop(self):\n        pass\n\n    @abstractmethod\n    def start(self, caller_callback):\n        pass\n\n    @abstractmethod\n    def process_event(self, event) -> Actions.Action or None:\n        pass\n\n    @abstractmethod\n    def on_order_handler(self, event: Actions.ActionNewOrder):\n        pass\n\n    @abstractmethod\n    def get_open_orders(self):\n        pass\n\n    @abstractmethod\n    async def on_cancel_handler(self, event: Actions.ActionCancel):\n        pass\n\n    @abstractmethod\n    def create_order(self, order):\n        pass\n\n    async def async_create_order(self, order):\n        self.create_order(order)\n\n    @abstractmethod\n    def get_part(self, symbol: str, quantity: float, price: float) -> float:\n        pass\n\n    @abstractmethod\n    def calc_quantity_from_part(self, symbol: str, quantityPart: float, price: float, side:str):\n        pass\n\n    def add_expected_order_id(self, id, callback):\n        self.expected_orders.append({'id': id,\n                                     'callback': callback})\n\n    def check_expected_order(self, order):\n        for expected_order in self.expected_orders:\n            if order.id == expected_order['id']:\n                expected_order['callback'](order)\n\n    async def close_position(self, event: Actions.ActionClosePosition):\n        print(f\" exchange {self.exchange_name} do not support event \\' close_position \\' \")\n\n    def is_program_order(self, _id) -> bool:\n        if _id in self.ids:\n            return True\n        return False\n\n    def delete_id(self, _id):\n        self.ids.remove(_id)\n"
  },
  {
    "path": "Helpers/Bitmex_websocket_mod.py",
    "content": "import websocket\nimport threading\nimport traceback\nfrom time import sleep\nimport json\nimport logging\nimport urllib\nimport math\nfrom util.api_key import generate_nonce, generate_signature\n# from bitmex_websocket import BitMEXWebsocket  # , find_by_keys, order_leaves_quantity\n\n\n# the copy of class from BitMEXWebsocket but with modified:\n# methods __init__() and __on_message()\n# for using callback\nclass BitMEXWebsocket_mod:\n\n    # Don't grow a table larger than this amount. Helps cap memory usage.\n    MAX_TABLE_LEN = 200\n\n    def __init__(self, endpoint, symbol, api_key=None, api_secret=None, on_order_calback=None, on_balance_update=None):\n        '''Connect to the websocket and initialize data stores.'''\n        self.logger = logging.getLogger(__name__)\n        self.logger.debug(\"Initializing WebSocket.\")\n\n        self.endpoint = endpoint\n        self.symbol = symbol\n\n        self.on_order_callback = on_order_calback\n        self.on_balance_update= on_balance_update\n\n        if api_key is not None and api_secret is None:\n            raise ValueError('api_secret is required if api_key is provided')\n        if api_key is None and api_secret is not None:\n            raise ValueError('api_key is required if api_secret is provided')\n\n        self.api_key = api_key\n        self.api_secret = api_secret\n\n        self.data = {}\n        self.keys = {}\n        self.exited = False\n\n        # We can subscribe right in the connection querystring, so let's build that.\n        # Subscribe to all pertinent endpoints\n        wsURL = self.__get_url()\n        self.logger.info(\"Connecting to %s\" % wsURL)\n        self.__connect(wsURL, symbol)\n        self.logger.info('Connected to WS.')\n\n        # Connected. Wait for partials\n        self.__wait_for_symbol(symbol)\n        if api_key:\n            self.__wait_for_account()\n        self.logger.info('Got all market data. Starting.')\n\n    def exit(self):\n        '''Call this to exit - will close websocket.'''\n        self.exited = True\n        self.ws.close()\n\n    def get_instrument(self):\n        '''Get the raw instrument data for this symbol.'''\n        # Turn the 'tickSize' into 'tickLog' for use in rounding\n        instrument = self.data['instrument'][0]\n        instrument['tickLog'] = int(math.fabs(math.log10(instrument['tickSize'])))\n        return instrument\n\n    def get_ticker(self):\n        '''Return a ticker object. Generated from quote and trade.'''\n        lastQuote = self.data['quote'][-1]\n        lastTrade = self.data['trade'][-1]\n        ticker = {\n            \"last\": lastTrade['price'],\n            \"buy\": lastQuote['bidPrice'],\n            \"sell\": lastQuote['askPrice'],\n            \"mid\": (float(lastQuote['bidPrice'] or 0) + float(lastQuote['askPrice'] or 0)) / 2\n        }\n\n        # The instrument has a tickSize. Use it to round values.\n        instrument = self.data['instrument'][0]\n        return {k: round(float(v or 0), instrument['tickLog']) for k, v in ticker.items()}\n\n    def funds(self):\n        '''Get your margin details.'''\n        return self.data['margin'][0]\n\n    def positions(self):\n        '''Get your positions.'''\n        return self.data['position']\n\n    def market_depth(self):\n        '''Get market depth (orderbook). Returns all levels.'''\n        return self.data['orderBookL2']\n\n    def open_orders(self, clOrdIDPrefix):\n        '''Get all your open orders.'''\n        orders = self.data['order']\n        # Filter to only open orders and those that we actually placed\n        return [o for o in orders if str(o['clOrdID']).startswith(clOrdIDPrefix) and order_leaves_quantity(o)]\n\n    def recent_trades(self):\n        '''Get recent trades.'''\n        return self.data['trade']\n\n    #\n    # End Public Methods\n    #\n\n    def __connect(self, wsURL, symbol):\n        '''Connect to the websocket in a thread.'''\n        self.logger.debug(\"Starting thread\")\n\n        self.ws = websocket.WebSocketApp(wsURL,\n                                         on_message=self.__on_message,\n                                         on_close=self.__on_close,\n                                         on_open=self.__on_open,\n                                         on_error=self.__on_error,\n                                         header=self.__get_auth())\n\n        self.wst = threading.Thread(target=lambda: self.ws.run_forever())\n        self.wst.daemon = True\n        self.wst.start()\n        self.logger.debug(\"Started thread\")\n\n        # Wait for connect before continuing\n        conn_timeout = 5\n        while not self.ws.sock or not self.ws.sock.connected and conn_timeout:\n            sleep(1)\n            conn_timeout -= 1\n        if not conn_timeout:\n            self.logger.error(\"Couldn't connect to WS! Exiting.\")\n            self.exit()\n            raise websocket.WebSocketTimeoutException('Couldn\\'t connect to WS! Exiting.')\n\n    def __get_auth(self):\n        '''Return auth headers. Will use API Keys if present in settings.'''\n        if self.api_key:\n            self.logger.info(\"Authenticating with API Key.\")\n            # To auth to the WS using an API key, we generate a signature of a nonce and\n            # the WS API endpoint.\n            expires = generate_nonce()\n            return [\n                \"api-expires: \" + str(expires),\n                \"api-signature: \" + generate_signature(self.api_secret, 'GET', '/realtime', expires, ''),\n                \"api-key:\" + self.api_key\n            ]\n        else:\n            self.logger.info(\"Not authenticating.\")\n            return []\n\n    def __get_url(self):\n        '''\n        Generate a connection URL. We can define subscriptions right in the querystring.\n        Most subscription topics are scoped by the symbol we're listening to.\n        '''\n\n        # You can sub to orderBookL2 for all levels, or orderBook10 for top 10 levels & save bandwidth\n        symbolSubs = [\"execution\", \"instrument\", \"order\", \"orderBookL2\", \"position\", \"quote\", \"trade\"]\n        genericSubs = [\"margin\"]\n\n        subscriptions = [sub + ':' + self.symbol for sub in symbolSubs]\n        subscriptions += genericSubs\n\n        urlParts = list(urllib.parse.urlparse(self.endpoint))\n        urlParts[0] = urlParts[0].replace('http', 'ws')\n        urlParts[2] = \"/realtime?subscribe={}\".format(','.join(subscriptions))\n        return urllib.parse.urlunparse(urlParts)\n\n    def __wait_for_account(self):\n        '''On subscribe, this data will come down. Wait for it.'''\n        # Wait for the keys to show up from the ws\n        while not {'margin', 'position', 'order', 'orderBookL2'} <= set(self.data):\n            sleep(0.1)\n\n    def __wait_for_symbol(self, symbol):\n        '''On subscribe, this data will come down. Wait for it.'''\n        while not {'instrument', 'trade', 'quote'} <= set(self.data):\n            sleep(0.1)\n\n    def __send_command(self, command, args=None):\n        '''Send a raw command.'''\n        if args is None:\n            args = []\n        self.ws.send(json.dumps({\"op\": command, \"args\": args}))\n\n    def __on_message(self, message):\n        '''Handler for parsing WS messages.'''\n        message = json.loads(message)\n        self.logger.debug(json.dumps(message))\n\n        table = message.get(\"table\")\n        action = message.get(\"action\")\n        try:\n            if 'subscribe' in message:\n                self.logger.debug(\"Subscribed to %s.\" % message['subscribe'])\n            elif action:\n\n                if table not in self.data:\n                    self.data[table] = []\n\n                if (self.on_order_callback != None) and table == \"order\":\n                    self.on_order_callback(message)\n\n                if(self.on_balance_update!= None) and table == \"margin\":\n                    self.on_balance_update(message['data'][0])\n\n                # There are four possible actions from the WS:\n                # 'partial' - full table image\n                # 'insert'  - new row\n                # 'update'  - update row\n                # 'delete'  - delete row\n                if action == 'partial':\n                    self.logger.debug(\"%s: partial\" % table)\n                    self.data[table] = message['data']\n                    # Keys are communicated on partials to let you know how to uniquely identify\n                    # an item. We use it for updates.\n                    self.keys[table] = message['keys']\n                elif action == 'insert':\n                    self.logger.debug('%s: inserting %s' % (table, message['data']))\n                    self.data[table] += message['data']\n\n                    # Limit the max length of the table to avoid excessive memory usage.\n                    # Don't trim orders because we'll lose valuable state if we do.\n                    if table not in ['order', 'orderBookL2'] and len(self.data[table]) > BitMEXWebsocket_mod.MAX_TABLE_LEN:\n                        self.data[table] = self.data[table][BitMEXWebsocket_mod.MAX_TABLE_LEN // 2:]\n\n                elif action == 'update':\n                    self.logger.debug('%s: updating %s' % (table, message['data']))\n                    # Locate the item in the collection and update it.\n                    for updateData in message['data']:\n                        item = find_by_keys(self.keys[table], self.data[table], updateData)\n                        if not item:\n                            return  # No item found to update. Could happen before push\n                        item.update(updateData)\n                        # Remove cancelled / filled orders\n                        if table == 'order' and not order_leaves_quantity(item):\n                            self.data[table].remove(item)\n                elif action == 'delete':\n                    self.logger.debug('%s: deleting %s' % (table, message['data']))\n                    # Locate the item in the collection and remove it.\n                    for deleteData in message['data']:\n                        item = find_by_keys(self.keys[table], self.data[table], deleteData)\n                        self.data[table].remove(item)\n                else:\n                    raise Exception(\"Unknown action: %s\" % action)\n        except:\n            self.logger.error(traceback.format_exc())\n\n    def __on_error(self, error):\n        '''Called on fatal websocket errors. We exit on these.'''\n        if not self.exited:\n            self.logger.error(\"Bitmex Websocket Error : %s\" % error)\n            # try to fix Error connection is already closed\n            self.__init__(self.endpoint, self.symbol, self.api_key, self.api_secret,\n                          on_order_calback=self.on_order_callback, on_balance_update=self.on_balance_update)\n            # raise websocket.WebSocketException(error)\n\n    def __on_open(self):\n        '''Called when the WS opens.'''\n        self.logger.debug(\"Websocket Opened.\")\n\n    def __on_close(self):\n        '''Called on websocket close.'''\n        self.logger.info('Websocket Closed')\n\n\n# Utility method for finding an item in the store.\n# When an update comes through on the websocket, we need to figure out which item in the array it is\n# in order to match that item.\n#\n# Helpfully, on a data push (or on an HTTP hit to /api/v1/schema), we have a \"keys\" array. These are the\n# fields we can use to uniquely identify an item. Sometimes there is more than one, so we iterate through all\n# provided keys.\ndef find_by_keys(keys, table, matchData):\n    for item in table:\n        if all(item[k] == matchData[k] for k in keys):\n            return item\n\ndef order_leaves_quantity(o):\n    if o['leavesQty'] is None:\n        return True\n    return o['leavesQty'] > 0\n"
  },
  {
    "path": "Helpers/Helpers.py",
    "content": "import json\nfrom SlaveContainer import SlaveContainer\nimport logging\nimport logging.config\nimport sys\nimport os\n\nimport numpy as np\n# import pandas as pd\nimport sqlite3 as sql\n\n\n############################################\n## -- Helper Functions\n############################################\n\nROOT_DIR = os.path.abspath(os.curdir)\n\n\ndef create_logger():\n    # create logger\n    logger = logging.getLogger('cct')\n\n    # check if logger already been created\n    if logger.hasHandlers():\n        return logger\n\n    # Create handlers\n    c_handler = logging.StreamHandler(stream=sys.stdout)\n    f_handler = logging.FileHandler(f'{ROOT_DIR}/logs/cct.log', mode='w')\n    c_handler.setLevel(logging.DEBUG)\n    f_handler.setLevel(logging.DEBUG)\n\n    # Create formatters and add it to handlers\n    c_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')\n    f_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')\n    c_handler.setFormatter(c_format)\n    f_handler.setFormatter(f_format)\n\n    # Add handlers to the logger\n    logger.addHandler(c_handler)\n    logger.addHandler(f_handler)\n    logger.setLevel(logging.DEBUG)\n\n    return logger\n\n\ndef server_begin():\n    ############################################\n    ## -- Server Preparation\n    ############################################\n\n    logger = create_logger()\n    logger.info('The Crypto-Copy-Trader starts launch')\n\n    with open(f'{ROOT_DIR}/config_files/config.json', 'r') as config_f:\n        config = json.load(config_f)\n\n    logger.info('Reading configuration file...')\n    logger.info(f'{len(config[\"slaves\"])} Slave accounts detected')\n\n    file = open(f'{ROOT_DIR}/config_files/symbols.csv', \"r\")\n    symbols = file.readlines()\n\n    slave_container = SlaveContainer(config, symbols)\n\n    return slave_container\n\n\n\n"
  },
  {
    "path": "Helpers/Order.py",
    "content": "class Order:\n    def __init__(self, price, amount, quantityPart, order_id, symbol, side, order_type, exchange, stop=0):\n        self.price = price\n        self.amount = amount\n        self.quantityPart = quantityPart\n        self.id = order_id\n        self.symbol = symbol\n        self.side = side\n        self.type = order_type\n        self.exchange = exchange\n        self.stop = stop\n\n    def __str__(self):\n        return f\"Order: id: {self.id}\"  \\\n               f\"price: {self.price},\" \\\n               f\" symbol: {self.symbol},\" \\\n               f\" amount: {self.amount},\" \\\n               f\" part: {self.quantityPart},\" \\\n               f\" side: {self.side},\" \\\n               f\" type: {self.type},\"\n\n    def __repr__(self):\n        return self.__str__()\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Crypto-Copy-Trader\nCopy trading tool for cryptocurrencies - written in Python & Flask\n<br/> Makes you copy high-performing masters without doing any effort\n\nDisclaimer : this is a hobby project not intended for commercial use, and is provided as-is without warranty or liability. capital at risk. \n\n\n# Intro\nbot used to make a mass buying or selling of identical bots to do basic copy trading. \n\n#### Supported Exchanges\n- Binance Spot\n- Bitmex\n- Bitmex Testnet\n\n# Installation and Launch\n\n1. Downland and install requirements\n    ``` \n    git clone https://github.com/MohammedRashad/Crypto-Copy-Trader.git\n    cd Crypto-Copy-Trader\n    pip install -r requirements.txt\n    cp ./config_files/config-sample.json ./config_files/config.json\n    ```\n2. Configure `config.json`\n    - Open `./config_files/config.json` in text editor and paste your api keys and secrets to master and slaves \n    - Possible values for a variable `exchange_name` you can find in folder ExchangeInterfaces. \n\n3. Run `python api.py`\n     \n4. Open GUI\n    - go to http://127.0.0.1:5000/ or http://0.0.0.0:5000/\n    - click `Run` button\n    - see log in terminal or in file `logs/cct.log` \n    - when will you see message `Launch complete` you can place the orders\n    \n    \n# Installation with Docker\n\n1. Build Docker container\n\n```\ndocker build -t crypto-copy-trader .\n```\n\n2. Run first Docker container\n\n```\ndocker run --publish 8000:5000 --detach --name crypto-copy-trader crypto-copy-trader\n```\n\n# Features\n- Database SQLite\n- Slave-Master Configuration\n- Copy active orders on launch\n- WebUI\n- Flask API\n- All orders Supported\n- Adding slaves in realtime\n- Ratio for not similar accounts \n- Built with bootstrap\n\n# Known Bugs\n- Add and delete slaves buttons not working with new config file. So need to fill `config.json` manually\n- database is not related with `config.json`\n- Bitmex working only with `XBTUSD` and `ETHUSD` pairs now\n- You may have an issue \"ModuleNotFoundError: No module named 'jsonschema.compat'\" (python 3.8.x)\n  Please refer this post: https://stackoverflow.com/questions/69426664/modulenotfounderror-no-module-named-jsonschema-compat\n  *solution*: pip install -Iv jsonschema==3.2.0\n \n Please [open an issue](https://github.com/MohammedRashad/Crypto-Copy-Trader/issues/new) to help us fix any bugs or request features if needed.\n \n\n# Contributors \n\nThanks to everyone trying to help, special thanks for [NickPius\n](https://github.com/mokolotron) for multiple patches.\n\nContact me if you want to join as a contributor. \n\n# License\nApache 2.0 License\n"
  },
  {
    "path": "SlaveContainer.py",
    "content": "import asyncio\nfrom ExchangeInterfaces.BinanceExchange import BinanceExchange\nfrom ExchangeInterfaces.BitmexExchange import BitmexExchange\nfrom ExchangeInterfaces.BitmexTest import BitmexTest\nfrom ExchangeInterfaces.Exchange import Exchange\nimport logging\nimport Actions.Actions as Actions\n\n\ndef factory_method_create_exchange(single_config, pairs) -> Exchange:\n    exchange_name = single_config['exchange_name']\n    necessary_class = globals()[exchange_name]\n    return necessary_class(single_config['key'], single_config['secret'], pairs, single_config['name'])\n\n\nclass SlaveContainer:\n    def __init__(self, config, pairs):\n\n        self.logger = logging.getLogger('cct')\n        self.logger.info(f\"Connecting to the master: {config['master']['name']}...\")\n        slaves = []\n        try:\n            self.master = factory_method_create_exchange(config['master'], pairs)\n            self.logger.info(\"Connecting to the slaves. Its can take time...\")\n\n            for slave_config in config['slaves']:\n                slave = factory_method_create_exchange(slave_config, pairs)\n                if self.master.isMargin == slave.isMargin:\n                    slaves.append(slave)\n                else:\n                    slave.stop()\n                    del slave\n        except:\n            self.logger.exception(\"Error initialing exchanges\")\n\n        self.slaves = slaves\n\n    def start(self):\n        self.logger.info(\"Open masters websocket... \")\n        self.master.start(self.on_event_handler)\n        self.logger.info('Launch complete. Now I can copy orders!')\n\n    def stop(self):\n        self.master.stop()\n        for slave in self.slaves:\n            slave.stop()\n\n    def on_event_handler(self, event):\n        # callback for event new order\n        self.logger.debug(f'Event came: {event}')\n\n        try:\n            p_event = self.master.process_event(event)\n        except:\n            self.logger.exception('Error in master.process_event()')\n\n        if p_event is None:\n            # ignore this event\n            return\n        self.logger.info('\\n')\n        self.logger.info(f'New action came: {p_event}')\n\n        if isinstance(p_event, Actions.ActionCancel):\n            for slave in self.slaves:\n                asyncio.run(slave.on_cancel_handler(p_event))\n        elif isinstance(p_event, Actions.ActionNewOrder):\n            for slave in self.slaves:\n                asyncio.run(slave.on_order_handler(p_event))\n        elif isinstance(p_event, Actions.ActionClosePosition):\n            for slave in self.slaves:\n                asyncio.run(slave.close_position(p_event))\n\n        # store order_id of master order to relate it with slave order\n        if isinstance(p_event, Actions.ActionNewOrder):\n            for slave in self.slaves:\n                slave.ids.append(p_event.order.id)\n\n        # delete already not existed order ids to avoid memory leak\n        elif isinstance(p_event, (Actions.ActionClosePosition, Actions.ActionCancel)):\n            ord_id = p_event.order_id\n            for slave in self.slaves:\n                if slave.is_program_order(ord_id):\n                    slave.delete_id(ord_id)\n\n    def first_copy(self, orders):\n        # copy open orders from master account to slaves\n\n        for slave in self.slaves:\n            for o in orders:\n                asyncio.run(slave.async_create_order(o))\n            slave.balance_updated = False\n        self.master.balance_updated = False\n"
  },
  {
    "path": "api.py",
    "content": "from flask import Flask, render_template, request, redirect\nfrom threading import Thread\nimport sqlite3 as sql\nimport csv\nfrom Helpers.Helpers import server_begin\nfrom SlaveContainer import SlaveContainer\nimport logging\n\napp = Flask(__name__)\n\nstop_run = False\ntest_false = True\nsocket_usage = False\n\n\ndef socket_function(container: SlaveContainer):\n    container.start()\n    # first_copy\n    container.first_copy(container.master.get_open_orders())\n    # set variable for stop socket\n    set_stop_run.container = container\n    global socket_usage\n    socket_usage = True\n\n\ndef manual_run():\n    container = server_begin()\n    t1 = Thread(target=socket_function, args=(container,))\n    t1.start()\n    return \"Processing\"\n\n\n@app.route(\"/stop\", methods=['GET'])\ndef set_stop_run():\n    logger = logging.getLogger('cct')\n    global stop_run\n    if not stop_run:\n        logger.warning('You cannot stop without starting. Think about it :)')\n        return redirect(\"/\")\n    stop_run = False\n    set_stop_run.container.stop()\n    logger.info('WebSocket closed')\n    return redirect(\"/\", code=302)\n\n\n@app.route(\"/run\", methods=['GET'])\ndef run_process():\n    global stop_run\n    if stop_run:\n        logger = logging.getLogger('cct')\n        logger.warning('The Program already has been running')\n        return redirect(\"/\")\n    stop_run = True\n    manual_run()\n    return redirect(\"/\", code=302)\n\n\n@app.route('/master', methods=['POST'])\ndef master_form():\n    print(request.form['comment_content'])\n    print(request.form['comment_content2'])\n    print(request.form['comment_content3'])\n    with sql.connect(\"database.db\") as con:\n        cur = con.cursor()\n        cur.execute(\"INSERT INTO keys (name,key,secret,type) VALUES (?,?,?,?)\", (\n            request.form['comment_content3'], request.form['comment_content'], request.form['comment_content2'],\n            \"master\"))\n        con.commit()\n        print(\"Record successfully added\")\n\n    con.close()\n\n    return redirect(\"/\", code=302)\n\n\n@app.route('/delete_master')\ndef delete_master():\n    with sql.connect(\"database.db\") as con:\n        cur = con.cursor()\n        cur.execute(\"delete from keys where type='master'\")\n        con.commit()\n        print(\"Record successfully deleted\")\n    con.close()\n    return redirect(\"/\", code=302)\n\n\n@app.route('/delete_slave')\ndef delete_slave():\n    with sql.connect(\"database.db\") as con:\n        cur = con.cursor()\n        cur.execute(\"delete from keys where type='slave'\")\n        con.commit()\n        print(\"Record successfully deleted\")\n    con.close()\n    return redirect(\"/\", code=302)\n\n\n@app.route('/slave', methods=['POST'])\ndef slave_form():\n    print(request.form['comment_content'])\n    print(request.form['comment_content2'])\n    print(request.form['comment_content3'])\n    with sql.connect(\"database.db\") as con:\n        cur = con.cursor()\n        cur.execute(\"INSERT INTO keys (name,key,secret,type) VALUES (?,?,?,?)\", (\n            request.form['comment_content3'], request.form['comment_content'], request.form['comment_content2'],\n            \"slave\"))\n        con.commit()\n        print(\"Record successfully added\")\n    con.close()\n    return redirect(\"/\", code=302)\n\n\n@app.route('/')\ndef homepage():\n    global test_false\n\n    if test_false == True:\n        test_false = False\n\n    final = bool(test_false) ^ bool(stop_run)\n\n    con = sql.connect(\"database.db\")\n    con.row_factory = sql.Row\n\n    cur = con.cursor()\n    cur.execute(\"select * from keys where type='slave'\")\n    rows = cur.fetchall()\n\n    cur = con.cursor()\n    cur.execute(\"select * from keys where type='master'\")\n    rows2 = cur.fetchall()\n\n    slave_keys = []\n    slave_sec = []\n    master_key = []\n    master_sec = []\n\n    for row in rows:\n        slave_keys.append(row[\"key\"])\n        slave_sec.append(row[\"secret\"])\n\n    for row in rows2:\n        master_key.append(row[\"key\"])\n        master_sec.append(row[\"secret\"])\n\n    with open('config_files/config.csv', mode='w', newline='') as file:\n        writer = csv.writer(file, delimiter=',', quotechar='\"', quoting=csv.QUOTE_MINIMAL)\n        writer.writerow(['Master API Key'] + master_key + [\"\"])\n        writer.writerow(['Master API Keys'] + master_sec + [\"\"])\n        writer.writerow(['Slave API Keys'] + slave_keys + [\"\"])\n        writer.writerow(['Slave API Secrets'] + slave_sec + [\"\"])\n\n    final_str = \"No\" if False else \"Yes\"\n    return render_template(\"home.html\", isRunning=\"Is App Running ? : \" + final_str, rows=rows, rows2=rows2)\n\n\nif __name__ == \"__main__\":\n    app.run(host='0.0.0.0', debug=True)\n"
  },
  {
    "path": "changelog.md",
    "content": ""
  },
  {
    "path": "config_files/config-sample.json",
    "content": "{\n  \"master\": {\n    \"name\": \"Master\" ,\n    \"key\":\"\" ,\n    \"secret\" :\"\",\n    \"exchange_name\": \"BitmexExchange\"\n     },\n  \"slaves\":\n    [{\n      \"name\": \"Slave1\",\n      \"key\": \"\",\n      \"secret\": \"\",\n      \"exchange_name\": \"BitmexExchange\"\n      },\n    {\n      \"name\": \"Slave2\",\n      \"key\": \"\",\n      \"secret\": \"\",\n      \"exchange_name\": \"BinanceExchange\"\n\n    }\n    ],\n  \"settings\": {\n\n  }\n}"
  },
  {
    "path": "config_files/config.csv",
    "content": "Master API Key,NBVX3hwfLkadvNUPJwIxUPkOWPlBfvMJivnrlnRhZZzvNveKI0FBKVLHlnj7KU5p,\r\nMaster API Keys,R0FXuxetL9NmLqRCkEleKcHm99Nbcaxt4PaSXv0wfxHFjFgD0SCA0rXEIqAbVB3S,\r\nSlave API Keys,QZcFeRrlqMBeu4qBWunVDIaAGZ7oq4TDZBexILfSS2vzEZGwcUQlE5cD1u5468QY,bOnKUP8bqg6PvT76o0TbIZf0wOtEVVe29GxmQT6WRvQ64fMYJUgtjYrCHhsWSlCG,\r\nSlave API Secrets,61kVTcJZ0NgXtHxneprQocqb9OUqzwXLTJ1FZvThHzu9ka9DBDtexc8CSMx2xA4k,sPInEZ7uRlSF0eTm1h0XmYL2JW1hkETc36ziJYM9jvYl8G8NhQwpAYQ1WI9MgWQd,\r\n"
  },
  {
    "path": "config_files/symbols.csv",
    "content": "ETHUSDT\nBTCUSDT\nETHBTC\nLTCBTC"
  },
  {
    "path": "config_files/symbols2.csv",
    "content": "THETAUSDT\nENJUSDT\nMITHUSDT\nMATICBTC\nMATICUSDT\nATOMBTC\nATOMUSDT\nPHBBTC\nTFUELBTC\nTFUELUSDT\nONEBTC\nONEUSDT\nFTMBTC\nFTMUSDT\nBTCBBTC\nALGOBTC\nALGOUSDT\nGTOUSDT\nERDBTC\nERDUSDT\nDOGEBTC\nDOGEUSDT\nDUSKBTC\nDUSKUSDT\nANKRBTC\nANKRUSDT\nWINBTC\nWINUSDT\nCOSBTC\nCOSUSDT\nNPXSUSDT\nCOCOSBTC\nCOCOSUSDT\nMTLUSDT\nTOMOBTC\nTOMOUSDT\nPERLBTC\nPERLUSDT\nKEYUSDT\nSTORMUSDT\nDOCKUSDT\nWANUSDT\nFUNUSDT\nCVCUSDT\nBTTTRX\nWINTRX\nCHZBTC\nCHZUSDT\nBANDBTC\nBANDUSDT\nBTCBUSD\nBUSDUSDT\nBEAMBTC\nBEAMUSDT\nXTZBTC\nXTZUSDT\nRENUSDT\nRVNUSDT\nHCUSDT\nHBARBTC\nHBARUSDT\nNKNBTC\nNKNUSDT\nXRPBUSD\nBCHABCBUSD\nLTCBUSD\nLINKBUSD\nETCBUSD\nSTXBTC\nSTXUSDT\nKAVABTC\nKAVAUSDT\nBUSDNGN\nBTCNGN\nARPABTC\nARPAUSDT\nTRXBUSD\nEOSBUSD\nIOTXUSDT\nRLCUSDT\nMCOUSDT\nXLMBUSD\nADABUSD\nCTXCBTC\nCTXCUSDT\nBCHBTC\nBCHUSDT\nBCHBUSD\nBTCRUB\nXRPRUB\nTROYBTC\nTROYUSDT\nBUSDRUB\nQTUMBUSD\nVETBUSD\nVITEBTC\nVITEUSDT\nNEOBTC\nBCCBTC\nGASBTC\nHSRBTC\nMCOBTC\nWTCBTC\nLRCBTC\nQTUMBTC\nYOYOBTC\nOMGBTC\nZRXBTC"
  },
  {
    "path": "config_files/symbols3.csv",
    "content": "STORMBTC\nQTUMUSDT\nXEMBTC\nWANBTC\nWPRBTC\nQLCBTC\nSYSBTC\nGRSBTC\nADAUSDT\nCLOAKBTC\nGNTBTC\nLOOMBTC\nXRPUSDT\nBCNBTC\nREPBTC\nZENBTC\nSKYBTC\nEOSUSDT\nCVCBTC\nTHETABTC\nIOTAUSDT\nXLMUSDT\nIOTXBTC\nQKCBTC\nAGIBTC\nNXSBTC\nDATABTC\nTRXUSDT\nETCUSDT\nICXUSDT\nSCBTC\nNPXSBTC\nVENUSDT\nKEYBTC\nNASBTC\nMFTBTC\nDENTBTC\nARDRBTC\nNULSUSDT\nHOTBTC\nVETBTC\nDOCKBTC\nPOLYBTC\nPHXBTC\nHCBTC\nGOBTC\nRVNBTC\nDCRBTC\nMITHBTC\nBCHABCBTC\nBCHSVBTC\nBCHABCUSDT\nBCHSVUSDT\nRENBTC\nTRXXRP\nXZCXRP\nLINKUSDT\nWAVESUSDT\nBTTBTC\nONGBTC\nONGUSDT\nZILUSDT\nZRXUSDT\nFETBTC\nXMRUSDT\nZECUSDT\nCELRBTC\nCELRUSDT\nDASHUSDT\nNANOUSDT\nOMGUSDT\nSTRATBTC\nSNGLSBTC\nBQXBTC\nKNCBTC\nFUNBTC\nSNMBTC\nIOTABTC\nLINKBTC\nXVGBTC\nSALTBTC\nMDABTC\nMTLBTC\nSUBBTC\nEOSBTC\nSNTBTC\nETCBTC\nMTHBTC\nENGBTC\nDNTBTC\nZECBTC\nBNTBTC\nASTBTC\nDASHBTC\nOAXBTC\nICNBTC\n"
  },
  {
    "path": "config_files/symbols4.csv",
    "content": "RCNBTC\nNULSBTC\nRDNBTC\nXMRBTC\nDLTBTC\nAMBBTC\nBCCUSDT\nBATBTC\nBCPTBTC\nARNBTC\nGVTBTC\nCDTBTC\nGXSBTC\nNEOUSDT\nPOEBTC\nQSPBTC\nBTSBTC\nXZCBTC\nLSKBTC\nTNTBTC\nFUELBTC\nMANABTC\nBCDBTC\nDGDBTC\nADXBTC\nADABTC\nPPTBTC\nCMTBTC\nXLMBTC\nCNDBTC\nLENDBTC\nWABIBTC\nLTCUSDT\nTNBBTC\nWAVESBTC\nGTOBTC\nICXBTC\nOSTBTC\nELFBTC\nAIONBTC\nNEBLBTC\nBRDBTC\nEDOBTC\nWINGSBTC\nNAVBTC\nLUNBTC\nTRIGBTC\nAPPCBTC\nVIBEBTC\nRLCBTC\nINSBTC\nPIVXBTC\nIOSTBTC\nCHATBTC\nSTEEMBTC\nNANOBTC\nVIABTC\nBLZBTC\nAEBTC\nRPXBTC\nNCASHBTC\nPOABTC\nZILBTC\nONTBTC\nBTGBTC\nEVXBTC\nREQBTC\nVIBBTC\nTRXBTC\nPOWRBTC\nARKBTC\nXRPBTC\nMODBTC\nENJBTC\nSTORJBTC\nVENBTC\nKMDBTC"
  },
  {
    "path": "logs/cct.log",
    "content": "2020-09-21 20:55:40,426 - INFO - The Crypto-Copy-Trader starts launch\n2020-09-21 20:55:40,427 - INFO - Reading configuration file...\n2020-09-21 20:55:40,428 - INFO - 2 Slave accounts detected\n2020-09-21 20:55:40,428 - DEBUG - Can't translate word ETHBTC in Bitmex\n2020-09-21 20:55:40,432 - DEBUG - Can't translate word LTCBTC in Bitmex\n2020-09-21 20:55:45,105 - DEBUG - Can't translate word ETHBTC in Bitmex\n2020-09-21 20:55:45,105 - DEBUG - Can't translate word LTCBTC in Bitmex\n2020-09-21 20:55:52,045 - DEBUG - {'table': 'order', 'action': 'partial', 'keys': ['orderID'], 'types': {'orderID': 'guid', 'clOrdID': 'string', 'clOrdLinkID': 'symbol', 'account': 'long', 'symbol': 'symbol', 'side': 'symbol', 'simpleOrderQty': 'float', 'orderQty': 'long', 'price': 'float', 'displayQty': 'long', 'stopPx': 'float', 'pegOffsetValue': 'float', 'pegPriceType': 'symbol', 'currency': 'symbol', 'settlCurrency': 'symbol', 'ordType': 'symbol', 'timeInForce': 'symbol', 'execInst': 'symbol', 'contingencyType': 'symbol', 'exDestination': 'symbol', 'ordStatus': 'symbol', 'triggered': 'symbol', 'workingIndicator': 'boolean', 'ordRejReason': 'symbol', 'simpleLeavesQty': 'float', 'leavesQty': 'long', 'simpleCumQty': 'float', 'cumQty': 'long', 'avgPx': 'float', 'multiLegReportingType': 'symbol', 'text': 'string', 'transactTime': 'timestamp', 'timestamp': 'timestamp'}, 'foreignKeys': {'symbol': 'instrument', 'side': 'side', 'ordStatus': 'ordStatus'}, 'attributes': {'orderID': 'grouped', 'account': 'grouped', 'ordStatus': 'grouped', 'workingIndicator': 'grouped'}, 'filter': {'account': 308752, 'symbol': 'XBTUSD'}, 'data': [{'orderID': '8705e1a0-75af-1dfa-14d4-a54070eec4e0', 'clOrdID': '', 'clOrdLinkID': '', 'account': 308752, 'symbol': 'XBTUSD', 'side': 'Buy', 'simpleOrderQty': None, 'orderQty': 40, 'price': 9500.5, 'displayQty': None, 'stopPx': None, 'pegOffsetValue': None, 'pegPriceType': '', 'currency': 'USD', 'settlCurrency': 'XBt', 'ordType': 'Limit', 'timeInForce': 'GoodTillCancel', 'execInst': '', 'contingencyType': '', 'exDestination': 'XBME', 'ordStatus': 'New', 'triggered': '', 'workingIndicator': True, 'ordRejReason': '', 'simpleLeavesQty': None, 'leavesQty': 40, 'simpleCumQty': None, 'cumQty': 0, 'avgPx': None, 'multiLegReportingType': 'SingleSecurity', 'text': 'Submission from testnet.bitmex.com', 'transactTime': '2020-09-21T17:55:48.385Z', 'timestamp': '2020-09-21T17:55:48.385Z'}]}\n2020-09-21 20:55:53,137 - DEBUG - {'table': 'order', 'action': 'partial', 'keys': ['orderID'], 'types': {'orderID': 'guid', 'clOrdID': 'string', 'clOrdLinkID': 'symbol', 'account': 'long', 'symbol': 'symbol', 'side': 'symbol', 'simpleOrderQty': 'float', 'orderQty': 'long', 'price': 'float', 'displayQty': 'long', 'stopPx': 'float', 'pegOffsetValue': 'float', 'pegPriceType': 'symbol', 'currency': 'symbol', 'settlCurrency': 'symbol', 'ordType': 'symbol', 'timeInForce': 'symbol', 'execInst': 'symbol', 'contingencyType': 'symbol', 'exDestination': 'symbol', 'ordStatus': 'symbol', 'triggered': 'symbol', 'workingIndicator': 'boolean', 'ordRejReason': 'symbol', 'simpleLeavesQty': 'float', 'leavesQty': 'long', 'simpleCumQty': 'float', 'cumQty': 'long', 'avgPx': 'float', 'multiLegReportingType': 'symbol', 'text': 'string', 'transactTime': 'timestamp', 'timestamp': 'timestamp'}, 'foreignKeys': {'symbol': 'instrument', 'side': 'side', 'ordStatus': 'ordStatus'}, 'attributes': {'orderID': 'grouped', 'account': 'grouped', 'ordStatus': 'grouped', 'workingIndicator': 'grouped'}, 'filter': {'account': 308752, 'symbol': 'ETHUSD'}, 'data': []}\n2020-09-21 20:55:55,274 - INFO - Launch complete. Now I can copy orders\n2020-09-21 20:55:55,280 - INFO - Slave Bitmex, balance: 900419; Create Order: amount 39.28880294330567, price: 9500.5  \n2020-09-21 20:55:55,417 - INFO - Create order request send. Response: ({'orderID': '734bece5-8e45-f12d-19a5-8ce9ea8056a4', 'clOrdID': '8705e1a0-75af-1dfa-14d4-a54070eec4e0', 'clOrdLinkID': '', 'account': 311240, 'symbol': 'XBTUSD', 'side': 'Buy', 'simpleOrderQty': None, 'orderQty': 39, 'price': 9500.5, 'displayQty': None, 'stopPx': None, 'pegOffsetValue': None, 'pegPriceType': '', 'currency': 'USD', 'settlCurrency': 'XBt', 'ordType': 'Limit', 'timeInForce': 'GoodTillCancel', 'execInst': '', 'contingencyType': '', 'exDestination': 'XBME', 'ordStatus': 'New', 'triggered': '', 'workingIndicator': True, 'ordRejReason': '', 'simpleLeavesQty': None, 'leavesQty': 39, 'simpleCumQty': None, 'cumQty': 0, 'avgPx': None, 'multiLegReportingType': 'SingleSecurity', 'text': 'Submitted via API.', 'transactTime': datetime.datetime(2020, 9, 21, 17, 55, 55, 378000, tzinfo=tzutc()), 'timestamp': datetime.datetime(2020, 9, 21, 17, 55, 55, 378000, tzinfo=tzutc())}, <bravado.requests_client.RequestsResponseAdapter object at 0x000001C304CC9748>) \n"
  },
  {
    "path": "requirements.txt",
    "content": "flask==1.0.2\ncython\npython_binance==0.7.4\ndeepdiff==4.0.9\nnumpy>=1.13.3\nbinance==0.3\nbitmex\nbitmex_ws==0.4.0\n\n"
  },
  {
    "path": "static/styles/base.css",
    "content": "body {\n  background: rgb(255, 255, 255);\n  color: rgb(0, 0, 0);\n}\n\n.navbar {\n  /* border-bottom: #000 3px solid; */\n  opacity: 1; \n}\n\n.container {\n  font-size: 1.3rem;\n}\n\n.btn-primary-outline {\n  background-color: #ffffff;\n  border-color: rgb(17, 21, 252);\n}\n\n.uploader-input {\n  background: #fff;\n  color: #696969;\n  font-size: 1rem;\n}\n\n.form-check-label {\n  font-size: 1rem;\n  color: #a9a9a9;\n}\n\n\n\n/* \n\n#home-section {\n  background: url(\"../img/home.jpg\") no-repeat;\n  min-height: 700px;\n  background-size: cover;\n  background-attachment: fixed; \n}\n\n#home-section .dark-overlay {\n    background-color: rgba(0, 0, 0, 0.7);\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    min-height: 700px; \n  }\n  \n#home-section .home-inner {\n  padding-top: 150px; \n}\n\n#home-section .card-form {\n  opacity: 0.8;\n}\n\n#home-section .fas {\n  color: #008ed6;\n  background: #fff;\n  padding: 4px;\n  border-radius: 5px;\n  font-size: 30px; \n}\n\n#explore-section .fas, #share-section .fas {\n  color: #fff;\n  background: #333;\n  padding: 4px;\n  border-radius: 5px;\n  font-size: 30px; \n}\n\n#create-section .fas {\n  color: #008ed6;\n  background: #fff;\n  padding: 4px;\n  border-radius: 5px;\n  font-size: 30px; \n} \n*/"
  },
  {
    "path": "templates/home.html",
    "content": "{% extends 'layout.html' %}\n{% block body %}\n<div class=\"jumbotron\">\n\t<br />\n\t<hr />\n\n\t<h1>Binance Cryptocurrency CopyTrader</h1>\n\t<p class=\"lead\">Easily Scale your Cryptocurrency trading workflow with automatic copying and trading</p>\n\t<hr />\n\t<br />\n\t<br />\n\t<b>{{isRunning}}</b><br /><br />\n\t<a href=\"/run\" class=\"btn btn-primary btn-lg\">Run</a>\n\t<a href=\"/stop\" class=\"btn btn-success btn-lg\">Stop</a>\n\n\n\t<hr />\n\n\t<b>Add/Replace Master Account</b><br />\n\n\t<form action=\"{{ url_for('master_form') }}\" method='POST'>\n\t\t<div>\n\n\t\t\t<br />\n\t\t\tMaster API Key : &nbsp;&nbsp; <input id=\"comment_content\" name=\"comment_content\"></input>\n\t\t\t<span class=\"character-counter\"></span></div><br />\n\t\tMaster API Secret : <input id=\"comment_content2\" name=\"comment_content2\"></input>\n\t\t<br /><br /> Master API Name : <input id=\"comment_content3\" name=\"comment_content3\"></input>\n\t\t<br />\n\n\t\t<br />\n\t\t<div>\n\t\t\t<button type=\"submit\" class=\"btn waves-effect waves-light\">Add</button>\n\t\t</div>\n\t</form>\n\n\n\t<hr />\n\n\t<b>Add New Slave</b><br />\n\n\t<form action=\"{{ url_for('slave_form') }}\" method='POST'>\n\t\t<div>\n\n\t\t\t<br />\n\t\t\tSlave API Key : &nbsp;&nbsp; <input id=\"comment_content\" name=\"comment_content\"></input>\n\t\t\t<span class=\"character-counter\"></span></div><br />\n\t\tSlave API Secret : <input id=\"comment_content2\" name=\"comment_content2\"></input>\n\t\t<br /><br />\n\t\tSlave API Name : <input id=\"comment_content3\" name=\"comment_content3\"></input>\n\n\t\t<br />\n\t\t<br />\n\t\t<div>\n\t\t\t<button type=\"submit\" class=\"btn waves-effect waves-light\">Add</button>\n\t\t</div>\n\t</form>\n\n\t<hr />\n\n\t<b>Current Master</b><br />\n\t<table class=\"table table-striped\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t<th>Name</th>\n\t\t\t\t<th>Key</th>\n\t\t\t\t<th>Secret</th>\n\t\t\t</tr>\n\t\t</thead>\n\t\t{% for row in rows2 %}\n\n\t\t<tr>\n\t\t\t<td>{{row[\"name\"]}}</td>\n\t\t\t<td>{{row[\"key\"]}}</td>\n\t\t\t<td> {{ row[\"secret\"]}}</td>\n\t\t</tr>\n\t\t{% endfor %}\n\t</table>\n\n\t<a href=\"/delete_master\" class=\"btn btn-success btn-lg\">Delete Master</a>\n\n\t<hr />\n\n\t<b>Slaves List</b><br />\n\n\t<table class=\"table table-striped\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t<th>Name</th>\n\t\t\t\t<th>Key</th>\n\t\t\t\t<th>Secret</th>\n\t\t\t</tr>\n\t\t</thead>\n\n\t\t{% for row in rows %}\n\t\t<tr>\n\t\t\t<td>{{row[\"name\"]}}</td>\n\t\t\t<td>{{row[\"key\"]}}</td>\n\t\t\t<td> {{ row[\"secret\"]}}</td>\n\t\t</tr> {% endfor %}\n\t</table>\n\t<a href=\"/delete_slave\" class=\"btn btn-success btn-lg\">Delete Slaves</a>\n\n\n</div>\n<!-- Footer -->\n<footer class=\"page-footer font-small blue pt-4\">\n\t\t<div class=\"footer-copyright text-center py-3\">© 2020 Copyright:\n\t\t\t<a href=\"https://mdbootstrap.com/education/bootstrap/\"> mrashad.tk</a>\n\t\t</div>\n\t</footer>\n{% endblock %}"
  },
  {
    "path": "templates/includes/_formhelpers.html",
    "content": "{% macro render_field(field) %}\n\t{{ field.label }}\n\t{{ field(**kwargs)|safe }}\n\t{% if field.errors %}\n\t\t{% for error in field.errors %}\n\t\t\t<span class=\"help-inline\">{{ error }}</span>\n\t\t{% endfor %}\n\t{% endif %}\n{% endmacro %}"
  },
  {
    "path": "templates/includes/_messages.html",
    "content": "{% with messages = get_flashed_messages(with_categories=true) %}\n  {% if messages %}\n    {% for category, message in messages %}\n      <div class=\"alert alert-{{ category }}\">{{ message }}</div>\n    {% endfor %}\n  {% endif %}\n{% endwith %}\n {% if error %}\n\t<div class=\"alert alert-danger\">{{error}}</div>\n{% endif %}\n {% if msg %}\n\t<div class=\"alert alert-success\">{{msg}}</div>\n{% endif %}"
  },
  {
    "path": "templates/includes/_navbar.html",
    "content": "<nav class=\"navbar navbar-expand-md navbar-dark bg-primary fixed-top\">\n  <div class=\"container\">\n    <a href=\"/\" class=\"navbar-brand\">Binance Cryptocurrency CopyTrader</a>\n    <button class=\"navbar-toggler\" data-toggle=\"collapse\" data-target=\"#navbarCollapse\">\n      <span class=\"navbar-toggler-icon\"></span>\n    </button>\n    <div class=\"collapse navbar-collapse\" id=\"navbarCollapse\">\n      <ul class=\"navbar-nav mr-auto\">\n        <!-- <li class=\"nav-item\">\n          <a href=\"/articles\" class=\"nav-link\">All Transactions</a>\n        </li> -->\n      </ul>\n    </div>\n  </div>\n</nav>\n"
  },
  {
    "path": "templates/layout.html",
    "content": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n\n<head>\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  <meta charset=\"utf-8\">\n  <title>Binance Cryptocurrency CopyTrader</title>\n  <link rel=\"stylesheet\" href=\"https://use.fontawesome.com/releases/v5.3.1/css/all.css\"\n    integrity=\"sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU\" crossorigin=\"anonymous\">\n  <link rel=\"icon\" href=\"https://pbs.twimg.com/profile_images/3271649663/14d11dc5dcc16f367cfaaf1dd6e33564_400x400.jpeg\"\n    type=\"image/gif\" sizes=\"16x16\">\n  <!-- <link rel=\"stylesheet\" href=\"{{ url_for('static',filename='styles/bootstrap.min.css') }}\"> -->\n  <link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css\"\n    integrity=\"sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO\" crossorigin=\"anonymous\">\n  <!-- <link rel=\"stylesheet\" href=\"{{ url_for('static',filename='styles/base.css') }}\"> {% block head %}{% endblock %} -->\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <!-- Font Awesome -->\n  <!-- Bootstrap core CSS -->\n   <!-- Material Design Bootstrap -->\n  <link href=\"https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.10.1/css/mdb.min.css rel=\"stylesheet\">\n  <!-- Your custom styles (optional) -->\n</head>\n\n<body>\n  {% include 'includes/_navbar.html' %}\n  <div class=\"\">\n    {% include 'includes/_messages.html' %} {% block body %}{% endblock%}\n  </div>\n  <!-- <script src=\"{{ url_for('static',filename='js/jquery.min.js') }}\"></script>\n    <script src=\"{{ url_for('static',filename='js/popper.min.js') }}\"></script>\n    <script src=\"{{ url_for('static',filename='js/bootstrap.min.js') }}\"></script> -->\n  <script src=\"https://code.jquery.com/jquery-3.3.1.slim.min.js\"\n    integrity=\"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo\" crossorigin=\"anonymous\">\n  </script>\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js\"\n    integrity=\"sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49\" crossorigin=\"anonymous\">\n  </script>\n  <script src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js\"\n    integrity=\"sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy\" crossorigin=\"anonymous\">\n  </script>\n  <script src=\"//cdn.ckeditor.com/4.6.2/basic/ckeditor.js\"></script>\n  <script type=\"text/javascript\">\n    CKEDITOR.replace('editor')\n  </script> {% block end_body %}{% endblock %}\n</body>\n\n</html>"
  }
]