Compare commits

...

38 Commits

Author SHA1 Message Date
gcarq c20030783b Merge branch 'release/0.10.0' 2017-09-28 19:17:01 +02:00
gcarq ff5a6633c6 version bump 2017-09-28 19:14:18 +02:00
gcarq af6b07efb1 Merge branch 'master' of https://github.com/gcarq/freqtrade into develop 2017-09-28 19:03:53 +02:00
gcarq d416aba95e add setup tutorial (closes #40) 2017-09-28 19:01:02 +02:00
gcarq 775414d494 add slack invite link 2017-09-28 19:00:42 +02:00
Michael Egger f493df7a82 Merge pull request #33 from gcarq/disable-debuglog-backtest
Disable debug level logging when running backtesting
2017-09-28 16:52:55 +02:00
Janne Sinivirta a2f7709cfd disable debug level logging when running backtesting 2017-09-28 17:00:14 +03:00
Janne Sinivirta 9a64522f45 Merge pull request #25 from alangvand/whitelist-on-sale
add whitelist to execute_sell and append sold pair to it
2017-09-28 11:02:23 +03:00
Janne Sinivirta e7620b46ae Merge pull request #29 from gcarq/backtesting
Backtesting
2017-09-28 10:52:42 +03:00
Janne Sinivirta 41849c4a1e add three more currency pairs to back testing. redownloaded all test data 2017-09-25 22:01:54 +03:00
Janne Sinivirta c9dcc1e57c disable the backtesting by default 2017-09-25 21:39:43 +03:00
Janne Sinivirta 9f7a72a990 shorten report text 2017-09-25 21:39:28 +03:00
Janne Sinivirta 5f98649b7d backtesting over 7 different coins and a month of 5min ticker data 2017-09-25 21:06:15 +03:00
Janne Sinivirta 4198220b68 extract sell criteria to it's own method for testing 2017-09-25 21:05:37 +03:00
Janne Sinivirta 877dd6d3fa simplify sell conditions 2017-09-25 15:17:29 +03:00
Janne Sinivirta f3ccca1c66 try running analyze_ticker with mock data 2017-09-24 17:23:29 +03:00
Janne Sinivirta 9b63f02e1c add set of test data 2017-09-24 17:20:25 +03:00
Janne Sinivirta 72432c1285 Fix link markup for issues 2017-09-21 09:08:13 -07:00
Michael Egger be86a40207 Merge pull request #28 from gcarq/contribute-readme
Updated README.md
2017-09-21 15:30:43 +02:00
Janne Sinivirta 73ad3b4c85 Updated README.md 2017-09-20 08:06:10 -07:00
Janne Sinivirta 2bd51d8be3 Merge pull request #26 from gcarq/bid-balance
Set balance for bid price between ask and last
2017-09-20 07:58:20 -07:00
Janne Sinivirta 358a1eb73f add type hint for ticker 2017-09-20 07:34:47 -07:00
Janne Sinivirta e5a742cf2e add a little more explanation for ask_last_balance to README 2017-09-18 05:09:45 -07:00
Janne Sinivirta 4d651e0082 add ask_last_balance to README.md 2017-09-18 05:08:05 -07:00
Janne Sinivirta 465dc47b23 balance bid price between ask and last 2017-09-17 23:21:46 +03:00
Janne Sinivirta 989682457e add a field to config for setting balance between trying to buy with ask price and last price 2017-09-17 22:37:46 +03:00
André Øien Langvand e9e76da054 add whitelist to execute_sell and append sold pair to it 2017-09-17 18:42:42 +02:00
gcarq f84c58c3eb add slack token 2017-09-12 19:20:49 +02:00
gcarq d4fb7bd776 Merge branch 'develop' of https://github.com/gcarq/freqtrade into develop 2017-09-12 17:50:08 +02:00
Michael Egger 8ed8e1e103 Merge pull request #20 from vertti/newer-strategy
New buy strategy
2017-09-12 16:01:18 +02:00
Janne Sinivirta 1280670ea3 use five minute ticker for a much more stable indicators 2017-09-12 10:53:42 +02:00
Janne Sinivirta cedc207097 remove unused import 2017-09-12 10:49:30 +02:00
Janne Sinivirta a5b3428552 rename variable to get rid of bunch of pylint shadowing complaints 2017-09-12 10:49:10 +02:00
Janne Sinivirta 2221a0fbbc implement new buying strategy 2017-09-12 10:47:23 +02:00
gcarq ffa32df40f remove poloniex from CONF_SCHEMA 2017-09-11 14:06:52 +02:00
gcarq f91cd8ea96 drop support for poloniex 2017-09-11 13:59:38 +02:00
gcarq 48beb279c0 rename btc_amount to stake_amount 2017-09-11 13:59:11 +02:00
gcarq dc1cfe7a7a Merge tag '0.9.0' into develop
0.9.0
2017-09-10 22:57:20 +02:00
24 changed files with 225 additions and 133 deletions
+12 -13
View File
@@ -1,28 +1,27 @@
sudo: false sudo: false
os: os:
- linux - linux
language: python language: python
python: python:
- 3.6 - 3.6
- nightly - nightly
matrix: matrix:
allow_failures: allow_failures:
- python: nightly - python: nightly
addons: addons:
apt: apt:
packages: packages:
- libelf-dev - libelf-dev
- libdw-dev - libdw-dev
- binutils-dev - binutils-dev
install: install:
- wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz - wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
- tar zxvf ta-lib-0.4.0-src.tar.gz - tar zxvf ta-lib-0.4.0-src.tar.gz
- cd ta-lib && ./configure && sudo make && sudo make install && cd .. - cd ta-lib && ./configure && sudo make && sudo make install && cd ..
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
- pip install -r requirements.txt - pip install -r requirements.txt
script: script:
- python -m unittest - python -m unittest
notifications:
slack:
secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q=
+14 -1
View File
@@ -3,7 +3,7 @@
[![Build Status](https://travis-ci.org/gcarq/freqtrade.svg?branch=develop)](https://travis-ci.org/gcarq/freqtrade) [![Build Status](https://travis-ci.org/gcarq/freqtrade.svg?branch=develop)](https://travis-ci.org/gcarq/freqtrade)
Simple High frequency trading bot for crypto currencies. Simple High frequency trading bot for crypto currencies.
Currently supported exchanges: bittrex, poloniex (partly implemented) Currently supports trading on Bittrex exchange.
This software is for educational purposes only. This software is for educational purposes only.
Don't risk money which you are afraid to lose. Don't risk money which you are afraid to lose.
@@ -44,6 +44,9 @@ profit dips below -10% for a given trade. This parameter is optional.
Possible values are `running` or `stopped`. (default=`running`) Possible values are `running` or `stopped`. (default=`running`)
If the value is `stopped` the bot has to be started with `/start` first. If the value is `stopped` the bot has to be started with `/start` first.
`ask_last_balance` sets the bidding price. Value `0.0` will use `ask` price, `1.0` will
use the `last` price and values between those interpolate between ask and last price. Using `ask` price will guarantee quick success in bid, but bot will also end up paying more then would probably have been necessary.
The other values should be self-explanatory, The other values should be self-explanatory,
if not feel free to raise a github issue. if not feel free to raise a github issue.
@@ -63,6 +66,8 @@ $ pip install -r requirements.txt
$ ./main.py $ ./main.py
``` ```
There is also an [article](https://www.sales4k.com/blockchain/high-frequency-trading-bot-tutorial/) about how to setup the bot (thanks [@gurghet](https://github.com/gurghet)).
#### Execute tests #### Execute tests
``` ```
@@ -75,3 +80,11 @@ $ cd freqtrade
$ docker build -t freqtrade . $ docker build -t freqtrade .
$ docker run --rm -it freqtrade $ docker run --rm -it freqtrade
``` ```
#### Contributing
Feel like our bot is missing a feature? We welcome your pull requests! Few pointers for contributions:
- Create your PR against the `develop` branch, not `master`.
- New features need to contain unit tests.
- If you are unsure, discuss the feature on [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) or in a [issue](https://github.com/gcarq/freqtrade/issues) before a PR.
+33 -35
View File
@@ -3,7 +3,6 @@ from datetime import timedelta
import logging import logging
import arrow import arrow
import requests import requests
from pandas.io.json import json_normalize
from pandas import DataFrame from pandas import DataFrame
import talib.abstract as ta import talib.abstract as ta
@@ -23,7 +22,7 @@ def get_ticker(pair: str, minimum_date: arrow.Arrow) -> dict:
} }
params = { params = {
'marketName': pair.replace('_', '-'), 'marketName': pair.replace('_', '-'),
'tickInterval': 'OneMin', 'tickInterval': 'fiveMin',
'_': minimum_date.timestamp * 1000 '_': minimum_date.timestamp * 1000
} }
data = requests.get(url, params=params, headers=headers).json() data = requests.get(url, params=params, headers=headers).json()
@@ -49,19 +48,9 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
""" """
Adds several different TA indicators to the given DataFrame Adds several different TA indicators to the given DataFrame
""" """
dataframe['close_30_ema'] = ta.EMA(dataframe, timeperiod=30) dataframe['ema'] = ta.EMA(dataframe, timeperiod=33)
dataframe['close_90_ema'] = ta.EMA(dataframe, timeperiod=90) dataframe['sar'] = ta.SAR(dataframe, 0.02, 0.22)
dataframe['adx'] = ta.ADX(dataframe)
dataframe['sar'] = ta.SAR(dataframe, 0.02, 0.2)
# calculate StochRSI
stochrsi = ta.STOCHRSI(dataframe)
dataframe['stochrsi'] = stochrsi['fastd'] # values between 0-100, not 0-1
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macds'] = macd['macdsignal']
dataframe['macdh'] = macd['macdhist']
return dataframe return dataframe
@@ -72,13 +61,29 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
:param dataframe: DataFrame :param dataframe: DataFrame
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
prev_sar = dataframe['sar'].shift(1)
prev_close = dataframe['close'].shift(1)
prev_sar2 = dataframe['sar'].shift(2)
prev_close2 = dataframe['close'].shift(2)
# wait for stable turn from bearish to bullish market
dataframe.loc[ dataframe.loc[
(dataframe['stochrsi'] < 20) (dataframe['close'] > dataframe['sar']) &
& (dataframe['macd'] > dataframe['macds']) (prev_close > prev_sar) &
& (dataframe['close'] > dataframe['sar']), (prev_close2 < prev_sar2),
'buy' 'swap'
] = 1 ] = 1
# consider prices above ema to be in upswing
dataframe.loc[dataframe['ema'] <= dataframe['close'], 'upswing'] = 1
dataframe.loc[
(dataframe['upswing'] == 1) &
(dataframe['swap'] == 1) &
(dataframe['adx'] > 25), # adx over 25 tells there's enough momentum
'buy'] = 1
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
return dataframe return dataframe
@@ -127,27 +132,20 @@ def plot_dataframe(dataframe: DataFrame, pair: str) -> None:
matplotlib.use("Qt5Agg") matplotlib.use("Qt5Agg")
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# Three subplots sharing x axe # Two subplots sharing x axis
fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) fig, (ax1, ax2) = plt.subplots(2, sharex=True)
fig.suptitle(pair, fontsize=14, fontweight='bold') fig.suptitle(pair, fontsize=14, fontweight='bold')
ax1.plot(dataframe.index.values, dataframe['sar'], 'g_', label='pSAR')
ax1.plot(dataframe.index.values, dataframe['close'], label='close') ax1.plot(dataframe.index.values, dataframe['close'], label='close')
ax1.plot(dataframe.index.values, dataframe['close_30_ema'], label='EMA(30)')
ax1.plot(dataframe.index.values, dataframe['close_90_ema'], label='EMA(90)')
# ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell') # ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy') ax1.plot(dataframe.index.values, dataframe['ema'], '--', label='EMA(20)')
ax1.plot(dataframe.index.values, dataframe['buy'], 'bo', label='buy')
ax1.legend() ax1.legend()
ax2.plot(dataframe.index.values, dataframe['macd'], label='MACD') ax2.plot(dataframe.index.values, dataframe['adx'], label='ADX')
ax2.plot(dataframe.index.values, dataframe['macds'], label='MACDS') ax2.plot(dataframe.index.values, [25] * len(dataframe.index.values))
ax2.plot(dataframe.index.values, dataframe['macdh'], label='MACD Histogram')
ax2.plot(dataframe.index.values, [0] * len(dataframe.index.values))
ax2.legend() ax2.legend()
ax3.plot(dataframe.index.values, dataframe['stochrsi'], label='StochRSI')
ax3.plot(dataframe.index.values, [80] * len(dataframe.index.values))
ax3.plot(dataframe.index.values, [20] * len(dataframe.index.values))
ax3.legend()
# Fine-tune figure; make subplots close to each other and hide x ticks for # Fine-tune figure; make subplots close to each other and hide x ticks for
# all but bottom plot. # all but bottom plot.
fig.subplots_adjust(hspace=0) fig.subplots_adjust(hspace=0)
@@ -158,8 +156,8 @@ def plot_dataframe(dataframe: DataFrame, pair: str) -> None:
if __name__ == '__main__': if __name__ == '__main__':
# Install PYQT5==5.9 manually if you want to test this helper function # Install PYQT5==5.9 manually if you want to test this helper function
while True: while True:
pair = 'BTC_ANT' test_pair = 'BTC_ANT'
#for pair in ['BTC_ANT', 'BTC_ETH', 'BTC_GNT', 'BTC_ETC']: #for pair in ['BTC_ANT', 'BTC_ETH', 'BTC_GNT', 'BTC_ETC']:
# get_buy_signal(pair) # get_buy_signal(pair)
plot_dataframe(analyze_ticker(pair), pair) plot_dataframe(analyze_ticker(test_pair), test_pair)
time.sleep(60) time.sleep(60)
+2 -5
View File
@@ -9,11 +9,8 @@
"0": 0.02 "0": 0.02
}, },
"stoploss": -0.10, "stoploss": -0.10,
"poloniex": { "bid_strategy": {
"enabled": false, "ask_last_balance": 0.0
"key": "key",
"secret": "secret",
"pair_whitelist": []
}, },
"bittrex": { "bittrex": {
"enabled": true, "enabled": true,
+7 -38
View File
@@ -3,7 +3,6 @@ import logging
from typing import List from typing import List
from bittrex.bittrex import Bittrex from bittrex.bittrex import Bittrex
from poloniex import Poloniex
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -14,7 +13,6 @@ _CONF = {}
class Exchange(enum.Enum): class Exchange(enum.Enum):
POLONIEX = 0
BITTREX = 1 BITTREX = 1
@@ -33,13 +31,8 @@ def init(config: dict) -> None:
if config['dry_run']: if config['dry_run']:
logger.info('Instance is running with dry_run enabled') logger.info('Instance is running with dry_run enabled')
use_poloniex = config.get('poloniex', {}).get('enabled', False)
use_bittrex = config.get('bittrex', {}).get('enabled', False) use_bittrex = config.get('bittrex', {}).get('enabled', False)
if use_bittrex:
if use_poloniex:
EXCHANGE = Exchange.POLONIEX
_API = Poloniex(key=config['poloniex']['key'], secret=config['poloniex']['secret'])
elif use_bittrex:
EXCHANGE = Exchange.BITTREX EXCHANGE = Exchange.BITTREX
_API = Bittrex(api_key=config['bittrex']['key'], api_secret=config['bittrex']['secret']) _API = Bittrex(api_key=config['bittrex']['key'], api_secret=config['bittrex']['secret'])
else: else:
@@ -47,9 +40,10 @@ def init(config: dict) -> None:
# Check if all pairs are available # Check if all pairs are available
markets = get_markets() markets = get_markets()
for pair in config[EXCHANGE.name.lower()]['pair_whitelist']: exchange_name = EXCHANGE.name.lower()
for pair in config[exchange_name]['pair_whitelist']:
if pair not in markets: if pair not in markets:
raise RuntimeError('Pair {} is not available at Poloniex'.format(pair)) raise RuntimeError('Pair {} is not available at {}'.format(pair, exchange_name))
def buy(pair: str, rate: float, amount: float) -> str: def buy(pair: str, rate: float, amount: float) -> str:
@@ -62,9 +56,6 @@ def buy(pair: str, rate: float, amount: float) -> str:
""" """
if _CONF['dry_run']: if _CONF['dry_run']:
return 'dry_run' return 'dry_run'
elif EXCHANGE == Exchange.POLONIEX:
_API.buy(pair, rate, amount)
# TODO: return order id
elif EXCHANGE == Exchange.BITTREX: elif EXCHANGE == Exchange.BITTREX:
data = _API.buy_limit(pair.replace('_', '-'), amount, rate) data = _API.buy_limit(pair.replace('_', '-'), amount, rate)
if not data['success']: if not data['success']:
@@ -82,9 +73,6 @@ def sell(pair: str, rate: float, amount: float) -> str:
""" """
if _CONF['dry_run']: if _CONF['dry_run']:
return 'dry_run' return 'dry_run'
elif EXCHANGE == Exchange.POLONIEX:
_API.sell(pair, rate, amount)
# TODO: return order id
elif EXCHANGE == Exchange.BITTREX: elif EXCHANGE == Exchange.BITTREX:
data = _API.sell_limit(pair.replace('_', '-'), amount, rate) data = _API.sell_limit(pair.replace('_', '-'), amount, rate)
if not data['success']: if not data['success']:
@@ -100,9 +88,6 @@ def get_balance(currency: str) -> float:
""" """
if _CONF['dry_run']: if _CONF['dry_run']:
return 999.9 return 999.9
elif EXCHANGE == Exchange.POLONIEX:
data = _API.returnBalances()
return float(data[currency])
elif EXCHANGE == Exchange.BITTREX: elif EXCHANGE == Exchange.BITTREX:
data = _API.get_balance(currency) data = _API.get_balance(currency)
if not data['success']: if not data['success']:
@@ -116,14 +101,7 @@ def get_ticker(pair: str) -> dict:
:param pair: Pair as str, format: BTC_ETC :param pair: Pair as str, format: BTC_ETC
:return: dict :return: dict
""" """
if EXCHANGE == Exchange.POLONIEX: if EXCHANGE == Exchange.BITTREX:
data = _API.returnTicker()
return {
'bid': float(data[pair]['highestBid']),
'ask': float(data[pair]['lowestAsk']),
'last': float(data[pair]['last'])
}
elif EXCHANGE == Exchange.BITTREX:
data = _API.get_ticker(pair.replace('_', '-')) data = _API.get_ticker(pair.replace('_', '-'))
if not data['success']: if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message'])) raise RuntimeError('BITTREX: {}'.format(data['message']))
@@ -142,8 +120,6 @@ def cancel_order(order_id: str) -> None:
""" """
if _CONF['dry_run']: if _CONF['dry_run']:
pass pass
elif EXCHANGE == Exchange.POLONIEX:
raise NotImplemented('Not implemented')
elif EXCHANGE == Exchange.BITTREX: elif EXCHANGE == Exchange.BITTREX:
data = _API.cancel(order_id) data = _API.cancel(order_id)
if not data['success']: if not data['success']:
@@ -158,8 +134,6 @@ def get_open_orders(pair: str) -> List[dict]:
""" """
if _CONF['dry_run']: if _CONF['dry_run']:
return [] return []
elif EXCHANGE == Exchange.POLONIEX:
raise NotImplemented('Not implemented')
elif EXCHANGE == Exchange.BITTREX: elif EXCHANGE == Exchange.BITTREX:
data = _API.get_open_orders(pair.replace('_', '-')) data = _API.get_open_orders(pair.replace('_', '-'))
if not data['success']: if not data['success']:
@@ -180,9 +154,7 @@ def get_pair_detail_url(pair: str) -> str:
:param pair: pair as str, format: BTC_ANT :param pair: pair as str, format: BTC_ANT
:return: url as str :return: url as str
""" """
if EXCHANGE == Exchange.POLONIEX: if EXCHANGE == Exchange.BITTREX:
raise NotImplemented('Not implemented')
elif EXCHANGE == Exchange.BITTREX:
return 'https://bittrex.com/Market/Index?MarketName={}'.format(pair.replace('_', '-')) return 'https://bittrex.com/Market/Index?MarketName={}'.format(pair.replace('_', '-'))
@@ -191,10 +163,7 @@ def get_markets() -> List[str]:
Returns all available markets Returns all available markets
:return: list of all available pairs :return: list of all available pairs
""" """
if EXCHANGE == Exchange.POLONIEX: if EXCHANGE == Exchange. BITTREX:
# TODO: implement
raise NotImplemented('Not implemented')
elif EXCHANGE == Exchange. BITTREX:
data = _API.get_markets() data = _API.get_markets()
if not data['success']: if not data['success']:
raise RuntimeError('BITTREX: {}'.format(data['message'])) raise RuntimeError('BITTREX: {}'.format(data['message']))
+38 -19
View File
@@ -4,7 +4,7 @@ import logging
import time import time
import traceback import traceback
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Dict, Optional
from jsonschema import validate from jsonschema import validate
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
__author__ = "gcarq" __author__ = "gcarq"
__copyright__ = "gcarq 2017" __copyright__ = "gcarq 2017"
__license__ = "GPLv3" __license__ = "GPLv3"
__version__ = "0.9.0" __version__ = "0.10.0"
_CONF = {} _CONF = {}
@@ -94,8 +94,10 @@ def execute_sell(trade: Trade, current_rate: float) -> None:
# Get available balance # Get available balance
currency = trade.pair.split('_')[1] currency = trade.pair.split('_')[1]
balance = exchange.get_balance(currency) balance = exchange.get_balance(currency)
whitelist = _CONF[trade.exchange.name.lower()]['pair_whitelist']
profit = trade.exec_sell_order(current_rate, balance) profit = trade.exec_sell_order(current_rate, balance)
whitelist.append(trade.pair)
message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format( message = '*{}:* Selling [{}]({}) at rate `{:f} (profit: {}%)`'.format(
trade.exchange.name, trade.exchange.name,
trade.pair.replace('_', '/'), trade.pair.replace('_', '/'),
@@ -107,6 +109,28 @@ def execute_sell(trade: Trade, current_rate: float) -> None:
telegram.send_msg(message) telegram.send_msg(message)
def should_sell(trade: Trade, current_rate: float, current_time: datetime) -> bool:
"""
Based an earlier trade and current price and configuration, decides whether bot should sell
:return True if bot should sell at current rate
"""
current_profit = (current_rate - trade.open_rate) / trade.open_rate
if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']):
logger.debug('Stop loss hit.')
return True
for duration, threshold in sorted(_CONF['minimal_roi'].items()):
duration, threshold = float(duration), float(threshold)
# Check if time matches and current rate is above threshold
time_diff = (current_time - trade.open_date).total_seconds() / 60
if time_diff > duration and current_profit > threshold:
return True
logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', current_profit * 100.0)
return False
def handle_trade(trade: Trade) -> None: def handle_trade(trade: Trade) -> None:
""" """
Sells the current pair if the threshold is reached and updates the trade record. Sells the current pair if the threshold is reached and updates the trade record.
@@ -117,27 +141,22 @@ def handle_trade(trade: Trade) -> None:
raise ValueError('attempt to handle closed trade: {}'.format(trade)) raise ValueError('attempt to handle closed trade: {}'.format(trade))
logger.debug('Handling open trade %s ...', trade) logger.debug('Handling open trade %s ...', trade)
# Get current rate
current_rate = exchange.get_ticker(trade.pair)['bid'] current_rate = exchange.get_ticker(trade.pair)['bid']
current_profit = 100.0 * ((current_rate - trade.open_rate) / trade.open_rate) if should_sell(trade, current_rate, datetime.utcnow()):
if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']) * 100.0:
logger.debug('Stop loss hit.')
execute_sell(trade, current_rate) execute_sell(trade, current_rate)
return return
for duration, threshold in sorted(_CONF['minimal_roi'].items()):
duration, threshold = float(duration), float(threshold)
# Check if time matches and current rate is above threshold
time_diff = (datetime.utcnow() - trade.open_date).total_seconds() / 60
if time_diff > duration and current_rate > (1 + threshold) * trade.open_rate:
execute_sell(trade, current_rate)
return
logger.debug('Threshold not reached. (cur_profit: %1.2f%%)', current_profit)
except ValueError: except ValueError:
logger.exception('Unable to handle open order') logger.exception('Unable to handle open order')
def get_target_bid(ticker: Dict[str, float]) -> float:
""" Calculates bid target between current ask price and last price """
if ticker['ask'] < ticker['last']:
return ticker['ask']
balance = _CONF['bid_strategy']['ask_last_balance']
return ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
def create_trade(stake_amount: float, _exchange: exchange.Exchange) -> Optional[Trade]: def create_trade(stake_amount: float, _exchange: exchange.Exchange) -> Optional[Trade]:
""" """
@@ -148,7 +167,7 @@ def create_trade(stake_amount: float, _exchange: exchange.Exchange) -> Optional[
""" """
logger.info('Creating new trade with stake_amount: %f ...', stake_amount) logger.info('Creating new trade with stake_amount: %f ...', stake_amount)
whitelist = _CONF[_exchange.name.lower()]['pair_whitelist'] whitelist = _CONF[_exchange.name.lower()]['pair_whitelist']
# Check if btc_amount is fulfilled # Check if stake_amount is fulfilled
if exchange.get_balance(_CONF['stake_currency']) < stake_amount: if exchange.get_balance(_CONF['stake_currency']) < stake_amount:
raise ValueError( raise ValueError(
'stake amount is not fulfilled (currency={}'.format(_CONF['stake_currency']) 'stake amount is not fulfilled (currency={}'.format(_CONF['stake_currency'])
@@ -174,7 +193,7 @@ def create_trade(stake_amount: float, _exchange: exchange.Exchange) -> Optional[
else: else:
return None return None
open_rate = exchange.get_ticker(pair)['ask'] open_rate = get_target_bid(exchange.get_ticker(pair))
amount = stake_amount / open_rate amount = stake_amount / open_rate
order_id = exchange.buy(pair, open_rate, amount) order_id = exchange.buy(pair, open_rate, amount)
@@ -188,7 +207,7 @@ def create_trade(stake_amount: float, _exchange: exchange.Exchange) -> Optional[
logger.info(message) logger.info(message)
telegram.send_msg(message) telegram.send_msg(message)
return Trade(pair=pair, return Trade(pair=pair,
btc_amount=stake_amount, stake_amount=stake_amount,
open_rate=open_rate, open_rate=open_rate,
open_date=datetime.utcnow(), open_date=datetime.utcnow(),
amount=amount, amount=amount,
+13 -2
View File
@@ -48,7 +48,18 @@ CONF_SCHEMA = {
'minProperties': 1 'minProperties': 1
}, },
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
'poloniex': {'$ref': '#/definitions/exchange'}, 'bid_strategy': {
'type': 'object',
'properties': {
'ask_last_balance': {
'type': 'number',
'minimum': 0,
'maximum': 1,
'exclusiveMaximum': False
},
},
'required': ['ask_last_balance']
},
'bittrex': {'$ref': '#/definitions/exchange'}, 'bittrex': {'$ref': '#/definitions/exchange'},
'telegram': { 'telegram': {
'type': 'object', 'type': 'object',
@@ -78,7 +89,6 @@ CONF_SCHEMA = {
} }
}, },
'anyOf': [ 'anyOf': [
{'required': ['poloniex']},
{'required': ['bittrex']} {'required': ['bittrex']}
], ],
'required': [ 'required': [
@@ -87,6 +97,7 @@ CONF_SCHEMA = {
'stake_amount', 'stake_amount',
'dry_run', 'dry_run',
'minimal_roi', 'minimal_roi',
'bid_strategy',
'telegram' 'telegram'
] ]
} }
+1 -1
View File
@@ -49,7 +49,7 @@ class Trade(Base):
open_rate = Column(Float, nullable=False) open_rate = Column(Float, nullable=False)
close_rate = Column(Float) close_rate = Column(Float)
close_profit = Column(Float) close_profit = Column(Float)
btc_amount = Column(Float, nullable=False) stake_amount = Column(Float, name='btc_amount', nullable=False)
amount = Column(Float, nullable=False) amount = Column(Float, nullable=False)
open_date = Column(DateTime, nullable=False, default=datetime.utcnow) open_date = Column(DateTime, nullable=False, default=datetime.utcnow)
close_date = Column(DateTime) close_date = Column(DateTime)
-1
View File
@@ -1,4 +1,3 @@
-e git+https://github.com/s4w3d0ff/python-poloniex.git#egg=Poloniex
-e git+https://github.com/ericsomdahl/python-bittrex.git#egg=python-bittrex -e git+https://github.com/ericsomdahl/python-bittrex.git#egg=python-bittrex
SQLAlchemy==1.1.13 SQLAlchemy==1.1.13
python-telegram-bot==7.0.1 python-telegram-bot==7.0.1
+1 -1
View File
@@ -157,7 +157,7 @@ def _profit(bot: Bot, update: Update) -> None:
current_rate = exchange.get_ticker(trade.pair)['bid'] current_rate = exchange.get_ticker(trade.pair)['bid']
profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate) profit = 100 * ((current_rate - trade.open_rate) / trade.open_rate)
profit_amounts.append((profit / 100) * trade.btc_amount) profit_amounts.append((profit / 100) * trade.stake_amount)
profits.append(profit) profits.append(profit)
best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \ best_pair = Trade.session.query(Trade.pair, func.sum(Trade.close_profit).label('profit_sum')) \
+71
View File
@@ -0,0 +1,71 @@
# pragma pylint: disable=missing-docstring
import unittest
from unittest.mock import patch
import os
import json
import logging
import arrow
from pandas import DataFrame
from analyze import analyze_ticker
from persistence import Trade
from main import should_sell
def print_results(results):
print('Made {} buys. Average profit {:.1f}%. Total profit was {:.3f}. Average duration {:.1f} mins.'.format(
len(results.index),
results.profit.mean() * 100.0,
results.profit.sum(),
results.duration.mean()*5
))
class TestMain(unittest.TestCase):
pairs = ['btc-neo', 'btc-eth', 'btc-omg', 'btc-edg', 'btc-pay', 'btc-pivx', 'btc-qtum', 'btc-mtl', 'btc-etc', 'btc-ltc']
conf = {
"minimal_roi": {
"2880": 0.005,
"720": 0.01,
"0": 0.02
},
"stoploss": -0.10
}
@classmethod
def setUpClass(cls):
logging.disable(logging.DEBUG) # disable debug logs that slow backtesting a lot
@unittest.skipIf(not os.environ.get('BACKTEST', False), "slow, should be run manually")
def test_backtest(self):
trades = []
with patch.dict('main._CONF', self.conf):
for pair in self.pairs:
with open('test/testdata/'+pair+'.json') as data_file:
data = json.load(data_file)
with patch('analyze.get_ticker', return_value=data):
with patch('arrow.utcnow', return_value=arrow.get('2017-08-20T14:50:00')):
ticker = analyze_ticker(pair)
# for each buy point
for index, row in ticker[ticker.buy == 1].iterrows():
trade = Trade(
open_rate=row['close'],
open_date=arrow.get(row['date']).datetime,
amount=1,
)
# calculate win/lose forwards from buy point
for index2, row2 in ticker[index:].iterrows():
if should_sell(trade, row2['close'], arrow.get(row2['date']).datetime):
current_profit = (row2['close'] - trade.open_rate) / trade.open_rate
trades.append((pair, current_profit, index2 - index))
break
labels = ['currency', 'profit', 'duration']
results = DataFrame.from_records(trades, columns=labels)
print('====================== BACKTESTING REPORT ================================')
for pair in self.pairs:
print('For currency {}:'.format(pair))
print_results(results[results.currency == pair])
print('TOTAL OVER ALL TRADES:')
print_results(results)
+16 -7
View File
@@ -4,7 +4,7 @@ from unittest.mock import patch, MagicMock
from jsonschema import validate from jsonschema import validate
import exchange import exchange
from main import create_trade, handle_trade, close_trade_if_fulfilled, init from main import create_trade, handle_trade, close_trade_if_fulfilled, init, get_target_bid
from misc import CONF_SCHEMA from misc import CONF_SCHEMA
from persistence import Trade from persistence import Trade
@@ -20,11 +20,8 @@ class TestMain(unittest.TestCase):
"720": 0.01, "720": 0.01,
"0": 0.02 "0": 0.02
}, },
"poloniex": { "bid_strategy": {
"enabled": False, "ask_last_balance": 0.0
"key": "key",
"secret": "secret",
"pair_whitelist": []
}, },
"bittrex": { "bittrex": {
"enabled": True, "enabled": True,
@@ -61,7 +58,7 @@ class TestMain(unittest.TestCase):
self.assertEqual(trade.pair, 'BTC_ETH') self.assertEqual(trade.pair, 'BTC_ETH')
self.assertEqual(trade.exchange, exchange.Exchange.BITTREX) self.assertEqual(trade.exchange, exchange.Exchange.BITTREX)
self.assertEqual(trade.amount, 206.43811673387373) self.assertEqual(trade.amount, 206.43811673387373)
self.assertEqual(trade.btc_amount, 15.0) self.assertEqual(trade.stake_amount, 15.0)
self.assertEqual(trade.is_open, True) self.assertEqual(trade.is_open, True)
self.assertIsNotNone(trade.open_date) self.assertIsNotNone(trade.open_date)
buy_signal.assert_called_once_with('BTC_ETH') buy_signal.assert_called_once_with('BTC_ETH')
@@ -96,6 +93,18 @@ class TestMain(unittest.TestCase):
self.assertTrue(closed) self.assertTrue(closed)
self.assertEqual(trade.is_open, False) self.assertEqual(trade.is_open, False)
def test_balance_fully_ask_side(self):
with patch.dict('main._CONF', {'bid_strategy': {'ask_last_balance': 0.0}}):
self.assertEqual(get_target_bid({'ask': 20, 'last': 10}), 20)
def test_balance_fully_last_side(self):
with patch.dict('main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}):
self.assertEqual(get_target_bid({'ask': 20, 'last': 10}), 10)
def test_balance_when_last_bigger_than_ask(self):
with patch.dict('main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}):
self.assertEqual(get_target_bid({'ask': 5, 'last': 10}), 5)
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
validate(cls.conf, CONF_SCHEMA) validate(cls.conf, CONF_SCHEMA)
+1 -1
View File
@@ -10,7 +10,7 @@ class TestTrade(unittest.TestCase):
with patch('main.exchange.sell', side_effect='mocked_order_id') as api_mock: with patch('main.exchange.sell', side_effect='mocked_order_id') as api_mock:
trade = Trade( trade = Trade(
pair='BTC_ETH', pair='BTC_ETH',
btc_amount=1.00, stake_amount=1.00,
open_rate=0.50, open_rate=0.50,
amount=10.00, amount=10.00,
exchange=Exchange.BITTREX, exchange=Exchange.BITTREX,
+2 -5
View File
@@ -28,11 +28,8 @@ class TestTelegram(unittest.TestCase):
"720": 0.01, "720": 0.01,
"0": 0.02 "0": 0.02
}, },
"poloniex": { "bid_strategy": {
"enabled": False, "ask_last_balance": 0.0
"key": "key",
"secret": "secret",
"pair_whitelist": []
}, },
"bittrex": { "bittrex": {
"enabled": True, "enabled": True,
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long