Full Code of EasyAI/Simple-Binance-Trader for AI

master 3ad6ad40e259 cached
14 files
87.6 KB
20.7k tokens
58 symbols
1 requests
Download .txt
Repository: EasyAI/Simple-Binance-Trader
Branch: master
Commit: 3ad6ad40e259
Files: 14
Total size: 87.6 KB

Directory structure:
gitextract_jeifm_e2/

├── LICENSE
├── README.md
├── core/
│   ├── botCore.py
│   ├── static/
│   │   ├── css/
│   │   │   └── style.css
│   │   └── js/
│   │       ├── charts.js
│   │       └── script.js
│   ├── templates/
│   │   ├── jinja_templates.html
│   │   ├── main_page.html
│   │   └── page_template.html
│   └── trader.py
├── patterns.py
├── requirements.txt
├── run.py
└── trader_configuration.py

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

================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021 John P Lennie

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Simple Binance Trader

# Disclaimer
I am not responsible for the trades you make with the script, this script has not been exstensivly tested on live trades.

## Please check the following:
- Make sure your account uses BNB for the trade fees and that you have plenty of BNB for the trader to use for trades as if not there will be issues with the trader.
- Please clear any cache files when updating the trader as there may be issues if not.
- Please if any updates are also available for binance_api or the technical indicators update those also.


NOTE: The current strtergy is also very weak and will mostlikley return only losses therefore I recomend you create your work or use some which would work. However the trader currently also does not support simulated trades and only support live trading, simulated trades should be added in the future so you may need to wait until then to use this to test stratergies.

NOTE: Trader now supports MARGIN trades allowing the placement of short and long positions. If SPOT trading put your conditions within the "long_entry/long_exit" sections within the trader_configuration.py file.


## Description
This is a simple binance trader that uses the REST api and sockets to allow automation or manipulation of account detail/data. The script which in the default configuration uses a basic MACD trading setup to trade. The script however is very customisable and you'll be able to configure it as you with via the help of the document in the usage section.

### Repository Contains:
- run.py : This is used to start/setup the bot.
- trader_configuration.py : Here is where you write your conditions using python logic.
- patterns.py : Can be used to trade based on specific patterns.
- Core
  - botCore.py : Is used to manage the socket and trader as well as pull data to be displayed.
  - trader.py : The main trader inchage or updating and watching orders.
  - static : Folder for static files for the website (js/css).
  - templates : Folder for HTML page templates.
  
### Setting File:
- PUBLIC_KEY -  Your public binanace api key
- PRIVATE_KEY - Your private binanace api key
- IS_TEST - Allow trader to be run in test mode (True/False)
- MARKET_TYPE - Market type to be traded (SPOT/MARGIN)
- UPDATE_BNB_BALANCE - Automatically update the BNB balance when low (for trading fees, only applicable to real trading)
- TRADER_INTERVAL - Interval used for the trader (1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d).
- TRADING_CURRENCY - The currency max the trader will use (in the quote key) also note this scales up with the number of markets i.e. 2 pairs each market will have 0.0015 as their trading currency pair.
- TRADING_MARKETS - The markets that are being traded and seperate with ',' (BTC-ETH,BTC-NEO)
- HOST_IP - The host IP for the web UI (if left blank default is 127.0.0.1)
- HOST_PORT - The host port for the web UI (if left blank default is 5000)
- MAX_CANDLES - Max candles the trader will use (if left brank default is 500)
- MAX_DEPTH - Max market depth the trader will use (if left brank default is 50)

## Usage
I recommend setting this all up within a virtual python enviornment:
First get the base modules:
 - To quickly install all the required modules use 'pip3 install -r requirements'.

Secondly get the required techinal indicators module adn binance api.
 - https://github.com/EasyAI/binance_api, This is the binance API that the trader uses.
 - https://github.com/EasyAI/Python-Charting-Indicators, This contains the logic to calculate technical indicators. (only the file technical_indicators.py is needed)

Move them into the site-packages folder. NOTE: If you get an error saying that either the technical_indicators or binance_api is not found you can move them in to the same directory as the run.py file for the trader.

Finally navigate to the trader directory.

To set up the bot and for any further detail please refer to the google doc link below:
https://docs.google.com/document/d/1VUx_1O5kQQxk0HfqqA8WyQpk6EbbnXcezAdqXkOMklo/edit?usp=sharing

### Contact
Please if you find any bugs or issues contact me so I can improve.
EMAIL: jlennie1996@gmail.com


================================================
FILE: core/botCore.py
================================================
#! /usr/bin/env python3
import os
import sys
import time
import json
import os.path
import hashlib
import logging
import threading
from decimal import Decimal
from flask_socketio import SocketIO
from flask import Flask, render_template, url_for, request

from binance_api import api_master_rest_caller
from binance_api import api_master_socket_caller

from . import trader


MULTI_DEPTH_INDICATORS = ['ema', 'sma', 'rma', 'order']

# Initilize globals.

## Setup flask app/socket
APP         = Flask(__name__)
SOCKET_IO   = SocketIO(APP)

## Initilize base core object.
core_object = None

started_updater = False

## Initilize IP/port pair globals.
host_ip     = ''
host_port   = ''

## Set traders cache file name.
CAHCE_FILES = 'traders.json'


@APP.context_processor
def override_url_for():
    return(dict(url_for=dated_url_for))


def dated_url_for(endpoint, **values):
    # Override to prevent cached assets being used.
    if endpoint == 'static':
        filename = values.get('filename', None)
        if filename:
            file_path = os.path.join(APP.root_path,
                                    endpoint,
                                    filename)
            values['q'] = int(os.stat(file_path).st_mtime)
    return url_for(endpoint, **values)


@APP.route('/', methods=['GET'])
def control_panel():
    # Base control panel configuration.
    global started_updater 

    ## Web updater used for live updating.
    if not(started_updater):
        started_updater = True
        web_updater_thread = threading.Thread(target=web_updater)
        web_updater_thread.start()

    ## Set socket ip/port.
    start_up_data = {
        'host':{'IP': host_ip, 'Port': host_port},
        'market_symbols': core_object.trading_markets
    }

    return(render_template('main_page.html', data=start_up_data))


@APP.route('/rest-api/v1/trader_update', methods=['POST'])
def update_trader():
    # Base API for managing trader interaction.
    data = request.get_json()

    ## Check if specified bot exists.
    current_trader = api_error_check(data)

    if current_trader == None:
        ## No trader therefore return false.
        return(json.dumps({'call':False, 'message':'INVALID_TRADER'}))
    elif data['action'] == 'start':
        ## Updating trader status to running.
        if current_trader.state_data['runtime_state'] == 'FORCE_PAUSE':
            current_trader.state_data['runtime_state'] = 'RUN'
    elif data['action'] == 'pause':
        ## Updating trader status to paused.
        if current_trader.state_data['runtime_state'] == 'RUN':
            current_trader.state_data['runtime_state'] = 'FORCE_PAUSE'
    else:
        ## If action was not found return false
        return(json.dumps({'call':False, 'message':'INVALID_ACTION'}))

    return(json.dumps({'call':True}))


@APP.route('/rest-api/v1/get_trader_charting', methods=['GET'])
def get_trader_charting():
    # Endpoint to pass trader indicator data.
    market = request.args.get('market')
    limit = int(request.args.get('limit'))
    data = {'market':market}

    ## Check if specified bot exists.
    current_trader = api_error_check(data)

    if current_trader == None:
        ## No trader therefore return false.
        return(json.dumps({'call':False, 'message':'INVALID_TRADER'}))

    candle_data = core_object.get_trader_candles(current_trader.print_pair)[:limit]
    indicator_data = core_object.get_trader_indicators(current_trader.print_pair)
    short_indicator_data = shorten_indicators(indicator_data, candle_data[-1][0])

    return(json.dumps({'call':True, 'data':{'market':market, 'indicators':short_indicator_data, 'candles':candle_data}}))


@APP.route('/rest-api/v1/get_trader_indicators', methods=['GET'])
def get_trader_indicators():
    # Endpoint to pass trader indicator data.
    market = request.args.get('market')
    limit = int(request.args.get('limit'))
    data = {'market':market}

    ## Check if specified bot exists.
    current_trader = api_error_check(data)

    if current_trader == None:
        ## No trader therefore return false.
        return(json.dumps({'call':False, 'message':'INVALID_TRADER'}))

    indicator_data = core_object.get_trader_indicators(current_trader.print_pair)

    return(json.dumps({'call':True, 'data':{'market':market, 'indicators':indicator_data}}))


@APP.route('/rest-api/v1/get_trader_candles', methods=['GET'])
def get_trader_candles():
    # Endpoint to pass trader candles.
    market = request.args.get('market')
    limit = int(request.args.get('limit'))
    data = {'market':market}

    ## Check if specified bot exists.
    current_trader = api_error_check(data)

    if current_trader == None:
        ## No trader therefore return false.
        return(json.dumps({'call':False, 'message':'INVALID_TRADER'}))

    candle_data = core_object.get_trader_candles(current_trader.print_pair)[:limit]

    return(json.dumps({'call':True, 'data':{'market':market, 'candles':candle_data}}))


@APP.route('/rest-api/v1/test', methods=['GET'])
def test_rest_call():
    # API endpoint test
    return(json.dumps({'call':True, 'message':'HELLO WORLD!'}))


def shorten_indicators(indicators, end_time):
    base_indicators = {}

    for ind in indicators:
        if ind in MULTI_DEPTH_INDICATORS:
            base_indicators.update({ind:{}})
            for sub_ind in indicators[ind]:
                base_indicators[ind].update({sub_ind:[ [val[0] if ind != 'order' else val[0]*1000,val[1]] for val in indicators[ind][sub_ind] if (val[0] if ind != 'order' else val[0]*1000) > end_time ]})
        else:
            base_indicators.update({ind:[ [val[0],val[1]] for val in indicators[ind] if val[0] > end_time]})

    return(base_indicators)


def api_error_check(data):
    ## Check if specified bot exists.
    current_trader = None
    for trader in core_object.trader_objects:
        if trader.print_pair == data['market']:
            current_trader = trader
            break
    return(current_trader)


def web_updater():
    # Web updater use to update live via socket.
    lastHash = None

    while True:
        if core_object.coreState == 'RUN':
            ## Get trader data and hash it to find out if there have been any changes.
            traderData = core_object.get_trader_data()
            currHash = hashlib.md5(str(traderData).encode())

            if lastHash != currHash:
                ## Update any new changes via socket.
                lastHash = currHash
                total_bulk_data = []
                for trader in traderData:
                    bulk_data = {}
                    bulk_data.update({'market':trader['market']})
                    bulk_data.update({'trade_recorder':trader['trade_recorder']})
                    bulk_data.update({'wallet_pair':trader['wallet_pair']})

                    bulk_data.update(trader['custom_conditions'])
                    bulk_data.update(trader['market_activity'])
                    bulk_data.update(trader['market_prices'])
                    bulk_data.update(trader['state_data'])
                    total_bulk_data.append(bulk_data)

                SOCKET_IO.emit('current_traders_data', {'data':total_bulk_data})
                time.sleep(.8)


class BotCore():

    def __init__(self, settings, logs_dir, cache_dir):
        # Initilization for the bot core managment object.
        logging.info('[BotCore] Initilizing the BotCore object.')

        ## Setup binance REST and socket API.
        self.rest_api           = api_master_rest_caller.Binance_REST(settings['public_key'], settings['private_key'])
        self.socket_api         = api_master_socket_caller.Binance_SOCK()

        ## Setup the logs/cache dir locations.
        self.logs_dir           = logs_dir
        self.cache_dir          = cache_dir

        ## Setup run type, market type, and update bnb balance.
        self.run_type           = settings['run_type']
        self.market_type        = settings['market_type']
        self.update_bnb_balance = settings['update_bnb_balance']

        ## Setup max candle/depth setting.
        self.max_candles        = settings['max_candles']
        self.max_depth          = settings['max_depth']

        ## Get base quote pair (This prevents multiple different pairs from conflicting.)
        pair_one = settings['trading_markets'][0]

        self.quote_asset        = pair_one[:pair_one.index('-')]
        self.base_currency      = settings['trading_currency']
        self.candle_Interval    = settings['trader_interval']

        ## Initilize base trader settings.
        self.trader_objects     = []
        self.trading_markets    = settings['trading_markets']

        ## Initilize core state
        self.coreState          = 'READY'


    def start(self):
        # Start the core object.
        logging.info('[BotCore] Starting the BotCore object.')
        self.coreState = 'SETUP'

        ## check markets
        found_markets = []
        not_supported = []

        for market in self.rest_api.get_exchangeInfo()['symbols']:
            fmtMarket = '{0}-{1}'.format(market['quoteAsset'], market['baseAsset'])

            # If the current market is not in the trading markets list then skip.
            if not fmtMarket in self.trading_markets:
                continue

            found_markets.append(fmtMarket)

            if (self.market_type == 'MARGIN' and market['isMarginTradingAllowed'] == False) or (self.market_type == 'SPOT' and market['isSpotTradingAllowed'] == False):
                not_supported.append(fmtMarket)
                continue

            # This is used to setup min quantity.
            if float(market['filters'][2]['minQty']) < 1.0:
                minQuantBase = (Decimal(market['filters'][2]['minQty'])).as_tuple()
                lS = abs(int(len(minQuantBase.digits)+minQuantBase.exponent))+1
            else: lS = 0

            # This is used to set up the price precision for the market.
            tickSizeBase = (Decimal(market['filters'][0]['tickSize'])).as_tuple()
            tS = abs(int(len(tickSizeBase.digits)+tickSizeBase.exponent))+1

            # This is used to get the markets minimal notation.
            mN = float(market['filters'][3]['minNotional'])

            # Put all rules into a json object to pass to the trader.
            market_rules = {'LOT_SIZE':lS, 'TICK_SIZE':tS, 'MINIMUM_NOTATION':mN}

            # Initilize trader objecta dn also set-up its inital required data.
            traderObject = trader.BaseTrader(market['quoteAsset'], market['baseAsset'], self.rest_api, socket_api=self.socket_api)
            traderObject.setup_initial_values(self.market_type, self.run_type, market_rules)
            self.trader_objects.append(traderObject)

        
        ## Show markets that dont exist on the binance exchange.
        if len(self.trading_markets) != len(found_markets):
            no_market_text = ''
            for market in [market for market in self.trading_markets if market not in found_markets]:
                no_market_text+=str(market)+', '
            logging.warning('Following pairs dont exist: {}'.format(no_market_text[:-2]))

        ## Show markets that dont support the market type.
        if len(not_supported) > 0:
            not_support_text = ''
            for market in not_supported:
                not_support_text += ' '+str(market)
            logging.warning('[BotCore] Following market pairs are not supported for {}: {}'.format(self.market_type, not_support_text))

        valid_tading_markets = [market for market in found_markets if market not in not_supported]

        ## setup the binance socket.
        for market in valid_tading_markets:
            self.socket_api.set_candle_stream(symbol=market, interval=self.candle_Interval)
            self.socket_api.set_manual_depth_stream(symbol=market, update_speed='1000ms')

        if self.run_type == 'REAL':
            self.socket_api.set_userDataStream(self.rest_api, self.market_type)

        self.socket_api.BASE_CANDLE_LIMIT = self.max_candles
        self.socket_api.BASE_DEPTH_LIMIT = self.max_depth

        self.socket_api.build_query()
        self.socket_api.set_live_and_historic_combo(self.rest_api)

        self.socket_api.start()

        # Load the wallets.
        if self.run_type == 'REAL':
            user_info = self.rest_api.get_account(self.market_type)
            if self.market_type == 'SPOT':
                wallet_balances = user_info['balances']
            elif self.market_type == 'MARGIN':
                wallet_balances = user_info['userAssets']
            current_tokens = {}
            
            for balance in wallet_balances:
                total_balance = (float(balance['free']) + float(balance['locked']))
                if total_balance > 0:
                    current_tokens.update({balance['asset']:[
                                        float(balance['free']),
                                        float(balance['locked'])]})
        else:
            current_tokens = {self.quote_asset:[float(self.base_currency), 0.0]}

        # Load cached data
        cached_traders_data = None
        if os.path.exists(self.cache_dir+CAHCE_FILES):
            with open(self.cache_dir+CAHCE_FILES, 'r') as f:
                cached_traders_data = json.load(f)['data']

        ## Setup the trader objects and start them.
        logging.info('[BotCore] Starting the trader objects.')
        for trader_ in self.trader_objects:
            currSymbol = "{0}{1}".format(trader_.base_asset, trader_.quote_asset)

            # Update trader with cached data (to resume trades/keep records of trades.)
            if cached_traders_data != '' and cached_traders_data:
                for cached_trader in cached_traders_data:
                    m_split = cached_trader['market'].split('-')
                    if (m_split[1]+m_split[0]) == currSymbol:
                        trader_.configuration           = cached_trader['configuration']
                        trader_.custom_conditional_data = cached_trader['custom_conditions']
                        trader_.market_activity         = cached_trader['market_activity']
                        trader_.trade_recorder          = cached_trader['trade_recorder']
                        trader_.state_data              = cached_trader['state_data']

            wallet_pair = {}

            if trader_.quote_asset in current_tokens:
                wallet_pair.update({trader_.quote_asset:current_tokens[trader_.quote_asset]})

            if trader_.base_asset in current_tokens:
                wallet_pair.update({trader_.base_asset:current_tokens[trader_.base_asset]})

            trader_.start(self.base_currency, wallet_pair)

        logging.debug('[BotCore] Starting trader manager')
        TM_thread = threading.Thread(target=self._trader_manager)
        TM_thread.start()

        if self.update_bnb_balance:
            logging.debug('[BotCore] Starting BNB manager')
            BNB_thread = threading.Thread(target=self._bnb_manager)
            BNB_thread.start()

        logging.debug('[BotCore] Starting connection manager thread.')
        CM_thread = threading.Thread(target=self._connection_manager)
        CM_thread.start()

        logging.debug('[BotCore] Starting file manager thread.')
        FM_thread = threading.Thread(target=self._file_manager)
        FM_thread.start()

        logging.info('[BotCore] BotCore successfully started.')
        self.coreState = 'RUN'


    def _trader_manager(self):
        ''' '''
        while self.coreState != 'STOP':
            pass


    def _bnb_manager(self):
        ''' This will manage BNB balance and update if there is low BNB in account. '''
        last_wallet_update_time = 0

        while self.coreState != 'STOP':
            socket_buffer_global = self.socket_api.socketBuffer

            # If outbound postion is seen then wallet has updated.
            if 'outboundAccountPosition' in socket_buffer_global:
                if last_wallet_update_time != socket_buffer_global['outboundAccountPosition']['E']:
                    last_wallet_update_time = socket_buffer_global['outboundAccountPosition']['E']

                    for wallet in socket_buffer_global['outboundAccountPosition']['B']:
                        if wallet['a'] == 'BNB':
                            if float(wallet['f']) < 0.01:
                                bnb_order = self.rest_api.place_order(self.market_type, symbol='BNBBTC', side='BUY', type='MARKET', quantity=0.1)
            time.sleep(2)


    def _file_manager(self):
        ''' This section is responsible for activly updating the traders cache files. '''
        while self.coreState != 'STOP':
            time.sleep(15)

            traders_data = self.get_trader_data()
            if os.path.exists(self.cache_dir):
                file_path = '{0}{1}'.format(self.cache_dir,CAHCE_FILES)
                with open(file_path, 'w') as f:
                    json.dump({'lastUpdateTime':time.time() ,'data':traders_data}, f)


    def _connection_manager(self):
        ''' This section is responsible for re-testing connectiongs in the event of a disconnect. '''
        update_time = 0
        retryCounter = 1
        time.sleep(20)

        while self.coreState != 'STOP':
            time.sleep(1)
            if self.coreState != 'RUN':
                continue
            if self.socket_api.last_data_recv_time != update_time:
                update_time = self.socket_api.last_data_recv_time
            else:
                if (update_time + (15*retryCounter)) < time.time():
                    retryCounter += 1
                    
                    try:
                        print(self.rest_api.test_ping())
                    except Exception as e:
                        logging.warning('[BotCore] Connection issue: {0}.'.format(e))
                        continue

                    logging.info('[BotCore] Connection issue resolved.')
                    if not(self.socket_api.socketRunning):
                        logging.info('[BotCore] Attempting socket restart.')
                        self.socket_api.start()


    def get_trader_data(self):
        ''' This can be called to return data for each of the active traders. '''
        rData = [ _trader.get_trader_data() for _trader in self.trader_objects ]
        return(rData)


    def get_trader_indicators(self, market):
        ''' This can be called to return the indicators that are used by the traders (Will be used to display web UI activity.) '''
        for _trader in self.trader_objects:
            if _trader.print_pair == market:
                indicator_data = _trader.indicators
                indicator_data.update({'order':{'buy':[], 'sell':[]}})
                indicator_data['order']['buy'] = [ [order[0],order[1]] for order in _trader.trade_recorder if order[4] == 'BUY']
                indicator_data['order']['sell'] = [ [order[0],order[1]] for order in _trader.trade_recorder if order[4] == 'SELL']
                return(indicator_data)


    def get_trader_candles(self, market):
        ''' This can be called to return the candle data for the traders (Will be used to display web UI activity.) '''
        for _trader in self.trader_objects:
            if _trader.print_pair == market:
                sock_symbol = str(_trader.base_asset)+str(_trader.quote_asset)
                return(self.socket_api.get_live_candles(sock_symbol))


def start(settings, logs_dir, cache_dir):
    global core_object, host_ip, host_port

    if core_object == None:
        core_object = BotCore(settings, logs_dir, cache_dir)
        core_object.start()

    logging.info('[BotCore] Starting traders in {0} mode, market type is {1}.'.format(settings['run_type'], settings['market_type']))
    log = logging.getLogger('werkzeug')
    log.setLevel(logging.ERROR)

    host_ip = settings['host_ip']
    host_port = settings['host_port']

    SOCKET_IO.run(APP, 
        host=settings['host_ip'], 
        port=settings['host_port'], 
        debug=True, 
        use_reloader=False)


================================================
FILE: core/static/css/style.css
================================================
@import url('https://fonts.googleapis.com/css?family=Josefin+Sans&display=swap');

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  list-style: none;
  text-decoration: none;
  font-family: arial;
}

body{
   	background-color: #f3f5f9;
	display: flex;
  	position: relative;
}

.sidebar{
  width: 200px;
  height: 100%;
  background: #4b4276;
  padding: 30px 0px;
  position: fixed;
  color: #bdb8d7;
}

.sidebar ul li{
  padding: 15px;
}    

.sidebar ul li a{
  display: block;
  color: #bdb8d7;
}

.sidebar ul li:hover{
  background-color: #594f8d;
}
    
.sidebar ul li:hover a{
  color: #fff;
}

main {
	width: 100%;
	margin-left: 200px;
	background-color: #F5F5F0;
	padding-left: 20px;
	padding-top: 10px;
}

.trader-panel {
	display: none;
}

.small-button {
	padding: 5px;
	font-size: 12px;
	display: block;
	display: inline-block;
	border: none;
	border-radius: 4px;
	cursor: pointer;
	color: white;
}

.green-button {
	background-color: #00802b;
}

.green-button:hover {
	background-color: #33ff77;
}

.amber-button {
	background-color: #e68a00
}

.amber-button:hover {
	background-color: #ffa31a;
}

================================================
FILE: core/static/js/charts.js
================================================
// Indicator meta details
const indicator_chart_types_mapping = {'patterns_data_lines':'line', 'patterns_data_points':'scatter', 'tops_bottoms':'scatter', 'data_lines':'line', 'cps':'line', 'ichi':'line', 'boll':'line', 'adx':'line', 'stock':'line', 'order':'scatter', 'ema':'line', 'sma':'line', 'rma':'line', 'rsi':'line', 'mfi':'line', 'cci':'line', 'zerolagmacd':'macd', 'macd':'macd'}
const indicator_home_type_mapping = {'patterns_data_lines':'MAIN', 'patterns_data_points':'MAIN', 'tops_bottoms':'MAIN', 'data_lines':'MAIN', 'ichi':'MAIN', 'cps':'MAIN', 'boll':'MAIN', 'adx':'OWN', 'stock':'OWN', 'order':'MAIN', 'ema':'MAIN', 'sma':'MAIN', 'rma':'MAIN', 'rsi':'OWN', 'mfi':'OWN', 'cci':'OWN', 'zerolagmacd':'OWN', 'macd':'OWN'}
const indicator_is_single_mapping = ['patterns_data_lines', 'patterns_data_points', 'tops_bottoms', 'data_lines', 'order', 'ema', 'sma', 'rma', 'rsi', 'mfi', 'cci', 'cps']
const double_depth_indicators = ['ema', 'sma', 'order', 'patterns_data_points', 'patterns_data_lines'];

// Base Apex chart configuration.
window.Apex = {
    chart: {
        animations: {
            enabled: false
        }
    },
    autoScaleYaxis: false
};

// Chart template for main chart.
var base_candle_chart_configuration = {
    series: [],
    chart: {
        height: 800,
        id: 'main_Chart',
        type: 'line'
    },
    fill: {
        type:'solid',
    },
    markers: {
        size: []
    },
    //colors: [],
    stroke: {
        width: []
    },
    tooltip: {
        shared: true,
        custom: []
    },
    xaxis: {
        type: 'datetime',
    },
    yaxis: {
        labels: {
            minWidth: 40,
            formatter: function (value) { return Math.round(value); }
        },
    }
};

let loaded_candles_chart = null;


function initial_build(target_element, charting_data) {
    // Add main chandle chart position.
    var candle_data = charting_data['candles'];
    var indicator_data = charting_data['indicators'];
    var main_chart_series = [];

    loaded_candles_chart = JSON.parse(JSON.stringify(base_candle_chart_configuration));

    populate_chart(indicator_data);

    var built_data = build_candle_data(candle_data);
    var built_candle_data = built_data[0];

    // Finally add the candle to the displayed chart.
    loaded_candles_chart["series"].push({
        name: 'candle',
        type: 'candlestick',
        data: built_candle_data
    });
    loaded_candles_chart["stroke"]["width"].push(1);
    loaded_candles_chart["markers"]["size"].push(0);
    loaded_candles_chart["tooltip"]["custom"].push(function({seriesIndex, dataPointIndex, w}) {
        var o = w.globals.seriesCandleO[seriesIndex][dataPointIndex]
        var h = w.globals.seriesCandleH[seriesIndex][dataPointIndex]
        var l = w.globals.seriesCandleL[seriesIndex][dataPointIndex]
        var c = w.globals.seriesCandleC[seriesIndex][dataPointIndex]
        return (`Open:${o}<br>High:${h}<br>Low:${l}<br>Close:${c}`)
    });

    candle_chart = new ApexCharts(target_element, loaded_candles_chart);
    candle_chart.render();
}


function build_candle_data(candle_data) {
    var built_candle_data = [];
    var built_volume_data = [];

    for (var i=0; i < candle_data.length; i++) {
        var candle = candle_data[i];
        built_candle_data.push({
            x: new Date(parseInt(candle[0])),
            y: [
                candle[1],
                candle[2],
                candle[3],
                candle[4]
            ]
        });

        built_volume_data.push({
            x: new Date(parseInt(candle[0])),
            y: Math.round(candle[5])
        });
    }
    return([built_candle_data, built_volume_data]);
}


function build_timeseries(ind_obj) {
    var indicator_lines = [];
    var keys = []

    // Use sorted timestamp to print out.
    for (var ind in ind_obj) {
        var current_set = ind_obj[ind]

        if (typeof current_set[0] == 'number') {
            indicator_lines.push({
                x: new Date(parseInt(current_set[0])),
                y: current_set[1].toFixed(8)
            });
        } else {
            for (var sub_ind in current_set) {
                if (!keys.includes(sub_ind)) {
                    keys.push(sub_ind)
                    indicator_lines[sub_ind] = []
                }
                indicator_lines[sub_ind].push({
                    x: new Date(parseInt(current_set[sub_ind][0])),
                    y: current_set[sub_ind][1].toFixed(8)
                });
            }
        }
    }
    return(indicator_lines);
}


function build_basic_indicator(chart_obj, ind_obj, chart_type, line_name=null, ind_name=null) {
    var indicator_lines = build_timeseries(ind_obj);

    if (!(line_name == null)) {
        chart_obj["series"].push({
            name: line_name,
            type: chart_type,
            data: indicator_lines
        });
        if (chart_type == "scatter") {
            chart_obj["stroke"]["width"].push(2);
            chart_obj["markers"]["size"].push(8);
        } else {
            chart_obj["stroke"]["width"].push(2);
            chart_obj["markers"]["size"].push(0);
        }
    } else {
        for (var sub_ind_name in indicator_lines) {
            chart_obj["series"].push({
                name: sub_ind_name,
                type: chart_type,
                data: indicator_lines[sub_ind_name]});
            if (chart_type == "scatter") {
                chart_obj["stroke"]["width"].push(2);
                chart_obj["markers"]["size"].push(8);
            } else {
                chart_obj["stroke"]["width"].push(2);
                chart_obj["markers"]["size"].push(0);
            }
        }
    }

    if ('custom' in chart_obj["tooltip"]) {
        if (!(line_name == null)) {
            chart_obj["tooltip"]["custom"].push(
                function({seriesIndex, dataPointIndex, w}) {
                    return w.globals.series[seriesIndex][dataPointIndex]
            });
        } else {
            for (var ind in indicator_lines) {
                chart_obj["tooltip"]["custom"].push(
                    function({seriesIndex, dataPointIndex, w}) {
                        return w.globals.series[seriesIndex][dataPointIndex]
                });
            }
        }
    }
}


function populate_chart(indicator_data) {

    for (var raw_indicator in indicator_data) {

        var patt = /[^\d]+/i;
        var indicator = raw_indicator.match(patt)[0];

        var current_ind = indicator_data[raw_indicator];
        var chart_type = indicator_chart_types_mapping[indicator];
        var home_chart = indicator_home_type_mapping[indicator];
        var line_name = null;
        var ind_name = null;

        if (home_chart == "MAIN") {
            target_chart = loaded_candles_chart;

            if (double_depth_indicators.includes(indicator)) {
                for (var sub_ind in current_ind) {
                    line_name = sub_ind;
                    var built_chart = build_basic_indicator(target_chart, current_ind[sub_ind], chart_type, line_name, ind_name);
                }
            } else {
                if (indicator_is_single_mapping.includes(indicator)) {
                    line_name = raw_indicator;
                }
                var built_chart = build_basic_indicator(target_chart, current_ind, chart_type, line_name, ind_name);
            }
        }
    }
}

================================================
FILE: core/static/js/script.js
================================================
//
// 
const socket = io('http://'+ip+':'+port);

const class_data_mapping = {
    'trader-state':'runtime_state', 
    'trader-lastupdate':'last_update_time', 
    'trader-lastprice':'lastPrice',
    'trader-markettype':'order_market_type', 
    'trader-orderside':'order_side', 
    'trader-ordertype':'order_type', 
    'trader-orderstatus':'order_status', 
    'trader-buyprice':'price', 
    'trader-sellprice':'price', 
    'trader-orderpoint':'order_point'
};

var current_chart = '';
var update_chart = false;


$(document).ready(function() {
    socket.on('current_traders_data', function(data) {
        update_trader_results(data);
    });
});


function update_trader_results(data) {
    // 
    var currentTraders = data['data'];

    var overall_total_trades = 0;
    var overall_total_pl = 0;

    for (x = 0; x < (currentTraders.length); x++){
        var current = currentTraders[x];
        var trade_recorder = current['trade_recorder'];
        var update_targets = [`trader_${current['market']}`, `overview_${current['market']}`]

        for (i = 0; i < (update_targets.length); i++){
            var trader_panel = document.getElementById(update_targets[i]);
            var target_el = null;

            for (var key in class_data_mapping) {
                target_el = trader_panel.getElementsByClassName(key);
                if (target_el.length != 0) {
                    if (current['order_side'] == 'SELL' && key == 'trader-buyprice') {
                        show_val = trade_recorder[trade_recorder.length-1][1];
                    } else {
                        show_val = current[class_data_mapping[key]];
                    }

                    if (show_val === null) {
                        show_val = 'Null';
                    }
                    target_el[0].innerText = show_val;
                }
            }

            target_el = trader_panel.getElementsByClassName('show-sellaction');
            if (target_el.length != 0) {
                if (current['order_side'] == 'SELL') {
                    target_el[0].style.display = 'block';
                } else {
                    target_el[0].style.display = 'none';
                }
            }

            var outcome = 0;
            var total_trades = 0;
            if (trade_recorder.length >= 2) {
                range = trade_recorder.length/2
                for (y = 0; y < (range); y++) {
                    buy_order = trade_recorder[(range*2)-2]
                    sell_order = trade_recorder[range*2-1]

                    buy_value = buy_order[1]*buy_order[2]
                    sell_value = sell_order[1]*buy_order[2]

                    if (buy_order[3].includes("SHORT")) {
                        outcome += buy_value-sell_value;
                    } else {
                        outcome += sell_value-buy_value;
                    }
                    total_trades += 1
                }
            }

            var r_outcome = Math.round(outcome*100000000)/100000000;

            target_el = trader_panel.getElementsByClassName('trader-trades')[0];
            target_el.innerText = total_trades;
            target_el = trader_panel.getElementsByClassName('trader-overall')[0];
            target_el.innerText = r_outcome;

            if (update_targets[i] != `overview_${current['market']}`) {
                overall_total_trades += total_trades;
                overall_total_pl += r_outcome;
            }

            if ((current_chart == update_targets[i]) && (update_chart == true) && current_chart != 'trader_Overview') {
                console.log(currentTraders);
                update_chart = false;
                target_el = trader_panel.getElementsByClassName('trader_charts')[0];
                build_chart(current['market'], target_el);
            }
        }
    }
    var overview_section = document.getElementById('trader_Overview');

    target_el = overview_section.getElementsByClassName('overview-totalpl')[0];
    target_el.innerText = overall_total_pl;
    target_el = overview_section.getElementsByClassName('overview-totaltrades')[0];
    target_el.innerText = overall_total_trades;
}


function hide_section(e, section_id){

    var section_el = document.getElementsByTagName('section');
    for (i = 0; i < (section_el.length); i++){
        if (section_el[i].id == `trader_${section_id}`) {
            current_chart = `trader_${section_id}`;
            update_chart = true;
            section_el[i].style.display = "block";
        } else {
            section_el[i].style.display = "none";
        }
    }
}


function start_trader(e, market_pair){
    e.preventDefault();
    rest_api('POST', 'trader_update', {'action':'start', 'market':market_pair});
}


function pause_trader(e, market_pair){
    e.preventDefault();
    rest_api('POST', 'trader_update', {'action':'pause', 'market':market_pair});
}


function build_chart(market_pair, element){
    rest_api('GET', `get_trader_charting?market=${market_pair}&limit=200`, null, initial_build, element);
}


function rest_api(method, endpoint, data=null, target_function=null, target_element=null){
    // if either the user has requested a force update on bot data or the user has added a new market to trade then send an update to the backend.
    console.log(`'M: ${method}, ULR: /rest-api/v1/${endpoint}, D:${data}`);
    let request = new XMLHttpRequest();
    request.open(method, '/rest-api/v1/'+endpoint, true);

    request.onload = function() {
        if (this.status == 200){
            var resp_data = JSON.parse(request.responseText);
            console.log(resp_data);
            if (target_function != null && target_element == null) {
                target_function(resp_data);
            } else if (target_function != null && target_element != null) {
                target_function(target_element, resp_data['data']);
            }
        } else {
            console.log(`error ${request.status} ${request.statusText}`);
        }
    }

    if (data == null){
        request.send();
    } else {
        request.setRequestHeader('content-type', 'application/json');
        request.send(JSON.stringify(data));
    }
}

================================================
FILE: core/templates/jinja_templates.html
================================================
{% macro overview_data_panel(market_symbols) %}
    <section id="trader_Overview" class="Overview-panel">
        <h3>Trader Overview</h3>
        <p>
            Total PL: <span class="overview-totalpl"></span> | Total Trades: <span class="overview-totaltrades"></span>
        </p>
        {% for market_symbol in market_symbols %}
        <div id="overview_{{ market_symbol }}">
            Symbol: {{ market_symbol }} | State: <span class="trader-state"></span> | Side: <span class="trader-orderside"></span> | Trades: <span class="trader-trades"></span> | Overall: <span class="trader-overall"></span></span> | Last Price: <span class="trader-lastprice"></span> | OP: <span class="trader-orderpoint"></span><br />
        </div>
        {% endfor %}
    </section>
{% endmacro %}


{% macro market_data_panel(market_symbol) %}
    <section id="trader_{{ market_symbol }}" class="trader-panel">
        <h3>{{ market_symbol }}</h3>
        <a href=# class="small-button green-button" onclick="start_trader(event, '{{ market_symbol }}');">Start</a>
        <a href=# class="small-button amber-button" onclick="pause_trader(event, '{{ market_symbol }}');">Pause</a>
        <p>
            State: <span class="trader-state"></span> | Trades: <span class="trader-trades"></span> | Overall: <span class="trader-overall"></span> | Last Update: <span class="trader-lastupdate"></span> | Last Price: <span class="trader-lastprice"></span><br />
            <span class="trader-markettype"></span> <span class="trader-orderside"></span> | Type: <span class="trader-ordertype"></span> | Status: <span class="trader-orderstatus"></span> | Buy Price: <span class="trader-buyprice"></span><span class="show-sellaction">| Sell Price: <span class="trader-sellprice"></span></span> | OP: <span class="trader-orderpoint"></span>
        </p>
        <div class="trader_charts">
        </div>
    </section>
{% endmacro %}

================================================
FILE: core/templates/main_page.html
================================================
<html>
    <head>
        <title>Trader Control Panel</title>
        <meta charst="en">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">

        <script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
        <script src="{{ url_for('static', filename='js/charts.js') }}"></script>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
        <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>

        <!-- This is used to load the modules that I made and will be using. -->
        <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
        <script type="text/javascript">
            var ip = '{{ data.host.IP }}';
            var port = '{{ data.host.Port }}';
        </script>
        
    </head>
    <body>
        {% from 'jinja_templates.html' import overview_data_panel %}
        {% from 'jinja_templates.html' import market_data_panel %}

        <div class="sidebar">
            <h2>Binance Trader</h2>
            <ul>
                <li><a href=# onclick="hide_section(event, 'Overview');">Overview</a></li>
            {% for market_symbol in data.market_symbols %}
                <li><a href=# onclick="hide_section(event, '{{ market_symbol }}');">{{ market_symbol }}</a></li>
            {% endfor %}
            </ul>
        </div>
        <main>
            {{ overview_data_panel(data.market_symbols) }}
            {% for market_symbol in data.market_symbols %}
                {{ market_data_panel(market_symbol) }}
            {% endfor %}
        </main>
        <script src="{{ url_for('static', filename='js/script.js') }}"></script>
    </body>
</html>

================================================
FILE: core/templates/page_template.html
================================================
<html>
    <head>
        <title>Trader Control Panel</title>
        <meta charst="en">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">

        <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/chart.js@2.8.0"></script>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
        <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>

        <!-- This is used to load the modules that I made and will be using. -->
        <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
        <script type="text/javascript">
            var ip = '{{ data.hostIP }}';
            var port = '{{ data.hostPort }}';
        </script>
        <script src="{{ url_for('static', filename='js/script.js') }}"></script>
        
    </head>
    <header>
        <h1>Binance Trader Control Panel</h1>
    </header>
    <body>
        {% block content %}{% endblock content %}
    </body>
    <footer>
    </footer>
</html>

================================================
FILE: core/trader.py
================================================
#! /usr/bin/env python3
import os
import sys
import copy
import time
import logging
import datetime
import threading
import trader_configuration as TC

MULTI_DEPTH_INDICATORS = ['ema', 'sma', 'rma']
TRADER_SLEEP = 1

# Base commission fee with binance.
COMMISION_FEE = 0.00075

# Base layout for market pricing.
BASE_TRADE_PRICE_LAYOUT = {
    'lastPrice':0,           # Last price seen for the market.
    'askPrice':0,            # Last ask price seen for the market.
    'bidPrice':0             # Last bid price seen for the market.
}

# Base layout for trader state.
BASE_STATE_LAYOUT = {
    'base_currency':0.0,     # The base mac value used as referance.
    'force_sell':False,      # If the trader should dump all tokens.
    'runtime_state':None,    # The state that actual trader object is at.
    'last_update_time':0     # The last time a full look of the trader was completed.
}

# Base layout used by the trader.
BASE_MARKET_LAYOUT = {
    'can_order':True,        # If the bot is able to trade in the current market.
    'price':0.0,             # The price related to BUY.
    'buy_price':0.0,         # Buy price of the asset.
    'stopPrice':0.0,         # The stopPrice relate
    'stopLimitPrice':0.0,    # The stopPrice relate
    'tokens_holding':0.0,    # Amount of tokens being held.
    'order_point':None,      # Used to visulise complex stratergy progression points.
    'order_id':None,         # The ID that is tied to the placed order.
    'order_status':0,        # The type of the order that is placed
    'order_side':'BUY',      # The status of the current order.
    'order_type':'WAIT',     # Used to show the type of order
    'order_description':0,   # The description of the order.
    'order_market_type':None,# The market type of the order placed.
    'market_status':None     # Last state the market trader is.    
}

# Market extra required data.
TYPE_MARKET_EXTRA = {
    'loan_cost':0,           # Loan cost.
    'loan_id':None,          # Loan id.
}

class BaseTrader(object):
    def __init__(self, quote_asset, base_asset, rest_api, socket_api=None, data_if=None):
        # Initilize the main trader object.
        symbol = '{0}{1}'.format(base_asset, quote_asset)

        ## Easy printable format for market symbol.
        self.print_pair = '{0}-{1}'.format(quote_asset, base_asset)
        self.quote_asset = quote_asset
        self.base_asset = base_asset

        logging.info('[BaseTrader][{0}] Initilizing trader object and empty attributes.'.format(self.print_pair))

        ## Sets the rest api that will be used by the trader.
        self.rest_api = rest_api

        if socket_api == None and data_if == None:
            logging.critical('[BaseTrader][{0}] Initilization failed, bot must have either socket_api OR data_if set.'.format(self.print_pair))
            return

        ## Setup socket/data interface.
        self.data_if = None
        self.socket_api = None

        if socket_api:
            ### Setup socket for live market data trading.
            self.candle_enpoint = socket_api.get_live_candles
            self.depth_endpoint = socket_api.get_live_depths
            self.socket_api = socket_api
        else:
            ### Setup data interface for past historic trading.
            self.data_if = data_if
            self.candle_enpoint = data_if.get_candle_data
            self.depth_endpoint = data_if.get_depth_data

        ## Setup the default path for the trader by market beeing traded.
        self.orders_log_path = 'logs/order_{0}_log.txt'.format(symbol)
        self.configuration = {}
        self.market_prices = {}
        self.wallet_pair = None
        self.custom_conditional_data = {}
        self.indicators = {}
        self.market_activity = {}
        self.trade_recorder = []
        self.state_data = {}
        self.rules = {}

        logging.debug('[BaseTrader][{0}] Initilized trader object.'.format(self.print_pair))


    def setup_initial_values(self, trading_type, run_type, filters):
        # Initilize trader values.
        logging.info('[BaseTrader][{0}] Initilizing trader object attributes with data.'.format(self.print_pair))

        ## Populate required settings.
        self.configuration.update({
            'trading_type':trading_type,
            'run_type':run_type,
            'base_asset':self.base_asset,
            'quote_asset':self.quote_asset,
            'symbol':'{0}{1}'.format(self.base_asset, self.quote_asset)
        })
        self.rules.update(filters)

        ## Initilize default values.
        self.market_activity.update(copy.deepcopy(BASE_MARKET_LAYOUT))
        self.market_prices.update(copy.deepcopy(BASE_TRADE_PRICE_LAYOUT))
        self.state_data.update(copy.deepcopy(BASE_STATE_LAYOUT))

        if trading_type == 'MARGIN':
            self.market_activity.update(copy.deepcopy(TYPE_MARKET_EXTRA))

        logging.debug('[BaseTrader][{0}] Initilized trader attributes with data.'.format(self.print_pair))


    def start(self, MAC, wallet_pair, open_orders=None):
        '''
        Start the trader.
        Requires: MAC (Max Allowed Currency, the max amount the trader is allowed to trade with in BTC).
        -> Check for previous trade.
            If a recent, not closed traded is seen, or leftover currency on the account over the min to place order then set trader to sell automatically.
        
        ->  Start the trader thread. 
            Once all is good the trader will then start the thread to allow for the market to be monitored.
        '''
        logging.info('[BaseTrader][{0}] Starting the trader object.'.format(self.print_pair))
        sock_symbol = self.base_asset+self.quote_asset

        if self.socket_api != None:
            while True:
                if self.socket_api.get_live_candles()[sock_symbol] and ('a' in self.socket_api.get_live_depths()[sock_symbol]):
                    break

        self.state_data['runtime_state'] = 'SETUP'
        self.wallet_pair = wallet_pair
        self.state_data['base_currency'] = float(MAC)

        ## Start the main of the trader in a thread.
        threading.Thread(target=self._main).start()
        return(True)


    def stop(self):
        ''' 
        Stop the trader.
        -> Trader cleanup.
            To gracefully stop the trader and cleanly eliminate the thread as well as market orders.
        '''
        logging.debug('[BaseTrader][{0}] Stopping trader.'.format(self.print_pair))

        self.state_data['runtime_state'] = 'STOP'
        return(True)


    def _main(self):
        '''
        Main body for the trader loop.
        -> Wait for candle data to be fed to trader.
            Infinite loop to check if candle has been populated with data,
        -> Call the updater.
            Updater is used to re-calculate the indicators as well as carry out timed checks.
        -> Call Order Manager.
            Order Manager is used to check on currently PLACED orders.
        -> Call Trader Manager.
            Trader Manager is used to check the current conditions of the indicators then set orders if any can be PLACED.
        '''
        sock_symbol = self.base_asset+self.quote_asset
        last_wallet_update_time = 0

        if self.configuration['trading_type'] == 'SPOT':
            position_types = ['LONG']
        elif self.configuration['trading_type'] == 'MARGIN':
            position_types = ['LONG', 'SHORT']

        ## Main trader loop
        while self.state_data['runtime_state'] != 'STOP':
            # Pull required data for the trader.
            candles = self.candle_enpoint(sock_symbol)
            books_data = self.depth_endpoint(sock_symbol)
            self.indicators = TC.technical_indicators(candles)
            indicators = self.strip_timestamps(self.indicators)

            logging.debug('[BaseTrader] Collected trader data. [{0}]'.format(self.print_pair))

            socket_buffer_symbol = None
            if self.configuration['run_type'] == 'REAL':

                if sock_symbol in self.socket_api.socketBuffer:
                    socket_buffer_symbol = self.socket_api.socketBuffer[sock_symbol]

                # get the global socket buffer and update the wallets for the used markets.
                socket_buffer_global = self.socket_api.socketBuffer
                if 'outboundAccountPosition' in socket_buffer_global:
                    if last_wallet_update_time != socket_buffer_global['outboundAccountPosition']['E']:
                        self.wallet_pair, last_wallet_update_time = self.update_wallets(socket_buffer_global)
            
            # Update martket prices with current data
            if books_data != None:
                self.market_prices = {
                    'lastPrice':candles[0][4],
                    'askPrice':books_data['a'][0][0],
                    'bidPrice':books_data['b'][0][0]}

            # Check to make sure there is enough crypto to place orders.
            if self.state_data['runtime_state'] == 'PAUSE_INSUFBALANCE':
                if self.wallet_pair[self.quote_asset][0] > self.state_data['base_currency']:
                    self.state_data['runtime_state'] = 'RUN' 

            if not self.state_data['runtime_state'] in ['STANDBY', 'FORCE_STANDBY', 'FORCE_PAUSE']:
                ## Call for custom conditions that can be used for more advanced managemenet of the trader.

                for market_type in position_types:
                    cp = self.market_activity

                    if cp['order_market_type'] != market_type and cp['order_market_type'] != None:
                        continue

                    ## For managing active orders.
                    if socket_buffer_symbol != None or self.configuration['run_type'] == 'TEST':
                        cp = self._order_status_manager(market_type, cp, socket_buffer_symbol)

                    ## For checking custom conditional actions
                    self.custom_conditional_data, cp = TC.other_conditions(
                        self.custom_conditional_data, 
                        cp,
                        self.trade_recorder,
                        market_type,
                        candles,
                        indicators, 
                        self.configuration['symbol'])

                    ## For managing the placement of orders/condition checking.
                    if cp['can_order'] and self.state_data['runtime_state'] == 'RUN' and cp['market_status'] == 'TRADING':
                        if cp['order_type'] == 'COMPLETE':
                            cp['order_type'] = 'WAIT'

                        tm_data = self._trade_manager(market_type, cp, indicators, candles)
                        cp = tm_data if tm_data else cp

                    if not cp['market_status']: 
                        cp['market_status'] = 'TRADING'

                    self.market_activity = cp

                    time.sleep(TRADER_SLEEP)

            current_localtime = time.localtime()
            self.state_data['last_update_time'] = '{0}:{1}:{2}'.format(current_localtime[3], current_localtime[4], current_localtime[5])

            if self.state_data['runtime_state'] == 'SETUP':
                self.state_data['runtime_state'] = 'RUN'
        

    def _order_status_manager(self, market_type, cp, socket_buffer_symbol):
        '''
        This is the manager for all and any active orders.
        -> Check orders (Test/Real).
            This checks both the buy and sell side for test orders and updates the trader accordingly.
        -> Monitor trade outcomes.
            Monitor and note down the outcome of trades for keeping track of progress.
        '''
        active_trade = False
        
        if self.configuration['run_type'] == 'REAL':
            # Manage order reports sent via the socket.
            if 'executionReport' in socket_buffer_symbol:
                order_seen = socket_buffer_symbol['executionReport']

                # Manage trader placed orders via order ID's to prevent order conflict.
                if order_seen['i'] == cp['order_id']:
                    active_trade = True

                elif cp['order_status'] == 'PLACED':
                    active_trade = True

        else:
            # Basic update for test orders.
            if cp['order_status'] == 'PLACED':
                active_trade = True
                order_seen = None

        trade_done = False
        if active_trade:
            # Determine the current state of an order.
            if self.state_data['runtime_state'] == 'CHECK_ORDERS':
                self.state_data['runtime_state'] = 'RUN'
                cp['order_status'] = None
            cp, trade_done, token_quantity = self._check_active_trade(cp['order_side'], market_type, cp, order_seen)

        ## Monitor trade outcomes.
        if trade_done:
            if self.configuration['run_type'] == 'REAL':
                print('order seen: ')
                print(order_seen)

            # Update order recorder.
            self.trade_recorder.append([time.time(), cp['price'], token_quantity, cp['order_description'], cp['order_side']])
            logging.info('[BaseTrader] Completed {0} order. [{1}]'.format(cp['order_side'], self.print_pair))

            if cp['order_side'] == 'BUY':
                cp['order_side'] = 'SELL'
                cp['order_point'] = None
                cp['buy_price'] = self.trade_recorder[-1][1]

            elif cp['order_side'] == 'SELL':
                cp['order_side'] = 'BUY'
                cp['buy_price'] = 0.0
                cp['order_point'] = None
                cp['order_market_type'] = None

                # If the trader is trading margin and the runtype is real then repay any loans.
                if self.configuration['trading_type']  == 'MARGIN':
                    if self.configuration['run_type'] == 'REAL' and cp['loan_cost'] != 0:
                        loan_repay_result = self.rest_api.margin_accountRepay(asset=self.base_asset, amount=cp['loan_cost'])

                # Format data to print it to a file.
                trB = self.trade_recorder[-2]
                trS = self.trade_recorder[-1]

                buyTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(trB[0]))
                sellTime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(trS[0]))

                outcome = ((trS[1]-trB[1])*trS[2])

                trade_details = 'BuyTime:{0}, BuyPrice:{1:.8f}, BuyQuantity:{2:.8f}, BuyType:{3}, SellTime:{4}, SellPrice:{5:.8f}, SellQuantity:{6:.8f}, SellType:{7}, Outcome:{8:.8f}\n'.format(
                    buyTime, trB[1], trB[2], trB[3], sellTime, trS[1], trS[2], trS[3], outcome) # (Sellprice - Buyprice) * tokensSold
                with open(self.orders_log_path, 'a') as file:
                    file.write(trade_details)

                # Reset trader variables.
                cp['market_status']     = 'COMPLETE_TRADE'
            cp['order_type']        = 'COMPLETE'
            cp['price']             = 0.0
            cp['stopPrice']         = 0.0
            cp['stopLimitPrice']    = 0.0
            cp['order_id']          = None
            cp['order_status']      = None
            cp['order_description'] = None

        return(cp)


    def _check_active_trade(self, side, market_type, cp, order_seen):
        trade_done = False
        token_quantity = None

        if side == 'BUY':
            if self.configuration['run_type'] == 'REAL':
                if order_seen['S'] == 'BUY' or (market_type == 'SHORT' and order_seen['S'] == 'SELL'):
                    cp['price'] = float(order_seen['L'])

                    if market_type == 'LONG':
                        target_wallet = self.base_asset
                        target_quantity = float(order_seen['q'])
                    elif market_type == 'SHORT':
                        target_wallet = self.quote_asset
                        target_quantity = float(order_seen['q'])*float(order_seen['L'])

                    if order_seen['X'] == 'FILLED' and target_wallet in self.wallet_pair:
                        wallet_pair = self.wallet_pair
                        if wallet_pair[target_wallet][0] >= target_quantity:
                            trade_done = True
                            token_quantity = float(order_seen['q'])
                    elif order_seen['X'] == 'PARTIALLY_FILLED' and cp['order_status'] != 'LOCKED':
                        cp['order_status'] = 'LOCKED'
            else:
                if market_type == 'LONG':
                    trade_done = True if ((self.market_prices['lastPrice'] <= cp['price']) or (cp['order_type'] == 'MARKET')) else False
                elif market_type == 'SHORT':
                    trade_done = True if ((self.market_prices['lastPrice'] >= cp['price']) or (cp['order_type'] == 'MARKET')) else False
                token_quantity = cp['tokens_holding']

        elif side == 'SELL':
            if self.configuration['run_type'] == 'REAL':
                if order_seen['S'] == 'SELL' or (market_type == 'SHORT' and order_seen['S'] == 'BUY'):
                    if order_seen['X'] == 'FILLED':
                        cp['price'] = float(order_seen['L'])
                        token_quantity = float(order_seen['q'])
                        trade_done = True
                    elif order_seen['X'] == 'PARTIALLY_FILLED' and cp['order_status'] != 'LOCKED':
                        cp['order_status'] = 'LOCKED'
            else:
                if market_type == 'LONG':
                    if cp['order_type'] != 'STOP_LOSS_LIMIT':
                        trade_done = True if ((self.market_prices['lastPrice'] >= cp['price']) or (self.market_prices['lastPrice'] >= cp['stopLimitPrice']) or (cp['order_type'] == 'MARKET')) else False
                    else:
                        trade_done = True if (self.market_prices['lastPrice'] <= cp['price']) else False

                elif market_type == 'SHORT':
                    if cp['order_type'] != 'STOP_LOSS_LIMIT':
                        trade_done = True if ((self.market_prices['lastPrice'] <= cp['price']) or (cp['order_type'] == 'MARKET')) else False
                    else:
                        trade_done = True if (self.market_prices['lastPrice'] >= cp['price']) else False
                token_quantity = cp['tokens_holding']
        return(cp, trade_done, token_quantity)


    def _trade_manager(self, market_type, cp, indicators, candles):
        ''' 
        Here both the sell and buy conditions are managed by the trader.
        -
        '''


        # Check for entry/exit conditions.

        ## If order status is locked then return.
        if cp['order_status'] == 'LOCKED':
            return

        ## Select the correct conditions function dynamically.
        if cp['order_side'] == 'SELL':
            current_conditions = TC.long_exit_conditions if market_type == 'LONG' else TC.short_exit_conditions

        elif cp['order_side'] == 'BUY':
            ### If the bot is forced set to froce prevent buy return.
            if self.state_data['runtime_state'] == 'FORCE_PREVENT_BUY':
                return

            current_conditions = TC.long_entry_conditions if market_type == 'LONG' else TC.short_entry_conditions


        # Check condition check results.
        logging.debug('[BaseTrader] Checking for {0} {1} condition. [{2}]'.format(cp['order_side'], market_type, self.print_pair))
        new_order = current_conditions(self.custom_conditional_data, cp, indicators,  self.market_prices, candles, self.print_pair)
                
        ## If no new order is returned then just return.
        if not(new_order):
            return

        ## Update order point.
        if 'order_point' in new_order:
            cp['order_point'] = new_order['order_point']

        order = None

        ## Check for a new possible order type update.
        if 'order_type' in new_order:

            ### If order type update is not WAIT then update the current order OR cancel the order.
            if new_order['order_type'] != 'WAIT':
                print(new_order)
                cp['order_description'] = new_order['description']

                #### Format the prices to be used.
                if 'price' in new_order:
                    if 'price' in new_order:
                        new_order['price'] = '{0:.{1}f}'.format(float(new_order['price']), self.rules['TICK_SIZE'])
                    if 'stopPrice' in new_order:
                        new_order['stopPrice'] = '{0:.{1}f}'.format(float(new_order['stopPrice']), self.rules['TICK_SIZE'])

                    if float(new_order['price']) != cp['price']:
                        order = new_order
                else:
                    #### If the order type has changed OR the placement price has been changed update the order with the new price/type.
                    if cp['order_type'] != new_order['order_type']:
                        order = new_order

            else:
                #### Cancel the order.
                cp['order_status'] = None
                cp['order_type'] = 'WAIT'

                #### Only reset market type IF its buy or as margin allows for 2 market types.
                if cp['order_side'] == 'BUY':
                    cp['order_market_type'] = None

                #### Cancel active order if one is placed.
                if cp['order_id'] != None and new_order['order_type'] == 'WAIT':
                    cancel_order_results = self._cancel_order(cp['order_id'], cp['order_type'])
                    cp['order_id'] = None

                return(cp)

        # Place a new market order.
        if order:
            order_results = self._place_order(market_type, cp, order)
            logging.info('order: {0}\norder result:\n{1}'.format(order, order_results))

            # Error handle for binance related errors from order placement:
            if 'code' in order_results['data']:
                if order_results['data']['code'] == -2010:
                    self.state_data['runtime_state'] = 'PAUSE_INSUFBALANCE'
                elif order_results['data']['code'] == -2011:
                    self.state_data['runtime_state'] = 'CHECK_ORDERS'
                return

            logging.info('[BaseTrader] {0} Order placed for {1}.'.format(self.print_pair, new_order['order_type']))
            logging.info('[BaseTrader] {0} Order placement results:\n{1}'.format(self.print_pair, str(order_results['data'])))

            if 'type' in order_results['data']:
                if order_results['data']['type'] == 'MARKET':
                    price1 = order_results['data']['fills'][0]['price']
                else:
                    price1 = order_results['data']['price']
            else: price1 = None

            # Set the price the order was placed at.
            price2 = None
            if 'price' in order:
                price2 = float(order['price'])
                if price1 == 0.0 or price1 == None: 
                    order_price = price2
                else: order_price = price1
            else: order_price = price1

            if 'stopPrice' in order:
                cp['stopPrice'] == ['stopPrice']

            # Setup the test order quantity and setup margin trade loan.
            if order['side'] == 'BUY':
                cp['order_market_type'] = market_type

                if self.configuration['run_type'] == 'REAL':
                    if self.configuration['trading_type'] == 'MARGIN' and 'loan_id' in order_results['data']:
                        cp['loan_id'] = order_results['data']['loan_id'] 
                        cp['loan_cost'] = order_results['data']['loan_cost']
                else:
                    cp['tokens_holding'] = order_results['data']['tester_quantity']

            # Update the live order id for real trades.
            if self.configuration['run_type'] == 'REAL':
                cp['order_id'] = order_results['data']['orderId']

            cp['price']         = float(order_price)
            cp['order_type']    = new_order['order_type']
            cp['order_status']  = 'PLACED'

            logging.info('type: {0}, status: {1}'.format(new_order['order_type'], cp['order_status']))
            return(cp)


    def _place_order(self, market_type, cp, order):
        ''' place order '''

        ## Calculate the quantity amount for the BUY/SELL side for long/short real/test trades.
        quantity = None
        if order['side'] == 'BUY':
            quantity = float(self.state_data['base_currency'])/float(self.market_prices['bidPrice'])

        elif order['side'] == 'SELL':
            if 'order_prec' in order:
                quantity = ((float(order['order_prec']/100))*float(self.trade_recorder[-1][2]))
            else:
                quantity = float(self.trade_recorder[-1][2])

        if self.configuration['run_type'] == 'REAL' and cp['order_id']:
            cancel_order_results = self._cancel_order(cp['order_id'], cp['order_type'])
            if 'code' in cancel_order_results:
                return({'action':'ORDER_ISSUE', 'data':cancel_order_results})

        ## Setup the quantity to be the correct precision.
        if quantity:
            split_quantity = str(quantity).split('.')
            f_quantity = float(split_quantity[0]+'.'+split_quantity[1][:self.rules['LOT_SIZE']])

        logging.info('Order: {0}'.format(order))

        ## Place orders for both SELL/BUY sides for both TEST/REAL run types.
        if self.configuration['run_type'] == 'REAL':
            rData = {}
            ## Convert BUY to SELL if the order is a short (for short orders are inverted)
            if market_type == 'LONG':
                side = order['side']
            elif market_type == 'SHORT':
                if order['side'] == 'BUY':
                    ## Calculate the quantity required for a short loan.
                    loan_get_result = self.rest_api.margin_accountBorrow(asset=self.base_asset, amount=f_quantity)
                    rData.update({'loan_id':loan_get_result['tranId'], 'loan_cost':f_quantity})
                    side = 'SELL'
                else:
                    side = 'BUY'

            if order['order_type'] == 'OCO_LIMIT':
                logging.info('[BaseTrader] symbol:{0}, side:{1}, type:{2}, quantity:{3} price:{4}, stopPrice:{5}, stopLimitPrice:{6}'.format(self.print_pair, order['side'], order['order_type'], f_quantity,order['price'], order['stopPrice'], order['stopLimitPrice']))
                rData.update(self.rest_api.place_order(self.configuration['trading_type'], symbol=self.configuration['symbol'], side=side, type=order['order_type'], timeInForce='GTC', quantity=f_quantity, price=order['price'], stopPrice=order['stopPrice'], stopLimitPrice=order['stopLimitPrice']))
                return({'action':'PLACED_MARKET_ORDER', 'data':rData})

            elif order['order_type'] == 'MARKET':
                logging.info('[BaseTrader] symbol:{0}, side:{1}, type:{2}, quantity:{3}'.format(self.print_pair, order['side'], order['order_type'], f_quantity))
                rData.update(self.rest_api.place_order(self.configuration['trading_type'], symbol=self.configuration['symbol'], side=side, type=order['order_type'], quantity=f_quantity))
                return({'action':'PLACED_MARKET_ORDER', 'data':rData})

            elif order['order_type'] == 'LIMIT':
                logging.info('[BaseTrader] symbol:{0}, side:{1}, type:{2}, quantity:{3} price:{4}'.format(self.print_pair, order['side'], order['order_type'], f_quantity, order['price']))
                rData.update(self.rest_api.place_order(self.configuration['trading_type'], symbol=self.configuration['symbol'], side=side, type=order['order_type'], timeInForce='GTC', quantity=f_quantity, price=order['price']))
                return({'action':'PLACED_LIMIT_ORDER', 'data':rData})

            elif order['order_type'] == 'STOP_LOSS_LIMIT':
                logging.info('[BaseTrader] symbol:{0}, side:{1}, type:{2}, quantity:{3} price:{4}, stopPrice:{5}'.format(self.print_pair, order['side'], order['order_type'], f_quantity, order['price'], order['stopPrice']))
                rData.update(self.rest_api.place_order(self.configuration['trading_type'], symbol=self.configuration['symbol'], side=side, type=order['order_type'], timeInForce='GTC', quantity=f_quantity, price=order['price'], stopPrice=order['stopPrice']))
                return({'action':'PLACED_STOPLOSS_ORDER', 'data':rData})

        else:
            placed_order = {'type':'test', 'price':0, 'tester_quantity':float(f_quantity)}
            
            if order['order_type'] == 'OCO_LIMIT':
                placed_order.update({'stopPrice':order['stopPrice'], 'stopLimitPrice':order['stopLimitPrice']})

            if order['order_type'] == 'MARKET':
                placed_order.update({'price':self.market_prices['lastPrice']})
            else:
                placed_order.update({'price':order['price']})

            return({'action':'PLACED_TEST_ORDER', 'data':placed_order})


    def _cancel_order(self, order_id, order_type):
        ''' cancel orders '''
        if self.configuration['run_type'] == 'REAL':
            if order_type == 'OCO_LIMIT':
                cancel_order_result = self.rest_api.cancel_oco_order(symbol=self.configuration['symbol'])
            else:
                cancel_order_result = self.rest_api.cancel_order(self.configuration['trading_type'], symbol=self.configuration['symbol'], orderId=order_id)
            logging.debug('[BaseTrader] {0} cancel order results:\n{1}'.format(self.print_pair, cancel_order_result))
            return(cancel_order_result)
        logging.debug('[BaseTrader] {0} cancel order.'.format(self.print_pair))
        return(True)


    def get_trader_data(self):
        ''' Access that is availble for the traders details. '''
        trader_data = {
            'market':self.print_pair,
            'configuration':self.configuration,
            'market_prices':self.market_prices,
            'wallet_pair':self.wallet_pair,
            'custom_conditions':self.custom_conditional_data,
            'market_activity':self.market_activity,
            'trade_recorder':self.trade_recorder,
            'state_data':self.state_data,
            'rules':self.rules
        }

        return(trader_data)


    def strip_timestamps(self, indicators):

        base_indicators = {}

        for ind in indicators:
            if ind in MULTI_DEPTH_INDICATORS:
                base_indicators.update({ind:{}})
                for sub_ind in indicators[ind]:
                    base_indicators[ind].update({sub_ind:[ val[1] for val in indicators[ind][sub_ind] ]})
            else:
                base_indicators.update({ind:[ val[1] for val in indicators[ind] ]})

        return(base_indicators)


    def update_wallets(self, socket_buffer_global):
        ''' Update the wallet data with that collected via the socket '''
        last_wallet_update_time = socket_buffer_global['outboundAccountPosition']['E']
        foundBase = False
        foundQuote = False
        wallet_pair = {}

        for wallet in socket_buffer_global['outboundAccountPosition']['B']:
            if wallet['a'] == self.base_asset:
                wallet_pair.update({self.base_asset:[float(wallet['f']), float(wallet['l'])]})
                foundBase = True
            elif wallet['a'] == self.quote_asset:
                wallet_pair.update({self.quote_asset:[float(wallet['f']), float(wallet['l'])]})
                foundQuote = True

            if foundQuote and foundBase:
                break

        if not(foundBase):
            wallet_pair.update({self.base_asset:[0.0, 0.0]})
        if not(foundQuote):
            wallet_pair.update({self.quote_asset:[0.0, 0.0]})

        logging.info('[BaseTrader] New account data pulled, wallets updated. [{0}]'.format(self.print_pair))
        return(wallet_pair, last_wallet_update_time)

================================================
FILE: patterns.py
================================================
import numpy as np
'''
x : Price list
y : Last comparible value.
z : Historic comparible value.

# find_high_high
( x : price list, y : last high, z : historic high value )
Return highest value seen vs both recent and historically or None.

# find_high
( x : price list, y : last high )
Return the highest value seen or None.

# find_low_high
( x : price list, y : last high, z : historic high value )
Return highest value seen recently but lower historically or None.

# find_low_low
( x : price list, y : last low, z : historic low value )
Return the lowest value seen vs both recent and historically or None.

# find_low
( x : price list, y : last low )
Return the lowest value seen or None.

# find_high_low
( x : price list, y : last low, z : historic low value )
Return lowest value seen recently but higher historically or None.
'''
## High setups
find_high_high  = lambda x, y, z: x.max() if z < x.max() > y else None
find_high       = lambda x, y: x.max() if x.max() > y else None
find_low_high   = lambda x, y, z: x.max() if z > x.max() > y else None
## Low setup
find_low_low    = lambda x, y, z: x.min() if z > x.min() < y else None
find_low        = lambda x, y: x.min() if x.min() < y else None
find_high_low   = lambda x, y, z: x.min() if z < x.min() < y else None

"""
Trading patterns.

"""
class pattern_W:
    def __init__(self):
        self.required_points = 4
        self.result_points = 1 # Only required for testing to view outcome.
        self.segment_span = 4
        self.price_point = 0

    def check_condition(self, point_set):
        if point_set[3] > point_set[1] > point_set[2] > point_set[0]:
            print('part 1')
            if point_set[1] > point_set[2]+((point_set[3]-point_set[2])/2) and (100 - ((point_set[0]/point_set[2])*100)) < 1.2:
                print('part 2')
                return(True)

        return(False)

================================================
FILE: requirements.txt
================================================
numpy
requests
flask
flask-socketio==4.3.2
websocket-client


================================================
FILE: run.py
================================================
#! /usr/bin/env python3
import os
import logging
from core import botCore

## Setup    
cwd = os.getcwd()
CACHE_DIR = 'cache/'.format(cwd)
LOGS_DIR = 'logs/'.format(cwd)

## Settup logging.
log_format = '%(asctime)s:%(name)s:%(message)s'
logging.basicConfig(
    format=log_format,
    level=logging.INFO)
logger = logging.getLogger()

SETTINGS_FILE_NAME = 'settings.conf'
DEFAULT_SETTINGS_DATA = '''# Public and Private key pais used for the for the trader.
PUBLIC_KEY=
PRIVATE_KEY=

# Allow trader to be run in test mode (True/False).
IS_TEST=True

# Allow trader to be run in either spot or margin type.
MARKET_TYPE=SPOT

# Automatically update the BNB balance when low (for trading fees, only applicable to real trading)
UPDATE_BNB_BALANCE=True

# Interval used for the trader (1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d).
TRADER_INTERVAL=15m

# The currency max the trader will use (in BTC) also note this scales up with the number of markets i.e. 2 pairs each market will have 0.0015 as their trading currency pair.
TRADING_CURRENCY=0.002

# The markets that will be traded (currently only BTC markets) seperate markets with a , for multi market trading.
TRADING_MARKETS=BTC-ETH,BTC-LTC

# Configuration for the webapp (default if left blank is IP=127.0.0.1, Port=5000)
HOST_IP=
HOST_PORT=

# Configuration for the candle range and depth range (default if left bank is candles=500, Depth=50)
MAX_CANDLES=
MAX_DEPTH=
'''


def settings_reader():
    # Setting reader function to parse over the settings file and collect kv pairs.

    ## Setup settings file object with initial default variables.
    settings_file_data = {'public_key':'', 'private_key':'', 'host_ip':'127.0.0.1', 'host_port':5000, 'max_candles':500,'max_depth':50}

    ## Read the settings file and extract the fields.
    with open(SETTINGS_FILE_NAME, 'r') as f:
        for line in f.readlines():

            ### Check for kv line.
            if not('=' in line) or line[0] == '#':
                continue

            key, data = line.split('=')

            ### Check if data holds a value.
            if data == None or data == '\n':
                continue

            data = data.replace('\n', '')

            if key == 'IS_TEST':
                data = 'TEST' if data.upper() == 'TRUE' else 'REAL'
                key = 'run_type'

            elif key == 'MARKET_TYPE':
                data = data.upper()

            elif key == 'TRADING_MARKETS':
                data = data.replace(' ', '')
                data = data.split(',') if ',' in data else [data]

            elif key == 'HOST_IP':
                default_ip = '127.0.0.1'

            elif key == 'HOST_PORT':
                data = int(data)

            elif key == 'MAX_CANDLES':
                data = int(data)

            elif key == 'MAX_DEPTH':
                data = int(data)

            settings_file_data.update({key.lower():data})

    return(settings_file_data)


if __name__ == '__main__':

    ## Check and make cache/logs dir.
    if not(os.path.exists(LOGS_DIR)):
        os.makedirs(LOGS_DIR, exist_ok=True)
    if not(os.path.exists(CACHE_DIR)):
        os.makedirs(CACHE_DIR, exist_ok=True)

    ## Load settings/create settings file.
    if os.path.exists(SETTINGS_FILE_NAME):
        settings = settings_reader()
        botCore.start(settings, LOGS_DIR, CACHE_DIR)
    else:
        with open(SETTINGS_FILE_NAME, 'w') as f:
            f.write(DEFAULT_SETTINGS_DATA)
        print('Created settings.conf file.')



================================================
FILE: trader_configuration.py
================================================
import logging
import numpy as np
import technical_indicators as TI

## Minimum price rounding.
pRounding = 8

def technical_indicators(candles):
    indicators = {}

    time_values     = [candle[0] for candle in candles]
    open_prices     = [candle[1] for candle in candles]
    high_prices     = [candle[2] for candle in candles]
    low_prices      = [candle[3] for candle in candles]
    close_prices    = [candle[4] for candle in candles]

    indicators.update({'macd':TI.get_MACD(close_prices, time_values=time_values, map_time=True)})

    indicators.update({'ema':{}})
    indicators['ema'].update({'ema200':TI.get_EMA(close_prices, 200, time_values=time_values, map_time=True)})

    return(indicators)

'''
--- Current Supported Order ---
    Below are the currently supported order types that can be placed which the trader
-- MARKET --
    To place a MARKET order you must pass:
        'side'              : 'SELL', 
        'description'       : 'Long exit signal', 
        'order_type'        : 'MARKET'
    
-- LIMIT STOP LOSS --
    To place a LIMIT STOP LOSS order you must pass:
        'side'              : 'SELL', 
        'price'             : price,
        'stopPrice'         : stopPrice,
        'description'       : 'Long exit stop-loss', 
        'order_type'        : 'STOP_LOSS_LIMIT'
-- LIMIT --
    To place a LIMIT order you must pass:
        'side'              : 'SELL', 
        'price'             : price,
        'description'       : 'Long exit stop-loss', 
        'order_type'        : 'LIMIT'
-- OCO LIMIT --
    To place a OCO LIMIT order you must pass:
        'side'              : 'SELL', 
        'price'             : price,
        'stopPrice'         : stopPrice,
        'stopLimitPrice'    : stopLimitPrice,
        'description'       : 'Long exit stop-loss', 
        'order_type'        : 'OCO_LIMIT'
--- Key Descriptions--- 
    Section will give brief descript of what each order placement key is and how its used.
        side            = The side the order is to be placed either buy or sell.
        price           = Price for the order to be placed.
        stopPrice       = Stop price to trigger limits.
        stopLimitPrice  = Used for OCO to to determine price placement for part 2 of the order.
        description     = A description for the order that can be used to identify multiple conditions.
        order_type      = The type of the order that is to be placed.
--- Candle Structure ---
    Candles are structured in a multidimensional list as follows:
        [[time, open, high, low, close, volume], ...]
'''


def other_conditions(custom_conditional_data, trade_information, previous_trades, position_type, candles, indicators, symbol):
    # Define defaults.
    can_order = True

    # Setup additional extra conditions for trading.
    if trade_information['market_status'] == 'COMPLETE_TRADE':
        trade_information['market_status'] = 'TRADING'

    trade_information.update({'can_order':can_order})
    return(custom_conditional_data, trade_information)


def long_exit_conditions(custom_conditional_data, trade_information, indicators, prices, candles, symbol):
    # Place Long exit (sell) conditions under this section.
    order_point = 0
    signal_id = 0
    macd = indicators['macd']

    if macd[0]['macd'] < macd[1]['macd']:
        order_point += 1
        if macd[1]['hist'] < macd[0]['hist']:
            return({'side':'SELL',
                'description':'LONG exit signal 1', 
                'order_type':'MARKET'})

    stop_loss_price = float('{0:.{1}f}'.format((trade_information['buy_price']-(trade_information['buy_price']*0.004)), pRounding))
    stop_loss_status = basic_stoploss_setup(trade_information, stop_loss_price, stop_loss_price, 'LONG')

    # Base return for waiting and updating order positions.
    if stop_loss_status:
        return(stop_loss_status)
    else:
        return({'order_point':'L_ext_{0}_{1}'.format(signal_id, order_point)})


def long_entry_conditions(custom_conditional_data, trade_information, indicators, prices, candles, symbol):
    # Place Long entry (buy) conditions under this section.
    order_point = 0
    signal_id = 0
    macd = indicators['macd']
    ema200 = indicators['ema']['ema200']

    if (candles[0][4] > ema200[0]):
        if macd[0]['macd'] > macd[1]['macd']:
            order_point += 1
            if macd[1]['hist'] > macd[0]['hist']:
                return({'side':'BUY',
                    'description':'LONG entry signal 1', 
                    'order_type':'MARKET'})

    # Base return for waiting and updating order positions.
    if order_point == 0:
        return({'order_type':'WAIT'})
    else:
        return({'order_type':'WAIT', 'order_point':'L_ent_{0}_{1}'.format(signal_id, order_point)})


def short_exit_conditions(custom_conditional_data, trade_information, indicators, prices, candles, symbol):
    ## Place Short exit (sell) conditions under this section.
    order_point = 0
    signal_id = 0
    macd = indicators['macd']

    if macd[0]['macd'] > macd[1]['macd']:
        order_point += 1
        if macd[1]['hist'] > macd[0]['hist']:
            return({'side':'SELL',
                'description':'SHORT exit signal 1', 
                'order_type':'MARKET'})

    stop_loss_price = float('{0:.{1}f}'.format((trade_information['buy_price']+(trade_information['buy_price']*0.004)), pRounding))
    stop_loss_status = basic_stoploss_setup(trade_information, stop_loss_price, stop_loss_price, 'SHORT')

    # Base return for waiting and updating order positions.
    if stop_loss_status:
        return(stop_loss_status)
    else:
        return({'order_point':'S_ext_{0}_{1}'.format(signal_id, order_point)})


def short_entry_conditions(custom_conditional_data, trade_information, indicators, prices, candles, symbol):
    ## Place Short entry (buy) conditions under this section.
    order_point = 0
    signal_id = 0
    macd = indicators['macd']
    ema200 = indicators['ema']['ema200']

    if (candles[0][4] < ema200[0]):
        if macd[0]['macd'] < macd[1]['macd'] and macd[0]['hist'] > macd[0]['macd']:
            order_point += 1
            if macd[1]['hist'] < macd[0]['hist']:
                return({'side':'BUY',
                    'description':'SHORT entry signal 1', 
                    'order_type':'MARKET'})

    # Base return for waiting and updating order positions.
    if order_point == 0:
        return({'order_type':'WAIT'})
    else:
        return({'order_type':'WAIT', 'order_point':'S_ent_{0}_{1}'.format(signal_id, order_point)})


def basic_stoploss_setup(trade_information, price, stop_price, position_type):
    # Basic stop-loss setup.
    if trade_information['order_type'] == 'STOP_LOSS_LIMIT':
        return

    return({'side':'SELL', 
        'price':price,
        'stopPrice':stop_price,
        'description':'{0} exit stop-loss'.format(position_type), 
        'order_type':'STOP_LOSS_LIMIT'})
Download .txt
gitextract_jeifm_e2/

├── LICENSE
├── README.md
├── core/
│   ├── botCore.py
│   ├── static/
│   │   ├── css/
│   │   │   └── style.css
│   │   └── js/
│   │       ├── charts.js
│   │       └── script.js
│   ├── templates/
│   │   ├── jinja_templates.html
│   │   ├── main_page.html
│   │   └── page_template.html
│   └── trader.py
├── patterns.py
├── requirements.txt
├── run.py
└── trader_configuration.py
Download .txt
SYMBOL INDEX (58 symbols across 7 files)

FILE: core/botCore.py
  function override_url_for (line 42) | def override_url_for():
  function dated_url_for (line 46) | def dated_url_for(endpoint, **values):
  function control_panel (line 59) | def control_panel():
  function update_trader (line 79) | def update_trader():
  function get_trader_charting (line 105) | def get_trader_charting():
  function get_trader_indicators (line 126) | def get_trader_indicators():
  function get_trader_candles (line 145) | def get_trader_candles():
  function test_rest_call (line 164) | def test_rest_call():
  function shorten_indicators (line 169) | def shorten_indicators(indicators, end_time):
  function api_error_check (line 183) | def api_error_check(data):
  function web_updater (line 193) | def web_updater():
  class BotCore (line 223) | class BotCore():
    method __init__ (line 225) | def __init__(self, settings, logs_dir, cache_dir):
    method start (line 261) | def start(self):
    method _trader_manager (line 408) | def _trader_manager(self):
    method _bnb_manager (line 414) | def _bnb_manager(self):
    method _file_manager (line 433) | def _file_manager(self):
    method _connection_manager (line 445) | def _connection_manager(self):
    method get_trader_data (line 473) | def get_trader_data(self):
    method get_trader_indicators (line 479) | def get_trader_indicators(self, market):
    method get_trader_candles (line 490) | def get_trader_candles(self, market):
  function start (line 498) | def start(settings, logs_dir, cache_dir):

FILE: core/static/js/charts.js
  function initial_build (line 53) | function initial_build(target_element, charting_data) {
  function build_candle_data (line 87) | function build_candle_data(candle_data) {
  function build_timeseries (line 112) | function build_timeseries(ind_obj) {
  function build_basic_indicator (line 142) | function build_basic_indicator(chart_obj, ind_obj, chart_type, line_name...
  function populate_chart (line 192) | function populate_chart(indicator_data) {

FILE: core/static/js/script.js
  function update_trader_results (line 29) | function update_trader_results(data) {
  function hide_section (line 119) | function hide_section(e, section_id){
  function start_trader (line 134) | function start_trader(e, market_pair){
  function pause_trader (line 140) | function pause_trader(e, market_pair){
  function build_chart (line 146) | function build_chart(market_pair, element){
  function rest_api (line 151) | function rest_api(method, endpoint, data=null, target_function=null, tar...

FILE: core/trader.py
  class BaseTrader (line 56) | class BaseTrader(object):
    method __init__ (line 57) | def __init__(self, quote_asset, base_asset, rest_api, socket_api=None,...
    method setup_initial_values (line 105) | def setup_initial_values(self, trading_type, run_type, filters):
    method start (line 130) | def start(self, MAC, wallet_pair, open_orders=None):
    method stop (line 157) | def stop(self):
    method _main (line 169) | def _main(self):
    method _order_status_manager (line 268) | def _order_status_manager(self, market_type, cp, socket_buffer_symbol):
    method _check_active_trade (line 357) | def _check_active_trade(self, side, market_type, cp, order_seen):
    method _trade_manager (line 412) | def _trade_manager(self, market_type, cp, indicators, candles):
    method _place_order (line 547) | def _place_order(self, market_type, cp, order):
    method _cancel_order (line 622) | def _cancel_order(self, order_id, order_type):
    method get_trader_data (line 635) | def get_trader_data(self):
    method strip_timestamps (line 652) | def strip_timestamps(self, indicators):
    method update_wallets (line 667) | def update_wallets(self, socket_buffer_global):

FILE: patterns.py
  class pattern_W (line 44) | class pattern_W:
    method __init__ (line 45) | def __init__(self):
    method check_condition (line 51) | def check_condition(self, point_set):

FILE: run.py
  function settings_reader (line 51) | def settings_reader():

FILE: trader_configuration.py
  function technical_indicators (line 8) | def technical_indicators(candles):
  function other_conditions (line 68) | def other_conditions(custom_conditional_data, trade_information, previou...
  function long_exit_conditions (line 80) | def long_exit_conditions(custom_conditional_data, trade_information, ind...
  function long_entry_conditions (line 103) | def long_entry_conditions(custom_conditional_data, trade_information, in...
  function short_exit_conditions (line 125) | def short_exit_conditions(custom_conditional_data, trade_information, in...
  function short_entry_conditions (line 148) | def short_entry_conditions(custom_conditional_data, trade_information, i...
  function basic_stoploss_setup (line 170) | def basic_stoploss_setup(trade_information, price, stop_price, position_...
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (93K chars).
[
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2021 John P Lennie\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "README.md",
    "chars": 4153,
    "preview": "# Simple Binance Trader\n\n# Disclaimer\nI am not responsible for the trades you make with the script, this script has not "
  },
  {
    "path": "core/botCore.py",
    "chars": 20146,
    "preview": "#! /usr/bin/env python3\nimport os\nimport sys\nimport time\nimport json\nimport os.path\nimport hashlib\nimport logging\nimport"
  },
  {
    "path": "core/static/css/style.css",
    "chars": 1119,
    "preview": "@import url('https://fonts.googleapis.com/css?family=Josefin+Sans&display=swap');\n\n*{\n  margin: 0;\n  padding: 0;\n  box-s"
  },
  {
    "path": "core/static/js/charts.js",
    "chars": 7408,
    "preview": "// Indicator meta details\nconst indicator_chart_types_mapping = {'patterns_data_lines':'line', 'patterns_data_points':'s"
  },
  {
    "path": "core/static/js/script.js",
    "chars": 6372,
    "preview": "//\r\n// \r\nconst socket = io('http://'+ip+':'+port);\r\n\r\nconst class_data_mapping = {\r\n    'trader-state':'runtime_state', "
  },
  {
    "path": "core/templates/jinja_templates.html",
    "chars": 1910,
    "preview": "{% macro overview_data_panel(market_symbols) %}\n    <section id=\"trader_Overview\" class=\"Overview-panel\">\n        <h3>Tr"
  },
  {
    "path": "core/templates/main_page.html",
    "chars": 1842,
    "preview": "<html>\n    <head>\n        <title>Trader Control Panel</title>\n        <meta charst=\"en\">\n        <meta name=\"viewport\" c"
  },
  {
    "path": "core/templates/page_template.html",
    "chars": 1166,
    "preview": "<html>\n    <head>\n        <title>Trader Control Panel</title>\n        <meta charst=\"en\">\n        <meta name=\"viewport\" c"
  },
  {
    "path": "core/trader.py",
    "chars": 32064,
    "preview": "#! /usr/bin/env python3\nimport os\nimport sys\nimport copy\nimport time\nimport logging\nimport datetime\nimport threading\nimp"
  },
  {
    "path": "patterns.py",
    "chars": 1926,
    "preview": "import numpy as np\r\n'''\r\nx : Price list\r\ny : Last comparible value.\r\nz : Historic comparible value.\r\n\r\n# find_high_high\r"
  },
  {
    "path": "requirements.txt",
    "chars": 65,
    "preview": "numpy\r\nrequests\r\nflask\r\nflask-socketio==4.3.2\r\nwebsocket-client\r\n"
  },
  {
    "path": "run.py",
    "chars": 3506,
    "preview": "#! /usr/bin/env python3\nimport os\nimport logging\nfrom core import botCore\n\n## Setup    \ncwd = os.getcwd()\nCACHE_DIR = 'c"
  },
  {
    "path": "trader_configuration.py",
    "chars": 6956,
    "preview": "import logging\nimport numpy as np\nimport technical_indicators as TI\n\n## Minimum price rounding.\npRounding = 8\n\ndef techn"
  }
]

About this extraction

This page contains the full source code of the EasyAI/Simple-Binance-Trader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (87.6 KB), approximately 20.7k tokens, and a symbol index with 58 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!