master 26016c5d8d99 cached
29 files
76.7 KB
19.4k tokens
109 symbols
1 requests
Download .txt
Repository: MohammedRashad/Crypto-Copy-Trader
Branch: master
Commit: 26016c5d8d99
Files: 29
Total size: 76.7 KB

Directory structure:
gitextract_wz8roz6m/

├── .gitignore
├── Actions/
│   └── Actions.py
├── Dockerfile
├── ExchangeInterfaces/
│   ├── BinanceExchange.py
│   ├── BitmexExchange.py
│   ├── BitmexTest.py
│   └── Exchange.py
├── Helpers/
│   ├── Bitmex_websocket_mod.py
│   ├── Helpers.py
│   └── Order.py
├── LICENSE
├── README.md
├── SlaveContainer.py
├── api.py
├── changelog.md
├── config_files/
│   ├── config-sample.json
│   ├── config.csv
│   ├── symbols.csv
│   ├── symbols2.csv
│   ├── symbols3.csv
│   └── symbols4.csv
├── logs/
│   └── cct.log
├── requirements.txt
├── static/
│   └── styles/
│       └── base.css
└── templates/
    ├── home.html
    ├── includes/
    │   ├── _formhelpers.html
    │   ├── _messages.html
    │   └── _navbar.html
    └── layout.html

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
dist
dist-*
cabal-dev
*.o
*.hi
*.chi
*.chs.h
*.dyn_o
*.dyn_hi
.hpc
.hsenv
.cabal-sandbox/
cabal.sandbox.config
*.prof
*.aux
*.hp
*.eventlog
.stack-work/
cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*
config_files/config.json

================================================
FILE: Actions/Actions.py
================================================
import inspect
from abc import ABC


class Action(ABC):
    name = 'abstract_action'

    def __init__(self, exchange, original_event):
        self.exchange = exchange
        self.original_event = original_event

    def __str__(self):
        # print all attributes
        attributes = {}
        for i in inspect.getmembers(self):
            # Ignores anything starting with underscore
            # (that is, private and protected attributes)
            if not i[0].startswith('_'):
                # Ignores methods
                if not inspect.ismethod(i[1]) and not i[0] == 'original_event':
                    attributes[i[0]] = i[1]
        return str(attributes)


class ActionNewOrder(Action):
    name = 'new_order'

    def __init__(self, order, exchange, original_event):
        super().__init__(exchange, original_event)
        self.order = order


class ActionCancel(Action):
    name = 'cancel'

    def __init__(self, symbol, price, order_id, exchange, original_event):
        super().__init__(exchange, original_event)
        self.symbol = symbol
        self.price = price
        self.order_id = order_id


class ActionClosePosition(Action):
    name = 'close_position'

    def __init__(self, symbol, order_type, price, order_id, exchange, original_event):
        super().__init__(exchange, original_event)
        self.symbol = symbol
        self.price = price
        self.order_id = order_id
        self.order_type = order_type


================================================
FILE: Dockerfile
================================================
FROM phusion/baseimage:latest

# Use baseimage-docker's init system.
CMD ["/sbin/my_init"]

RUN apt-get update -y && \
    apt-get install -y python3-pip python3-dev python-dev

RUN 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

RUN pip3 install --no-cache-dir Cython
# We copy just the requirements.txt first to leverage Docker cache
COPY ./requirements.txt /app/requirements.txt

WORKDIR /app

RUN pip3 install --no-cache-dir Cython

RUN pip3 install -r requirements.txt


COPY . /app

EXPOSE 5000

ENTRYPOINT [ "python3" ]

CMD [ "api.py" ]


================================================
FILE: ExchangeInterfaces/BinanceExchange.py
================================================
import math
import Actions.Actions as Actions

from binance.exceptions import BinanceAPIException

from .Exchange import Exchange
from binance.client import Client
from binance.websockets import BinanceSocketManager
from Helpers.Order import Order


class BinanceExchange(Exchange):
    exchange_name = "Binance"
    isMargin = False

    def __init__(self, apiKey, apiSecret, pairs, name):
        super().__init__(apiKey, apiSecret, pairs, name)

        self.connection = Client(self.api['key'], self.api['secret'])

        symbol_info_arr = self.connection.get_exchange_info()
        dict_symbols_info = {item['symbol']: item for item in symbol_info_arr["symbols"]}
        actual_symbols_info = {symbol: dict_symbols_info[symbol] for symbol in self.pairs}
        self.symbols_info = actual_symbols_info

        self.update_balance()
        self.socket = BinanceSocketManager(self.connection)
        self.socket.start_user_socket(self.on_balance_update)
        self.socket.start()
        self.is_last_order_event_completed = True
        self.step_sizes = {}
        self.balance_updated = True

        for symbol_info in symbol_info_arr['symbols']:
            if symbol_info['symbol'] in self.pairs:
                self.step_sizes[symbol_info['symbol']] = \
                    [f['stepSize'] for f in symbol_info['filters'] if f['filterType'] == 'LOT_SIZE'][0]

    def start(self, caller_callback):
        self.socket.start_user_socket(caller_callback)

    def update_balance(self):
        account_information = self.connection.get_account()
        self.set_balance(account_information['balances'])

    def get_trading_symbols(self):
        symbols = set()
        if not self.symbols_info:
            raise RuntimeError("Cant get exchange info")
        for key, value in self.symbols_info.items():
            symbols.add(value["quoteAsset"])
            symbols.add(value["baseAsset"])
        return symbols

    def set_balance(self, balances):
        symbols = self.get_trading_symbols()
        dict_balances = {item['asset']: item for item in balances}
        actual_balance = {symbol: dict_balances[symbol] for symbol in symbols}
        self.balance = actual_balance

    def on_balance_update(self, upd_balance_ev):
        if upd_balance_ev['e'] == 'outboundAccountPosition':
            balance = []
            for ev in upd_balance_ev['B']:
                balance.append({'asset': ev['a'],
                                'free': ev['f'],
                                'locked': ev['l']})
            self.balance.update({item['asset']: item for item in balance})

    def get_open_orders(self):
        orders = self.connection.get_open_orders()
        general_orders = []
        for o in orders:
            quantityPart = self.get_part(o['symbol'], o["origQty"], o['price'], o['side'])
            general_orders.append(
                Order(o['price'], o["origQty"], quantityPart, o['orderId'], o['symbol'], o['side'], o['type'],
                      self.exchange_name))
        return general_orders

    def _cancel_order(self, order_id, symbol):
        self.connection.cancel_order(symbol=symbol, orderId=order_id)
        self.logger.info(f'{self.name}: Order canceled')

    async def on_cancel_handler(self, event: Actions.ActionCancel):
        try:
            slave_order_id = self._cancel_order_detector(event.price)
            self._cancel_order(slave_order_id, event.symbol)
        except BinanceAPIException as error:
            self.logger.error(f'{self.name}: error {error.message}')
        except:
            self.logger.error(f"{self.name}: error in action: {event.name} in slave {self.name}")

    def stop(self):
        self.socket.close()

    def _cancel_order_detector(self, price):
        # detect order id which need to be canceled
        slave_open_orders = self.connection.get_open_orders()
        for ordr_open in slave_open_orders:
            if float(ordr_open['price']) == float(price):
                return ordr_open['orderId']

    def process_event(self, event):
        # return event in generic type from websocket

        # if this event in general type it was send from start function and need call firs_copy
        if 'exchange' in event:
            return event

        if event['e'] == 'outboundAccountPosition':
            self.on_balance_update(event)

        elif event['e'] == 'executionReport':
            if event['X'] == 'FILLED':
                return
            elif event['x'] == 'CANCELED':
                return Actions.ActionCancel(
                    event['s'],
                    event['p'],
                    event['i'],
                    self.exchange_name,
                    event
                )
            elif event['X'] == 'NEW':
                order_event = event

                if order_event['s'] not in self.pairs:
                    return

                if order_event['o'] == 'MARKET':  # if market order, we haven't price and cant calculate quantity
                    order_event['p'] = self.connection.get_ticker(symbol=order_event['s'])['lastPrice']

                # part = self.get_part(order_event['s'], order_event['q'], order_event['p'], order_event['S'])

                # shortcut mean https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md#order-update
                order = Order(order_event['p'],
                              order_event['q'],
                              self.get_part(order_event['s'], order_event['q'], order_event['p'], order_event['S']),
                              order_event['i'],
                              order_event['s'],
                              order_event['S'],
                              order_event['o'],
                              self.exchange_name,
                              order_event['P'])
                return Actions.ActionNewOrder(order,
                                              self.exchange_name,
                                              event)
            return

    async def on_order_handler(self, event: Actions.ActionNewOrder):
        self.create_order(event.order)

    def create_order(self, order):
        """
        :param order:
        """
        quantity = self.calc_quantity_from_part(order.symbol, order.quantityPart, order.price, order.side)
        self.logger.info('Slave ' + self.name + ' ' + str(self._get_quote_balance(order.symbol)) + ' '
                         + str(self._get_base_balance(order.symbol)) +
                         ', Create Order:' + ' amount: ' + str(quantity) + ', price: ' + str(order.price))
        try:
            if order.type == 'STOP_LOSS_LIMIT' or order.type == "TAKE_PROFIT_LIMIT":
                self.connection.create_order(symbol=order.symbol,
                                             side=order.side,
                                             type=order.type,
                                             price=order.price,
                                             quantity=quantity,
                                             timeInForce='GTC',
                                             stopPrice=order.stop)
            if order.type == 'MARKET':
                self.connection.create_order(symbol=order.symbol,
                                             side=order.side,
                                             type=order.type,
                                             quantity=quantity)
            else:
                self.connection.create_order(symbol=order.symbol,
                                             side=order.side,
                                             type=order.type,
                                             quantity=quantity,
                                             price=order.price,
                                             timeInForce='GTC')
            self.logger.info(f"{self.name}: order created")
        except Exception as e:
            self.logger.error(str(e))

    def _get_quote_balance(self, symbol):
        return self.balance[self.symbols_info[symbol]['quoteAsset']]

    def _get_base_balance(self, symbol):
        return self.balance[self.symbols_info[symbol]['baseAsset']]

    def get_part(self, symbol: str, quantity: float, price: float, side: str):
        # get part of the total balance of this coin

        # if order[side] == sell: need obtain coin balance
        if side == 'BUY':
            get_context_balance = self._get_quote_balance
            market_value = float(quantity) * float(price)
        else:
            get_context_balance = self._get_base_balance
            market_value = float(quantity)

        balance = float(get_context_balance(symbol)['free'])

        # if first_copy the balance was update before
        if self.balance_updated:
            balance += float(get_context_balance(symbol)['locked'])
        # else:
        #     balance += market_value

        part = market_value / balance
        part = part * 0.99  # decrease part for 1% for avoid rounding errors in calculation
        return part

    def calc_quantity_from_part(self, symbol, quantityPart, price, side):
        # calculate quantity from quantityPart

        # if order[side] == sell: need obtain coin balance

        if side == 'BUY':
            get_context_balance = self._get_quote_balance
            buy_koef = float(price)
        else:
            get_context_balance = self._get_base_balance
            buy_koef = 1

        cur_bal = float(get_context_balance(symbol)['free'])

        if self.balance_updated:
            cur_bal += float(get_context_balance(symbol)['locked'])

        quantity = quantityPart * cur_bal / buy_koef

        stepSize = float(self.step_sizes[symbol])
        precision = int(round(-math.log(stepSize, 10), 0))
        quantity = round(quantity, precision)
        return quantity


================================================
FILE: ExchangeInterfaces/BitmexExchange.py
================================================
from .Exchange import Exchange
# from bitmex_websocket import BitMEXWebsocket
from Helpers.Bitmex_websocket_mod import BitMEXWebsocket_mod as BitMEXWebsocket
import bitmex
from Helpers.Order import Order
import Actions.Actions as Actions


# TEST_BITMEX_URL = "wss://testnet.bitmex.com"
# BITMEX_URL = "wss://www.bitmex.com"

class BitmexExchange(Exchange):
    exchange_name = "Bitmex"
    isMargin = True
    ENDPOINT = "https://www.bitmex.com/api/v1"
    TEST = False

    def __init__(self, apiKey, apiSecret, pairs, name):

        super().__init__(apiKey, apiSecret, pairs, name)
        self.pairs = list(map(lambda pair: self.translate(pair) if pair != self.translate(pair)
        else self.logger.debug(f"Can't translate word {pair} in {self.exchange_name}"), self.pairs))
        self.pairs = list(filter(None, self.pairs))
        self.connection = bitmex.bitmex(api_key=apiKey, api_secret=apiSecret, test=self.TEST)
        self.socket = {}
        # self.firts_copy_flag = True
        self.balance_updated = False
        for pair in self.pairs:
            if pair == 'XBTUSD':
                self.socket['XBTUSD'] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol='XBTUSD',
                                                        api_key=self.api['key'],
                                                        api_secret=self.api['secret'],
                                                        on_balance_update=self.on_balance_update)
            else:
                self.socket[pair] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol=pair,
                                                api_key=self.api['key'],
                                                api_secret=self.api['secret']
                                                )

    def start(self, caller_callback):
        self.stop()
        self.socket['XBTUSD'] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol='XBTUSD',
                                                api_key=self.api['key'],
                                                api_secret=self.api['secret'], on_balance_update=self.on_balance_update,
                                                on_order_calback=caller_callback)
        for pair in self.pairs:
            if pair == 'XBTUSD':
                continue
            self.socket[pair] = BitMEXWebsocket(endpoint=self.ENDPOINT, symbol=pair,
                                                api_key=self.api['key'],
                                                api_secret=self.api['secret'], on_order_calback=caller_callback,
                                                )

    def stop(self):
        for socket in self.socket:
            self.socket[socket].exit()

    def on_balance_update(self, event):
        if 'availableMargin' in event:
            self.balance = event['availableMargin'] / (10**8) * (self.socket['XBTUSD'].get_instrument()['midPrice']\
            if "XBTUSD" in self.socket else self.connection.Instrument.Instrument_get(symbol='XBTUSD', count=1, reverse=True).result()[0][0]['midPrice'])

            self.balance_updated = True

    def update_balance(self):
        self.balance = self.socket['XBTUSD'].funds()['availableMargin'] / 10**8 *\
                       self.socket['XBTUSD'].get_instrument()['midPrice']

    def get_open_orders(self):
        open_orders = []
        for pair in self.pairs:
            open_orders += self.socket[pair].open_orders(clOrdIDPrefix="")

        general_orders = []
        for o in open_orders:
            general_orders.append(self._self_order_to_global(o))
        return general_orders

    def get_part(self, symbol,  quantity: float, price: float):
        # btc = float(quantity) / float(price)
        # btc_satoshi = btc * (10 ** 8)

        usd_order_value = quantity

        balance = self.get_balance()
        # if self.balance_updated:
        #     part = usd_order_value / float(balance + usd_order_value)
        # else:
        #     part = usd_order_value / balance
        part = usd_order_value / balance
        part = part * 0.99  # decrease part for 1% for avoid rounding errors in calculation
        return part

    def calc_quantity_from_part(self, symbol, quantityPart, price, **kwargs):
        amount_usd = float(quantityPart) * float(self.get_balance())
        # btc = btc_satoshi / (10 ** 8)
        # amount_usd = float(btc) * float(price)
        return amount_usd

    def process_event(self, event):
        # self.update_balance()
        self.balance_updated = False

        if event['action'] == "insert":

            check_result = self.check_expected_order(event)
            if check_result:
                return check_result

            data = event['data'][0]
            if data['ordStatus'] == 'New' \
                    or (data['ordStatus'] == 'Filled' and 'ordType' in data):
                if event['data'][0]['execInst'] == 'Close':
                    # order to close position came
                    close_order = event['data'][0]

                    if close_order['ordType'] == 'Market':
                        price = None
                    else:
                        price = close_order['price']

                    return Actions.ActionClosePosition(
                        self.translate(close_order['symbol']),
                        self.translate(close_order['ordType']),
                        price,
                        close_order['orderID'],
                        self.exchange_name,
                        event
                    )
                else:
                    order = self._self_order_to_global(event['data'][0])

                    return Actions.ActionNewOrder(
                        order,
                        self.exchange_name,
                        event)

        elif event['action'] == 'update':
            if 'ordStatus' not in event['data'][0]:
                return
            if event['data'][0]['ordStatus'] == 'Canceled':
                orders = self.socket[event['data'][0]['symbol']].open_orders(clOrdIDPrefix="")
                order = list(filter(lambda o: o['orderID'] == event['data'][0]['orderID'],
                                    orders))[0]
                global_order = self._self_order_to_global(order)
                return Actions.ActionCancel(
                    global_order.symbol,
                    global_order.price,
                    global_order.id,
                    self.exchange_name,
                    event
                )

    async def on_order_handler(self, event: Actions.ActionNewOrder):
        self.create_order(event.order)

    async def on_cancel_handler(self, event: Actions.ActionCancel):
        if self.is_program_order(event.order_id):
            order_id = None
            clOrderId = event.order_id
        else:
            order_id = self._cancel_order_detector(event.price)
            clOrderId = None

        if order_id or clOrderId:
            self._cancel_order(order_id, clOrderId)
        else:
            self.logger.error(f'Cancel rejected: Cant find necessary order in slave {self.name}')

    def _self_order_to_global(self, o) -> Order:
        if 'stopPx' not in o:
            o['stopPx'] = 0
        if o['price'] is None:
            o['price'] = self.socket[o['symbol']].get_instrument()['midPrice']

        return Order(o['price'], o["orderQty"],
                     self.get_part(o['symbol'], o["orderQty"], self.socket['XBTUSD'].get_instrument()['midPrice']),
                     o['orderID'],
                     self.translate(o['symbol']),
                     o['side'].upper(),
                     self.translate(o['ordType']),
                     self.exchange_name,
                     stop=o['stopPx'])

    def _cancel_order_detector(self, price):
        # detect order id which need to be canceled
        open_orders = self.get_open_orders()
        for ordr_open in open_orders:
            if ordr_open.price == price:
                return ordr_open.id

    def _cancel_order(self, order_id, clOrderID=None):
        try:
            if clOrderID:
                result = self.connection.Order.Order_cancel(clOrdID=clOrderID).result()
            else:
                result = self.connection.Order.Order_cancel(orderID=order_id).result()
            self.logger.info(f'{self.name}: Cancel order request send. Response: {result}')
            self.logger.info(f'{self.name}: Order canceled')
        except:
            self.logger.exception(f'{self.name}: Error cancel order')

    def create_order(self, order: Order):
        try:
            quantity = self.calc_quantity_from_part(order.symbol, order.quantityPart,
                                                    self.socket['XBTUSD'].get_instrument()['midPrice'])

            self.logger.info(f"Slave {self.name}, balance: {self.get_balance()}; "
                             f"Create Order: amount {quantity}, price: {order.price}  ")
            self.ids.append(order.id)
            if order.type == 'MARKET' or order.type == 'Stop' or order.type == 'MarketIfTouched':
                new_order = self.connection.Order.Order_new(symbol=self.translate(order.symbol),
                                                            side=self.translate(order.side),
                                                            orderQty=quantity,
                                                            stopPx=order.stop,
                                                            ordType=self.translate(order.type),
                                                            clOrdID=order.id,
                                                            timeInForce='GoodTillCancel')
            else:
                new_order = self.connection.Order.Order_new(symbol=self.translate(order.symbol),
                                                            side=self.translate(order.side),
                                                            orderQty=quantity,
                                                            price=order.price,
                                                            stopPx=order.stop,
                                                            clOrdID=order.id,
                                                            ordType=self.translate(order.type),
                                                            timeInForce='GoodTillCancel'
                                                            )
            self.logger.info(f'{self.name} Create order request send')
            self.logger.debug(f'Response: {new_order.result()} ')
        except:
            self.logger.exception(f'{self.name}: Error create order')

    async def close_position(self, event: Actions.ActionClosePosition):
        self.logger.info(f'{self.name}: close_position {event.symbol}')

        if event.order_type == 'MARKET':
            return self.connection.Order.Order_new(symbol=self.translate(event.symbol), ordType='Market',
                                                   execInst='Close').result()
        else:
            self.ids.append(event.order_id)
            return self.connection.Order.Order_new(symbol=self.translate(event.symbol), ordType='Limit',
                                                   price=event.price,
                                                   execInst='Close',
                                                   clOrdID=event.order_id).result()

    translate_dict = {
        'BTCUSDT': 'XBTUSD',
        'ETHUSDT': 'ETHUSD',
        'LIMIT': 'Limit',
        'STOP_LOSS_LIMIT': 'StopLimit',
        'MARKET': 'Market',
        'BUY': 'Buy',
        'SELL': 'Sell'
        # 'BCHUSDT': 'BCHUSD',
        # 'TRXUSDT': 'TRXU20',
        # 'XRPUSDT': 'XRPUSD'
    }

    @staticmethod
    def translate(word) -> str:
        translate_dict = BitmexExchange.translate_dict
        if not word in translate_dict:
            translate_dict = dict(zip(BitmexExchange.translate_dict.values(), BitmexExchange.translate_dict.keys()))
            if not word in translate_dict:
                return word
        return translate_dict[word]


================================================
FILE: ExchangeInterfaces/BitmexTest.py
================================================
from ExchangeInterfaces.BitmexExchange import BitmexExchange


class BitmexTest(BitmexExchange):
    ENDPOINT = "https://testnet.bitmex.com/api/v1"
    TEST = True


================================================
FILE: ExchangeInterfaces/Exchange.py
================================================
from abc import ABC, abstractmethod
import logging
import Actions.Actions as Actions

class Exchange(ABC):
    exchange_name = None
    isMargin = None

    def __init__(self, apiKey, apiSecret, pairs, name):
        self.api = {'key': apiKey,
                    'secret': apiSecret}
        # delete '\n' from symbols'
        self.pairs = list(map(lambda pair: pair.replace('\n', ''), pairs))
        self.name = name
        self.balance = None
        self.expected_orders = list()
        self.ids = []  # store here order which was created by program
        self.logger = logging.getLogger('cct')

    def get_balance(self) -> float:
        return self.balance



    @abstractmethod
    def stop(self):
        pass

    @abstractmethod
    def start(self, caller_callback):
        pass

    @abstractmethod
    def process_event(self, event) -> Actions.Action or None:
        pass

    @abstractmethod
    def on_order_handler(self, event: Actions.ActionNewOrder):
        pass

    @abstractmethod
    def get_open_orders(self):
        pass

    @abstractmethod
    async def on_cancel_handler(self, event: Actions.ActionCancel):
        pass

    @abstractmethod
    def create_order(self, order):
        pass

    async def async_create_order(self, order):
        self.create_order(order)

    @abstractmethod
    def get_part(self, symbol: str, quantity: float, price: float) -> float:
        pass

    @abstractmethod
    def calc_quantity_from_part(self, symbol: str, quantityPart: float, price: float, side:str):
        pass

    def add_expected_order_id(self, id, callback):
        self.expected_orders.append({'id': id,
                                     'callback': callback})

    def check_expected_order(self, order):
        for expected_order in self.expected_orders:
            if order.id == expected_order['id']:
                expected_order['callback'](order)

    async def close_position(self, event: Actions.ActionClosePosition):
        print(f" exchange {self.exchange_name} do not support event \' close_position \' ")

    def is_program_order(self, _id) -> bool:
        if _id in self.ids:
            return True
        return False

    def delete_id(self, _id):
        self.ids.remove(_id)


================================================
FILE: Helpers/Bitmex_websocket_mod.py
================================================
import websocket
import threading
import traceback
from time import sleep
import json
import logging
import urllib
import math
from util.api_key import generate_nonce, generate_signature
# from bitmex_websocket import BitMEXWebsocket  # , find_by_keys, order_leaves_quantity


# the copy of class from BitMEXWebsocket but with modified:
# methods __init__() and __on_message()
# for using callback
class BitMEXWebsocket_mod:

    # Don't grow a table larger than this amount. Helps cap memory usage.
    MAX_TABLE_LEN = 200

    def __init__(self, endpoint, symbol, api_key=None, api_secret=None, on_order_calback=None, on_balance_update=None):
        '''Connect to the websocket and initialize data stores.'''
        self.logger = logging.getLogger(__name__)
        self.logger.debug("Initializing WebSocket.")

        self.endpoint = endpoint
        self.symbol = symbol

        self.on_order_callback = on_order_calback
        self.on_balance_update= on_balance_update

        if api_key is not None and api_secret is None:
            raise ValueError('api_secret is required if api_key is provided')
        if api_key is None and api_secret is not None:
            raise ValueError('api_key is required if api_secret is provided')

        self.api_key = api_key
        self.api_secret = api_secret

        self.data = {}
        self.keys = {}
        self.exited = False

        # We can subscribe right in the connection querystring, so let's build that.
        # Subscribe to all pertinent endpoints
        wsURL = self.__get_url()
        self.logger.info("Connecting to %s" % wsURL)
        self.__connect(wsURL, symbol)
        self.logger.info('Connected to WS.')

        # Connected. Wait for partials
        self.__wait_for_symbol(symbol)
        if api_key:
            self.__wait_for_account()
        self.logger.info('Got all market data. Starting.')

    def exit(self):
        '''Call this to exit - will close websocket.'''
        self.exited = True
        self.ws.close()

    def get_instrument(self):
        '''Get the raw instrument data for this symbol.'''
        # Turn the 'tickSize' into 'tickLog' for use in rounding
        instrument = self.data['instrument'][0]
        instrument['tickLog'] = int(math.fabs(math.log10(instrument['tickSize'])))
        return instrument

    def get_ticker(self):
        '''Return a ticker object. Generated from quote and trade.'''
        lastQuote = self.data['quote'][-1]
        lastTrade = self.data['trade'][-1]
        ticker = {
            "last": lastTrade['price'],
            "buy": lastQuote['bidPrice'],
            "sell": lastQuote['askPrice'],
            "mid": (float(lastQuote['bidPrice'] or 0) + float(lastQuote['askPrice'] or 0)) / 2
        }

        # The instrument has a tickSize. Use it to round values.
        instrument = self.data['instrument'][0]
        return {k: round(float(v or 0), instrument['tickLog']) for k, v in ticker.items()}

    def funds(self):
        '''Get your margin details.'''
        return self.data['margin'][0]

    def positions(self):
        '''Get your positions.'''
        return self.data['position']

    def market_depth(self):
        '''Get market depth (orderbook). Returns all levels.'''
        return self.data['orderBookL2']

    def open_orders(self, clOrdIDPrefix):
        '''Get all your open orders.'''
        orders = self.data['order']
        # Filter to only open orders and those that we actually placed
        return [o for o in orders if str(o['clOrdID']).startswith(clOrdIDPrefix) and order_leaves_quantity(o)]

    def recent_trades(self):
        '''Get recent trades.'''
        return self.data['trade']

    #
    # End Public Methods
    #

    def __connect(self, wsURL, symbol):
        '''Connect to the websocket in a thread.'''
        self.logger.debug("Starting thread")

        self.ws = websocket.WebSocketApp(wsURL,
                                         on_message=self.__on_message,
                                         on_close=self.__on_close,
                                         on_open=self.__on_open,
                                         on_error=self.__on_error,
                                         header=self.__get_auth())

        self.wst = threading.Thread(target=lambda: self.ws.run_forever())
        self.wst.daemon = True
        self.wst.start()
        self.logger.debug("Started thread")

        # Wait for connect before continuing
        conn_timeout = 5
        while not self.ws.sock or not self.ws.sock.connected and conn_timeout:
            sleep(1)
            conn_timeout -= 1
        if not conn_timeout:
            self.logger.error("Couldn't connect to WS! Exiting.")
            self.exit()
            raise websocket.WebSocketTimeoutException('Couldn\'t connect to WS! Exiting.')

    def __get_auth(self):
        '''Return auth headers. Will use API Keys if present in settings.'''
        if self.api_key:
            self.logger.info("Authenticating with API Key.")
            # To auth to the WS using an API key, we generate a signature of a nonce and
            # the WS API endpoint.
            expires = generate_nonce()
            return [
                "api-expires: " + str(expires),
                "api-signature: " + generate_signature(self.api_secret, 'GET', '/realtime', expires, ''),
                "api-key:" + self.api_key
            ]
        else:
            self.logger.info("Not authenticating.")
            return []

    def __get_url(self):
        '''
        Generate a connection URL. We can define subscriptions right in the querystring.
        Most subscription topics are scoped by the symbol we're listening to.
        '''

        # You can sub to orderBookL2 for all levels, or orderBook10 for top 10 levels & save bandwidth
        symbolSubs = ["execution", "instrument", "order", "orderBookL2", "position", "quote", "trade"]
        genericSubs = ["margin"]

        subscriptions = [sub + ':' + self.symbol for sub in symbolSubs]
        subscriptions += genericSubs

        urlParts = list(urllib.parse.urlparse(self.endpoint))
        urlParts[0] = urlParts[0].replace('http', 'ws')
        urlParts[2] = "/realtime?subscribe={}".format(','.join(subscriptions))
        return urllib.parse.urlunparse(urlParts)

    def __wait_for_account(self):
        '''On subscribe, this data will come down. Wait for it.'''
        # Wait for the keys to show up from the ws
        while not {'margin', 'position', 'order', 'orderBookL2'} <= set(self.data):
            sleep(0.1)

    def __wait_for_symbol(self, symbol):
        '''On subscribe, this data will come down. Wait for it.'''
        while not {'instrument', 'trade', 'quote'} <= set(self.data):
            sleep(0.1)

    def __send_command(self, command, args=None):
        '''Send a raw command.'''
        if args is None:
            args = []
        self.ws.send(json.dumps({"op": command, "args": args}))

    def __on_message(self, message):
        '''Handler for parsing WS messages.'''
        message = json.loads(message)
        self.logger.debug(json.dumps(message))

        table = message.get("table")
        action = message.get("action")
        try:
            if 'subscribe' in message:
                self.logger.debug("Subscribed to %s." % message['subscribe'])
            elif action:

                if table not in self.data:
                    self.data[table] = []

                if (self.on_order_callback != None) and table == "order":
                    self.on_order_callback(message)

                if(self.on_balance_update!= None) and table == "margin":
                    self.on_balance_update(message['data'][0])

                # There are four possible actions from the WS:
                # 'partial' - full table image
                # 'insert'  - new row
                # 'update'  - update row
                # 'delete'  - delete row
                if action == 'partial':
                    self.logger.debug("%s: partial" % table)
                    self.data[table] = message['data']
                    # Keys are communicated on partials to let you know how to uniquely identify
                    # an item. We use it for updates.
                    self.keys[table] = message['keys']
                elif action == 'insert':
                    self.logger.debug('%s: inserting %s' % (table, message['data']))
                    self.data[table] += message['data']

                    # Limit the max length of the table to avoid excessive memory usage.
                    # Don't trim orders because we'll lose valuable state if we do.
                    if table not in ['order', 'orderBookL2'] and len(self.data[table]) > BitMEXWebsocket_mod.MAX_TABLE_LEN:
                        self.data[table] = self.data[table][BitMEXWebsocket_mod.MAX_TABLE_LEN // 2:]

                elif action == 'update':
                    self.logger.debug('%s: updating %s' % (table, message['data']))
                    # Locate the item in the collection and update it.
                    for updateData in message['data']:
                        item = find_by_keys(self.keys[table], self.data[table], updateData)
                        if not item:
                            return  # No item found to update. Could happen before push
                        item.update(updateData)
                        # Remove cancelled / filled orders
                        if table == 'order' and not order_leaves_quantity(item):
                            self.data[table].remove(item)
                elif action == 'delete':
                    self.logger.debug('%s: deleting %s' % (table, message['data']))
                    # Locate the item in the collection and remove it.
                    for deleteData in message['data']:
                        item = find_by_keys(self.keys[table], self.data[table], deleteData)
                        self.data[table].remove(item)
                else:
                    raise Exception("Unknown action: %s" % action)
        except:
            self.logger.error(traceback.format_exc())

    def __on_error(self, error):
        '''Called on fatal websocket errors. We exit on these.'''
        if not self.exited:
            self.logger.error("Bitmex Websocket Error : %s" % error)
            # try to fix Error connection is already closed
            self.__init__(self.endpoint, self.symbol, self.api_key, self.api_secret,
                          on_order_calback=self.on_order_callback, on_balance_update=self.on_balance_update)
            # raise websocket.WebSocketException(error)

    def __on_open(self):
        '''Called when the WS opens.'''
        self.logger.debug("Websocket Opened.")

    def __on_close(self):
        '''Called on websocket close.'''
        self.logger.info('Websocket Closed')


# Utility method for finding an item in the store.
# When an update comes through on the websocket, we need to figure out which item in the array it is
# in order to match that item.
#
# Helpfully, on a data push (or on an HTTP hit to /api/v1/schema), we have a "keys" array. These are the
# fields we can use to uniquely identify an item. Sometimes there is more than one, so we iterate through all
# provided keys.
def find_by_keys(keys, table, matchData):
    for item in table:
        if all(item[k] == matchData[k] for k in keys):
            return item

def order_leaves_quantity(o):
    if o['leavesQty'] is None:
        return True
    return o['leavesQty'] > 0


================================================
FILE: Helpers/Helpers.py
================================================
import json
from SlaveContainer import SlaveContainer
import logging
import logging.config
import sys
import os

import numpy as np
# import pandas as pd
import sqlite3 as sql


############################################
## -- Helper Functions
############################################

ROOT_DIR = os.path.abspath(os.curdir)


def create_logger():
    # create logger
    logger = logging.getLogger('cct')

    # check if logger already been created
    if logger.hasHandlers():
        return logger

    # Create handlers
    c_handler = logging.StreamHandler(stream=sys.stdout)
    f_handler = logging.FileHandler(f'{ROOT_DIR}/logs/cct.log', mode='w')
    c_handler.setLevel(logging.DEBUG)
    f_handler.setLevel(logging.DEBUG)

    # Create formatters and add it to handlers
    c_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    f_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    c_handler.setFormatter(c_format)
    f_handler.setFormatter(f_format)

    # Add handlers to the logger
    logger.addHandler(c_handler)
    logger.addHandler(f_handler)
    logger.setLevel(logging.DEBUG)

    return logger


def server_begin():
    ############################################
    ## -- Server Preparation
    ############################################

    logger = create_logger()
    logger.info('The Crypto-Copy-Trader starts launch')

    with open(f'{ROOT_DIR}/config_files/config.json', 'r') as config_f:
        config = json.load(config_f)

    logger.info('Reading configuration file...')
    logger.info(f'{len(config["slaves"])} Slave accounts detected')

    file = open(f'{ROOT_DIR}/config_files/symbols.csv', "r")
    symbols = file.readlines()

    slave_container = SlaveContainer(config, symbols)

    return slave_container





================================================
FILE: Helpers/Order.py
================================================
class Order:
    def __init__(self, price, amount, quantityPart, order_id, symbol, side, order_type, exchange, stop=0):
        self.price = price
        self.amount = amount
        self.quantityPart = quantityPart
        self.id = order_id
        self.symbol = symbol
        self.side = side
        self.type = order_type
        self.exchange = exchange
        self.stop = stop

    def __str__(self):
        return f"Order: id: {self.id}"  \
               f"price: {self.price}," \
               f" symbol: {self.symbol}," \
               f" amount: {self.amount}," \
               f" part: {self.quantityPart}," \
               f" side: {self.side}," \
               f" type: {self.type},"

    def __repr__(self):
        return self.__str__()


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
# Crypto-Copy-Trader
Copy trading tool for cryptocurrencies - written in Python & Flask
<br/> Makes you copy high-performing masters without doing any effort

Disclaimer : this is a hobby project not intended for commercial use, and is provided as-is without warranty or liability. capital at risk. 


# Intro
bot used to make a mass buying or selling of identical bots to do basic copy trading. 

#### Supported Exchanges
- Binance Spot
- Bitmex
- Bitmex Testnet

# Installation and Launch

1. Downland and install requirements
    ``` 
    git clone https://github.com/MohammedRashad/Crypto-Copy-Trader.git
    cd Crypto-Copy-Trader
    pip install -r requirements.txt
    cp ./config_files/config-sample.json ./config_files/config.json
    ```
2. Configure `config.json`
    - Open `./config_files/config.json` in text editor and paste your api keys and secrets to master and slaves 
    - Possible values for a variable `exchange_name` you can find in folder ExchangeInterfaces. 

3. Run `python api.py`
     
4. Open GUI
    - go to http://127.0.0.1:5000/ or http://0.0.0.0:5000/
    - click `Run` button
    - see log in terminal or in file `logs/cct.log` 
    - when will you see message `Launch complete` you can place the orders
    
    
# Installation with Docker

1. Build Docker container

```
docker build -t crypto-copy-trader .
```

2. Run first Docker container

```
docker run --publish 8000:5000 --detach --name crypto-copy-trader crypto-copy-trader
```

# Features
- Database SQLite
- Slave-Master Configuration
- Copy active orders on launch
- WebUI
- Flask API
- All orders Supported
- Adding slaves in realtime
- Ratio for not similar accounts 
- Built with bootstrap

# Known Bugs
- Add and delete slaves buttons not working with new config file. So need to fill `config.json` manually
- database is not related with `config.json`
- Bitmex working only with `XBTUSD` and `ETHUSD` pairs now
- You may have an issue "ModuleNotFoundError: No module named 'jsonschema.compat'" (python 3.8.x)
  Please refer this post: https://stackoverflow.com/questions/69426664/modulenotfounderror-no-module-named-jsonschema-compat
  *solution*: pip install -Iv jsonschema==3.2.0
 
 Please [open an issue](https://github.com/MohammedRashad/Crypto-Copy-Trader/issues/new) to help us fix any bugs or request features if needed.
 

# Contributors 

Thanks to everyone trying to help, special thanks for [NickPius
](https://github.com/mokolotron) for multiple patches.

Contact me if you want to join as a contributor. 

# License
Apache 2.0 License


================================================
FILE: SlaveContainer.py
================================================
import asyncio
from ExchangeInterfaces.BinanceExchange import BinanceExchange
from ExchangeInterfaces.BitmexExchange import BitmexExchange
from ExchangeInterfaces.BitmexTest import BitmexTest
from ExchangeInterfaces.Exchange import Exchange
import logging
import Actions.Actions as Actions


def factory_method_create_exchange(single_config, pairs) -> Exchange:
    exchange_name = single_config['exchange_name']
    necessary_class = globals()[exchange_name]
    return necessary_class(single_config['key'], single_config['secret'], pairs, single_config['name'])


class SlaveContainer:
    def __init__(self, config, pairs):

        self.logger = logging.getLogger('cct')
        self.logger.info(f"Connecting to the master: {config['master']['name']}...")
        slaves = []
        try:
            self.master = factory_method_create_exchange(config['master'], pairs)
            self.logger.info("Connecting to the slaves. Its can take time...")

            for slave_config in config['slaves']:
                slave = factory_method_create_exchange(slave_config, pairs)
                if self.master.isMargin == slave.isMargin:
                    slaves.append(slave)
                else:
                    slave.stop()
                    del slave
        except:
            self.logger.exception("Error initialing exchanges")

        self.slaves = slaves

    def start(self):
        self.logger.info("Open masters websocket... ")
        self.master.start(self.on_event_handler)
        self.logger.info('Launch complete. Now I can copy orders!')

    def stop(self):
        self.master.stop()
        for slave in self.slaves:
            slave.stop()

    def on_event_handler(self, event):
        # callback for event new order
        self.logger.debug(f'Event came: {event}')

        try:
            p_event = self.master.process_event(event)
        except:
            self.logger.exception('Error in master.process_event()')

        if p_event is None:
            # ignore this event
            return
        self.logger.info('\n')
        self.logger.info(f'New action came: {p_event}')

        if isinstance(p_event, Actions.ActionCancel):
            for slave in self.slaves:
                asyncio.run(slave.on_cancel_handler(p_event))
        elif isinstance(p_event, Actions.ActionNewOrder):
            for slave in self.slaves:
                asyncio.run(slave.on_order_handler(p_event))
        elif isinstance(p_event, Actions.ActionClosePosition):
            for slave in self.slaves:
                asyncio.run(slave.close_position(p_event))

        # store order_id of master order to relate it with slave order
        if isinstance(p_event, Actions.ActionNewOrder):
            for slave in self.slaves:
                slave.ids.append(p_event.order.id)

        # delete already not existed order ids to avoid memory leak
        elif isinstance(p_event, (Actions.ActionClosePosition, Actions.ActionCancel)):
            ord_id = p_event.order_id
            for slave in self.slaves:
                if slave.is_program_order(ord_id):
                    slave.delete_id(ord_id)

    def first_copy(self, orders):
        # copy open orders from master account to slaves

        for slave in self.slaves:
            for o in orders:
                asyncio.run(slave.async_create_order(o))
            slave.balance_updated = False
        self.master.balance_updated = False


================================================
FILE: api.py
================================================
from flask import Flask, render_template, request, redirect
from threading import Thread
import sqlite3 as sql
import csv
from Helpers.Helpers import server_begin
from SlaveContainer import SlaveContainer
import logging

app = Flask(__name__)

stop_run = False
test_false = True
socket_usage = False


def socket_function(container: SlaveContainer):
    container.start()
    # first_copy
    container.first_copy(container.master.get_open_orders())
    # set variable for stop socket
    set_stop_run.container = container
    global socket_usage
    socket_usage = True


def manual_run():
    container = server_begin()
    t1 = Thread(target=socket_function, args=(container,))
    t1.start()
    return "Processing"


@app.route("/stop", methods=['GET'])
def set_stop_run():
    logger = logging.getLogger('cct')
    global stop_run
    if not stop_run:
        logger.warning('You cannot stop without starting. Think about it :)')
        return redirect("/")
    stop_run = False
    set_stop_run.container.stop()
    logger.info('WebSocket closed')
    return redirect("/", code=302)


@app.route("/run", methods=['GET'])
def run_process():
    global stop_run
    if stop_run:
        logger = logging.getLogger('cct')
        logger.warning('The Program already has been running')
        return redirect("/")
    stop_run = True
    manual_run()
    return redirect("/", code=302)


@app.route('/master', methods=['POST'])
def master_form():
    print(request.form['comment_content'])
    print(request.form['comment_content2'])
    print(request.form['comment_content3'])
    with sql.connect("database.db") as con:
        cur = con.cursor()
        cur.execute("INSERT INTO keys (name,key,secret,type) VALUES (?,?,?,?)", (
            request.form['comment_content3'], request.form['comment_content'], request.form['comment_content2'],
            "master"))
        con.commit()
        print("Record successfully added")

    con.close()

    return redirect("/", code=302)


@app.route('/delete_master')
def delete_master():
    with sql.connect("database.db") as con:
        cur = con.cursor()
        cur.execute("delete from keys where type='master'")
        con.commit()
        print("Record successfully deleted")
    con.close()
    return redirect("/", code=302)


@app.route('/delete_slave')
def delete_slave():
    with sql.connect("database.db") as con:
        cur = con.cursor()
        cur.execute("delete from keys where type='slave'")
        con.commit()
        print("Record successfully deleted")
    con.close()
    return redirect("/", code=302)


@app.route('/slave', methods=['POST'])
def slave_form():
    print(request.form['comment_content'])
    print(request.form['comment_content2'])
    print(request.form['comment_content3'])
    with sql.connect("database.db") as con:
        cur = con.cursor()
        cur.execute("INSERT INTO keys (name,key,secret,type) VALUES (?,?,?,?)", (
            request.form['comment_content3'], request.form['comment_content'], request.form['comment_content2'],
            "slave"))
        con.commit()
        print("Record successfully added")
    con.close()
    return redirect("/", code=302)


@app.route('/')
def homepage():
    global test_false

    if test_false == True:
        test_false = False

    final = bool(test_false) ^ bool(stop_run)

    con = sql.connect("database.db")
    con.row_factory = sql.Row

    cur = con.cursor()
    cur.execute("select * from keys where type='slave'")
    rows = cur.fetchall()

    cur = con.cursor()
    cur.execute("select * from keys where type='master'")
    rows2 = cur.fetchall()

    slave_keys = []
    slave_sec = []
    master_key = []
    master_sec = []

    for row in rows:
        slave_keys.append(row["key"])
        slave_sec.append(row["secret"])

    for row in rows2:
        master_key.append(row["key"])
        master_sec.append(row["secret"])

    with open('config_files/config.csv', mode='w', newline='') as file:
        writer = csv.writer(file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
        writer.writerow(['Master API Key'] + master_key + [""])
        writer.writerow(['Master API Keys'] + master_sec + [""])
        writer.writerow(['Slave API Keys'] + slave_keys + [""])
        writer.writerow(['Slave API Secrets'] + slave_sec + [""])

    final_str = "No" if False else "Yes"
    return render_template("home.html", isRunning="Is App Running ? : " + final_str, rows=rows, rows2=rows2)


if __name__ == "__main__":
    app.run(host='0.0.0.0', debug=True)


================================================
FILE: changelog.md
================================================


================================================
FILE: config_files/config-sample.json
================================================
{
  "master": {
    "name": "Master" ,
    "key":"" ,
    "secret" :"",
    "exchange_name": "BitmexExchange"
     },
  "slaves":
    [{
      "name": "Slave1",
      "key": "",
      "secret": "",
      "exchange_name": "BitmexExchange"
      },
    {
      "name": "Slave2",
      "key": "",
      "secret": "",
      "exchange_name": "BinanceExchange"

    }
    ],
  "settings": {

  }
}

================================================
FILE: config_files/config.csv
================================================
Master API Key,NBVX3hwfLkadvNUPJwIxUPkOWPlBfvMJivnrlnRhZZzvNveKI0FBKVLHlnj7KU5p,
Master API Keys,R0FXuxetL9NmLqRCkEleKcHm99Nbcaxt4PaSXv0wfxHFjFgD0SCA0rXEIqAbVB3S,
Slave API Keys,QZcFeRrlqMBeu4qBWunVDIaAGZ7oq4TDZBexILfSS2vzEZGwcUQlE5cD1u5468QY,bOnKUP8bqg6PvT76o0TbIZf0wOtEVVe29GxmQT6WRvQ64fMYJUgtjYrCHhsWSlCG,
Slave API Secrets,61kVTcJZ0NgXtHxneprQocqb9OUqzwXLTJ1FZvThHzu9ka9DBDtexc8CSMx2xA4k,sPInEZ7uRlSF0eTm1h0XmYL2JW1hkETc36ziJYM9jvYl8G8NhQwpAYQ1WI9MgWQd,


================================================
FILE: config_files/symbols.csv
================================================
ETHUSDT
BTCUSDT
ETHBTC
LTCBTC

================================================
FILE: config_files/symbols2.csv
================================================
THETAUSDT
ENJUSDT
MITHUSDT
MATICBTC
MATICUSDT
ATOMBTC
ATOMUSDT
PHBBTC
TFUELBTC
TFUELUSDT
ONEBTC
ONEUSDT
FTMBTC
FTMUSDT
BTCBBTC
ALGOBTC
ALGOUSDT
GTOUSDT
ERDBTC
ERDUSDT
DOGEBTC
DOGEUSDT
DUSKBTC
DUSKUSDT
ANKRBTC
ANKRUSDT
WINBTC
WINUSDT
COSBTC
COSUSDT
NPXSUSDT
COCOSBTC
COCOSUSDT
MTLUSDT
TOMOBTC
TOMOUSDT
PERLBTC
PERLUSDT
KEYUSDT
STORMUSDT
DOCKUSDT
WANUSDT
FUNUSDT
CVCUSDT
BTTTRX
WINTRX
CHZBTC
CHZUSDT
BANDBTC
BANDUSDT
BTCBUSD
BUSDUSDT
BEAMBTC
BEAMUSDT
XTZBTC
XTZUSDT
RENUSDT
RVNUSDT
HCUSDT
HBARBTC
HBARUSDT
NKNBTC
NKNUSDT
XRPBUSD
BCHABCBUSD
LTCBUSD
LINKBUSD
ETCBUSD
STXBTC
STXUSDT
KAVABTC
KAVAUSDT
BUSDNGN
BTCNGN
ARPABTC
ARPAUSDT
TRXBUSD
EOSBUSD
IOTXUSDT
RLCUSDT
MCOUSDT
XLMBUSD
ADABUSD
CTXCBTC
CTXCUSDT
BCHBTC
BCHUSDT
BCHBUSD
BTCRUB
XRPRUB
TROYBTC
TROYUSDT
BUSDRUB
QTUMBUSD
VETBUSD
VITEBTC
VITEUSDT
NEOBTC
BCCBTC
GASBTC
HSRBTC
MCOBTC
WTCBTC
LRCBTC
QTUMBTC
YOYOBTC
OMGBTC
ZRXBTC

================================================
FILE: config_files/symbols3.csv
================================================
STORMBTC
QTUMUSDT
XEMBTC
WANBTC
WPRBTC
QLCBTC
SYSBTC
GRSBTC
ADAUSDT
CLOAKBTC
GNTBTC
LOOMBTC
XRPUSDT
BCNBTC
REPBTC
ZENBTC
SKYBTC
EOSUSDT
CVCBTC
THETABTC
IOTAUSDT
XLMUSDT
IOTXBTC
QKCBTC
AGIBTC
NXSBTC
DATABTC
TRXUSDT
ETCUSDT
ICXUSDT
SCBTC
NPXSBTC
VENUSDT
KEYBTC
NASBTC
MFTBTC
DENTBTC
ARDRBTC
NULSUSDT
HOTBTC
VETBTC
DOCKBTC
POLYBTC
PHXBTC
HCBTC
GOBTC
RVNBTC
DCRBTC
MITHBTC
BCHABCBTC
BCHSVBTC
BCHABCUSDT
BCHSVUSDT
RENBTC
TRXXRP
XZCXRP
LINKUSDT
WAVESUSDT
BTTBTC
ONGBTC
ONGUSDT
ZILUSDT
ZRXUSDT
FETBTC
XMRUSDT
ZECUSDT
CELRBTC
CELRUSDT
DASHUSDT
NANOUSDT
OMGUSDT
STRATBTC
SNGLSBTC
BQXBTC
KNCBTC
FUNBTC
SNMBTC
IOTABTC
LINKBTC
XVGBTC
SALTBTC
MDABTC
MTLBTC
SUBBTC
EOSBTC
SNTBTC
ETCBTC
MTHBTC
ENGBTC
DNTBTC
ZECBTC
BNTBTC
ASTBTC
DASHBTC
OAXBTC
ICNBTC


================================================
FILE: config_files/symbols4.csv
================================================
RCNBTC
NULSBTC
RDNBTC
XMRBTC
DLTBTC
AMBBTC
BCCUSDT
BATBTC
BCPTBTC
ARNBTC
GVTBTC
CDTBTC
GXSBTC
NEOUSDT
POEBTC
QSPBTC
BTSBTC
XZCBTC
LSKBTC
TNTBTC
FUELBTC
MANABTC
BCDBTC
DGDBTC
ADXBTC
ADABTC
PPTBTC
CMTBTC
XLMBTC
CNDBTC
LENDBTC
WABIBTC
LTCUSDT
TNBBTC
WAVESBTC
GTOBTC
ICXBTC
OSTBTC
ELFBTC
AIONBTC
NEBLBTC
BRDBTC
EDOBTC
WINGSBTC
NAVBTC
LUNBTC
TRIGBTC
APPCBTC
VIBEBTC
RLCBTC
INSBTC
PIVXBTC
IOSTBTC
CHATBTC
STEEMBTC
NANOBTC
VIABTC
BLZBTC
AEBTC
RPXBTC
NCASHBTC
POABTC
ZILBTC
ONTBTC
BTGBTC
EVXBTC
REQBTC
VIBBTC
TRXBTC
POWRBTC
ARKBTC
XRPBTC
MODBTC
ENJBTC
STORJBTC
VENBTC
KMDBTC

================================================
FILE: logs/cct.log
================================================
2020-09-21 20:55:40,426 - INFO - The Crypto-Copy-Trader starts launch
2020-09-21 20:55:40,427 - INFO - Reading configuration file...
2020-09-21 20:55:40,428 - INFO - 2 Slave accounts detected
2020-09-21 20:55:40,428 - DEBUG - Can't translate word ETHBTC in Bitmex
2020-09-21 20:55:40,432 - DEBUG - Can't translate word LTCBTC in Bitmex
2020-09-21 20:55:45,105 - DEBUG - Can't translate word ETHBTC in Bitmex
2020-09-21 20:55:45,105 - DEBUG - Can't translate word LTCBTC in Bitmex
2020-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'}]}
2020-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': []}
2020-09-21 20:55:55,274 - INFO - Launch complete. Now I can copy orders
2020-09-21 20:55:55,280 - INFO - Slave Bitmex, balance: 900419; Create Order: amount 39.28880294330567, price: 9500.5  
2020-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>) 


================================================
FILE: requirements.txt
================================================
flask==1.0.2
cython
python_binance==0.7.4
deepdiff==4.0.9
numpy>=1.13.3
binance==0.3
bitmex
bitmex_ws==0.4.0



================================================
FILE: static/styles/base.css
================================================
body {
  background: rgb(255, 255, 255);
  color: rgb(0, 0, 0);
}

.navbar {
  /* border-bottom: #000 3px solid; */
  opacity: 1; 
}

.container {
  font-size: 1.3rem;
}

.btn-primary-outline {
  background-color: #ffffff;
  border-color: rgb(17, 21, 252);
}

.uploader-input {
  background: #fff;
  color: #696969;
  font-size: 1rem;
}

.form-check-label {
  font-size: 1rem;
  color: #a9a9a9;
}



/* 

#home-section {
  background: url("../img/home.jpg") no-repeat;
  min-height: 700px;
  background-size: cover;
  background-attachment: fixed; 
}

#home-section .dark-overlay {
    background-color: rgba(0, 0, 0, 0.7);
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    min-height: 700px; 
  }
  
#home-section .home-inner {
  padding-top: 150px; 
}

#home-section .card-form {
  opacity: 0.8;
}

#home-section .fas {
  color: #008ed6;
  background: #fff;
  padding: 4px;
  border-radius: 5px;
  font-size: 30px; 
}

#explore-section .fas, #share-section .fas {
  color: #fff;
  background: #333;
  padding: 4px;
  border-radius: 5px;
  font-size: 30px; 
}

#create-section .fas {
  color: #008ed6;
  background: #fff;
  padding: 4px;
  border-radius: 5px;
  font-size: 30px; 
} 
*/

================================================
FILE: templates/home.html
================================================
{% extends 'layout.html' %}
{% block body %}
<div class="jumbotron">
	<br />
	<hr />

	<h1>Binance Cryptocurrency CopyTrader</h1>
	<p class="lead">Easily Scale your Cryptocurrency trading workflow with automatic copying and trading</p>
	<hr />
	<br />
	<br />
	<b>{{isRunning}}</b><br /><br />
	<a href="/run" class="btn btn-primary btn-lg">Run</a>
	<a href="/stop" class="btn btn-success btn-lg">Stop</a>


	<hr />

	<b>Add/Replace Master Account</b><br />

	<form action="{{ url_for('master_form') }}" method='POST'>
		<div>

			<br />
			Master API Key : &nbsp;&nbsp; <input id="comment_content" name="comment_content"></input>
			<span class="character-counter"></span></div><br />
		Master API Secret : <input id="comment_content2" name="comment_content2"></input>
		<br /><br /> Master API Name : <input id="comment_content3" name="comment_content3"></input>
		<br />

		<br />
		<div>
			<button type="submit" class="btn waves-effect waves-light">Add</button>
		</div>
	</form>


	<hr />

	<b>Add New Slave</b><br />

	<form action="{{ url_for('slave_form') }}" method='POST'>
		<div>

			<br />
			Slave API Key : &nbsp;&nbsp; <input id="comment_content" name="comment_content"></input>
			<span class="character-counter"></span></div><br />
		Slave API Secret : <input id="comment_content2" name="comment_content2"></input>
		<br /><br />
		Slave API Name : <input id="comment_content3" name="comment_content3"></input>

		<br />
		<br />
		<div>
			<button type="submit" class="btn waves-effect waves-light">Add</button>
		</div>
	</form>

	<hr />

	<b>Current Master</b><br />
	<table class="table table-striped">
		<thead>
			<tr>
				<th>Name</th>
				<th>Key</th>
				<th>Secret</th>
			</tr>
		</thead>
		{% for row in rows2 %}

		<tr>
			<td>{{row["name"]}}</td>
			<td>{{row["key"]}}</td>
			<td> {{ row["secret"]}}</td>
		</tr>
		{% endfor %}
	</table>

	<a href="/delete_master" class="btn btn-success btn-lg">Delete Master</a>

	<hr />

	<b>Slaves List</b><br />

	<table class="table table-striped">
		<thead>
			<tr>
				<th>Name</th>
				<th>Key</th>
				<th>Secret</th>
			</tr>
		</thead>

		{% for row in rows %}
		<tr>
			<td>{{row["name"]}}</td>
			<td>{{row["key"]}}</td>
			<td> {{ row["secret"]}}</td>
		</tr> {% endfor %}
	</table>
	<a href="/delete_slave" class="btn btn-success btn-lg">Delete Slaves</a>


</div>
<!-- Footer -->
<footer class="page-footer font-small blue pt-4">
		<div class="footer-copyright text-center py-3">© 2020 Copyright:
			<a href="https://mdbootstrap.com/education/bootstrap/"> mrashad.tk</a>
		</div>
	</footer>
{% endblock %}

================================================
FILE: templates/includes/_formhelpers.html
================================================
{% macro render_field(field) %}
	{{ field.label }}
	{{ field(**kwargs)|safe }}
	{% if field.errors %}
		{% for error in field.errors %}
			<span class="help-inline">{{ error }}</span>
		{% endfor %}
	{% endif %}
{% endmacro %}

================================================
FILE: templates/includes/_messages.html
================================================
{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    {% for category, message in messages %}
      <div class="alert alert-{{ category }}">{{ message }}</div>
    {% endfor %}
  {% endif %}
{% endwith %}
 {% if error %}
	<div class="alert alert-danger">{{error}}</div>
{% endif %}
 {% if msg %}
	<div class="alert alert-success">{{msg}}</div>
{% endif %}

================================================
FILE: templates/includes/_navbar.html
================================================
<nav class="navbar navbar-expand-md navbar-dark bg-primary fixed-top">
  <div class="container">
    <a href="/" class="navbar-brand">Binance Cryptocurrency CopyTrader</a>
    <button class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarCollapse">
      <ul class="navbar-nav mr-auto">
        <!-- <li class="nav-item">
          <a href="/articles" class="nav-link">All Transactions</a>
        </li> -->
      </ul>
    </div>
  </div>
</nav>


================================================
FILE: templates/layout.html
================================================
<!DOCTYPE html>
<html class="no-js" lang="en">

<head>
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta charset="utf-8">
  <title>Binance Cryptocurrency CopyTrader</title>
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
    integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
  <link rel="icon" href="https://pbs.twimg.com/profile_images/3271649663/14d11dc5dcc16f367cfaaf1dd6e33564_400x400.jpeg"
    type="image/gif" sizes="16x16">
  <!-- <link rel="stylesheet" href="{{ url_for('static',filename='styles/bootstrap.min.css') }}"> -->
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
    integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  <!-- <link rel="stylesheet" href="{{ url_for('static',filename='styles/base.css') }}"> {% block head %}{% endblock %} -->
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <!-- Font Awesome -->
  <!-- Bootstrap core CSS -->
   <!-- Material Design Bootstrap -->
  <link href="https://cdnjs.cloudflare.com/ajax/libs/mdbootstrap/4.10.1/css/mdb.min.css rel="stylesheet">
  <!-- Your custom styles (optional) -->
</head>

<body>
  {% include 'includes/_navbar.html' %}
  <div class="">
    {% include 'includes/_messages.html' %} {% block body %}{% endblock%}
  </div>
  <!-- <script src="{{ url_for('static',filename='js/jquery.min.js') }}"></script>
    <script src="{{ url_for('static',filename='js/popper.min.js') }}"></script>
    <script src="{{ url_for('static',filename='js/bootstrap.min.js') }}"></script> -->
  <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
    integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">
  </script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
    integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous">
  </script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
    integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous">
  </script>
  <script src="//cdn.ckeditor.com/4.6.2/basic/ckeditor.js"></script>
  <script type="text/javascript">
    CKEDITOR.replace('editor')
  </script> {% block end_body %}{% endblock %}
</body>

</html>
Download .txt
gitextract_wz8roz6m/

├── .gitignore
├── Actions/
│   └── Actions.py
├── Dockerfile
├── ExchangeInterfaces/
│   ├── BinanceExchange.py
│   ├── BitmexExchange.py
│   ├── BitmexTest.py
│   └── Exchange.py
├── Helpers/
│   ├── Bitmex_websocket_mod.py
│   ├── Helpers.py
│   └── Order.py
├── LICENSE
├── README.md
├── SlaveContainer.py
├── api.py
├── changelog.md
├── config_files/
│   ├── config-sample.json
│   ├── config.csv
│   ├── symbols.csv
│   ├── symbols2.csv
│   ├── symbols3.csv
│   └── symbols4.csv
├── logs/
│   └── cct.log
├── requirements.txt
├── static/
│   └── styles/
│       └── base.css
└── templates/
    ├── home.html
    ├── includes/
    │   ├── _formhelpers.html
    │   ├── _messages.html
    │   └── _navbar.html
    └── layout.html
Download .txt
SYMBOL INDEX (109 symbols across 10 files)

FILE: Actions/Actions.py
  class Action (line 5) | class Action(ABC):
    method __init__ (line 8) | def __init__(self, exchange, original_event):
    method __str__ (line 12) | def __str__(self):
  class ActionNewOrder (line 25) | class ActionNewOrder(Action):
    method __init__ (line 28) | def __init__(self, order, exchange, original_event):
  class ActionCancel (line 33) | class ActionCancel(Action):
    method __init__ (line 36) | def __init__(self, symbol, price, order_id, exchange, original_event):
  class ActionClosePosition (line 43) | class ActionClosePosition(Action):
    method __init__ (line 46) | def __init__(self, symbol, order_type, price, order_id, exchange, orig...

FILE: ExchangeInterfaces/BinanceExchange.py
  class BinanceExchange (line 12) | class BinanceExchange(Exchange):
    method __init__ (line 16) | def __init__(self, apiKey, apiSecret, pairs, name):
    method start (line 39) | def start(self, caller_callback):
    method update_balance (line 42) | def update_balance(self):
    method get_trading_symbols (line 46) | def get_trading_symbols(self):
    method set_balance (line 55) | def set_balance(self, balances):
    method on_balance_update (line 61) | def on_balance_update(self, upd_balance_ev):
    method get_open_orders (line 70) | def get_open_orders(self):
    method _cancel_order (line 80) | def _cancel_order(self, order_id, symbol):
    method on_cancel_handler (line 84) | async def on_cancel_handler(self, event: Actions.ActionCancel):
    method stop (line 93) | def stop(self):
    method _cancel_order_detector (line 96) | def _cancel_order_detector(self, price):
    method process_event (line 103) | def process_event(self, event):
    method on_order_handler (line 150) | async def on_order_handler(self, event: Actions.ActionNewOrder):
    method create_order (line 153) | def create_order(self, order):
    method _get_quote_balance (line 186) | def _get_quote_balance(self, symbol):
    method _get_base_balance (line 189) | def _get_base_balance(self, symbol):
    method get_part (line 192) | def get_part(self, symbol: str, quantity: float, price: float, side: s...
    method calc_quantity_from_part (line 215) | def calc_quantity_from_part(self, symbol, quantityPart, price, side):

FILE: ExchangeInterfaces/BitmexExchange.py
  class BitmexExchange (line 12) | class BitmexExchange(Exchange):
    method __init__ (line 18) | def __init__(self, apiKey, apiSecret, pairs, name):
    method start (line 40) | def start(self, caller_callback):
    method stop (line 54) | def stop(self):
    method on_balance_update (line 58) | def on_balance_update(self, event):
    method update_balance (line 65) | def update_balance(self):
    method get_open_orders (line 69) | def get_open_orders(self):
    method get_part (line 79) | def get_part(self, symbol,  quantity: float, price: float):
    method calc_quantity_from_part (line 94) | def calc_quantity_from_part(self, symbol, quantityPart, price, **kwargs):
    method process_event (line 100) | def process_event(self, event):
    method on_order_handler (line 154) | async def on_order_handler(self, event: Actions.ActionNewOrder):
    method on_cancel_handler (line 157) | async def on_cancel_handler(self, event: Actions.ActionCancel):
    method _self_order_to_global (line 170) | def _self_order_to_global(self, o) -> Order:
    method _cancel_order_detector (line 185) | def _cancel_order_detector(self, price):
    method _cancel_order (line 192) | def _cancel_order(self, order_id, clOrderID=None):
    method create_order (line 203) | def create_order(self, order: Order):
    method close_position (line 234) | async def close_position(self, event: Actions.ActionClosePosition):
    method translate (line 261) | def translate(word) -> str:

FILE: ExchangeInterfaces/BitmexTest.py
  class BitmexTest (line 4) | class BitmexTest(BitmexExchange):

FILE: ExchangeInterfaces/Exchange.py
  class Exchange (line 5) | class Exchange(ABC):
    method __init__ (line 9) | def __init__(self, apiKey, apiSecret, pairs, name):
    method get_balance (line 20) | def get_balance(self) -> float:
    method stop (line 26) | def stop(self):
    method start (line 30) | def start(self, caller_callback):
    method process_event (line 34) | def process_event(self, event) -> Actions.Action or None:
    method on_order_handler (line 38) | def on_order_handler(self, event: Actions.ActionNewOrder):
    method get_open_orders (line 42) | def get_open_orders(self):
    method on_cancel_handler (line 46) | async def on_cancel_handler(self, event: Actions.ActionCancel):
    method create_order (line 50) | def create_order(self, order):
    method async_create_order (line 53) | async def async_create_order(self, order):
    method get_part (line 57) | def get_part(self, symbol: str, quantity: float, price: float) -> float:
    method calc_quantity_from_part (line 61) | def calc_quantity_from_part(self, symbol: str, quantityPart: float, pr...
    method add_expected_order_id (line 64) | def add_expected_order_id(self, id, callback):
    method check_expected_order (line 68) | def check_expected_order(self, order):
    method close_position (line 73) | async def close_position(self, event: Actions.ActionClosePosition):
    method is_program_order (line 76) | def is_program_order(self, _id) -> bool:
    method delete_id (line 81) | def delete_id(self, _id):

FILE: Helpers/Bitmex_websocket_mod.py
  class BitMEXWebsocket_mod (line 16) | class BitMEXWebsocket_mod:
    method __init__ (line 21) | def __init__(self, endpoint, symbol, api_key=None, api_secret=None, on...
    method exit (line 57) | def exit(self):
    method get_instrument (line 62) | def get_instrument(self):
    method get_ticker (line 69) | def get_ticker(self):
    method funds (line 84) | def funds(self):
    method positions (line 88) | def positions(self):
    method market_depth (line 92) | def market_depth(self):
    method open_orders (line 96) | def open_orders(self, clOrdIDPrefix):
    method recent_trades (line 102) | def recent_trades(self):
    method __connect (line 110) | def __connect(self, wsURL, symbol):
    method __get_auth (line 136) | def __get_auth(self):
    method __get_url (line 152) | def __get_url(self):
    method __wait_for_account (line 170) | def __wait_for_account(self):
    method __wait_for_symbol (line 176) | def __wait_for_symbol(self, symbol):
    method __send_command (line 181) | def __send_command(self, command, args=None):
    method __on_message (line 187) | def __on_message(self, message):
    method __on_error (line 250) | def __on_error(self, error):
    method __on_open (line 259) | def __on_open(self):
    method __on_close (line 263) | def __on_close(self):
  function find_by_keys (line 275) | def find_by_keys(keys, table, matchData):
  function order_leaves_quantity (line 280) | def order_leaves_quantity(o):

FILE: Helpers/Helpers.py
  function create_logger (line 20) | def create_logger():
  function server_begin (line 48) | def server_begin():

FILE: Helpers/Order.py
  class Order (line 1) | class Order:
    method __init__ (line 2) | def __init__(self, price, amount, quantityPart, order_id, symbol, side...
    method __str__ (line 13) | def __str__(self):
    method __repr__ (line 22) | def __repr__(self):

FILE: SlaveContainer.py
  function factory_method_create_exchange (line 10) | def factory_method_create_exchange(single_config, pairs) -> Exchange:
  class SlaveContainer (line 16) | class SlaveContainer:
    method __init__ (line 17) | def __init__(self, config, pairs):
    method start (line 38) | def start(self):
    method stop (line 43) | def stop(self):
    method on_event_handler (line 48) | def on_event_handler(self, event):
    method first_copy (line 85) | def first_copy(self, orders):

FILE: api.py
  function socket_function (line 16) | def socket_function(container: SlaveContainer):
  function manual_run (line 26) | def manual_run():
  function set_stop_run (line 34) | def set_stop_run():
  function run_process (line 47) | def run_process():
  function master_form (line 59) | def master_form():
  function delete_master (line 77) | def delete_master():
  function delete_slave (line 88) | def delete_slave():
  function slave_form (line 99) | def slave_form():
  function homepage (line 115) | def homepage():
Condensed preview — 29 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (83K chars).
[
  {
    "path": ".gitignore",
    "chars": 243,
    "preview": "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*."
  },
  {
    "path": "Actions/Actions.py",
    "chars": 1467,
    "preview": "import inspect\nfrom abc import ABC\n\n\nclass Action(ABC):\n    name = 'abstract_action'\n\n    def __init__(self, exchange, o"
  },
  {
    "path": "Dockerfile",
    "chars": 809,
    "preview": "FROM phusion/baseimage:latest\n\n# Use baseimage-docker's init system.\nCMD [\"/sbin/my_init\"]\n\nRUN apt-get update -y && \\\n "
  },
  {
    "path": "ExchangeInterfaces/BinanceExchange.py",
    "chars": 9904,
    "preview": "import math\nimport Actions.Actions as Actions\n\nfrom binance.exceptions import BinanceAPIException\n\nfrom .Exchange import"
  },
  {
    "path": "ExchangeInterfaces/BitmexExchange.py",
    "chars": 12141,
    "preview": "from .Exchange import Exchange\n# from bitmex_websocket import BitMEXWebsocket\nfrom Helpers.Bitmex_websocket_mod import B"
  },
  {
    "path": "ExchangeInterfaces/BitmexTest.py",
    "chars": 164,
    "preview": "from ExchangeInterfaces.BitmexExchange import BitmexExchange\n\n\nclass BitmexTest(BitmexExchange):\n    ENDPOINT = \"https:/"
  },
  {
    "path": "ExchangeInterfaces/Exchange.py",
    "chars": 2248,
    "preview": "from abc import ABC, abstractmethod\nimport logging\nimport Actions.Actions as Actions\n\nclass Exchange(ABC):\n    exchange_"
  },
  {
    "path": "Helpers/Bitmex_websocket_mod.py",
    "chars": 11589,
    "preview": "import websocket\nimport threading\nimport traceback\nfrom time import sleep\nimport json\nimport logging\nimport urllib\nimpor"
  },
  {
    "path": "Helpers/Helpers.py",
    "chars": 1810,
    "preview": "import json\nfrom SlaveContainer import SlaveContainer\nimport logging\nimport logging.config\nimport sys\nimport os\n\nimport "
  },
  {
    "path": "Helpers/Order.py",
    "chars": 763,
    "preview": "class Order:\n    def __init__(self, price, amount, quantityPart, order_id, symbol, side, order_type, exchange, stop=0):\n"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 2551,
    "preview": "# Crypto-Copy-Trader\nCopy trading tool for cryptocurrencies - written in Python & Flask\n<br/> Makes you copy high-perfor"
  },
  {
    "path": "SlaveContainer.py",
    "chars": 3442,
    "preview": "import asyncio\nfrom ExchangeInterfaces.BinanceExchange import BinanceExchange\nfrom ExchangeInterfaces.BitmexExchange imp"
  },
  {
    "path": "api.py",
    "chars": 4547,
    "preview": "from flask import Flask, render_template, request, redirect\nfrom threading import Thread\nimport sqlite3 as sql\nimport cs"
  },
  {
    "path": "changelog.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "config_files/config-sample.json",
    "chars": 391,
    "preview": "{\n  \"master\": {\n    \"name\": \"Master\" ,\n    \"key\":\"\" ,\n    \"secret\" :\"\",\n    \"exchange_name\": \"BitmexExchange\"\n     },\n  "
  },
  {
    "path": "config_files/config.csv",
    "chars": 462,
    "preview": "Master API Key,NBVX3hwfLkadvNUPJwIxUPkOWPlBfvMJivnrlnRhZZzvNveKI0FBKVLHlnj7KU5p,\r\nMaster API Keys,R0FXuxetL9NmLqRCkEleKc"
  },
  {
    "path": "config_files/symbols.csv",
    "chars": 29,
    "preview": "ETHUSDT\nBTCUSDT\nETHBTC\nLTCBTC"
  },
  {
    "path": "config_files/symbols2.csv",
    "chars": 875,
    "preview": "THETAUSDT\nENJUSDT\nMITHUSDT\nMATICBTC\nMATICUSDT\nATOMBTC\nATOMUSDT\nPHBBTC\nTFUELBTC\nTFUELUSDT\nONEBTC\nONEUSDT\nFTMBTC\nFTMUSDT\nB"
  },
  {
    "path": "config_files/symbols3.csv",
    "chars": 736,
    "preview": "STORMBTC\nQTUMUSDT\nXEMBTC\nWANBTC\nWPRBTC\nQLCBTC\nSYSBTC\nGRSBTC\nADAUSDT\nCLOAKBTC\nGNTBTC\nLOOMBTC\nXRPUSDT\nBCNBTC\nREPBTC\nZENBTC"
  },
  {
    "path": "config_files/symbols4.csv",
    "chars": 566,
    "preview": "RCNBTC\nNULSBTC\nRDNBTC\nXMRBTC\nDLTBTC\nAMBBTC\nBCCUSDT\nBATBTC\nBCPTBTC\nARNBTC\nGVTBTC\nCDTBTC\nGXSBTC\nNEOUSDT\nPOEBTC\nQSPBTC\nBTSB"
  },
  {
    "path": "logs/cct.log",
    "chars": 4780,
    "preview": "2020-09-21 20:55:40,426 - INFO - The Crypto-Copy-Trader starts launch\n2020-09-21 20:55:40,427 - INFO - Reading configura"
  },
  {
    "path": "requirements.txt",
    "chars": 110,
    "preview": "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",
    "chars": 1206,
    "preview": "body {\n  background: rgb(255, 255, 255);\n  color: rgb(0, 0, 0);\n}\n\n.navbar {\n  /* border-bottom: #000 3px solid; */\n  op"
  },
  {
    "path": "templates/home.html",
    "chars": 2588,
    "preview": "{% extends 'layout.html' %}\n{% block body %}\n<div class=\"jumbotron\">\n\t<br />\n\t<hr />\n\n\t<h1>Binance Cryptocurrency CopyTr"
  },
  {
    "path": "templates/includes/_formhelpers.html",
    "chars": 226,
    "preview": "{% macro render_field(field) %}\n\t{{ field.label }}\n\t{{ field(**kwargs)|safe }}\n\t{% if field.errors %}\n\t\t{% for error in "
  },
  {
    "path": "templates/includes/_messages.html",
    "chars": 390,
    "preview": "{% with messages = get_flashed_messages(with_categories=true) %}\n  {% if messages %}\n    {% for category, message in mes"
  },
  {
    "path": "templates/includes/_navbar.html",
    "chars": 584,
    "preview": "<nav class=\"navbar navbar-expand-md navbar-dark bg-primary fixed-top\">\n  <div class=\"container\">\n    <a href=\"/\" class=\""
  },
  {
    "path": "templates/layout.html",
    "chars": 2578,
    "preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n\n<head>\n  <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n  <meta n"
  }
]

About this extraction

This page contains the full source code of the MohammedRashad/Crypto-Copy-Trader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 29 files (76.7 KB), approximately 19.4k tokens, and a symbol index with 109 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!