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 : <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 : <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>
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
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.