-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
30795c0
commit 3f772a8
Showing
14 changed files
with
1,898 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,134 @@ | ||
# Crypto-bot-in-python | ||
# python-crypto-bot | ||
|
||
Automated crypto trading in Python. | ||
|
||
This is my attempt to write a robust python3 framework to implement different automated trading strategies. | ||
|
||
This project works with [CCXT](https://github.com/ccxt/ccxt) and is therefor compatible with many exchanges. | ||
|
||
## !!! Disclaimer !!! | ||
|
||
This software is for educational purposes only. Do not risk money which you are not ready to lose. USE THE SOFTWARE AT YOUR OWN RISK. I WILL NOT ASSUME RESPONSIBILITY FOR YOUR LOSSES. | ||
|
||
NEVER RUN THE SOFTWARE IN LIVE MODE IF YOU'RE NOT 100% SURE OF WHAT YOU ARE DOING. Once again, I will NOT take ANY responsibilities regarding the results of your trades. | ||
|
||
As is, the software is obviously not viable, but with some research and a bit of coding, it could become your best ally in automating strategies. | ||
|
||
## Shoutouts | ||
|
||
This project is strongly inspired by [bwentzloff](https://github.com/bwentzloff)'s trading bot which you can find here: https://github.com/bwentzloff/trading-bot. | ||
If you're new to python trading I strongly recommend checking his [Youtube Chanel](https://youtube.com/cryptocurrencytrading) | ||
|
||
Shoutout to [CCXT](https://github.com/ccxt) for their wonderful work on their super awesome [library](https://github.com/ccxt/ccxt) | ||
|
||
And finally a big thx to [sentdex](https://github.com/Sentdex) who's youtube tutorials inspired me to get into python automated trading years ago | ||
|
||
## GETTING STARTED | ||
|
||
This project is compatible with python3 and there's no reason why I should spend more time to make it compatible with python2. | ||
|
||
### Prerequisites | ||
|
||
``` | ||
pip install ccxt | ||
pip install numpy | ||
pip install pandas | ||
``` | ||
|
||
### Install | ||
|
||
``` | ||
git clone https://github.com/Viandoks/python-crypto-bot.git | ||
cd python-crypto-bot | ||
``` | ||
|
||
### How to | ||
|
||
#### Configuration | ||
Rename `.env.sample` to `.env` and edit it to your preferences. | ||
```.env | ||
API_KEY= YourApiKey | ||
API_SECRET= YourApiSecret | ||
ASSET=BTC or any asset available on the exchance | ||
MARKET=USDT or any base asset available on the exchange | ||
TIMEFRAME=1m or any timeframce available | ||
COINS_ASSET=1 used for backtest, how much assets to start with | ||
COINS_MARKET=100 used for backtest, how much base assets to start with | ||
EXCHANGE=poloniex your choice of exchange available in CCXT | ||
FEES=0.125 used for backtest, emulate fees in % | ||
SPREAD=0.00 used for backtest, emulate spread in % | ||
ALLOCATION=1 how much of your assets do you want to play at each order (1==100%) | ||
INTERVAL=10 used for forward test and live, interval between each call to the API in seconds | ||
START_DATE=2021-01-01 00:00:00 date at which backtest should start, if not specified, bot will run in forward test mode | ||
``` | ||
|
||
#### Run backtest | ||
|
||
``` | ||
python bot.py | ||
``` | ||
|
||
This will launch the trading strategy on the pair `ASSET`/`MARKET`, with a candlestick period of `TIMEFRAME`, starting on `START_DATE`. | ||
|
||
You can see the result in the command line AND the output/index.html file in the project | ||
|
||
#### Run forward test | ||
|
||
Leave `START_DATE` empty to run forward test | ||
|
||
A new call is made every `INTERVAL` seconds. You can follow the trades almost live be reloading output/index.html | ||
|
||
#### Run live | ||
!!!!! DO NOT DO THIS IF YOU'RE NOT 100% SURE OF WHAT YOU'RE DOING! I WILL NOT BE HELD RESPONSIBLE IF YOU LOSE MONEY!!!!! | ||
To run the bot live you need to enter your api key and secret in the shared.py file. If you don't know what an api key is, you shouldn't even try to launch that bot live. However feel free to have fun in backtest and forward test mode. | ||
``` | ||
python bot.py --live | ||
``` | ||
|
||
### Important files | ||
`shared.py` contains a bunch of variable shared across the different modules | ||
|
||
`bot.py` is where everything start | ||
|
||
`botapi.py` is where the connections are made between the bot and the registered APIs. | ||
|
||
`botstrategy.py` is where the magic happens. The default strategy is a simple Moving Average Crossover strategy. IT WILL NOT WORK LIVE! Feel free to experiment and write your own strategy | ||
|
||
`botindicators.py` contains all the indicator available in bostrategy.py | ||
|
||
`botchart.py` generates the candlesticks data in backtest mode, as well as the data for the final graph viewable in `output/output.html`. It is based on [googlecharts](https://developers.google.com/chart/) and can be overwelming work with at first. If you change the strategy, there is a 99% that you need to modify output.html and botchart.py as well. | ||
If you want rendering of your trades, you gonna have to dig into this. Sorry. | ||
|
||
Other files are pretty much self explanatory. Feel free to checkout [bwentzloff](https://github.com/bwentzloff/trading-bot) if something is not clear. | ||
|
||
### Last notes | ||
|
||
This project is not perfect, it probably still have a few bugs/unwanted behaviors. It could probably be improved a lot. I already have a few improvement/changes in mind. Feel free to open ticket if you find/think about something. I'll try to work on them when I have time/faith. Even better, work on it yourself and do a pull request :D | ||
Unfortunately coding is not my main goal in life anymore so please be patient. | ||
|
||
What is this spreadPercentage thingy in shared.py? | ||
I implemented this to emulate the difference between ask and bid price. How many time did I think I was gonna get rich in a matter of days just because I forgot about the spread. Going live is full of bad surprises. | ||
|
||
Kraken's api has been quite a turbulent child recently, often returning a time out. There is a [pull request](https://github.com/veox/python3-krakenex/pull/100) on krakenex to attempt to fix that but is not merge to master yet... so be patient or try and fix it by yourself ;) | ||
Once again I do not recommend the use of Kraken API. | ||
|
||
Also pairs on kraken are a bit weird, if you intend to trade on the ETHXBT market you should use XXBT_XETH as the currency_pair argument. I'll probably do some more work on that in the future, but for now... that's the way. | ||
|
||
I know this is not the best readme ever but hey... at least there is one. | ||
|
||
As I said earlier, the strategy in there DOES NOT WORK. It is merely a tool for you to write your own and pwn the market. It took me years to write a winning strategy, hang in there! | ||
|
||
### Tip Me | ||
|
||
You like what you see? Feel free to tip me, you'll earn my eternal gratitude: | ||
|
||
BTC - 1MT45xgCJe68c3eL2iTJMSQLwsDobmzz7r | ||
|
||
ETH - 0x4Aa63028CA72D8Ce79E904DE1c53AbC91d2F2347 | ||
|
||
### Final Words | ||
|
||
HAVE FUN! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import sys, getopt | ||
import time | ||
import pprint | ||
import copy | ||
import shared | ||
from botchart import BotChart | ||
from botstrategy import BotStrategy | ||
from botcandlestick import BotCandlestick | ||
import ccxt | ||
|
||
|
||
def main(argv): | ||
live = False | ||
|
||
try: | ||
opts, args = getopt.getopt(argv, "", ["live"]) | ||
except getopt.GetoptError: | ||
print('trading-bot.py') | ||
sys.exit(2) | ||
|
||
for opt, arg in opts: | ||
if opt == "--live": | ||
print("You're going live... Losses are your responsibility only!") | ||
live = True | ||
|
||
# START_DATE specified: we are in backtest mode | ||
if shared.strategy['start_date']: | ||
|
||
chart = BotChart() | ||
|
||
strategy = BotStrategy() | ||
strategy.showPortfolio() | ||
|
||
for candlestick in chart.getPoints(): | ||
strategy.tick(candlestick) | ||
|
||
chart.drawChart(strategy.candlesticks, strategy.trades, strategy.movingAverages) | ||
|
||
strategy.showPortfolio() | ||
|
||
else: | ||
|
||
chart = BotChart(False) | ||
|
||
strategy = BotStrategy(False, live) | ||
strategy.showPortfolio() | ||
|
||
candlestick = BotCandlestick() | ||
|
||
x = 0 | ||
while True: | ||
try: | ||
currentPrice = chart.getCurrentPrice() | ||
candlestick.tick(currentPrice) | ||
strategy.tick(candlestick) | ||
|
||
except ccxt.NetworkError as e: | ||
print(type(e).__name__, e.args, 'Exchange error (ignoring)') | ||
except ccxt.ExchangeError as e: | ||
print(type(e).__name__, e.args, 'Exchange error (ignoring)') | ||
except ccxt.DDoSProtection as e: | ||
print(type(e).__name__, e.args, 'DDoS Protection (ignoring)') | ||
except ccxt.RequestTimeout as e: | ||
print(type(e).__name__, e.args, 'Request Timeout (ignoring)') | ||
except ccxt.ExchangeNotAvailable as e: | ||
print(type(e).__name__, e.args, 'Exchange Not Available due to downtime or maintenance (ignoring)') | ||
except ccxt.AuthenticationError as e: | ||
print(type(e).__name__, e.args, 'Authentication Error (missing API keys, ignoring)') | ||
|
||
drawingCandles = copy.copy(strategy.candlesticks) | ||
if not candlestick.isClosed(): | ||
drawingCandles.append(copy.copy(candlestick)) | ||
drawingCandles[-1].close = candlestick.currentPrice | ||
chart.drawChart(drawingCandles, strategy.trades, strategy.movingAverages) | ||
|
||
if candlestick.isClosed(): | ||
candlestick = BotCandlestick() | ||
|
||
x += 1 | ||
time.sleep(shared.exchange['interval']) | ||
|
||
|
||
if __name__ == "__main__": | ||
main(sys.argv[1:]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import ccxt | ||
import shared | ||
|
||
def BotApi(): | ||
ccxt.exchange = getattr(ccxt, shared.exchange['name'])({ | ||
'apiKey': shared.api['key'], | ||
'secret': shared.api['secret'] | ||
}) | ||
return ccxt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from botlog import BotLog | ||
|
||
import shared | ||
import sys, getopt | ||
import time | ||
import utils | ||
|
||
|
||
class BotCandlestick(object): | ||
def __init__(self,date=None,open=None,high=None,low=None,close=None,volume=None): | ||
self.close = close | ||
self.currentPrice = close | ||
self.date = date | ||
self.high = high | ||
self.low = low | ||
self.open = open | ||
self.output = BotLog() | ||
self.priceAverage = False | ||
self.startTime = time.time() | ||
self.volume = volume | ||
|
||
if self.close: | ||
self.currentPrice = self.close | ||
|
||
def __setitem__(self, key, value): | ||
setattr(self, key, value) | ||
|
||
def __getitem__(self, key): | ||
return getattr(self, key) | ||
|
||
def toDict(self): | ||
return { | ||
'close': self.close, | ||
'currentPrice': self.currentPrice, | ||
'date': self.date, | ||
'high': self.high, | ||
'low': self.low, | ||
'open' : self.open, | ||
'priceAverage': self.priceAverage, | ||
'startTime': self.startTime, | ||
'volume': self.volume | ||
} | ||
|
||
def tick(self, price): | ||
self.currentPrice = float(price) | ||
|
||
if self.date is None: | ||
self.date = time.time() | ||
if (self.open is None): | ||
self.open = self.currentPrice | ||
|
||
if (self.high is None) or (self.currentPrice > self.high): | ||
self.high = self.currentPrice | ||
|
||
if (self.low is None) or (self.currentPrice < self.low): | ||
self.low = self.currentPrice | ||
|
||
|
||
timedelta = utils.parseTimedelta(shared.strategy['timeframe']) | ||
if time.time() >= ( self.startTime + timedelta): | ||
self.close = self.currentPrice | ||
self.priceAverage = ( self.high + self.low + self.close ) / float(3) | ||
|
||
self.output.log("Start time: "+str(self.startTime)+", Open: "+str(self.open)+" Close: "+str(self.close)+" High: "+str(self.high)+" Low: "+str(self.low)+" currentPrice: "+str(self.currentPrice)) | ||
|
||
def isClosed(self): | ||
if (self.close is not None): | ||
return True | ||
else: | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
from datetime import datetime, timedelta | ||
import pandas as pd | ||
import time | ||
import shared | ||
import sys | ||
|
||
from botapi import BotApi | ||
from botcandlestick import BotCandlestick | ||
from botlog import BotLog | ||
|
||
|
||
class BotChart(object): | ||
"""Draws a classic trading chart, humanely readable""" | ||
|
||
def __init__(self,backTest=True): | ||
|
||
self.pair = shared.exchange['pair'] | ||
self.backTest = bool(backTest) | ||
self.output = BotLog() | ||
self.tempCandle = None | ||
|
||
self.data = [] | ||
|
||
# API | ||
self.api = BotApi() | ||
|
||
if backTest: | ||
from_timestamp = self.api.exchange.parse8601(shared.strategy['start_date']) | ||
try: | ||
print(self.api.exchange.milliseconds(), 'Fetching candles starting from', self.api.exchange.iso8601(from_timestamp)) | ||
ohlcvs = self.api.exchange.fetch_ohlcv(shared.exchange['pair'], timeframe=shared.strategy['timeframe'], since=from_timestamp) | ||
print(self.api.exchange.milliseconds(), 'Fetched', len(ohlcvs), 'candles') | ||
|
||
for ohlcv in ohlcvs: | ||
self.data.append(BotCandlestick(float(ohlcv[0])/1000, float(ohlcv[1]), float(ohlcv[2]), float(ohlcv[3]), float(ohlcv[4]), float(ohlcv[5]))) | ||
|
||
|
||
except (self.api.ExchangeError, self.api.AuthenticationError, self.api.ExchangeNotAvailable, self.api.RequestTimeout) as error: | ||
print('Got an error', type(error).__name__, error.args) | ||
exit(2) | ||
|
||
def getPoints(self): | ||
return self.data | ||
|
||
def getCurrentPrice(self): | ||
if not self.backTest: | ||
ticker = self.api.exchange.fetchTicker(self.pair) | ||
price = ticker["last"] | ||
return float(price) | ||
|
||
def drawChart(self, candlesticks, orders, movingAverages): | ||
|
||
# googlecharts | ||
output = open("./output/data.js",'w') | ||
output.truncate() | ||
|
||
# candlesticks | ||
candlesticks = pd.DataFrame.from_records([c.toDict() for c in candlesticks]) | ||
ma = pd.DataFrame(movingAverages) | ||
if len(ma) > 0: | ||
candlesticks['ma'] = ma | ||
else: | ||
candlesticks['ma'] = 0 | ||
candlesticks['date'] = candlesticks['date'] | ||
candlesticks.set_index('date', inplace=True) | ||
|
||
# orders | ||
orders = pd.DataFrame.from_records([o.toDict() for o in orders]) | ||
if len(orders) > 1: | ||
orders['date'] = orders['date'] | ||
orders.set_index('date', inplace=True) | ||
else: | ||
orders['orderNumber'] = 0 | ||
orders['rate'] = 0.0 | ||
orders['direction'] = 'None' | ||
orders['stopLoss'] = 0.0 | ||
orders['takeProfit'] = 0.0 | ||
orders['exitRate'] = 0.0 | ||
|
||
# concat all to one dataframe | ||
data = pd.concat([candlesticks, orders], axis=1) | ||
data['direction'].fillna('None', inplace=True) | ||
data['ma'].fillna(method='ffill', inplace=True) | ||
data.fillna(0, inplace=True) | ||
|
||
# add to data.js | ||
output.write("var dataRows = "+data.to_json(orient='index')+";") | ||
output.write("var lastcall = '"+str(time.ctime())+"'") |
Oops, something went wrong.