Skip to content

Commit

Permalink
Code Refactoring (whittlem#174)
Browse files Browse the repository at this point in the history
* Move Telegram into chat package

* Move configuration parsing into a package
- remove duplicate
- create parsing functions for Coinbase and Binance
- change the way app's arguments are interpreted (dict instead of object) in order
to make possible to merge them with the configuration

* Move Binance and CoinbasePro into the package exchange

* Rename test to follow the new package structure
(add pytest in the requirement too)

* Fix issue with Config and Args merge and fix Binance test

* Use DOGEUSDT that is valid for more type of account

Co-authored-by: Michael Whittle <whittlem@users.noreply.github.com>
  • Loading branch information
dthevenin and whittlem authored May 13, 2021
1 parent c856523 commit 790a436
Show file tree
Hide file tree
Showing 23 changed files with 2,734 additions and 1,267 deletions.
2 changes: 0 additions & 2 deletions create-graphs.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from models.PyCryptoBot import PyCryptoBot
from models.Trading import TechnicalAnalysis
from models.Binance import AuthAPI as BAuthAPI, PublicAPI as BPublicAPI
from models.CoinbasePro import AuthAPI as CBAuthAPI, PublicAPI as CBPublicAPI
from views.TradingGraphs import TradingGraphs

#app = PyCryptoBot()
Expand Down
1,244 changes: 28 additions & 1,216 deletions models/PyCryptoBot.py

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions models/Trading.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Technical analysis on a trading Pandas DataFrame"""

import json, math
import math
import numpy as np
import pandas as pd
import re, sys
import re
from statsmodels.tsa.statespace.sarimax import SARIMAX
from models.CoinbasePro import AuthAPI


class TechnicalAnalysis():
def __init__(self, data=pd.DataFrame()):
Expand Down
6 changes: 3 additions & 3 deletions models/TradingAccount.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import pandas as pd
from binance.client import Client

from models.Binance import AuthAPI as BAuthAPI, PublicAPI as BPublicAPI, AuthAPI
from models.CoinbasePro import AuthAPI as CBAuthAPI
from models.exchange.binance import AuthAPI as BAuthAPI, PublicAPI as BPublicAPI
from models.exchange.coinbase_pro import AuthAPI as CBAuthAPI


class TradingAccount():
Expand Down Expand Up @@ -129,7 +129,7 @@ def getBalance(self, currency=''):

if self.app.getExchange() == 'binance':
if self.mode == 'live':
model = AuthAPI(self.app.getAPIKey(), self.app.getAPISecret())
model = BAuthAPI(self.app.getAPIKey(), self.app.getAPISecret())
df = model.getAccount()
if isinstance(df, pd.DataFrame):
if currency == '':
Expand Down
1 change: 1 addition & 0 deletions models/chat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .telegram import Telegram
22 changes: 14 additions & 8 deletions models/Telegram.py → models/chat/telegram.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import requests, re
import logging

class Telegram():
def __init__(self, token='', client_id=''):
self.api = 'https://api.telegram.org/bot'
self._token = token
self._client_id = str(client_id)
self.logger = logging.getLogger('pyCryptoBot')

p = re.compile(r"^\d{1,10}:[A-z0-9-_]{35,35}$")
if not p.match(token):
Expand All @@ -14,27 +16,31 @@ def __init__(self, token='', client_id=''):
if not p.match(client_id):
raise Exception('Telegram client_id is invalid')

def send(self, message=''):
self.logger.info('Telegram configure with for client "' + client_id + '" with token "' + token + '"')

def send(self, message='') -> str:
try:
payload = self.api + self._token + '/sendMessage?chat_id=' + self._client_id + '&parse_mode=Markdown&text=' + message
resp = requests.get(payload)

self.logger.debug('Telegram send:' + payload)

if resp.status_code != 200:
return None
return ''

resp.raise_for_status()
json = resp.json()

except requests.ConnectionError as err:
print (err)
return ('')
self.logger.error(err)
return ''

except requests.exceptions.HTTPError as err:
print (err)
return ('')
self.logger.error(err)
return ''

except requests.Timeout as err:
print (err)
return ('')
print(err)
return ''

return json
2 changes: 2 additions & 0 deletions models/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .binance_parser import parser as binanceConfigParser, parseMarket as binanceParseMarket
from .coinbase_pro_parser import parser as coinbaseProConfigParser, parseMarket as coinbaseProParseMarket
159 changes: 159 additions & 0 deletions models/config/binance_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import re, logging

from .default_parser import isCurrencyValid, defaultConfigParse, merge_config_and_args

def isMarketValid(market):
if market == None:
return False
p = re.compile(r"^[A-Z]{6,12}$")
return p.match(market)

def parseMarket(market):
base_currency = 'BTC'
quote_currency = 'GBP'

if not isMarketValid(market):
raise ValueError('Binance market invalid: ' + market)

if market.endswith('BTC'):
base_currency = market.replace('BTC', '')
quote_currency = 'BTC'
elif market.endswith('BNB'):
base_currency = market.replace('BNB', '')
quote_currency = 'BNB'
elif market.endswith('ETH'):
base_currency = market.replace('ETH', '')
quote_currency = 'ETH'
elif market.endswith('USDT'):
base_currency = market.replace('USDT', '')
quote_currency = 'USDT'
elif market.endswith('TUSD'):
base_currency = market.replace('TUSD', '')
quote_currency = 'TUSD'
elif market.endswith('BUSD'):
base_currency = market.replace('BUSD', '')
quote_currency = 'BUSD'
elif market.endswith('DAX'):
base_currency = market.replace('DAX', '')
quote_currency = 'DAX'
elif market.endswith('NGN'):
base_currency = market.replace('NGN', '')
quote_currency = 'NGN'
elif market.endswith('RUB'):
base_currency = market.replace('RUB', '')
quote_currency = 'RUB'
elif market.endswith('TRY'):
base_currency = market.replace('TRY', '')
quote_currency = 'TRY'
elif market.endswith('EUR'):
base_currency = market.replace('EUR', '')
quote_currency = 'EUR'
elif market.endswith('GBP'):
base_currency = market.replace('GBP', '')
quote_currency = 'GBP'
elif market.endswith('ZAR'):
base_currency = market.replace('ZAR', '')
quote_currency = 'ZAR'
elif market.endswith('UAH'):
base_currency = market.replace('UAH', '')
quote_currency = 'UAH'
elif market.endswith('DAI'):
base_currency = market.replace('DAI', '')
quote_currency = 'DAI'
elif market.endswith('BIDR'):
base_currency = market.replace('BIDR', '')
quote_currency = 'BIDR'
elif market.endswith('AUD'):
base_currency = market.replace('AUD', '')
quote_currency = 'AUD'
elif market.endswith('US'):
base_currency = market.replace('US', '')
quote_currency = 'US'
elif market.endswith('NGN'):
base_currency = market.replace('NGN', '')
quote_currency = 'NGN'
elif market.endswith('BRL'):
base_currency = market.replace('BRL', '')
quote_currency = 'BRL'
elif market.endswith('BVND'):
base_currency = market.replace('BVND', '')
quote_currency = 'BVND'
elif market.endswith('VAI'):
base_currency = market.replace('VAI', '')
quote_currency = 'VAI'

if len(market) != len(base_currency) + len(quote_currency):
raise ValueError('Binance market error.')

return market, base_currency, quote_currency

def parser(app, binance_config, args = {}):
logging.info('CoinbasePro Configuration parse')

app.granularity = '1h'

if not binance_config:
raise Exception('There is an error in your config dictionnary')

if not app:
raise Exception('No app is passed')

if 'api_key' in binance_config and 'api_secret' in binance_config and 'api_url' in binance_config:
# validates the api key is syntactically correct
p = re.compile(r"^[A-z0-9]{64,64}$")
if not p.match(binance_config['api_key']):
raise TypeError('Binance API key is invalid')

app.api_key = binance_config['api_key']

# validates the api secret is syntactically correct
p = re.compile(r"^[A-z0-9]{64,64}$")
if not p.match(binance_config['api_secret']):
raise TypeError('Binance API secret is invalid')

app.api_secret = binance_config['api_secret']

valid_urls = [
'https://api.binance.com/',
'https://testnet.binance.vision/api/',
'https://api.binance.com',
'https://testnet.binance.vision/api'
]

# validate Binance API
if binance_config['api_url'] not in valid_urls:
raise ValueError('Binance API URL is invalid')

app.api_url = binance_config['api_url']
app.base_currency = 'BTC'
app.quote_currency = 'GBP'
app.granularity = '1h'

config = merge_config_and_args(binance_config, args)

defaultConfigParse(app, config)

if 'base_currency' in config and config['base_currency'] != None:
if not isCurrencyValid(config['base_currency']):
raise TypeError('Base currency is invalid.')
app.base_currency = config['base_currency']

if 'quote_currency' in config and config['quote_currency'] != None:
if not isCurrencyValid(config['quote_currency']):
raise TypeError('Quote currency is invalid.')
app.quote_currency = config['quote_currency']

if 'market' in config and config['market'] != None:
app.market, app.base_currency, app.quote_currency = parseMarket(config['market'])

if app.base_currency != '' and app.quote_currency != '':
app.market = app.base_currency + app.quote_currency

if 'granularity' in config and config['granularity'] != None:
if isinstance(config['granularity'], str):
if config['granularity'] in ['1m', '5m', '15m', '1h', '6h', '1d']:
app.granularity = config['granularity']
app.smart_switch = 0

else:
raise Exception('There is an error in your config dictionnary')
88 changes: 88 additions & 0 deletions models/config/coinbase_pro_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import re, logging

from .default_parser import isCurrencyValid, defaultConfigParse, merge_config_and_args

def isMarketValid(market):
p = re.compile(r"^[1-9A-Z]{2,5}\-[1-9A-Z]{2,5}$")
return p.match(market)

def parseMarket(market):
if not isMarketValid(market):
raise ValueError('Coinbase Pro market invalid: ' + market)

base_currency, quote_currency = market.split('-', 2)
return market, base_currency, quote_currency

def parser(app, coinbase_config, args = {}):
logging.info('CoinbasePro Configuration parse')

app.granularity = 3600

if not coinbase_config:
raise Exception('There is an error in your config dictionnary')

if not app:
raise Exception('No app is passed')

if 'api_key' in coinbase_config and 'api_secret' in coinbase_config and 'api_passphrase' in coinbase_config and 'api_url' in coinbase_config:

# validates the api key is syntactically correct
p = re.compile(r"^[a-f0-9]{32}$")
if not p.match(coinbase_config['api_key']):
raise TypeError('Coinbase Pro API key is invalid')

app.api_key = coinbase_config['api_key']

# validates the api secret is syntactically correct
p = re.compile(r"^[A-z0-9+\/]+==$")
if not p.match(coinbase_config['api_secret']):
raise TypeError('Coinbase Pro API secret is invalid')

app.api_secret = coinbase_config['api_secret']

# validates the api passphrase is syntactically correct
p = re.compile(r"^[a-z0-9]{10,11}$")
if not p.match(coinbase_config['api_passphrase']):
raise TypeError('Coinbase Pro API passphrase is invalid')

app.api_passphrase = coinbase_config['api_passphrase']

valid_urls = [
'https://api.pro.coinbase.com/',
'https://api.pro.coinbase.com'
]

# validate Coinbase Pro API
if coinbase_config['api_url'] not in valid_urls:
raise ValueError('Coinbase Pro API URL is invalid')

app.api_url = coinbase_config['api_url']

config = merge_config_and_args(coinbase_config, args)

defaultConfigParse(app, config)

if 'base_currency' in config and config['base_currency'] != None:
if not isCurrencyValid(config['base_currency']):
raise TypeError('Base currency is invalid.')
app.base_currency = config['base_currency']

if 'quote_currency' in config and config['quote_currency'] != None:
if not isCurrencyValid(config['quote_currency']):
raise TypeError('Quote currency is invalid.')
app.quote_currency = config['quote_currency']

if 'market' in config and config['market'] != None:
app.market, app.base_currency, app.quote_currency = parseMarket(config['market'])

if app.base_currency != '' and app.quote_currency != '':
app.market = app.base_currency + '-' + app.quote_currency

if 'granularity' in config and config['granularity'] != None:
if isinstance(config['granularity'], int):
if config['granularity'] in [60, 300, 900, 3600, 21600, 86400]:
app.granularity = config['granularity']
app.smart_switch = 0

else:
raise Exception('There is an error in your config dictionnary')
Loading

0 comments on commit 790a436

Please sign in to comment.