├── .gitignore ├── CryptoBot.py ├── CryptoStats.py ├── CryptoStream.py ├── CryptoTrader.py ├── DISCLAIMER.md ├── README.md ├── example_config.py ├── github ├── example_CryptoStats.png └── example_Reporting.png ├── log └── balance ├── models.py ├── reporting.py ├── test.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | config.py 2 | *.log 3 | *.report 4 | *.stdout 5 | *.key 6 | *.db 7 | *.db-journal 8 | *.sqlite3 9 | __pycache__/ 10 | -------------------------------------------------------------------------------- /CryptoBot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | # 7 | 8 | import sys 9 | import datetime as dt 10 | 11 | from time import sleep 12 | from config import Config 13 | from utils import IPC, Processing 14 | 15 | StreamPID = None 16 | 17 | def main(): 18 | 19 | global StreamPID 20 | 21 | # Clean-up before start 22 | if IPC.get( IPC.isRunning ): 23 | IPC.set( IPC.isRunning, remove=True ) 24 | 25 | # we first need to start the Stream and wait a little 26 | StreamPID = Processing.start( Config.CryptoStream ) 27 | print( f'{str(dt.datetime.now())} CryptoStream started PID:{StreamPID}' ) 28 | sleep(15) 29 | 30 | while True: 31 | 32 | if not IPC.get( IPC.isRunning ): 33 | Processing.start( Config.CryptoTrader, Config.STDOUT ) 34 | 35 | sleep(5) 36 | 37 | 38 | if __name__ == '__main__': 39 | try: 40 | main() 41 | except KeyboardInterrupt: 42 | ## 43 | # TODO: here must be some cleanup code e.g. shutdown Stream etc. 44 | ## 45 | print( f'\n\nBot will be stopped.. this may take 5 seconds...') 46 | Processing.stop( StreamPID ) 47 | IPC.set( IPC.isRunning, remove=True ) 48 | sleep(5) 49 | sys.exit(0) 50 | -------------------------------------------------------------------------------- /CryptoStats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | # 7 | 8 | import os 9 | import time 10 | import ast 11 | import numpy as np 12 | import pandas as pd 13 | import datetime as dt 14 | 15 | 16 | from config import Config 17 | 18 | #logfile = Config.Logfile 19 | logfile = '/home/dave/code/crypto/bot/log/ms_test9-2.log' 20 | 21 | clear = lambda: os.system('clear') 22 | 23 | def reporting(): 24 | data = [] 25 | with open( logfile ) as fd: 26 | lines = fd.readlines() 27 | for line in lines: 28 | data.append( ast.literal_eval( line.rstrip() ) ) 29 | fd.close() 30 | 31 | df = pd.DataFrame( data ) 32 | df.ask = df.ask.astype(float) 33 | df.ask_qty = df.ask_qty.astype(float) 34 | df.bid = df.bid.astype(float) 35 | df.bid_qty = df.bid_qty.astype(float) 36 | df.profit = df.profit.astype(float) 37 | df.total_profit = df.total_profit.astype(float) 38 | df.duration = pd.to_timedelta(df.duration) 39 | #df = df.set_index('ts') 40 | 41 | # das ist nur die effektive laufzeit des trading, nicht die gesamtlaufzeit des bots! 42 | #runtime=str( dt.datetime.fromisoformat(df.ts.iloc[-1]) - dt.datetime.fromisoformat(df.ts[1]) ) 43 | runtime=str( dt.datetime.now() - dt.datetime.fromisoformat(df.ts[1]) ) 44 | rt = ( dt.datetime.fromisoformat(df.ts.iloc[-1]) - dt.datetime.fromisoformat(df.ts[1]) ) 45 | 46 | rth = rt.seconds//3600 47 | if rth < 1: 48 | rth = 1 49 | 50 | 51 | invest=100 52 | SL= Config.StopLoss 53 | TP = Config.TargetProfit 54 | 55 | trades_total = df.state.count() 56 | trades_won = np.sum(df.state == 'WON') 57 | trades_won_rel = round(trades_won/trades_total*100,2) 58 | trades_won_profit = round( df[df.state == 'WON'].sum()['total_profit'], 4 ) 59 | trades_lost = np.sum(df.state == 'LOST') 60 | trades_lost_rel = round(trades_lost/trades_total*100,2) 61 | trades_lost_profit = abs(round( df[df.state == 'LOST'].sum()['total_profit'], 4 )) 62 | turnover = round( trades_won_profit + trades_lost_profit, 4 ) 63 | turnover_rel = round(turnover/invest*100,2) 64 | profit = round( trades_won_profit - trades_lost_profit, 4 ) 65 | profit_rel = round(profit/invest*100,2) 66 | 67 | roi = round((profit - invest / invest), 2) 68 | 69 | #tpm = int(trades_total)/60 70 | tpm = trades_total/(rth*60) 71 | #tph = tpm*60 72 | tph = tpm*60 73 | avg_runtime = df.duration.mean() 74 | 75 | #p = P / G 76 | print( f'Report {logfile} created at {dt.datetime.now()}' ) 77 | print( f'Bot runtime: {runtime}') 78 | print( f'Investment:{invest} USDT \nSL:{SL}% \nTP:{TP}%') 79 | print( f'Trades {trades_total}' ) 80 | print( f'Won {trades_won} ({trades_won_rel}%) {trades_won_profit} USDT' ) 81 | print( f'Lost {trades_lost} ({trades_lost_rel}%) {trades_lost_profit} USDT' ) 82 | print( f'Turnover {turnover} USDT ({turnover_rel}%)') 83 | print( f'Profit {profit} USDT ({profit_rel}%)' ) 84 | print( f'ROI {roi}%') 85 | print( f'TpH {tph} \nTpM {tpm}') 86 | print( f'AVG Duration {avg_runtime}') 87 | print( f'\nAsset Details\n' ) 88 | trades = df.groupby('symbol') 89 | print( trades.sum() ) 90 | 91 | 92 | while True: 93 | reporting() 94 | time.sleep(3) 95 | clear() 96 | -------------------------------------------------------------------------------- /CryptoStream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | 7 | import os 8 | import asyncio 9 | from utils import Credentials, fetch_NonLeveragedTradePairs, build_Frame 10 | from binance import Client, AsyncClient, BinanceSocketManager 11 | from sqlalchemy import create_engine, engine 12 | 13 | from config import Config 14 | 15 | credentials = Credentials( 'key/binance.key' ) 16 | client = Client( credentials.key, credentials.secret ) 17 | 18 | # Cleanup old data 19 | if os.path.exists( Config.Database ): 20 | os.remove( Config.Database ) 21 | 22 | engine = create_engine( f'sqlite:///{Config.Database}' ) 23 | 24 | #prepare multistream list 25 | tp = fetch_NonLeveragedTradePairs( client ) 26 | tp = [ i.lower() + '@trade' for i in tp ] 27 | 28 | async def main(): 29 | asyncClient = await AsyncClient.create() 30 | bsm = BinanceSocketManager( asyncClient ) 31 | ms = bsm.multiplex_socket( tp ) 32 | async with ms as tscm: 33 | while True: 34 | response = await tscm.recv() 35 | if response: 36 | frame = build_Frame( response, isMultiStream=True ) 37 | frame.to_sql( frame.Symbol[0], engine, if_exists='append', index=False ) 38 | 39 | await asyncClient.close_connection() 40 | 41 | if __name__ == '__main__': 42 | loop = asyncio.get_event_loop() 43 | loop.run_until_complete( main() ) 44 | 45 | -------------------------------------------------------------------------------- /CryptoTrader.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | # 7 | 8 | import asyncio 9 | from decimal import Decimal, ROUND_DOWN 10 | import numpy as np 11 | import pandas as pd 12 | import datetime as dt 13 | 14 | from binance import Client, BinanceSocketManager 15 | from sqlalchemy import create_engine 16 | 17 | from config import Config 18 | from utils import Credentials, IPC, queryDB, build_Frame, log 19 | from models import Asset, Order, Trade 20 | 21 | credentials = Credentials( 'key/binance.key' ) 22 | client = Client( credentials.key, credentials.secret ) 23 | client.timestamp_offset = -2000 #binance.exceptions.BinanceAPIException: APIError(code=-1021): Timestamp for this request was 1000ms ahead of the server's time. 24 | engine = create_engine( f'sqlite:///{Config.Database}' ) 25 | 26 | ### Start Bot place order 27 | if not IPC.get( IPC.isRunning ): 28 | 29 | # pull all symbols from DB 30 | symbols = pd.read_sql( 'SELECT name FROM sqlite_master WHERE type = "table"', engine ).name.to_list() 31 | 32 | # calculate cumulative return for each symbol 33 | returns = [] 34 | for symbol in symbols: 35 | prices = queryDB( engine, symbol, 2 ).Price #last 2 minutes 36 | cumulative_return = ( prices.pct_change() + 1 ).prod() - 1 37 | returns.append( { 'symbol' : symbol, 'cumret' : cumulative_return } ) 38 | 39 | # sort descending 40 | sorted_returns = sorted(returns, key=lambda d: d['cumret'], reverse=True) 41 | 42 | # prepare our Asset 43 | asset = Asset( client, sorted_returns[0]['symbol'], OHLCV=True, Indicators=True ) 44 | 45 | # if the momentum already ends skip to next asset or wait a moment 46 | if asset.OHLCV.ROC.iloc[-1] < Config.minROC: 47 | print( f'{str(dt.datetime.now())} Opportunity not given, we skip this trade : {asset.symbol} (LvL0)' ) 48 | # check next Symbol 49 | asset = Asset( client, sorted_returns[1]['symbol'], OHLCV=True, Indicators=True ) 50 | if asset.OHLCV.ROC.iloc[-1] < Config.minROC: 51 | print( f'{str(dt.datetime.now())} Opportunity not given, we skip this trade : {asset.symbol} (LvL1)' ) 52 | # check next Symbol 53 | asset = Asset( client, sorted_returns[2]['symbol'], OHLCV=True, Indicators=True ) 54 | if asset.OHLCV.ROC.iloc[-1] < Config.minROC: 55 | print( f'{str(dt.datetime.now())} Opportunity not given, we skip this trade : {asset.symbol} (LvL2)' ) 56 | print( f'{str(dt.datetime.now())} No opportunities we give up and wait a moment...' ) 57 | quit() 58 | 59 | ### Momentum detected so place order 60 | # check again if an instance is already running 61 | if not IPC.get( IPC.isRunning ): 62 | IPC.set( IPC.isRunning ) 63 | price = asset.getRecentPrice() # vielleicht sollten wir den letzten preis aus der DB nehmen? -> spart uns ein HTTP query + laufzeit 64 | qty = asset.calculateQTY( Config.Investment, price=price ) 65 | order = Order( client, asset, Order.BUY, qty, price ) 66 | ##TODO: implement logging 67 | else: 68 | ### bot is already running 69 | print( f'{str(dt.datetime.now())} Found an opportunity, but we are already invested.' ) 70 | quit() 71 | 72 | else: 73 | ### bot is already running 74 | print( f'{str(dt.datetime.now())} We are already invested.' ) 75 | quit() 76 | 77 | 78 | 79 | ################################# 80 | ### Monitor the current trade ### 81 | ################################# 82 | async def main( asset, BuyOrder ): 83 | bsm = BinanceSocketManager( client ) 84 | ts = bsm.trade_socket( asset.symbol ) 85 | print( f'{str(dt.datetime.now())} Start trading {asset.symbol}' ) 86 | async with ts as tscm: 87 | while True: 88 | response = await tscm.recv() 89 | if response: 90 | # build df from BSM response 91 | frame = build_Frame( response ) 92 | CurrentPrice = frame.Price.iloc[-1] 93 | 94 | TargetProfit = Decimal( BuyOrder.price ) + ( Decimal( BuyOrder.price ) * Decimal( Config.TargetProfit ) ) / 100 95 | StopLoss = Decimal( BuyOrder.price ) + ( Decimal( BuyOrder.price ) * Decimal( -Config.StopLoss ) ) / 100 96 | BreakEven = Decimal( BuyOrder.price ) + ( Decimal( BuyOrder.price ) * Decimal( Config.BreakEven ) ) / 100 97 | 98 | # Trailing TakeProfit 99 | if BuyOrder.TTP is not None: 100 | TargetProfit = Decimal( BuyOrder.trail( 'get', 'TTP' ) ) 101 | StopLoss = Decimal( BuyOrder.trail( 'get', 'TSL' ) ) 102 | 103 | if CurrentPrice > TargetProfit: 104 | TargetProfit = Decimal( CurrentPrice ) + ( Decimal( CurrentPrice ) * Decimal( .1 ) ) / 100 105 | 106 | StopLoss = Decimal( TargetProfit ) + ( Decimal( TargetProfit ) * Decimal( -.1 ) ) / 100 107 | 108 | BuyOrder.trail( 'set', 'TTP', TargetProfit ) 109 | BuyOrder.trail( 'set', 'TSL', StopLoss ) 110 | print( f'{str(dt.datetime.now())} TTP set TP:{TargetProfit} SL:{StopLoss}' ) 111 | 112 | #print( f'{str(dt.datetime.now())} {asset.symbol} BP:{BuyOrder.price:.4f} CP:{CurrentPrice} TP:{TargetProfit:.4f} SL:{StopLoss:.4f}' ) 113 | 114 | # benchmark for TSL! 115 | if CurrentPrice < StopLoss or CurrentPrice > TargetProfit: 116 | 117 | # This trade is closed, set Signal for a new one immediately 118 | IPC.set( IPC.isRunning, remove=True ) 119 | 120 | # binance.exceptions.BinanceAPIException: APIError(code=-2010): Account has insufficient balance for requested action 121 | # If we buy an asset we pay fee's with the bought asset, therefore we need to deduct the fee amount before we try to sell the position 122 | # If we sell an asset the fee will be calculated (in our case) in USDT 123 | 124 | SellQTY = Decimal( BuyOrder.qty - BuyOrder.commission ) # floor??? 125 | 126 | # binance.exceptions.BinanceAPIException: APIError(code=-1013): Filter failure: LOT_SIZE 127 | #order = client.create_order( symbol=symbol, side='SELL', type='MARKET', quantity=sell_qty ) 128 | SellOrder = Order( client, asset, Order.SELL, SellQTY, CurrentPrice ) 129 | 130 | Dust = Decimal( BuyOrder.qty - SellOrder.qty ) 131 | ProfitPerShare = Decimal( SellOrder.price - BuyOrder.price ).quantize(Decimal('.00000001'), rounding=ROUND_DOWN) 132 | ProfitTotal = Decimal( ProfitPerShare * SellOrder.qty ).quantize(Decimal('.00000001'), rounding=ROUND_DOWN) 133 | ProfitRelative = Decimal( ( SellOrder.price - BuyOrder.price ) / BuyOrder.price ).quantize(Decimal('.00000001'), rounding=ROUND_DOWN) 134 | 135 | Diff = round(( SellOrder.price - BuyOrder.price ) / BuyOrder.price *100, 2 ) 136 | 137 | #p = P / G 138 | 139 | 140 | Duration = str( dt.datetime.now() - dt.datetime.fromtimestamp( BuyOrder.timestamp ) ) 141 | 142 | # create Trade Object 143 | #FinalTrade = Trade() 144 | 145 | # TODO: implement logging 146 | 147 | state = None 148 | if SellOrder.price > BuyOrder.price: 149 | state = 'WON' 150 | else: 151 | state = 'LOST' 152 | 153 | ds = { 'ts' : str(dt.datetime.now()), 'state' : state, 'symbol' : asset.symbol, 'duration' : str(Duration), 'ask' : str(BuyOrder.price), 'ask_qty' : str(BuyOrder.qty), 'bid' : str(SellOrder.price), 'bid_qty' : str(SellOrder.qty), 'profit' : str(ProfitPerShare), 'total_profit' : str(ProfitTotal), 'ROC' : asset.OHLCV.ROC.iloc[-1], 'RSI' : asset.OHLCV.RSI.iloc[-1], 'ATR' : asset.OHLCV.ATR.iloc[-1], 'OBV' : asset.OHLCV.OBV.iloc[-1] } 154 | log( Config.Logfile, ds, timestamp=False ) 155 | 156 | print( f'###_Report_###' ) 157 | print( f'Symbol: {asset.symbol}' ) 158 | print( f'Condition: {state}' ) 159 | print( f'Investment: {Config.Investment} USDT' ) 160 | print( f'TP: {Config.TargetProfit}% SL: {Config.StopLoss}%') 161 | print( f'Opened: {dt.datetime.fromtimestamp(BuyOrder.timestamp)}' ) 162 | print( f'Duration: {Duration}' ) 163 | print( f'Closed: {dt.datetime.fromtimestamp(SellOrder.timestamp)}' ) 164 | print( f'Ask: {BuyOrder.price} ({BuyOrder.qty})' ) 165 | print( f'Bid: {SellOrder.price} ({SellOrder.qty})' ) 166 | print( f'Dust: {Dust}' ) 167 | print( f'PPS: {ProfitPerShare}' ) 168 | print( f'Profit: {ProfitTotal}' ) 169 | print( f'Relative Profit: {ProfitRelative}' ) 170 | print( f'Diff: {Diff}' ) 171 | print( f'ROC: {asset.OHLCV.ROC.iloc[-1]}') 172 | print( f'RSI: {asset.OHLCV.RSI.iloc[-1]}') 173 | print( f'ATR: {asset.OHLCV.ATR.iloc[-1]}') 174 | print( f'OBV: {asset.OHLCV.OBV.iloc[-1]}') 175 | print( f'##############' ) 176 | 177 | #Stop & Exit the Loop 178 | loop.stop() 179 | # funktioniert ein break hier?? 180 | #RuntimeError: Event loop stopped before Future completed. 181 | 182 | await client.close_connection() 183 | 184 | 185 | if __name__ == "__main__": 186 | loop = asyncio.get_event_loop() 187 | ''' 188 | Traceback (most recent call last): 189 | File "/home/dave/code/crypto/bot/CryptoPro.py", line 173, in 190 | loop.run_until_complete( main( asset, order ) ) 191 | File "/usr/lib/python3.9/asyncio/base_events.py", line 640, in run_until_complete 192 | raise RuntimeError('Event loop stopped before Future completed.') 193 | RuntimeError: Event loop stopped before Future completed. 194 | ''' 195 | try: 196 | loop.run_until_complete( main( asset, order ) ) 197 | except RuntimeError as e: 198 | if e == 'Event loop stopped before Future completed.': 199 | pass 200 | -------------------------------------------------------------------------------- /DISCLAIMER.md: -------------------------------------------------------------------------------- 1 | None of the information contained here constitutes an offer (or solicitation of an offer) to buy or sell any currency, product or financial instrument, to make any investment, or to participate in any particular trading strategy. 2 | The authors does not take into account of your personal investment objectives, specific investment goals, specific needs or financial situation and makes no representation and assumes no liability to the accuracy or completeness of the information provided here. 3 | The information and publications are not intended to be and do not constitute financial advice, investment advice, trading advice or any other advice or recommendation of any sort offered or endorsed by the authors. 4 | The authors also does not warrant that such information and publications are accurate, up to date or applicable to the circumstances of any particular case. 5 | Any expression of opinion (which may be subject to change without notice) is personal to the author and the author makes no guarantee of any sort regarding accuracy or completeness of any information or analysis supplied. 6 | The authors are not responsible for any loss arising from any investment based on any perceived recommendation, forecast or any other information contained here. 7 | The contents of these publications should not be construed as an express or implied promise, guarantee or implication by the authors that clients or users will profit or that losses in connection therewith can or will be limited, from reliance on any information set out here. 8 | Trading risks are magnified by leverage – losses can exceed your deposits. 9 | Trade only after you have acknowledged and accepted the risks. 10 | You should carefully consider whether trading in leveraged products is appropriate for you based on your financial circumstances and seek independent consultation. 11 | Please also consider our Risk Warning and General Business Terms before trading with us. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | >This repo was checked into Git as part of my cleanup work. 2 | 3 |
4 |
5 | 6 | # CryptoTrader - a Python based full auto crypto trading bot 🔥 7 | Uiiii we will get rich... sry nope 🧐 just a few lines of Python code to trade all sort of sh*tcoins on Binance.
8 | If you not have an Account, please consider to use this Referal-Link: https://accounts.binance.com/en/register?ref=GS2A2GHH it will result in a Fee-Discount for you :) 9 |
10 | ⚠️To say it loud and clear, in the current state the bot will lose your money!⚠️
11 | You need to have a basic understanding of the topic and be hands-on to make this piece of code profitable
12 | 13 | 14 | ### 🔹 Dependencies 15 | > pip install pandas
pipi install numpy
pip install sqlalchemy
pip install binance
16 | 17 | ### 🔹 Usage 18 | First run CryptoStream to acquire the necessary live datastream 19 | > python CryptoStream.py 20 | 21 | After a while we acquired enough data to start the trading bot. 22 | > python CryptoBot.py 23 | 24 | U can monitor the Bot using CryptoStats.py or by watching the logstream. 25 | > python CryptoStats.py
or
26 | tail -f path/to/logfile.log 27 | 28 | ### 🔹 Screenshots 29 | Output of CryptoStats.py 30 | ![CryptoStats](https://raw.githubusercontent.com/DaveBeusing/CryptoTrader/master/github/example_CryptoStats.png) 31 |

32 | Output of logstream created by CryptoTrader.py 33 | ![Reporting](https://raw.githubusercontent.com/DaveBeusing/CryptoTrader/master/github/example_Reporting.png) 34 | 35 | ## ⚠️ DISCLAIMER ⚠️ 36 | The Content is for informational purposes only, you should not construe any such information or other material as legal, tax, investment, financial, or other advice. 37 |

38 | Please read and understand DISCLAIMER.md in addition to the aforementioned disclaimer. -------------------------------------------------------------------------------- /example_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # Rename this file to config.py 6 | # 7 | 8 | 9 | 10 | class Config: 11 | # Amount of USDT which will be invested per trade 12 | Investment = 100 13 | TargetProfit = 0.8 14 | StopLoss = 1.0 # We need to have fee's in mind 0.1% each direction so 0.2 total for trade 15 | BreakEven = 0.2 # At least we need the fee's to be paid 16 | # Indicator settings 17 | minROC = 1 18 | Logfile = '/path/to/logfile.log' 19 | Database = '/path/to/database.sqlite3' 20 | CryptoTrader = '/path/to/CryptoTrader.py' 21 | CryptoStream = '/path/to/CryptoStream.py' 22 | STDOUT = 'path/to/name.stdout' -------------------------------------------------------------------------------- /github/example_CryptoStats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Crypto-Tradingbot.blockchain-python.project/c51befe8a30ccac6d6e457237b7e46fea49c188c/github/example_CryptoStats.png -------------------------------------------------------------------------------- /github/example_Reporting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upwork-Job32/Crypto-Tradingbot.blockchain-python.project/c51befe8a30ccac6d6e457237b7e46fea49c188c/github/example_Reporting.png -------------------------------------------------------------------------------- /log/balance: -------------------------------------------------------------------------------- 1 | 100 -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | # 7 | 8 | from math import floor 9 | 10 | from binance import Client 11 | from decimal import Decimal, ROUND_DOWN 12 | 13 | from utils import fetch_OHLCV, applyIndicators 14 | 15 | import numpy as np 16 | import pandas as pd 17 | import datetime as dt 18 | 19 | 20 | class Asset: 21 | 22 | def __init__( self, client, symbol, OHLCV=None, Indicators=None ): 23 | 24 | self.binance = client 25 | self.symbol = symbol 26 | self.OHLCV = None 27 | 28 | self.fetchMetadata() 29 | 30 | if OHLCV is not None: 31 | self.fetchOHLCV() 32 | 33 | if OHLCV and Indicators is not None: 34 | self.applyOHLCVindicators() 35 | 36 | 37 | def fetchOHLCV(self): 38 | self.OHLCV = fetch_OHLCV( self.binance, self.symbol, interval='1m', start_date='60 minutes ago UTC' ) 39 | 40 | 41 | def applyOHLCVindicators(self): 42 | applyIndicators( self.OHLCV ) 43 | 44 | 45 | def calculateQTY( self, amount, price=None ): 46 | if not price: 47 | price = price 48 | else: 49 | price = self.getRecentPrice() 50 | return Decimal( floor( Decimal(amount) / ( Decimal( price ) * self.minQTY ) ) * self.minQTY ) 51 | 52 | 53 | def getRecentPrice( self ): 54 | #TODO catch Exceptions 55 | return Decimal( self.binance.get_symbol_ticker( symbol=self.symbol )['price'] ) 56 | 57 | 58 | def fetchMetadata( self ): 59 | #TODO catch Exceptions 60 | data = self.binance.get_symbol_info( self.symbol ) 61 | self.base = data['baseAsset'] 62 | self.precision = int( data['baseAssetPrecision'] ) 63 | self.quote = data['quoteAsset'] 64 | self.quotePrecision = int( data['quoteAssetPrecision'] ) 65 | self.isSpot = data['isSpotTradingAllowed'] 66 | self.isMargin = data['isMarginTradingAllowed'] 67 | self.minQTY = Decimal( data['filters'][2]['minQty'] ) 68 | self.maxQTY = Decimal( data['filters'][2]['maxQty'] ) 69 | self.step = Decimal( data['filters'][2]['stepSize'] ) 70 | ''' 71 | {'symbol': 'LRCUSDT', 72 | 'status': 'TRADING', 73 | 'baseAsset': 'LRC', 74 | 'baseAssetPrecision': 8, 75 | 'quoteAsset': 'USDT', 76 | 'quotePrecision': 8, 77 | 'quoteAssetPrecision': 8, 78 | 'baseCommissionPrecision': 8, 79 | 'quoteCommissionPrecision': 8, 80 | 'orderTypes': ['LIMIT', 81 | 'LIMIT_MAKER', 82 | 'MARKET', 83 | 'STOP_LOSS_LIMIT', 84 | 'TAKE_PROFIT_LIMIT'], 85 | 'icebergAllowed': True, 86 | 'ocoAllowed': True, 87 | 'quoteOrderQtyMarketAllowed': True, 88 | 'isSpotTradingAllowed': True, 89 | 'isMarginTradingAllowed': True, 90 | 'filters': [{'filterType': 'PRICE_FILTER', 91 | 'minPrice': '0.00010000', 92 | 'maxPrice': '1000.00000000', 93 | 'tickSize': '0.00010000'}, 94 | {'filterType': 'PERCENT_PRICE', 95 | 'multiplierUp': '5', 96 | 'multiplierDown': '0.2', 97 | 'avgPriceMins': 5}, 98 | {'filterType': 'LOT_SIZE', 99 | 'minQty': '1.00000000', 100 | 'maxQty': '9000000.00000000', 101 | 'stepSize': '1.00000000'}, 102 | {'filterType': 'MIN_NOTIONAL', 103 | 'minNotional': '10.00000000', 104 | 'applyToMarket': True, 105 | 'avgPriceMins': 5}, 106 | {'filterType': 'ICEBERG_PARTS', 'limit': 10}, 107 | {'filterType': 'MARKET_LOT_SIZE', 108 | 'minQty': '0.00000000', 109 | 'maxQty': '448756.44475330', 110 | 'stepSize': '0.00000000'}, 111 | {'filterType': 'MAX_NUM_ORDERS', 'maxNumOrders': 200}, 112 | {'filterType': 'MAX_NUM_ALGO_ORDERS', 'maxNumAlgoOrders': 5}], 113 | 'permissions': ['SPOT', 'MARGIN']} 114 | ''' 115 | 116 | 117 | class Order: 118 | 119 | BUY = 'BUY' 120 | SELL = 'SELL' 121 | 122 | def __init__( self, client, asset, side, quantity, price ): 123 | self.binance = client 124 | self.asset = asset 125 | self.symbol = asset.symbol 126 | self.side = side 127 | self.qty = quantity 128 | self.bid = Decimal(price) 129 | #self.order = client.create_order( symbol=self.symbol, side=self.side, type='MARKET', quantity=self.qty ) 130 | #self.order = {'symbol': 'LRCUSDT', 'orderId': 366051943, 'orderListId': -1, 'clientOrderId': 'W96WjbdkTqPgB0yGAd5jtS', 'transactTime': 1635869565122, 'price': '0.00000000', 'origQty': '61.00000000', 'executedQty': '61.00000000', 'cummulativeQuoteQty': '100.36770000', 'status': 'FILLED', 'timeInForce': 'GTC', 'type': 'MARKET', 'side': 'BUY', 'fills': [{'price': '1.64530000', 'qty': '39.00000000', 'commission': '0.03900000', 'commissionAsset': 'LRC', 'tradeId': 23543885}, {'price': '1.64550000', 'qty': '22.00000000', 'commission': '0.02200000', 'commissionAsset': 'LRC', 'tradeId': 23543886}]} 131 | ''' 132 | {'symbol': 'LRCUSDT', 'orderId': 366051943, 'orderListId': -1, 'clientOrderId': 'W96WjbdkTqPgB0yGAd5jtS', 'transactTime': 1635869565122, 133 | 'price': '0.00000000', 'origQty': '61.00000000', 'executedQty': '61.00000000', 'cummulativeQuoteQty': '100.36770000', 'status': 'FILLED', 134 | 'timeInForce': 'GTC', 'type': 'MARKET', 'side': 'BUY', 'fills': [ 135 | {'price': '1.64530000', 'qty': '39.00000000', 'commission': '0.03900000', 'commissionAsset': 'LRC', 'tradeId': 23543885}, 136 | {'price': '1.64550000', 'qty': '22.00000000', 'commission': '0.02200000', 'commissionAsset': 'LRC', 'tradeId': 23543886}]} 137 | ''' 138 | self.order = self.pseudoOrder( self.side, self.symbol, self.qty, self.bid, self.asset.precision ) 139 | 140 | order_prices = [] 141 | order_fees = [] 142 | for val in self.order['fills']: 143 | order_prices.append( Decimal( val['price'] ) ) 144 | order_fees.append( Decimal( val['commission']) ) 145 | 146 | self.price = Decimal( max( order_prices) ).quantize(Decimal('.00000001'), rounding=ROUND_DOWN) 147 | self.qty = Decimal( self.order['executedQty'] ) 148 | self.commission = Decimal( np.sum( order_fees ) ) 149 | self.slippage = Decimal( self.price - self.bid ) 150 | self.id = self.order['orderId'] 151 | self.timestamp = dt.datetime.now().timestamp() 152 | 153 | self.TP = None 154 | self.TTP = None 155 | self.SL = None 156 | self.TSL = None 157 | 158 | 159 | def trail( self, mode, type, value=None ): 160 | ''' Basic get/set of Traling TakeProfit/StopLoss 161 | 162 | :param mode:str: -> get|set 163 | :param type:str: -> TTP|TSL 164 | :param value:float: -> value to sets 165 | ''' 166 | if type == 'TTP': 167 | if mode == 'set': 168 | self.TTP = value 169 | return self.TTP 170 | if type == 'TSL': 171 | if mode == 'set': 172 | self.TSL = value 173 | return self.TSL 174 | 175 | 176 | 177 | def pseudoOrder( self, side, symbol, qty, price, precision ): 178 | 179 | #'origQty': '12.30000000' '0.00020000' '0.01210000' 180 | origQTY = qty 181 | quoteQTY = Decimal( qty*price ) 182 | halfedQTY = Decimal( origQTY/2 ) 183 | batch1QTY = Decimal( halfedQTY + (halfedQTY/2) ) 184 | batch1Fee = Decimal( round( ( batch1QTY / 100 ) * Decimal(0.1), precision ) ) 185 | batch2QTY = Decimal( halfedQTY/2 ) 186 | batch2Fee = Decimal( round( ( batch2QTY / 100 ) * Decimal(0.1), precision ) ) 187 | 188 | order = { 189 | 'symbol': symbol, 190 | 'orderId': 22107854, 191 | 'orderListId': -1, 192 | 'clientOrderId': 'DkGnomuTY9lz4kkALHQ87f', 193 | 'transactTime': 1637194823283, 194 | 'price': '0.00000000', 195 | 'origQty': origQTY, 196 | 'executedQty': origQTY, 197 | 'cummulativeQuoteQty': quoteQTY, 198 | 'status': 'FILLED', 199 | 'timeInForce': 'GTC', 200 | 'type': 'MARKET', 201 | 'side': side, 202 | 'fills': [ 203 | { 204 | 'price': price, 205 | 'qty': batch1QTY, 206 | 'commission': batch1Fee, 207 | 'commissionAsset': symbol, 208 | 'tradeId': 2498963 209 | }, 210 | { 211 | 'price': price, 212 | 'qty': batch2QTY, 213 | 'commission': batch2Fee, 214 | 'commissionAsset': symbol, 215 | 'tradeId': 2498964 216 | } 217 | ] 218 | } 219 | 220 | return order 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | class Trade: 230 | #https://www.investopedia.com/terms/b/bid-and-ask.asp 231 | def __init__( self, ask, bid ): 232 | 233 | self.ask = ask.price 234 | self.bid = bid.price -------------------------------------------------------------------------------- /reporting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | # 7 | 8 | import os 9 | import time 10 | import ast 11 | import numpy as np 12 | import pandas as pd 13 | import datetime as dt 14 | 15 | 16 | from config import Config 17 | 18 | #logfile = Config.Logfile 19 | logfile = '/home/dave/code/crypto/bot/log/ms_test9-2.log' 20 | 21 | 22 | data = [] 23 | with open( logfile ) as fd: 24 | lines = fd.readlines() 25 | for line in lines: 26 | data.append( ast.literal_eval( line.rstrip() ) ) 27 | fd.close() 28 | df = pd.DataFrame( data ) 29 | df.ask = df.ask.astype(float) 30 | df.ask_qty = df.ask_qty.astype(float) 31 | df.bid = df.bid.astype(float) 32 | df.bid_qty = df.bid_qty.astype(float) 33 | df.profit = df.profit.astype(float) 34 | df.total_profit = df.total_profit.astype(float) 35 | df.duration = pd.to_timedelta(df.duration) 36 | 37 | # set index to timestamp 38 | df = df.set_index('ts') 39 | 40 | # change state to boolean indicator 41 | #df.state = (df.state == 'WON').astype(int) 42 | 43 | # change state to +1/-1 values 44 | df.loc[df.state == 'WON', 'state'] = 1 45 | df.loc[df.state == 'LOST', 'state'] = -1 46 | 47 | 48 | 49 | import matplotlib.pyplot as plt 50 | #plt.style.use('seaborn-whitegrid') 51 | #plt.figure( figsize=(20,10) ) 52 | #plt.title( 'CryptoPro Bot Test' ) 53 | #plt.xlabel( 'Time' ) 54 | #plt.ylabel( 'State/ATR/ROC' ) 55 | #plt.plot(df.index, df.state, label='State') 56 | #plt.plot(df.index, df.ATR, label='ATR') 57 | #plt.plot(df.index, df.ROC, label='ROC') 58 | #plt.plot(df.index, df.RSI, label='RSI') 59 | #plt.plot(df.index, df.OBV, label='OBV') 60 | #plt.legend() 61 | #plt.show() 62 | 63 | 64 | 65 | plt.figure( figsize=(20,10) ) 66 | 67 | fig, [ax1, ax2, ax3, ax4] = plt.subplots( 4, 1, sharex=True ) 68 | 69 | ax1.set_title('Trade Status', loc='left', y=0.85, x=0.02, fontsize='medium') 70 | ax1.plot(df.state, label='State') 71 | ax1.grid(True) 72 | ax1.text(0.5, 0.5, 'BSNG Quantitative Private Equity', transform=ax1.transAxes, fontsize=10, color='black', alpha=0.5, ha='center', va='center', rotation='30') 73 | 74 | ax2.set_title('OBV', loc='left', y=0.85, x=0.02, fontsize='medium') 75 | ax2.plot(df.OBV, label='OBV') 76 | ax2.grid(True) 77 | 78 | ax3.set_title('ROC', loc='left', y=0.85, x=0.02, fontsize='medium') 79 | ax3.plot(df.ROC, label='ROC') 80 | ax3.grid(True) 81 | 82 | ax4.set_title('ATR', loc='left', y=0.85, x=0.02, fontsize='medium') 83 | ax4.plot(df.ATR, label='ATR') 84 | ax4.grid(True) 85 | 86 | plt.show() 87 | 88 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | # 7 | 8 | import datetime as dt 9 | from binance import Client 10 | from utils import Credentials, applyIndicators, fetch_OHLCV 11 | 12 | from models import Asset 13 | 14 | credentials = Credentials( 'key/binance.key' ) 15 | client = Client( credentials.key, credentials.secret ) 16 | client.timestamp_offset = -2000 #binance.exceptions.BinanceAPIException: APIError(code=-1021): Timestamp for this request was 1000ms ahead of the server's time. 17 | 18 | #asset = Asset( client, 'LRCUSDT' ) 19 | 20 | 21 | #start = str( int( dt.datetime.timestamp( dt.datetime.now() - dt.timedelta(minutes=60) ) ) ) 22 | #start = str( int( ( dt.datetime.now() - dt.timedelta(minutes=60) ).timestamp() ) ) 23 | 24 | 25 | #df = fetch_OHLCV( client, asset.symbol, interval='1m', start_date='60 minutes ago UTC' ) 26 | 27 | #applyIndicators( df ) 28 | 29 | #print( f'{df.ROC.iloc[-1]} {df.ROC.iloc[-1:]}' ) 30 | #print(df) 31 | 32 | 33 | 34 | 35 | #https://stackoverflow.com/a/7224186 36 | #https://stackoverflow.com/a/51950538 37 | #https://docs.python.org/3/library/subprocess.html#module-subprocess 38 | #import subprocess 39 | #proc = subprocess.Popen(["rm","-r","some.file"]) 40 | #proc = subprocess.Popen( [ 'python', '/home/dave/code/crypto/bot/CryptoPro.py' ], close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) 41 | #with open( '/home/dave/code/crypto/bot/log/stdout.log', 'a+' ) as logfile: 42 | # proc = subprocess.Popen( [ 'python', '/home/dave/code/crypto/bot/CryptoPro.py' ], close_fds=True, stdout=logfile, stderr=subprocess.STDOUT ) 43 | #print( proc.pid ) 44 | #proc.terminate() 45 | # oder 46 | #import os 47 | #import signal 48 | #os.kill(proc.pid, signal.SIGTERM) #or signal.SIGKILL 49 | 50 | 51 | start = '2021-11-24 18:53:21.726234' 52 | end = '2021-11-24 21:53:45.679110' 53 | 54 | #runtime = dt.datetime.fromisoformat( start ) 55 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Copyright (c) Dave Beusing 5 | # 6 | # 7 | 8 | import os 9 | import ta 10 | import time 11 | 12 | import pandas as pd 13 | import datetime as dt 14 | 15 | from binance import Client 16 | from binance.exceptions import BinanceAPIException 17 | 18 | from decimal import Decimal, ROUND_DOWN 19 | 20 | class Credentials: 21 | """ Load key and secret from file. 22 | Expected file format is key and secret on separate lines. 23 | :param path: path to keyfile 24 | :type path: str 25 | :returns: None 26 | """ 27 | def __init__(self, path): 28 | self.key = '' 29 | self.secret = '' 30 | self.path = os.path.dirname( os.path.abspath(__file__) ) + '/' + path 31 | 32 | with open(self.path, 'r') as fd: 33 | self.key = fd.readline().strip() 34 | self.secret = fd.readline().strip() 35 | 36 | 37 | class IPC: 38 | 39 | isRunning = 'isRunning' 40 | isPaused = 'isPaused' 41 | 42 | def get( signal ): 43 | #TODO make it a global config 44 | path = getPath() + '/ipc/' + signal 45 | if os.path.isfile( path ): 46 | return True 47 | else: 48 | return False 49 | 50 | def set( signal, remove=False ): 51 | #TODO make it a global config 52 | path = getPath() + '/ipc/' + signal 53 | with open( path, 'a+' ) as fd: 54 | fd.close() 55 | if remove: 56 | os.remove( path ) 57 | 58 | 59 | class Tools: 60 | 61 | def round_crypto( amount ): 62 | return Decimal( amount ).quantize(Decimal('.00000001'), rounding=ROUND_DOWN) 63 | 64 | def round_fiat( amount ): 65 | return Decimal( amount ).quantize(Decimal('.01'), rounding=ROUND_DOWN) 66 | 67 | #import os 68 | import signal 69 | import subprocess 70 | class Processing: 71 | ''' 72 | #https://stackoverflow.com/a/7224186 73 | #https://stackoverflow.com/a/51950538 74 | #https://docs.python.org/3/library/subprocess.html#module-subprocess 75 | ''' 76 | def start( script, logfile=None ): 77 | if logfile is not None: 78 | with open( logfile, 'a+' ) as lf: 79 | proc = subprocess.Popen( [ '/usr/bin/python', script ], close_fds=True, stdout=lf, stderr=lf ) 80 | else: 81 | proc = subprocess.Popen( [ '/usr/bin/python', script ], close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) 82 | return proc.pid 83 | 84 | def stop( pid:int ): 85 | os.kill( pid, signal.SIGTERM ) 86 | 87 | 88 | 89 | 90 | def getPath(): 91 | ''' Returns the current script path 92 | ''' 93 | return os.path.dirname( os.path.abspath( __file__ ) ) 94 | 95 | 96 | 97 | 98 | def log( file, data, timestamp=True ): 99 | ts = dt.datetime.now() 100 | with open( file, 'a+' ) as fd: 101 | if timestamp: 102 | fd.write( f'{ts} {data}\n' ) 103 | else: 104 | fd.write( f'{data}\n' ) 105 | 106 | 107 | def calculate_commission( quantity: float, fee: float ) -> float: 108 | ''' Calculate trading commission based on given quantity and fee % 109 | :param quantity (float): amount of CC you will trade 110 | :param fee (float): applicable fee in % 111 | :return (float): commission quantity of CC 112 | ''' 113 | #return float( round( ( quantity / 100 ) * fee, self.asset_precision ) ) 114 | # precision varies with CC 115 | return float( round( ( quantity / 100 ) * fee ) ) 116 | 117 | 118 | def fetch_NonLeveragedTradePairs( client, quoteAsset='USDT' ): 119 | ''' Return a List of Non Leveraged Trade Pairs 120 | 121 | :param client -> binance.Client object 122 | :param quoteAsset -> type:str: Symbol of quote Asset 123 | :return List 124 | ''' 125 | data = client.get_exchange_info() 126 | symbols = [ x['symbol'] for x in data['symbols'] ] 127 | # leveraged tokens contain UP/DOWN BULL/BEAR in name 128 | # Was ist mit FIAT paaren -> EUR/USDT, AUD, BIDR, BRL, GBP, RUB, TRY, TUSD, USDC, DAI. IDTZ, UAH, NGN, VAI, USDP 'EUR', 'GBP', 'USD', 'AUD', 'JPY', 'RUB' 129 | exclude_pairs = [ 'UP', 'DOWN', 'BEAR', 'BULL' ] 130 | non_pairs = [ symbol for symbol in symbols if all( excludes not in symbol for excludes in exclude_pairs ) ] 131 | pairs = [ symbol for symbol in non_pairs if symbol.endswith( quoteAsset ) ] 132 | return pairs 133 | 134 | 135 | def fetch_OHLCV( client, symbol, interval='1d', start_date='1 day ago UTC', end_date=None ): 136 | '''Fetch historical kline data (Candlesticks) 137 | 138 | https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data 139 | 140 | :param client -> binance.Client object 141 | :param symbol -> type:str: Name of symbol pair e.g BTCUSDT 142 | :param interval -> type:str: Data interval e.g. 1m | 3m | 5m | 15m | 30m | 1h | 2h | 4h | 6h | 8h | 12h | 1d | 3d | 1W | 1M 143 | :param start -> type:str|int: Start date string in UTC format or timestamp in milliseconds 144 | :param end -> type:str|int: (optional) - end date string in UTC format or timestamp in milliseconds (default will fetch everything up to now) 145 | :return DataFrame['Date','Open','High','Low','Close','Volume'] | None 146 | ''' 147 | if end_date is not None: 148 | try: 149 | data = client.get_historical_klines( symbol, interval, start_date, end_date ) 150 | except BinanceAPIException as e: 151 | print(e) 152 | time.sleep(60) 153 | data = client.get_historical_klines( symbol, interval, start_date, end_date ) 154 | else: 155 | try: 156 | data = client.get_historical_klines( symbol, interval, start_date ) 157 | except BinanceAPIException as e: 158 | print(e) 159 | time.sleep(60) 160 | data = client.get_historical_klines( symbol, interval, start_date ) 161 | 162 | # convert result to DataFrame 163 | df = pd.DataFrame( data ) 164 | # We fetched more data than we need, we just need the first six columns 165 | df = df.iloc[:,:6] 166 | # Now we will name our columns to the standard OHLCV 167 | df.columns = ['Date','Open','High','Low','Close','Volume'] 168 | # Our index will be the UNIX timestamp, therefore we set datetime to index and make it more readable 169 | df = df.set_index('Date') 170 | df.index = pd.to_datetime( df.index, unit='ms' ) 171 | # We are handling mostly currencies so using float is necessary to calculate on the values later on 172 | df = df.astype(float) 173 | return df 174 | 175 | 176 | def fetch_Portfolio( client ): 177 | '''Fetch Assets in Portolio 178 | 179 | https://binance-docs.github.io/apidocs/spot/en/#account-information-user_data 180 | 181 | :return Dict | None 182 | ''' 183 | try: 184 | assets = client.get_account()['balances'] 185 | except BinanceAPIException as e: 186 | print(e) 187 | portfolio = {} 188 | for asset in assets: 189 | if float( asset['free'] ) > 0.00000000: 190 | portfolio[asset['asset']] = float( asset['free'] ) 191 | return portfolio 192 | 193 | 194 | def fetch_Balance( client ): 195 | assets = client.get_account()['balances'] 196 | for asset in assets: 197 | if asset['asset'] == 'USDT': 198 | return float( asset['free'] ) 199 | 200 | 201 | def applyIndicators( df ): 202 | ''' Apply Technical Indicators to given DataFrame 203 | 204 | We expect the following OHLCV columns within the given DataFrame 205 | ['Time','Open','High','Low','Close','Volume'] 206 | 207 | Basic reading https://www.investopedia.com/terms/t/technical-analysis-of-stocks-and-trends.asp 208 | ''' 209 | 210 | ## Momentum Indicators 211 | # https://www.investopedia.com/investing/momentum-and-relative-strength-index/ 212 | ## 213 | 214 | ''' 215 | Relative Strength Index (RSI) 216 | 217 | Compares the magnitude of recent gains and losses over a specified time period to measure speed and change of price movements of a security. 218 | It is primarily used to attempt to identify overbought or oversold conditions in the trading of an asset. 219 | 220 | https://www.investopedia.com/terms/r/rsi.asp 221 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.momentum.rsi 222 | ''' 223 | df[ 'RSI' ] = ta.momentum.rsi( df.Close, window=14, fillna=True ) 224 | 225 | 226 | ''' 227 | Moving Average Convergence Divergence (MACD) 228 | 229 | Is a trend-following momentum indicator that shows the relationship between two moving averages of prices. 230 | The MACD is calculated by subtracting the 26-period exponential moving average (EMA) from the 12-period EMA. 231 | 232 | https://www.investopedia.com/terms/m/macd.asp 233 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.trend.MACD 234 | ''' 235 | macd = ta.trend.MACD( df.Close, window_fast=12, window_slow=26, window_sign=9, fillna=True ) 236 | df[ 'MACD' ] = macd.macd() 237 | df[ 'MACD_Diff' ] = macd.macd_diff() 238 | df[ 'MACD_Signal' ] = macd.macd_signal() 239 | 240 | 241 | ''' 242 | Simple Moving Average (SMA) 243 | 244 | A simple moving average is an arithmetic moving average calculated by adding recent prices and then dividing that figure by the number of time periods in the calculation average. 245 | For example, one could add the closing price of a security for a number of time periods and then divide this total by that same number of periods. 246 | Short-term averages respond quickly to changes in the price of the underlying security, while long-term averages are slower to react. 247 | 248 | https://www.investopedia.com/terms/s/sma.asp 249 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.trend.sma_indicator 250 | ''' 251 | # SMAs according to Binance 252 | df[ 'SMA7' ] = ta.trend.sma_indicator( df.Close, window=7, fillna=True ) 253 | df[ 'SMA25' ] = ta.trend.sma_indicator( df.Close, window=25, fillna=True ) 254 | df[ 'SMA60' ] = ta.trend.sma_indicator( df.Close, window=60, fillna=True ) 255 | # Commonly used SMAs 256 | df[ 'SMA12' ] = ta.trend.sma_indicator( df.Close, window=12, fillna=True ) 257 | df[ 'SMA26' ] = ta.trend.sma_indicator( df.Close, window=26, fillna=True ) 258 | df[ 'SMA50' ] = ta.trend.sma_indicator( df.Close, window=50, fillna=True ) 259 | df[ 'SMA200' ] = ta.trend.sma_indicator( df.Close, window=200, fillna=True ) 260 | 261 | 262 | ''' 263 | Parabolic Stop and Reverse (Parabolic SAR) 264 | 265 | The parabolic SAR is a widely used technical indicator to determine market direction, but at the same moment to draw attention to it once the market direction is changing. 266 | This indicator also can be called the "stop and reversal system," the parabolic SAR was developed by J. Welles Wilder Junior. - the creator of the relative strength index (RSI). 267 | 268 | https://www.investopedia.com/terms/p/parabolicindicator.asp 269 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.trend.PSARIndicator 270 | ''' 271 | psar = ta.trend.PSARIndicator( high=df.High, low=df.Low, close=df.Close, step=0.02, max_step=2, fillna=True ) 272 | df[ 'PSAR' ] = psar.psar() 273 | df[ 'PSAR_down' ] = psar.psar_down() 274 | df[ 'PSAR_down_ind' ] = psar.psar_down_indicator() 275 | df[ 'PSAR_up' ] = psar.psar_up() 276 | df[ 'PSAR_up_ind' ] = psar.psar_up_indicator() 277 | 278 | 279 | ''' 280 | Bollinger Bands 281 | 282 | A Bollinger Band is a technical analysis tool outlined by a group of trend lines with calculated 2 standard deviations (positively and negatively) far from a straightforward moving average (SMA) of a market's value, 283 | however which may be adjusted to user preferences. Bollinger Bands were developed and copyrighted by notable technical day trader John Bollinger and designed to get opportunities that could offer investors a better 284 | likelihood of properly identifying market conditions (oversold or overbought). Bollinger Bands are a highly popular technique. Many traders believe the closer the prices move to the upper band, 285 | the more overbought the market is, and the closer the prices move to the lower band, the more oversold the market is. 286 | 287 | https://www.investopedia.com/terms/b/bollingerbands.asp 288 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.volatility.BollingerBands 289 | ''' 290 | bb = ta.volatility.BollingerBands( close=df.Close, window=20, window_dev=2, fillna=True ) 291 | df[ 'bb_avg' ] = bb.bollinger_mavg() 292 | df[ 'bb_high' ] = bb.bollinger_hband() 293 | df[ 'bb_low' ] = bb.bollinger_lband() 294 | 295 | 296 | ''' 297 | Average True Range (ATR) 298 | 299 | The indicator provide an indication of the degree of price volatility. Strong moves, in either direction, are often accompanied by large ranges, or large True Ranges. 300 | 301 | The average true range (ATR) is a technical analysis indicator, introduced by market technician J. Welles Wilder Jr. in his book New Concepts in Technical Trading Systems, 302 | that measures market volatility by decomposing the entire range of an asset price for that period. 303 | The true range indicator is taken as the greatest of the following: current high less the current low; the absolute value of the current high less the previous close; and the absolute value of the current low less the previous close. 304 | The ATR is then a moving average, generally using 14 days, of the true ranges. 305 | 306 | https://www.investopedia.com/terms/a/atr.asp 307 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.volatility.AverageTrueRange 308 | ''' 309 | df[ 'ATR' ] = ta.volatility.AverageTrueRange( high=df.High, low=df.Low, close=df.Close, window=14, fillna=True ).average_true_range() 310 | 311 | 312 | ''' 313 | On-balance volume (OBV) 314 | 315 | It relates price and volume in the stock market. OBV is based on a cumulative total volume. 316 | 317 | What is On-Balance Volume (OBV)? 318 | On-balance volume (OBV) is a technical trading momentum indicator that uses volume flow to predict changes in stock price. Joseph Granville first developed the OBV metric in the 1963 book Granville's New Key to Stock Market Profits. 319 | Granville believed that volume was the key force behind markets and designed OBV to project when major moves in the markets would occur based on volume changes. 320 | In his book, he described the predictions generated by OBV as "a spring being wound tightly." 321 | He believed that when volume increases sharply without a significant change in the stock's price, the price will eventually jump upward or fall downward. 322 | 323 | https://www.investopedia.com/terms/o/onbalancevolume.asp 324 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.volume.OnBalanceVolumeIndicator 325 | ''' 326 | df[ 'OBV' ] = ta.volume.OnBalanceVolumeIndicator( close=df.Close, volume=df.Volume, fillna=True).on_balance_volume() 327 | 328 | 329 | ''' 330 | Rate of Change (ROC) 331 | 332 | The Rate-of-Change (ROC) indicator, which is also referred to as simply Momentum, is a pure momentum oscillator that measures the percent change in price from one period to the next. 333 | The ROC calculation compares the current price with the price “n” periods ago. 334 | The plot forms an oscillator that fluctuates above and below the zero line as the Rate-of-Change moves from positive to negative. 335 | As a momentum oscillator, ROC signals include centerline crossovers, divergences and overbought-oversold readings. 336 | Divergences fail to foreshadow reversals more often than not, so this article will forgo a detailed discussion on them. 337 | Even though centerline crossovers are prone to whipsaw, especially short-term, these crossovers can be used to identify the overall trend. 338 | Identifying overbought or oversold extremes comes naturally to the Rate-of-Change oscillator. 339 | 340 | https://www.investopedia.com/terms/p/pricerateofchange.asp 341 | https://technical-analysis-library-in-python.readthedocs.io/en/latest/ta.html#ta.momentum.ROCIndicator 342 | ''' 343 | df[ 'ROC' ] = ta.momentum.ROCIndicator( close=df.Close, window=3, fillna=True ).roc() 344 | 345 | 346 | 347 | ''' 348 | return the augmented DataFrame 349 | ''' 350 | return df 351 | 352 | 353 | def build_Frame( msg, isMultiStream=None ): 354 | ''' Build a DataFrame from a Websocket response message 355 | 356 | :param msg -> type:List: Websocket response 357 | :param isMultiStream -> type:bool: True if message comes from a Websocket Multistream 358 | :return DataFrame[ 'Time', 'Symbol', 'Price'] 359 | ''' 360 | if isMultiStream is not None: 361 | #https://binance-docs.github.io/apidocs/spot/en/#trade-streams 362 | ''' 363 | { 364 | 'stream': 'btcusdt@trade', 365 | 'data': { 366 | 'e': 'trade', // Event type 367 | 'E': 1637081506351, // Event time 368 | 's': 'BTCUSDT', // Symbol 369 | 't': 1148079548, // Trade ID 370 | 'p': '60646.40000000', // Price 371 | 'q': '0.00031000', // Quantity 372 | 'b': 8283146847, // Buyer order ID 373 | 'a': 8283146313, // Seller order ID 374 | 'T': 1637081506349, // Trade time 375 | 'm': False, // Is the buyer the market maker? 376 | 'M': True // Ignore 377 | } 378 | } 379 | ''' 380 | df = pd.DataFrame( [ msg[ 'data' ] ] ) 381 | else: 382 | df = pd.DataFrame( [msg] ) 383 | 384 | df = df.loc[ :, [ 'E', 's', 'p' ] ] #E: event time | s: Symbol | p: Price ->https://binance-docs.github.io/apidocs/spot/en/#trade-streams 385 | df.columns = [ 'Time', 'Symbol', 'Price' ] 386 | df.Price = df.Price.astype( float ) 387 | df.Time = pd.to_datetime( df.Time, unit='ms' ) 388 | #pd.options.display.float_format = "{:,.8f}".format 389 | return df 390 | 391 | 392 | def fetch_Lotsize( client, symbol ): 393 | info = client.get_symbol_info( symbol ) 394 | return float( info['filters'][2]['minQty'] ) 395 | 396 | 397 | def fetch_AssetMetadata( client, symbol ): 398 | ''' Fetch Metadata needed for placing Orders 399 | ''' 400 | info = client.get_symbol_info( symbol ) 401 | precision = info['baseAssetPrecision'] 402 | lotsize = info['filters'][2]['minQty'] 403 | 404 | return { 'lotsize' : float(lotsize), 'precision' : precision } 405 | 406 | 407 | def queryDB( engine, symbol, lookback:int ): 408 | ''' Query Crypto.db 409 | 410 | :param engine -> SQLalchemy Engine Object 411 | :param symbol 412 | :param lookback 413 | ''' 414 | lookback = lookback * 60 #minute to second conversion 415 | now = dt.datetime.now() - dt.timedelta( hours=1 ) #binance server are 1hour ahead 416 | before = now - dt.timedelta( seconds=lookback) 417 | #before = now - dt.timedelta( minutes=lookback ) 418 | querystr = f"SELECT * FROM '{symbol}' WHERE TIME >= '{before}'" 419 | return pd.read_sql( querystr, engine ) 420 | 421 | 422 | 423 | # we need a function to create a pseudo order response for testing 424 | def pseudoMarketOrder( side, symbol, qty, price, precision ): 425 | 426 | #'origQty': '12.30000000' '0.00020000' '0.01210000' 427 | origQTY = qty 428 | quoteQTY = Decimal( qty*price ) 429 | halfedQTY = Decimal( origQTY/2 ) 430 | batch1QTY = Decimal( halfedQTY + (halfedQTY/2) ) 431 | batch1Fee = float( round( ( batch1QTY / 100 ) * 0.1, precision ) ) 432 | batch2QTY = float( halfedQTY/2 ) 433 | batch2Fee = float( round( ( batch2QTY / 100 ) * 0.1, precision ) ) 434 | 435 | order = { 436 | 'symbol': symbol, 437 | 'orderId': 22107854, 438 | 'orderListId': -1, 439 | 'clientOrderId': 'DkGnomuTY9lz4kkALHQ87f', 440 | 'transactTime': 1637194823283, 441 | 'price': '0.00000000', 442 | 'origQty': origQTY, 443 | 'executedQty': origQTY, 444 | 'cummulativeQuoteQty': quoteQTY, 445 | 'status': 'FILLED', 446 | 'timeInForce': 'GTC', 447 | 'type': 'MARKET', 448 | 'side': side, 449 | 'fills': [ 450 | { 451 | 'price': price, 452 | 'qty': batch1QTY, 453 | 'commission': batch1Fee, 454 | 'commissionAsset': symbol, 455 | 'tradeId': 2498963 456 | }, 457 | { 458 | 'price': price, 459 | 'qty': batch2QTY, 460 | 'commission': batch2Fee, 461 | 'commissionAsset': symbol, 462 | 'tradeId': 2498964 463 | } 464 | ] 465 | } 466 | 467 | return order 468 | 469 | 470 | 471 | def pseudoBalance( balance=None ): 472 | file = '/home/dave/code/crypto/binance/log/balance' 473 | if balance is not None: 474 | with open( file, 'r' ) as fd: 475 | bal = fd.readline().strip() 476 | 477 | if float(balance) > float(0): 478 | balance = float( float(bal) + float(balance) ) 479 | else: 480 | balance = float( float(bal) - float(abs(balance)) ) 481 | 482 | with open( file, 'w+' ) as fd: 483 | fd.write( str( balance ) ) 484 | else: 485 | with open( file, 'r' ) as fd: 486 | balance = fd.readline().strip() 487 | 488 | return float( balance ) 489 | 490 | 491 | --------------------------------------------------------------------------------