Compare commits

...

88 Commits

Author SHA1 Message Date
gcarq
e2eceaa904 Merge branch 'release/0.13.0' 2017-11-01 01:15:31 +01:00
gcarq
f34af0ad67 version bump 2017-11-01 01:15:06 +01:00
gcarq
e07904d436 PEP8 linting 2017-10-31 00:36:35 +01:00
gcarq
26468bef83 balance: filter currencies with 0.0 balances 2017-10-31 00:29:22 +01:00
Michael Egger
ea1b1e11ea Merge pull request #88 from gcarq/reduce_memory_use
Reduce memory use in backtesting
2017-10-31 00:28:38 +01:00
Janne Sinivirta
e68e6c0a1a reuse mock in hyperopt also 2017-10-30 22:31:28 +02:00
Janne Sinivirta
7190226c84 reuse same mock for get_ticker_history, just change return_value 2017-10-30 22:06:09 +02:00
gcarq
6f2915e25e move qtpylib to vendor folder
This is necessary to distribute qtpylib with
freqtrade when you install it globally.
2017-10-30 20:41:36 +01:00
gcarq
6f7ac0720b add qtpylib to manifest 2017-10-30 20:24:58 +01:00
gcarq
b76554a487 add __init__ file for qtpylib 2017-10-30 20:23:19 +01:00
Janne Sinivirta
8da55c3742 move patching of arrow.utcnow to remove 500 unnecessary mock objects 2017-10-30 19:56:53 +02:00
Michael Egger
05111edd04 Merge pull request #87 from gcarq/more_triggers
More triggers and guards to hyperopt
2017-10-30 14:43:18 +01:00
Michael Egger
4c2dea83c5 Merge pull request #84 from gcarq/telegram/show-balance
Telegram command: /show balance
2017-10-29 22:05:10 +01:00
Janne Sinivirta
d066817d0b removed below_sma and over_sma to wait for better implementation 2017-10-29 21:33:57 +02:00
Janne Sinivirta
a632121368 add macd cross signal trigger to hyperopt 2017-10-29 21:33:57 +02:00
Janne Sinivirta
473d09b5cd add ema50 and ema100. add long uptrend ema guard to hyperopt 2017-10-29 21:33:57 +02:00
Janne Sinivirta
893738d6f0 add MACD to analyze 2017-10-29 21:33:57 +02:00
Janne Sinivirta
22cfef7d36 add ema5 cross ema10 trigger to hyperopt 2017-10-29 21:33:57 +02:00
Janne Sinivirta
e1bbe1d9a9 adjust indicator ranges in hyperopt 2017-10-29 21:33:57 +02:00
Janne Sinivirta
ec981b415a add RSI to hyperopt 2017-10-29 21:33:57 +02:00
Janne Sinivirta
57a17697a0 add RSI, MOM, EMA5 and EMA10 to analyze 2017-10-29 21:33:57 +02:00
Samuel Husso
f4fe09ffbf added get_balances as a abstract method to the interface baseclass 2017-10-29 17:57:57 +02:00
Michael Egger
871b5e17ee Merge pull request #85 from gcarq/datetime_fixes
Performance improvements for backtesting
2017-10-29 15:56:20 +01:00
Janne Sinivirta
9b00fc3474 use .ix instead of .loc for small perf boost 2017-10-29 16:28:55 +02:00
Janne Sinivirta
3b1dc36d8a switch to using itertuples instead of iterrows as it's a lot faster 2017-10-29 16:28:55 +02:00
Janne Sinivirta
4edf8f2079 copy only needed columns before iterating over them 2017-10-29 16:28:55 +02:00
Janne Sinivirta
54987fd9ca do date parsing while loading json, not later 2017-10-29 16:28:55 +02:00
Janne Sinivirta
da9c3e7d7d remove leftover dates from removing date filtering 2017-10-29 16:28:55 +02:00
Michael Egger
a948142ef5 Merge pull request #83 from gcarq/better-hyperopt-objective
Better hyperopt objective
2017-10-29 14:13:44 +01:00
Samuel Husso
4f6c3f94e0 added tests to /balance, minor cleanup 2017-10-29 10:10:00 +02:00
Janne Sinivirta
25d6d6bbe5 remove unused imports from test_hyperopt 2017-10-28 15:32:29 +03:00
Janne Sinivirta
649781d823 store result strings, display best result in summary. switch to a lot better objective algo 2017-10-28 15:26:22 +03:00
Janne Sinivirta
08ca7a8166 change print to format so result can be used in hyperopt Trials 2017-10-28 15:26:22 +03:00
Samuel Husso
dd78c62c3d added new command to return balance across all currencies 2017-10-28 08:59:43 +03:00
Samuel Husso
29de1645fe Merge pull request #82 from gcarq/feature/handle-process-signals
handle SIGINT, SIGTERM and SIGABRT process signals
2017-10-28 08:49:42 +03:00
gcarq
4139b0b0c7 add signal handler for SIGINT, SIGTERM and SIGABRT 2017-10-27 15:52:14 +02:00
Samuel Husso
0c33e917d5 Merge pull request #79 from gcarq/qtpylib
Include new indicators from qtpylib
2017-10-27 12:11:04 +03:00
Janne Sinivirta
e401a016f5 change analyze tests to use full json dump from bittrex 2017-10-26 16:50:31 +03:00
Janne Sinivirta
e0fde8665c Merge pull request #80 from gcarq/fix-testdate-dl-path
download testdata to correct folder when running from project root
2017-10-26 10:37:38 +03:00
Samuel Husso
752520c823 When running from project root download the files to the testdata folder instead of cwd 2017-10-26 10:24:22 +03:00
Janne Sinivirta
6ba2492360 add Awesome Oscillator and try it in hyperopt 2017-10-25 18:37:20 +03:00
Janne Sinivirta
d5d798f6fa pull in new indicators from QTPYLib 2017-10-25 18:37:20 +03:00
Janne Sinivirta
9c9cf76a0d Merge pull request #78 from gcarq/refactor-backtest
Refactor backtest functionality
2017-10-25 18:19:44 +03:00
Samuel Husso
041e201713 remove duplicated backtesting from hyperopt 2017-10-25 08:17:17 +03:00
gcarq
e09505b22d Merge tag '0.12.0' into develop
0.12.0
2017-10-24 18:14:41 +02:00
gcarq
6b15cb9b10 Merge branch 'release/0.12.0' 2017-10-24 18:14:37 +02:00
gcarq
ff4fcdc760 version bump 2017-10-24 18:14:31 +02:00
Samuel Husso
f43ba44b15 refactor backtesting to its own method as we use it also in hyperopt 2017-10-24 07:58:42 +03:00
Michael Egger
79c3e0583d Merge pull request #76 from gcarq/hyperopt
Use hyperopt to find optimal parameters for buy strategy
2017-10-23 09:40:13 +02:00
Janne Sinivirta
f6ef8383bb remove filtering from analyze that is super slow and not really needed 2017-10-22 21:50:07 +03:00
Janne Sinivirta
6f5307fda7 use more aggressive stop loss for hyperopt 2017-10-22 17:15:57 +03:00
Janne Sinivirta
37004e331a remove unused import and commented out code 2017-10-22 17:14:55 +03:00
Janne Sinivirta
57acf85b42 try a different objective function 2017-10-22 17:11:01 +03:00
Michael Egger
96790d50e5 Merge pull request #77 from gcarq/help-command
Help command to Telegram bot
2017-10-21 13:51:08 +02:00
Janne Sinivirta
d32ff3410c add help command to telegram bot 2017-10-21 11:08:08 +03:00
Janne Sinivirta
35838f5e64 upgrade to latest telegram lib 2017-10-21 11:07:29 +03:00
Janne Sinivirta
913488910c bump minimum evaluations to 40 rounds 2017-10-21 10:29:05 +03:00
Janne Sinivirta
17b984a7cd adjust objective function to emphasize trade lenghts more 2017-10-21 10:28:43 +03:00
Janne Sinivirta
f79b44eefe adjust ROI map for shorter trades 2017-10-21 10:28:02 +03:00
Janne Sinivirta
146c254c0f start adding other triggers than just the lower BBands 2017-10-21 10:26:38 +03:00
Janne Sinivirta
ce2966dd7f add uptrend_sma to hyperopt 2017-10-20 18:29:38 +03:00
Janne Sinivirta
0fbca8b8ef add CCI to hyperopt 2017-10-20 13:14:28 +03:00
Janne Sinivirta
3f7a583de6 add SAR to hyperopt. add over/under sma options to hyperopt 2017-10-20 12:56:44 +03:00
Janne Sinivirta
1196983d5f change objective to emphasize shorter trades and include average profit 2017-10-20 10:39:36 +03:00
Janne Sinivirta
bbb2c7cf62 more parametrizing. adjust ranges for previous parameters 2017-10-20 10:39:04 +03:00
Janne Sinivirta
ff100bdac4 the optimizer expects values in the order of smaller is better 2017-10-19 18:29:57 +03:00
Janne Sinivirta
4feb038d0a add hyperopt dependencies 2017-10-19 17:46:41 +03:00
Janne Sinivirta
1792e0fb9b use hyperopt to find optimal parameter values for indicators 2017-10-19 17:12:49 +03:00
Janne Sinivirta
d4f8b3ebbc remove setup.cfg as it's not used but it messes with running a single test 2017-10-19 17:12:08 +03:00
Michael Egger
aeef9bac33 Merge pull request #70 from dertione/patch-2
Download automatically altcoin datas
2017-10-17 13:36:33 +02:00
Michael Egger
eff361a104 Merge pull request #73 from gcarq/small_tweaks_to_strategy
Small tweaks to strategy
2017-10-15 18:08:18 +02:00
dertione
389f9b45bb update pylint 10/10 2017-10-15 17:24:49 +02:00
Janne Sinivirta
c9741cb291 adjust roi settings for faster trades 2017-10-15 17:32:07 +03:00
Janne Sinivirta
bf6f563df2 small tweaks to buy strategy and it's visualization 2017-10-15 17:32:07 +03:00
Michael Egger
58f34d4f4b Merge pull request #71 from steerio/develop
More efficient and flexible Docker builds
2017-10-15 15:46:39 +02:00
Janne Sinivirta
2c4d0144ba Add note about binding sqlite with dry_run enabled 2017-10-15 14:40:02 +03:00
dertione
afd1a0bf9b update for pylint 2017-10-14 14:40:26 +02:00
dertione
37f6c213f6 fork test 2017-10-13 15:50:50 +02:00
Roland Venesz
76736902c6 Merge branch 'master' into develop 2017-10-13 15:48:25 +02:00
Roland Venesz
d266171ed8 Docker improvements (faster and more secure builds) 2017-10-13 15:47:13 +02:00
Michael Egger
e7df373544 Merge pull request #67 from gcarq/upgrade-deps
Upgrade dependencies
2017-10-12 09:49:45 +02:00
Michael Egger
aa4b64d0bb Merge pull request #65 from xsmile/patch-4
set exchange in analyze.__main__ to fix plotting
2017-10-12 09:42:20 +02:00
Michael Egger
4559ddd74f Merge pull request #64 from xsmile/patch-1
Bittrex provider
2017-10-12 09:37:15 +02:00
xsmile
eecc45f8ba set exchange in analyze.__main__ to fix plotting
requires #64
2017-10-11 20:04:31 +02:00
xsmile
d76476040a Bittrex provider
remove redundant 'name' property and pair validation call
2017-10-11 19:51:37 +02:00
Janne Sinivirta
0c8c149b86 Fix the command for running backtesting in README.md 2017-10-11 13:09:57 +03:00
Janne Sinivirta
60a7f8614c upgrade dependencies 2017-10-10 19:04:05 +03:00
gcarq
c31b67bf7a Merge tag '0.11.0' into develop
0.11.0
2017-10-10 17:55:10 +02:00
26 changed files with 1102 additions and 123 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
.gitignore
Dockerfile
.dockerignore
config.json*
*.sqlite

View File

@@ -1,20 +1,23 @@
FROM python:3.6.2 FROM python:3.6.2
RUN apt-get update
RUN apt-get -y install build-essential
# Install TA-lib # Install TA-lib
RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz RUN apt-get update && apt-get -y install build-essential && apt-get clean
RUN tar zxvf ta-lib-0.4.0-src.tar.gz RUN curl -L http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz | \
RUN cd ta-lib && ./configure && make && make install tar xzvf - && \
cd ta-lib && \
./configure && make && make install && \
cd .. && rm -rf ta-lib
ENV LD_LIBRARY_PATH /usr/local/lib ENV LD_LIBRARY_PATH /usr/local/lib
# Prepare environment # Prepare environment
RUN mkdir /freqtrade RUN mkdir /freqtrade
COPY . /freqtrade/
WORKDIR /freqtrade WORKDIR /freqtrade
# Install dependencies and execute # Install dependencies
COPY requirements.txt /freqtrade/
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
# Install and execute
COPY . /freqtrade/
RUN pip install -e . RUN pip install -e .
CMD ["freqtrade"] CMD ["freqtrade"]

View File

@@ -1,7 +1,5 @@
include LICENSE include LICENSE
include README.md include README.md
include config.json.example include config.json.example
include freqtrade/exchange/*.py recursive-include freqtrade *.py
include freqtrade/rpc/*.py
include freqtrade/tests/*.py
include freqtrade/tests/testdata/*.json include freqtrade/tests/testdata/*.json

View File

@@ -30,11 +30,10 @@ in minutes and the value is the minimum ROI in percent.
See the example below: See the example below:
``` ```
"minimal_roi": { "minimal_roi": {
"2880": 0.005, # Sell after 48 hours if there is at least 0.5% profit "50": 0.0, # Sell after 30 minutes if the profit is not negative
"1440": 0.01, # Sell after 24 hours if there is at least 1% profit "40": 0.01, # Sell after 25 minutes if there is at least 1% profit
"720": 0.02, # Sell after 12 hours if there is at least 2% profit "30": 0.02, # Sell after 15 minutes if there is at least 2% profit
"360": 0.02, # Sell after 6 hours if there is at least 2% profit "0": 0.045 # Sell immediately if there is at least 4.5% profit
"0": 0.025 # Sell immediately if there is at least 2.5% profit
}, },
``` ```
@@ -47,7 +46,9 @@ 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 `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. 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.
@@ -84,16 +85,57 @@ $ pytest
This will by default skip the slow running backtest set. To run backtest set: This will by default skip the slow running backtest set. To run backtest set:
``` ```
$ BACKTEST=true pytest $ BACKTEST=true pytest -s freqtrade/tests/test_backtesting.py
``` ```
#### Docker #### Docker
Building the image:
``` ```
$ cd freqtrade $ cd freqtrade
$ docker build -t freqtrade . $ docker build -t freqtrade .
$ docker run --rm -it freqtrade
``` ```
For security reasons, your configuration file will not be included in the
image, you will need to bind mount it. It is also advised to bind mount
a SQLite database file (see second example) to keep it between updates.
You can run a one-off container that is immediately deleted upon exiting with
the following command (config.json must be in the current working directory):
```
$ docker run --rm -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
To run a restartable instance in the background (feel free to place your
configuration and database files wherever it feels comfortable on your
filesystem):
```
$ cd ~/.freq
$ touch tradesv2.sqlite
$ docker run -d \
--name freqtrade \
-v ~/.freq/config.json:/freqtrade/config.json \
-v ~/.freq/tradesv2.sqlite:/freqtrade/tradesv2.sqlite \
freqtrade
```
If you are using `dry_run=True` you need to bind `tradesv2.dry_run.sqlite` instead of `tradesv2.sqlite`.
You can then use the following commands to monitor and manage your container:
```
$ docker logs freqtrade
$ docker logs -f freqtrade
$ docker restart freqtrade
$ docker stop freqtrade
$ docker start freqtrade
```
You do not need to rebuild the image for configuration
changes, it will suffice to edit `config.json` and restart the container.
#### Contributing #### Contributing
Feel like our bot is missing a feature? We welcome your pull requests! Few pointers for contributions: Feel like our bot is missing a feature? We welcome your pull requests! Few pointers for contributions:

View File

@@ -4,10 +4,10 @@
"stake_amount": 0.05, "stake_amount": 0.05,
"dry_run": false, "dry_run": false,
"minimal_roi": { "minimal_roi": {
"60": 0.0, "50": 0.0,
"40": 0.01, "40": 0.01,
"20": 0.02, "30": 0.02,
"0": 0.03 "0": 0.045
}, },
"stoploss": -0.40, "stoploss": -0.40,
"bid_strategy": { "bid_strategy": {

View File

@@ -1,3 +1,3 @@
__version__ = '0.11.0' __version__ = '0.13.0'
from . import main from . import main

View File

@@ -4,16 +4,18 @@ from datetime import timedelta
import arrow import arrow
import talib.abstract as ta import talib.abstract as ta
from pandas import DataFrame from pandas import DataFrame, to_datetime
from freqtrade.exchange import get_ticker_history from freqtrade import exchange
from freqtrade.exchange import Bittrex, get_ticker_history
from freqtrade.vendor.qtpylib.indicators import awesome_oscillator
logging.basicConfig(level=logging.DEBUG, logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def parse_ticker_dataframe(ticker: list, minimum_date: arrow.Arrow) -> DataFrame: def parse_ticker_dataframe(ticker: list) -> DataFrame:
""" """
Analyses the trend for the given pair Analyses the trend for the given pair
:param pair: pair as str in format BTC_ETH or BTC-ETH :param pair: pair as str in format BTC_ETH or BTC-ETH
@@ -21,25 +23,37 @@ def parse_ticker_dataframe(ticker: list, minimum_date: arrow.Arrow) -> DataFrame
""" """
df = DataFrame(ticker) \ df = DataFrame(ticker) \
.drop('BV', 1) \ .drop('BV', 1) \
.rename(columns={'C':'close', 'V':'volume', 'O':'open', 'H':'high', 'L':'low', 'T':'date'}) \ .rename(columns={'C':'close', 'V':'volume', 'O':'open', 'H':'high', 'L':'low', 'T':'date'})
.sort_values('date') df['date'] = to_datetime(df['date'], utc=True, infer_datetime_format=True)
return df[df['date'].map(arrow.get) > minimum_date] df.sort_values('date', inplace=True)
return df
def populate_indicators(dataframe: DataFrame) -> DataFrame: 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['sar'] = ta.SAR(dataframe, 0.02, 0.22) dataframe['sar'] = ta.SAR(dataframe)
dataframe['adx'] = ta.ADX(dataframe) dataframe['adx'] = ta.ADX(dataframe)
stoch = ta.STOCHF(dataframe) stoch = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch['fastd'] dataframe['fastd'] = stoch['fastd']
dataframe['fastk'] = stoch['fastk'] dataframe['fastk'] = stoch['fastk']
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
dataframe['cci'] = ta.CCI(dataframe, timeperiod=5) dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
dataframe['sma'] = ta.SMA(dataframe, timeperiod=100) dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=4)
dataframe['mfi'] = ta.MFI(dataframe) dataframe['mfi'] = ta.MFI(dataframe)
dataframe['cci'] = ta.CCI(dataframe)
dataframe['rsi'] = ta.RSI(dataframe)
dataframe['mom'] = ta.MOM(dataframe)
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
dataframe['ao'] = awesome_oscillator(dataframe)
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
return dataframe return dataframe
@@ -49,16 +63,14 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
:param dataframe: DataFrame :param dataframe: DataFrame
:return: DataFrame with buy column :return: DataFrame with buy column
""" """
dataframe.ix[
dataframe.loc[
(dataframe['close'] < dataframe['sma']) & (dataframe['close'] < dataframe['sma']) &
(dataframe['cci'] < -100) &
(dataframe['tema'] <= dataframe['blower']) & (dataframe['tema'] <= dataframe['blower']) &
(dataframe['mfi'] < 30) & (dataframe['mfi'] < 25) &
(dataframe['fastd'] < 20) & (dataframe['fastd'] < 25) &
(dataframe['adx'] > 20), (dataframe['adx'] > 30),
'buy'] = 1 'buy'] = 1
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] dataframe.ix[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
return dataframe return dataframe
@@ -71,7 +83,7 @@ def analyze_ticker(pair: str) -> DataFrame:
""" """
minimum_date = arrow.utcnow().shift(hours=-24) minimum_date = arrow.utcnow().shift(hours=-24)
data = get_ticker_history(pair, minimum_date) data = get_ticker_history(pair, minimum_date)
dataframe = parse_ticker_dataframe(data['result'], minimum_date) dataframe = parse_ticker_dataframe(data['result'])
if dataframe.empty: if dataframe.empty:
logger.warning('Empty dataframe for pair %s', pair) logger.warning('Empty dataframe for pair %s', pair)
@@ -119,20 +131,26 @@ def plot_dataframe(dataframe: DataFrame, pair: str) -> None:
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
# Two subplots sharing x axis # Two subplots sharing x axis
fig, (ax1, ax2) = plt.subplots(2, sharex=True) fig, (ax1, ax2, ax3) = plt.subplots(3, 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['sell'], 'ro', label='sell') # ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell')
ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA') ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA')
ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA')
ax1.plot(dataframe.index.values, dataframe['blower'], '-.', label='BB low')
ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy') ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy')
ax1.legend() ax1.legend()
# ax2.plot(dataframe.index.values, dataframe['adx'], label='ADX') ax2.plot(dataframe.index.values, dataframe['adx'], label='ADX')
ax2.plot(dataframe.index.values, dataframe['mfi'], label='MFI') ax2.plot(dataframe.index.values, dataframe['mfi'], label='MFI')
# ax2.plot(dataframe.index.values, [25] * len(dataframe.index.values)) # ax2.plot(dataframe.index.values, [25] * len(dataframe.index.values))
ax2.legend() ax2.legend()
ax3.plot(dataframe.index.values, dataframe['fastk'], label='k')
ax3.plot(dataframe.index.values, dataframe['fastd'], label='d')
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)
@@ -143,6 +161,7 @@ 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:
exchange.EXCHANGE = Bittrex({'key': '', 'secret': ''})
test_pair = 'BTC_ETH' test_pair = 'BTC_ETH'
# 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)

View File

@@ -85,6 +85,10 @@ def get_balance(currency: str) -> float:
return EXCHANGE.get_balance(currency) return EXCHANGE.get_balance(currency)
def get_balances():
return EXCHANGE.get_balances()
def get_ticker(pair: str) -> dict: def get_ticker(pair: str) -> dict:
return EXCHANGE.get_ticker(pair) return EXCHANGE.get_ticker(pair)

View File

@@ -26,10 +26,6 @@ class Bittrex(Exchange):
# Sleep time to avoid rate limits, used in the main loop # Sleep time to avoid rate limits, used in the main loop
SLEEP_TIME: float = 25 SLEEP_TIME: float = 25
@property
def name(self) -> str:
return self.__class__.__name__
@property @property
def sleep_time(self) -> float: def sleep_time(self) -> float:
return self.SLEEP_TIME return self.SLEEP_TIME
@@ -40,13 +36,6 @@ class Bittrex(Exchange):
_EXCHANGE_CONF.update(config) _EXCHANGE_CONF.update(config)
_API = _Bittrex(api_key=_EXCHANGE_CONF['key'], api_secret=_EXCHANGE_CONF['secret']) _API = _Bittrex(api_key=_EXCHANGE_CONF['key'], api_secret=_EXCHANGE_CONF['secret'])
# Check if all pairs are available
markets = self.get_markets()
exchange_name = self.name
for pair in _EXCHANGE_CONF['pair_whitelist']:
if pair not in markets:
raise RuntimeError('Pair {} is not available at {}'.format(pair, exchange_name))
def buy(self, pair: str, rate: float, amount: float) -> str: def buy(self, pair: str, rate: float, amount: float) -> str:
data = _API.buy_limit(pair.replace('_', '-'), amount, rate) data = _API.buy_limit(pair.replace('_', '-'), amount, rate)
if not data['success']: if not data['success']:
@@ -65,6 +54,12 @@ class Bittrex(Exchange):
raise RuntimeError('{}: {}'.format(self.name.upper(), data['message'])) raise RuntimeError('{}: {}'.format(self.name.upper(), data['message']))
return float(data['result']['Balance'] or 0.0) return float(data['result']['Balance'] or 0.0)
def get_balances(self):
data = _API.get_balances()
if not data['success']:
raise RuntimeError('{}: {}'.format(self.name.upper(), data['message']))
return data['result']
def get_ticker(self, pair: str) -> dict: def get_ticker(self, pair: str) -> dict:
data = _API.get_ticker(pair.replace('_', '-')) data = _API.get_ticker(pair.replace('_', '-'))
if not data['success']: if not data['success']:

View File

@@ -49,6 +49,21 @@ class Exchange(ABC):
:return: float :return: float
""" """
@abstractmethod
def get_balances(self) -> List[dict]:
"""
Gets account balances across currencies
:return: List of dicts, format: [
{
'Currency': str,
'Balance': float,
'Available': float,
'Pending': float,
}
...
]
"""
@abstractmethod @abstractmethod
def get_ticker(self, pair: str) -> dict: def get_ticker(self, pair: str) -> dict:
""" """

View File

@@ -6,6 +6,7 @@ import time
import traceback import traceback
from datetime import datetime from datetime import datetime
from typing import Dict, Optional from typing import Dict, Optional
from signal import signal, SIGINT, SIGABRT, SIGTERM
from jsonschema import validate from jsonschema import validate
@@ -223,6 +224,23 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
else: else:
update_state(State.STOPPED) update_state(State.STOPPED)
# Register signal handlers
for sig in (SIGINT, SIGTERM, SIGABRT):
signal(sig, cleanup)
def cleanup(*args, **kwargs) -> None:
"""
Cleanup the application state und finish all pending tasks
:return: None
"""
telegram.send_msg('*Status:* `Stopping trader...`')
logger.info('Stopping trader and cleaning up modules...')
update_state(State.STOPPED)
persistence.cleanup()
telegram.cleanup()
exit(0)
def app(config: dict) -> None: def app(config: dict) -> None:
""" """
@@ -251,10 +269,10 @@ def app(config: dict) -> None:
time.sleep(exchange.EXCHANGE.sleep_time) time.sleep(exchange.EXCHANGE.sleep_time)
old_state = new_state old_state = new_state
except RuntimeError: except RuntimeError:
telegram.send_msg('*Status:* Got RuntimeError: ```\n{}\n```'.format(traceback.format_exc())) telegram.send_msg(
'*Status:* Got RuntimeError:\n```\n{}\n```'.format(traceback.format_exc())
)
logger.exception('RuntimeError. Trader stopped!') logger.exception('RuntimeError. Trader stopped!')
finally:
telegram.send_msg('*Status:* `Trader has stopped`')
def main(): def main():

View File

@@ -5,7 +5,6 @@ from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, create
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.scoping import scoped_session from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.types import Enum
from freqtrade import exchange from freqtrade import exchange
@@ -37,6 +36,14 @@ def init(config: dict, db_url: Optional[str] = None) -> None:
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
def cleanup() -> None:
"""
Flushes all pending operations to disk.
:return: None
"""
Trade.session.flush()
class Trade(Base): class Trade(Base):
__tablename__ = 'trades' __tablename__ = 'trades'

View File

@@ -17,7 +17,7 @@ logging.getLogger('requests.packages.urllib3').setLevel(logging.INFO)
logging.getLogger('telegram').setLevel(logging.INFO) logging.getLogger('telegram').setLevel(logging.INFO)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_updater = None _updater: Updater = None
_CONF = {} _CONF = {}
@@ -41,10 +41,12 @@ def init(config: dict) -> None:
handles = [ handles = [
CommandHandler('status', _status), CommandHandler('status', _status),
CommandHandler('profit', _profit), CommandHandler('profit', _profit),
CommandHandler('balance', _balance),
CommandHandler('start', _start), CommandHandler('start', _start),
CommandHandler('stop', _stop), CommandHandler('stop', _stop),
CommandHandler('forcesell', _forcesell), CommandHandler('forcesell', _forcesell),
CommandHandler('performance', _performance), CommandHandler('performance', _performance),
CommandHandler('help', _help),
] ]
for handle in handles: for handle in handles:
_updater.dispatcher.add_handler(handle) _updater.dispatcher.add_handler(handle)
@@ -60,6 +62,14 @@ def init(config: dict) -> None:
) )
def cleanup() -> None:
"""
Stops all running telegram threads.
:return: None
"""
_updater.stop()
def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[..., Any]: def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[..., Any]:
""" """
Decorator to check if the message comes from the correct chat_id Decorator to check if the message comes from the correct chat_id
@@ -193,6 +203,27 @@ def _profit(bot: Bot, update: Update) -> None:
send_msg(markdown_msg, bot=bot) send_msg(markdown_msg, bot=bot)
@authorized_only
def _balance(bot: Bot, update: Update) -> None:
"""
Handler for /balance
Returns current account balance per crypto
"""
output = ""
balances = exchange.get_balances()
for currency in balances:
if not currency['Balance'] and not currency['Available'] and not currency['Pending']:
continue
output += """*Currency*: {Currency}
*Available*: {Available}
*Balance*: {Balance}
*Pending*: {Pending}
""".format(**currency)
send_msg(output)
@authorized_only @authorized_only
def _start(bot: Bot, update: Update) -> None: def _start(bot: Bot, update: Update) -> None:
""" """
@@ -301,6 +332,28 @@ def _performance(bot: Bot, update: Update) -> None:
send_msg(message, parse_mode=ParseMode.HTML) send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
def _help(bot: Bot, update: Update) -> None:
"""
Handler for /help.
Show commands of the bot
:param bot: telegram bot
:param update: message update
:return: None
"""
message = """
*/start:* `Starts the trader`
*/stop:* `Stops the trader`
*/status:* `Lists all open trades`
*/profit:* `Lists cumulative profit from all finished trades`
*/forcesell <trade_id>:* `Instantly sells the given trade, regardless of profit`
*/performance:* `Show performance of each finished trade grouped by pair`
*/balance:* `Show account balance per currency`
*/help:* `This help message`
"""
send_msg(message, bot=bot)
def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDOWN) -> None: def send_msg(msg: str, bot: Bot = None, parse_mode: ParseMode = ParseMode.MARKDOWN) -> None:
""" """
Send given markdown message Send given markdown message

View File

@@ -1,47 +1,41 @@
# pragma pylint: disable=missing-docstring # pragma pylint: disable=missing-docstring
from datetime import datetime
import json
import pytest import pytest
import arrow
from pandas import DataFrame from pandas import DataFrame
from freqtrade.analyze import parse_ticker_dataframe, populate_buy_trend, populate_indicators, \ from freqtrade.analyze import parse_ticker_dataframe, populate_buy_trend, populate_indicators, \
get_buy_signal get_buy_signal
RESULT_BITTREX = {
'success': True,
'message': '',
'result': [
{'O': 0.00065311, 'H': 0.00065311, 'L': 0.00065311, 'C': 0.00065311, 'V': 22.17210568, 'T': '2017-08-30T10:40:00', 'BV': 0.01448082},
{'O': 0.00066194, 'H': 0.00066195, 'L': 0.00066194, 'C': 0.00066195, 'V': 33.4727437, 'T': '2017-08-30T10:34:00', 'BV': 0.02215696},
{'O': 0.00065311, 'H': 0.00065311, 'L': 0.00065311, 'C': 0.00065311, 'V': 53.85127609, 'T': '2017-08-30T10:37:00', 'BV': 0.0351708},
{'O': 0.00066194, 'H': 0.00066194, 'L': 0.00065311, 'C': 0.00065311, 'V': 46.29210665, 'T': '2017-08-30T10:42:00', 'BV': 0.03063118},
]
}
@pytest.fixture @pytest.fixture
def result(): def result():
return parse_ticker_dataframe(RESULT_BITTREX['result'], arrow.get('2017-08-30T10:00:00')) with open('freqtrade/tests/testdata/btc-eth.json') as data_file:
data = json.load(data_file)
return parse_ticker_dataframe(data['result'])
def test_dataframe_has_correct_columns(result): def test_dataframe_has_correct_columns(result):
assert result.columns.tolist() == \ assert result.columns.tolist() == \
['close', 'high', 'low', 'open', 'date', 'volume'] ['close', 'high', 'low', 'open', 'date', 'volume']
def test_orders_by_date(result):
assert result['date'].tolist() == \ def test_dataframe_has_correct_length(result):
['2017-08-30T10:34:00', assert len(result.index) == 5751
'2017-08-30T10:37:00',
'2017-08-30T10:40:00',
'2017-08-30T10:42:00']
def test_populates_buy_trend(result): def test_populates_buy_trend(result):
dataframe = populate_buy_trend(populate_indicators(result)) dataframe = populate_buy_trend(populate_indicators(result))
assert 'buy' in dataframe.columns assert 'buy' in dataframe.columns
assert 'buy_price' in dataframe.columns assert 'buy_price' in dataframe.columns
def test_returns_latest_buy_signal(mocker): def test_returns_latest_buy_signal(mocker):
buydf = DataFrame([{'buy': 1, 'date': arrow.utcnow()}]) buydf = DataFrame([{'buy': 1, 'date': datetime.today()}])
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=buydf) mocker.patch('freqtrade.analyze.analyze_ticker', return_value=buydf)
assert get_buy_signal('BTC-ETH') assert get_buy_signal('BTC-ETH')
buydf = DataFrame([{'buy': 0, 'date': arrow.utcnow()}]) buydf = DataFrame([{'buy': 0, 'date': datetime.today()}])
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=buydf) mocker.patch('freqtrade.analyze.analyze_ticker', return_value=buydf)
assert not get_buy_signal('BTC-ETH') assert not get_buy_signal('BTC-ETH')

View File

@@ -13,65 +13,70 @@ from freqtrade.persistence import Trade
logging.disable(logging.DEBUG) # disable debug logs that slow backtesting a lot logging.disable(logging.DEBUG) # disable debug logs that slow backtesting a lot
def print_results(results):
print('Made {} buys. Average profit {:.2f}%. Total profit was {:.3f}. Average duration {:.1f} mins.'.format( def format_results(results):
return 'Made {} buys. Average profit {:.2f}%. Total profit was {:.3f}. Average duration {:.1f} mins.'.format(
len(results.index), len(results.index),
results.profit.mean() * 100.0, results.profit.mean() * 100.0,
results.profit.sum(), results.profit.sum(),
results.duration.mean()*5 results.duration.mean() * 5
)) )
def print_pair_results(pair, results):
print('For currency {}:'.format(pair))
print(format_results(results[results.currency == pair]))
@pytest.fixture @pytest.fixture
def pairs(): def pairs():
return ['btc-neo', 'btc-eth', 'btc-omg', 'btc-edg', 'btc-pay', return ['btc-neo', 'btc-eth', 'btc-omg', 'btc-edg', 'btc-pay',
'btc-pivx', 'btc-qtum', 'btc-mtl', 'btc-etc', 'btc-ltc'] 'btc-pivx', 'btc-qtum', 'btc-mtl', 'btc-etc', 'btc-ltc']
@pytest.fixture @pytest.fixture
def conf(): def conf():
return { return {
"minimal_roi": { "minimal_roi": {
"60": 0.0, "50": 0.0,
"40": 0.01, "40": 0.01,
"20": 0.02, "30": 0.02,
"0": 0.03 "0": 0.045
}, },
"stoploss": -0.40 "stoploss": -0.40
} }
@pytest.mark.skipif(not os.environ.get('BACKTEST', False), reason="BACKTEST not set") def backtest(conf, pairs, mocker):
def test_backtest(conf, pairs, mocker):
trades = [] trades = []
mocked_history = mocker.patch('freqtrade.analyze.get_ticker_history')
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
mocker.patch('arrow.utcnow', return_value=arrow.get('2017-08-20T14:50:00'))
for pair in pairs: for pair in pairs:
with open('freqtrade/tests/testdata/'+pair+'.json') as data_file: with open('freqtrade/tests/testdata/'+pair+'.json') as data_file:
data = json.load(data_file) data = json.load(data_file)
mocked_history.return_value = data
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=data) ticker = analyze_ticker(pair)[['close', 'date', 'buy']].copy()
mocker.patch('arrow.utcnow', return_value=arrow.get('2017-08-20T14:50:00'))
ticker = analyze_ticker(pair)
# for each buy point # for each buy point
for index, row in ticker[ticker.buy == 1].iterrows(): for row in ticker[ticker.buy == 1].itertuples(index=True):
trade = Trade( trade = Trade(open_rate=row.close, open_date=row.date, amount=1)
open_rate=row['close'],
open_date=arrow.get(row['date']).datetime,
amount=1,
)
# calculate win/lose forwards from buy point # calculate win/lose forwards from buy point
for index2, row2 in ticker[index:].iterrows(): for row2 in ticker[row.Index:].itertuples(index=True):
if should_sell(trade, row2['close'], arrow.get(row2['date']).datetime): if should_sell(trade, row2.close, row2.date):
current_profit = (row2['close'] - trade.open_rate) / trade.open_rate current_profit = (row2.close - trade.open_rate) / trade.open_rate
trades.append((pair, current_profit, index2 - index)) trades.append((pair, current_profit, row2.Index - row.Index))
break break
labels = ['currency', 'profit', 'duration'] labels = ['currency', 'profit', 'duration']
results = DataFrame.from_records(trades, columns=labels) results = DataFrame.from_records(trades, columns=labels)
return results
@pytest.mark.skipif(not os.environ.get('BACKTEST', False), reason="BACKTEST not set")
def test_backtest(conf, pairs, mocker, report=True):
results = backtest(conf, pairs, mocker)
print('====================== BACKTESTING REPORT ================================') print('====================== BACKTESTING REPORT ================================')
[print_pair_results(pair, results) for pair in pairs]
for pair in pairs:
print('For currency {}:'.format(pair))
print_results(results[results.currency == pair])
print('TOTAL OVER ALL TRADES:') print('TOTAL OVER ALL TRADES:')
print_results(results) print(format_results(results))

View File

@@ -0,0 +1,154 @@
# pragma pylint: disable=missing-docstring
import logging
import os
from functools import reduce
from math import exp
from operator import itemgetter
import pytest
from hyperopt import fmin, tpe, hp, Trials, STATUS_OK
from pandas import DataFrame
from freqtrade.tests.test_backtesting import backtest, format_results
from freqtrade.vendor.qtpylib.indicators import crossed_above
logging.disable(logging.DEBUG) # disable debug logs that slow backtesting a lot
# set TARGET_TRADES to suit your number concurrent trades so its realistic to 20days of data
TARGET_TRADES = 1200
@pytest.fixture
def pairs():
return ['btc-neo', 'btc-eth', 'btc-omg', 'btc-edg', 'btc-pay',
'btc-pivx', 'btc-qtum', 'btc-mtl', 'btc-etc', 'btc-ltc']
@pytest.fixture
def conf():
return {
"minimal_roi": {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
},
"stoploss": -0.05
}
def buy_strategy_generator(params):
print(params)
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
conditions = []
# GUARDS AND TRENDS
if params['uptrend_long_ema']['enabled']:
conditions.append(dataframe['ema50'] > dataframe['ema100'])
if params['mfi']['enabled']:
conditions.append(dataframe['mfi'] < params['mfi']['value'])
if params['fastd']['enabled']:
conditions.append(dataframe['fastd'] < params['fastd']['value'])
if params['adx']['enabled']:
conditions.append(dataframe['adx'] > params['adx']['value'])
if params['cci']['enabled']:
conditions.append(dataframe['cci'] < params['cci']['value'])
if params['rsi']['enabled']:
conditions.append(dataframe['rsi'] < params['rsi']['value'])
if params['over_sar']['enabled']:
conditions.append(dataframe['close'] > dataframe['sar'])
if params['uptrend_sma']['enabled']:
prevsma = dataframe['sma'].shift(1)
conditions.append(dataframe['sma'] > prevsma)
prev_fastd = dataframe['fastd'].shift(1)
# TRIGGERS
triggers = {
'lower_bb': dataframe['tema'] <= dataframe['blower'],
'faststoch10': (dataframe['fastd'] >= 10) & (prev_fastd < 10),
'ao_cross_zero': (crossed_above(dataframe['ao'], 0.0)),
'ema5_cross_ema10': (crossed_above(dataframe['ema5'], dataframe['ema10'])),
'macd_cross_signal': (crossed_above(dataframe['macd'], dataframe['macdsignal'])),
}
conditions.append(triggers.get(params['trigger']['type']))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close']
return dataframe
return populate_buy_trend
@pytest.mark.skipif(not os.environ.get('BACKTEST', False), reason="BACKTEST not set")
def test_hyperopt(conf, pairs, mocker):
mocked_buy_trend = mocker.patch('freqtrade.analyze.populate_buy_trend')
def optimizer(params):
mocked_buy_trend.side_effect = buy_strategy_generator(params)
results = backtest(conf, pairs, mocker)
result = format_results(results)
print(result)
total_profit = results.profit.sum() * 1000
trade_count = len(results.index)
trade_loss = 1 - 0.8 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5)
profit_loss = exp(-total_profit**3 / 10**11)
return {
'loss': trade_loss + profit_loss,
'status': STATUS_OK,
'result': result
}
space = {
'mfi': hp.choice('mfi', [
{'enabled': False},
{'enabled': True, 'value': hp.uniform('mfi-value', 5, 15)}
]),
'fastd': hp.choice('fastd', [
{'enabled': False},
{'enabled': True, 'value': hp.uniform('fastd-value', 5, 40)}
]),
'adx': hp.choice('adx', [
{'enabled': False},
{'enabled': True, 'value': hp.uniform('adx-value', 10, 30)}
]),
'cci': hp.choice('cci', [
{'enabled': False},
{'enabled': True, 'value': hp.uniform('cci-value', -150, -100)}
]),
'rsi': hp.choice('rsi', [
{'enabled': False},
{'enabled': True, 'value': hp.uniform('rsi-value', 20, 30)}
]),
'uptrend_long_ema': hp.choice('uptrend_long_ema', [
{'enabled': False},
{'enabled': True}
]),
'over_sar': hp.choice('over_sar', [
{'enabled': False},
{'enabled': True}
]),
'uptrend_sma': hp.choice('uptrend_sma', [
{'enabled': False},
{'enabled': True}
]),
'trigger': hp.choice('trigger', [
{'type': 'lower_bb'},
{'type': 'faststoch10'},
{'type': 'ao_cross_zero'},
{'type': 'ema5_cross_ema10'},
{'type': 'macd_cross_signal'},
]),
}
trials = Trials()
best = fmin(fn=optimizer, space=space, algo=tpe.suggest, max_evals=40, trials=trials)
print('\n\n\n\n====================== HYPEROPT BACKTESTING REPORT ================================')
print('Best parameters {}'.format(best))
newlist = sorted(trials.results, key=itemgetter('loss'))
print('Result: {}'.format(newlist[0]['result']))

View File

@@ -48,6 +48,7 @@ def conf():
validate(configuration, CONF_SCHEMA) validate(configuration, CONF_SCHEMA)
return configuration return configuration
def test_create_trade(conf, mocker): def test_create_trade(conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
buy_signal = mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) buy_signal = mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
@@ -82,6 +83,7 @@ def test_create_trade(conf, mocker):
[call('BTC_ETH'), call('BTC_TKN'), call('BTC_TRST'), call('BTC_SWT')] [call('BTC_ETH'), call('BTC_TKN'), call('BTC_TRST'), call('BTC_SWT')]
) )
def test_handle_trade(conf, mocker): def test_handle_trade(conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.main.telegram', init=MagicMock(), send_msg=MagicMock())
@@ -101,6 +103,7 @@ def test_handle_trade(conf, mocker):
assert trade.close_date is not None assert trade.close_date is not None
assert trade.open_order_id == 'dry_run' assert trade.open_order_id == 'dry_run'
def test_close_trade(conf, mocker): def test_close_trade(conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
trade = Trade.query.filter(Trade.is_open.is_(True)).first() trade = Trade.query.filter(Trade.is_open.is_(True)).first()
@@ -113,14 +116,17 @@ def test_close_trade(conf, mocker):
assert closed assert closed
assert not trade.is_open assert not trade.is_open
def test_balance_fully_ask_side(mocker): def test_balance_fully_ask_side(mocker):
mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 0.0}}) mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 0.0}})
assert get_target_bid({'ask': 20, 'last': 10}) == 20 assert get_target_bid({'ask': 20, 'last': 10}) == 20
def test_balance_fully_last_side(mocker): def test_balance_fully_last_side(mocker):
mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}) mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}})
assert get_target_bid({'ask': 20, 'last': 10}) == 10 assert get_target_bid({'ask': 20, 'last': 10}) == 10
def test_balance_when_last_bigger_than_ask(mocker): def test_balance_when_last_bigger_than_ask(mocker):
mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}}) mocker.patch.dict('freqtrade.main._CONF', {'bid_strategy': {'ask_last_balance': 1.0}})
assert get_target_bid({'ask': 5, 'last': 10}) == 5 assert get_target_bid({'ask': 5, 'last': 10}) == 5

View File

@@ -2,6 +2,7 @@
from freqtrade.exchange import Exchanges from freqtrade.exchange import Exchanges
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
def test_exec_sell_order(mocker): def test_exec_sell_order(mocker):
api_mock = mocker.patch('freqtrade.main.exchange.sell', side_effect='mocked_order_id') api_mock = mocker.patch('freqtrade.main.exchange.sell', side_effect='mocked_order_id')
trade = Trade( trade = Trade(

View File

@@ -9,7 +9,7 @@ from telegram import Bot, Update, Message, Chat
from freqtrade.main import init, create_trade from freqtrade.main import init, create_trade
from freqtrade.misc import update_state, State, get_state, CONF_SCHEMA from freqtrade.misc import update_state, State, get_state, CONF_SCHEMA
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc.telegram import _status, _profit, _forcesell, _performance, _start, _stop from freqtrade.rpc.telegram import _status, _profit, _forcesell, _performance, _start, _stop, _balance
@pytest.fixture @pytest.fixture
@@ -82,6 +82,7 @@ def test_status_handle(conf, update, mocker):
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2
assert '[BTC_ETH]' in msg_mock.call_args_list[-1][0][0] assert '[BTC_ETH]' in msg_mock.call_args_list[-1][0][0]
def test_profit_handle(conf, update, mocker): def test_profit_handle(conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
@@ -112,6 +113,7 @@ def test_profit_handle(conf, update, mocker):
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2
assert '(100.00%)' in msg_mock.call_args_list[-1][0][0] assert '(100.00%)' in msg_mock.call_args_list[-1][0][0]
def test_forcesell_handle(conf, update, mocker): def test_forcesell_handle(conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
@@ -140,6 +142,7 @@ def test_forcesell_handle(conf, update, mocker):
assert 'Selling [BTC/ETH]' in msg_mock.call_args_list[-1][0][0] assert 'Selling [BTC/ETH]' in msg_mock.call_args_list[-1][0][0]
assert '0.072561' in msg_mock.call_args_list[-1][0][0] assert '0.072561' in msg_mock.call_args_list[-1][0][0]
def test_performance_handle(conf, update, mocker): def test_performance_handle(conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True) mocker.patch('freqtrade.main.get_buy_signal', side_effect=lambda _: True)
@@ -171,6 +174,7 @@ def test_performance_handle(conf, update, mocker):
assert 'Performance' in msg_mock.call_args_list[-1][0][0] assert 'Performance' in msg_mock.call_args_list[-1][0][0]
assert 'BTC_ETH 100.00%' in msg_mock.call_args_list[-1][0][0] assert 'BTC_ETH 100.00%' in msg_mock.call_args_list[-1][0][0]
def test_start_handle(conf, update, mocker): def test_start_handle(conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
msg_mock = MagicMock() msg_mock = MagicMock()
@@ -184,6 +188,7 @@ def test_start_handle(conf, update, mocker):
assert get_state() == State.RUNNING assert get_state() == State.RUNNING
assert msg_mock.call_count == 0 assert msg_mock.call_count == 0
def test_stop_handle(conf, update, mocker): def test_stop_handle(conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', conf) mocker.patch.dict('freqtrade.main._CONF', conf)
msg_mock = MagicMock() msg_mock = MagicMock()
@@ -197,3 +202,22 @@ def test_stop_handle(conf, update, mocker):
assert get_state() == State.STOPPED assert get_state() == State.STOPPED
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Stopping trader' in msg_mock.call_args_list[0][0][0] assert 'Stopping trader' in msg_mock.call_args_list[0][0][0]
def test_balance_handle(conf, update, mocker):
mock_balance = [{
'Currency': 'BTC',
'Balance': 10.0,
'Available': 12.0,
'Pending': 0.0,
'CryptoAddress': 'XXXX'}]
mocker.patch.dict('freqtrade.main._CONF', conf)
msg_mock = MagicMock()
mocker.patch.multiple('freqtrade.main.telegram', _CONF=conf, init=MagicMock(), send_msg=msg_mock)
mocker.patch.multiple('freqtrade.main.exchange',
get_balances=MagicMock(return_value=mock_balance))
_balance(bot=MagicBot(), update=update)
assert msg_mock.call_count == 1
assert '*Currency*: BTC' in msg_mock.call_args_list[0][0][0]
assert 'Balance' in msg_mock.call_args_list[0][0][0]

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python3
"""This script generate json data from bittrex"""
from urllib.request import urlopen
CURRENCIES = ["ok", "neo", "dash", "etc", "eth", "snt"]
OUTPUT_DIR = 'freqtrade/tests/testdata/'
for cur in CURRENCIES:
url1 = 'https://bittrex.com/Api/v2.0/pub/market/GetTicks?marketName=BTC-'
url = url1+cur+'&tickInterval=fiveMin'
x = urlopen(url)
json_data = x.read()
json_str = str(json_data, 'utf-8')
output = OUTPUT_DIR + 'btc-'+cur+'.json'
with open(output, 'w') as file:
file.write(json_str)

0
freqtrade/vendor/__init__.py vendored Normal file
View File

0
freqtrade/vendor/qtpylib/__init__.py vendored Normal file
View File

619
freqtrade/vendor/qtpylib/indicators.py vendored Normal file
View File

@@ -0,0 +1,619 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# QTPyLib: Quantitative Trading Python Library
# https://github.com/ranaroussi/qtpylib
#
# Copyright 2016 Ran Aroussi
#
# Licensed under the GNU Lesser General Public License, v3.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.gnu.org/licenses/lgpl-3.0.en.html
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import numpy as np
import pandas as pd
import warnings
import sys
from datetime import datetime, timedelta
from pandas.core.base import PandasObject
# =============================================
# check min, python version
if sys.version_info < (3, 4):
raise SystemError("QTPyLib requires Python version >= 3.4")
# =============================================
warnings.simplefilter(action="ignore", category=RuntimeWarning)
# =============================================
def numpy_rolling_window(data, window):
shape = data.shape[:-1] + (data.shape[-1] - window + 1, window)
strides = data.strides + (data.strides[-1],)
return np.lib.stride_tricks.as_strided(data, shape=shape, strides=strides)
def numpy_rolling_series(func):
def func_wrapper(data, window, as_source=False):
series = data.values if isinstance(data, pd.Series) else data
new_series = np.empty(len(series)) * np.nan
calculated = func(series, window)
new_series[-len(calculated):] = calculated
if as_source and isinstance(data, pd.Series):
return pd.Series(index=data.index, data=new_series)
return new_series
return func_wrapper
@numpy_rolling_series
def numpy_rolling_mean(data, window, as_source=False):
return np.mean(numpy_rolling_window(data, window), -1)
@numpy_rolling_series
def numpy_rolling_std(data, window, as_source=False):
return np.std(numpy_rolling_window(data, window), -1)
# ---------------------------------------------
def session(df, start='17:00', end='16:00'):
""" remove previous globex day from df """
if len(df) == 0:
return df
# get start/end/now as decimals
int_start = list(map(int, start.split(':')))
int_start = (int_start[0] + int_start[1] - 1 / 100) - 0.0001
int_end = list(map(int, end.split(':')))
int_end = int_end[0] + int_end[1] / 100
int_now = (df[-1:].index.hour[0] + (df[:1].index.minute[0]) / 100)
# same-dat session?
is_same_day = int_end > int_start
# set pointers
curr = prev = df[-1:].index[0].strftime('%Y-%m-%d')
# globex/forex session
if is_same_day == False:
prev = (datetime.strptime(curr, '%Y-%m-%d') -
timedelta(1)).strftime('%Y-%m-%d')
# slice
if int_now >= int_start:
df = df[df.index >= curr + ' ' + start]
else:
df = df[df.index >= prev + ' ' + start]
return df.copy()
# ---------------------------------------------
def heikinashi(bars):
bars = bars.copy()
bars['ha_close'] = (bars['open'] + bars['high'] +
bars['low'] + bars['close']) / 4
bars['ha_open'] = (bars['open'].shift(1) + bars['close'].shift(1)) / 2
bars.loc[:1, 'ha_open'] = bars['open'].values[0]
bars.loc[1:, 'ha_open'] = (
(bars['ha_open'].shift(1) + bars['ha_close'].shift(1)) / 2)[1:]
bars['ha_high'] = bars.loc[:, ['high', 'ha_open', 'ha_close']].max(axis=1)
bars['ha_low'] = bars.loc[:, ['low', 'ha_open', 'ha_close']].min(axis=1)
return pd.DataFrame(index=bars.index, data={'open': bars['ha_open'],
'high': bars['ha_high'], 'low': bars['ha_low'], 'close': bars['ha_close']})
# ---------------------------------------------
def tdi(series, rsi_len=13, bollinger_len=34, rsi_smoothing=2, rsi_signal_len=7, bollinger_std=1.6185):
rsi_series = rsi(series, rsi_len)
bb_series = bollinger_bands(rsi_series, bollinger_len, bollinger_std)
signal = sma(rsi_series, rsi_signal_len)
rsi_series = sma(rsi_series, rsi_smoothing)
return pd.DataFrame(index=series.index, data={
"rsi": rsi_series,
"signal": signal,
"bbupper": bb_series['upper'],
"bblower": bb_series['lower'],
"bbmid": bb_series['mid']
})
# ---------------------------------------------
def awesome_oscillator(df, weighted=False, fast=5, slow=34):
midprice = (df['high'] + df['low']) / 2
if weighted:
ao = (midprice.ewm(fast).mean() - midprice.ewm(slow).mean()).values
else:
ao = numpy_rolling_mean(midprice, fast) - \
numpy_rolling_mean(midprice, slow)
return pd.Series(index=df.index, data=ao)
# ---------------------------------------------
def nans(len=1):
mtx = np.empty(len)
mtx[:] = np.nan
return mtx
# ---------------------------------------------
def typical_price(bars):
res = (bars['high'] + bars['low'] + bars['close']) / 3.
return pd.Series(index=bars.index, data=res)
# ---------------------------------------------
def mid_price(bars):
res = (bars['high'] + bars['low']) / 2.
return pd.Series(index=bars.index, data=res)
# ---------------------------------------------
def ibs(bars):
""" Internal bar strength """
res = np.round((bars['close'] - bars['low']) /
(bars['high'] - bars['low']), 2)
return pd.Series(index=bars.index, data=res)
# ---------------------------------------------
def true_range(bars):
return pd.DataFrame({
"hl": bars['high'] - bars['low'],
"hc": abs(bars['high'] - bars['close'].shift(1)),
"lc": abs(bars['low'] - bars['close'].shift(1))
}).max(axis=1)
# ---------------------------------------------
def atr(bars, window=14, exp=False):
tr = true_range(bars)
if exp:
res = rolling_weighted_mean(tr, window)
else:
res = rolling_mean(tr, window)
res = pd.Series(res)
return (res.shift(1) * (window - 1) + res) / window
# ---------------------------------------------
def crossed(series1, series2, direction=None):
if isinstance(series1, np.ndarray):
series1 = pd.Series(series1)
if isinstance(series2, int) or isinstance(series2, float) or isinstance(series2, np.ndarray):
series2 = pd.Series(index=series1.index, data=series2)
if direction is None or direction == "above":
above = pd.Series((series1 > series2) & (
series1.shift(1) <= series2.shift(1)))
if direction is None or direction == "below":
below = pd.Series((series1 < series2) & (
series1.shift(1) >= series2.shift(1)))
if direction is None:
return above or below
return above if direction is "above" else below
def crossed_above(series1, series2):
return crossed(series1, series2, "above")
def crossed_below(series1, series2):
return crossed(series1, series2, "below")
# ---------------------------------------------
def rolling_std(series, window=200, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
if min_periods == window:
return numpy_rolling_std(series, window, True)
else:
try:
return series.rolling(window=window, min_periods=min_periods).std()
except:
return pd.Series(series).rolling(window=window, min_periods=min_periods).std()
except:
return pd.rolling_std(series, window=window, min_periods=min_periods)
# ---------------------------------------------
def rolling_mean(series, window=200, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
if min_periods == window:
return numpy_rolling_mean(series, window, True)
else:
try:
return series.rolling(window=window, min_periods=min_periods).mean()
except:
return pd.Series(series).rolling(window=window, min_periods=min_periods).mean()
except:
return pd.rolling_mean(series, window=window, min_periods=min_periods)
# ---------------------------------------------
def rolling_min(series, window=14, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
try:
return series.rolling(window=window, min_periods=min_periods).min()
except:
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
except:
return pd.rolling_min(series, window=window, min_periods=min_periods)
# ---------------------------------------------
def rolling_max(series, window=14, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
try:
return series.rolling(window=window, min_periods=min_periods).min()
except:
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
except:
return pd.rolling_min(series, window=window, min_periods=min_periods)
# ---------------------------------------------
def rolling_weighted_mean(series, window=200, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
return series.ewm(span=window, min_periods=min_periods).mean()
except:
return pd.ewma(series, span=window, min_periods=min_periods)
# ---------------------------------------------
def hull_moving_average(series, window=200):
wma = (2 * rolling_weighted_mean(series, window=window / 2)) - \
rolling_weighted_mean(series, window=window)
return rolling_weighted_mean(wma, window=np.sqrt(window))
# ---------------------------------------------
def sma(series, window=200, min_periods=None):
return rolling_mean(series, window=window, min_periods=min_periods)
# ---------------------------------------------
def wma(series, window=200, min_periods=None):
return rolling_weighted_mean(series, window=window, min_periods=min_periods)
# ---------------------------------------------
def hma(series, window=200):
return hull_moving_average(series, window=window)
# ---------------------------------------------
def vwap(bars):
"""
calculate vwap of entire time series
(input can be pandas series or numpy array)
bars are usually mid [ (h+l)/2 ] or typical [ (h+l+c)/3 ]
"""
typical = ((bars['high'] + bars['low'] + bars['close']) / 3).values
volume = bars['volume'].values
return pd.Series(index=bars.index,
data=np.cumsum(volume * typical) / np.cumsum(volume))
# ---------------------------------------------
def rolling_vwap(bars, window=200, min_periods=None):
"""
calculate vwap using moving window
(input can be pandas series or numpy array)
bars are usually mid [ (h+l)/2 ] or typical [ (h+l+c)/3 ]
"""
min_periods = window if min_periods is None else min_periods
typical = ((bars['high'] + bars['low'] + bars['close']) / 3)
volume = bars['volume']
left = (volume * typical).rolling(window=window,
min_periods=min_periods).sum()
right = volume.rolling(window=window, min_periods=min_periods).sum()
return pd.Series(index=bars.index, data=(left / right))
# ---------------------------------------------
def rsi(series, window=14):
"""
compute the n period relative strength indicator
"""
# 100-(100/relative_strength)
deltas = np.diff(series)
seed = deltas[:window + 1]
# default values
ups = seed[seed > 0].sum() / window
downs = -seed[seed < 0].sum() / window
rsival = np.zeros_like(series)
rsival[:window] = 100. - 100. / (1. + ups / downs)
# period values
for i in range(window, len(series)):
delta = deltas[i - 1]
if delta > 0:
upval = delta
downval = 0
else:
upval = 0
downval = -delta
ups = (ups * (window - 1) + upval) / window
downs = (downs * (window - 1.) + downval) / window
rsival[i] = 100. - 100. / (1. + ups / downs)
# return rsival
return pd.Series(index=series.index, data=rsival)
# ---------------------------------------------
def macd(series, fast=3, slow=10, smooth=16):
"""
compute the MACD (Moving Average Convergence/Divergence)
using a fast and slow exponential moving avg'
return value is emaslow, emafast, macd which are len(x) arrays
"""
macd = rolling_weighted_mean(series, window=fast) - \
rolling_weighted_mean(series, window=slow)
signal = rolling_weighted_mean(macd, window=smooth)
histogram = macd - signal
# return macd, signal, histogram
return pd.DataFrame(index=series.index, data={
'macd': macd.values,
'signal': signal.values,
'histogram': histogram.values
})
# ---------------------------------------------
def bollinger_bands(series, window=20, stds=2):
sma = rolling_mean(series, window=window)
std = rolling_std(series, window=window)
upper = sma + std * stds
lower = sma - std * stds
return pd.DataFrame(index=series.index, data={
'upper': upper,
'mid': sma,
'lower': lower
})
# ---------------------------------------------
def weighted_bollinger_bands(series, window=20, stds=2):
ema = rolling_weighted_mean(series, window=window)
std = rolling_std(series, window=window)
upper = ema + std * stds
lower = ema - std * stds
return pd.DataFrame(index=series.index, data={
'upper': upper.values,
'mid': ema.values,
'lower': lower.values
})
# ---------------------------------------------
def returns(series):
try:
res = (series / series.shift(1) -
1).replace([np.inf, -np.inf], float('NaN'))
except:
res = nans(len(series))
return pd.Series(index=series.index, data=res)
# ---------------------------------------------
def log_returns(series):
try:
res = np.log(series / series.shift(1)
).replace([np.inf, -np.inf], float('NaN'))
except:
res = nans(len(series))
return pd.Series(index=series.index, data=res)
# ---------------------------------------------
def implied_volatility(series, window=252):
try:
logret = np.log(series / series.shift(1)
).replace([np.inf, -np.inf], float('NaN'))
res = numpy_rolling_std(logret, window) * np.sqrt(window)
except:
res = nans(len(series))
return pd.Series(index=series.index, data=res)
# ---------------------------------------------
def keltner_channel(bars, window=14, atrs=2):
typical_mean = rolling_mean(typical_price(bars), window)
atrval = atr(bars, window) * atrs
upper = typical_mean + atrval
lower = typical_mean - atrval
return pd.DataFrame(index=bars.index, data={
'upper': upper.values,
'mid': typical_mean.values,
'lower': lower.values
})
# ---------------------------------------------
def roc(series, window=14):
"""
compute rate of change
"""
res = (series - series.shift(window)) / series.shift(window)
return pd.Series(index=series.index, data=res)
# ---------------------------------------------
def cci(series, window=14):
"""
compute commodity channel index
"""
price = typical_price(series)
typical_mean = rolling_mean(price, window)
res = (price - typical_mean) / (.015 * np.std(typical_mean))
return pd.Series(index=series.index, data=res)
# ---------------------------------------------
def stoch(df, window=14, d=3, k=3, fast=False):
"""
compute the n period relative strength indicator
http://excelta.blogspot.co.il/2013/09/stochastic-oscillator-technical.html
"""
highs_ma = pd.concat([df['high'].shift(i)
for i in np.arange(window)], 1).apply(list, 1)
highs_ma = highs_ma.T.max().T
lows_ma = pd.concat([df['low'].shift(i)
for i in np.arange(window)], 1).apply(list, 1)
lows_ma = lows_ma.T.min().T
fast_k = ((df['close'] - lows_ma) / (highs_ma - lows_ma)) * 100
fast_d = numpy_rolling_mean(fast_k, d)
if fast:
data = {
'k': fast_k,
'd': fast_d
}
else:
slow_k = numpy_rolling_mean(fast_k, k)
slow_d = numpy_rolling_mean(slow_k, d)
data = {
'k': slow_k,
'd': slow_d
}
return pd.DataFrame(index=df.index, data=data)
# ---------------------------------------------
def zscore(bars, window=20, stds=1, col='close'):
""" get zscore of price """
std = numpy_rolling_std(bars[col], window)
mean = numpy_rolling_mean(bars[col], window)
return (bars[col] - mean) / (std * stds)
# ---------------------------------------------
def pvt(bars):
""" Price Volume Trend """
pvt = ((bars['close'] - bars['close'].shift(1)) /
bars['close'].shift(1)) * bars['volume']
return pvt.cumsum()
# =============================================
PandasObject.session = session
PandasObject.atr = atr
PandasObject.bollinger_bands = bollinger_bands
PandasObject.cci = cci
PandasObject.crossed = crossed
PandasObject.crossed_above = crossed_above
PandasObject.crossed_below = crossed_below
PandasObject.heikinashi = heikinashi
PandasObject.hull_moving_average = hull_moving_average
PandasObject.ibs = ibs
PandasObject.implied_volatility = implied_volatility
PandasObject.keltner_channel = keltner_channel
PandasObject.log_returns = log_returns
PandasObject.macd = macd
PandasObject.returns = returns
PandasObject.roc = roc
PandasObject.rolling_max = rolling_max
PandasObject.rolling_min = rolling_min
PandasObject.rolling_mean = rolling_mean
PandasObject.rolling_std = rolling_std
PandasObject.rsi = rsi
PandasObject.stoch = stoch
PandasObject.zscore = zscore
PandasObject.pvt = pvt
PandasObject.tdi = tdi
PandasObject.true_range = true_range
PandasObject.mid_price = mid_price
PandasObject.typical_price = typical_price
PandasObject.vwap = vwap
PandasObject.rolling_vwap = rolling_vwap
PandasObject.weighted_bollinger_bands = weighted_bollinger_bands
PandasObject.rolling_weighted_mean = rolling_weighted_mean
PandasObject.sma = sma
PandasObject.wma = wma
PandasObject.hma = hma

View File

@@ -1,6 +1,6 @@
-e git+https://github.com/ericsomdahl/python-bittrex.git@d7033d0#egg=python-bittrex -e git+https://github.com/ericsomdahl/python-bittrex.git@d7033d0#egg=python-bittrex
SQLAlchemy==1.1.13 SQLAlchemy==1.1.14
python-telegram-bot==8.0 python-telegram-bot==8.1.1
arrow==0.10.0 arrow==0.10.0
requests==2.18.4 requests==2.18.4
urllib3==1.22 urllib3==1.22
@@ -11,10 +11,13 @@ scipy==0.19.1
jsonschema==2.6.0 jsonschema==2.6.0
numpy==1.13.3 numpy==1.13.3
TA-Lib==0.4.10 TA-Lib==0.4.10
pytest==3.2.2 pytest==3.2.3
pytest-mock==1.6.3 pytest-mock==1.6.3
pytest-cov==2.5.1 pytest-cov==2.5.1
hyperopt==0.1
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
networkx==1.11
# Required for plotting data # Required for plotting data
#matplotlib==2.0.2 #matplotlib==2.1.0
#PYQT5==5.9 #PYQT5==5.9

View File

@@ -1,5 +0,0 @@
[aliases]
test=pytest
[tool:pytest]
addopts = --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/

View File

@@ -17,7 +17,7 @@ setup(name='freqtrade',
install_requires=[ install_requires=[
'python-bittrex==0.1.3', 'python-bittrex==0.1.3',
'SQLAlchemy==1.1.13', 'SQLAlchemy==1.1.13',
'python-telegram-bot==8.0', 'python-telegram-bot==8.1.1',
'arrow==0.10.0', 'arrow==0.10.0',
'requests==2.18.4', 'requests==2.18.4',
'urllib3==1.22', 'urllib3==1.22',