commit
d571ce266a
4
.gitignore
vendored
4
.gitignore
vendored
@ -26,8 +26,8 @@ dist/
|
|||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
#lib/
|
||||||
lib64/
|
#lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
FROM python:3.6.5-slim-stretch
|
FROM python:3.6.5-slim-stretch
|
||||||
|
|
||||||
# Install TA-lib
|
# Install TA-lib
|
||||||
RUN apt-get update && apt-get -y install curl build-essential && apt-get clean
|
RUN apt-get update && apt-get -y install curl build-essential git && apt-get clean
|
||||||
RUN curl -L http://prdownloads.sourceforge.net/ta-lib/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 | \
|
||||||
tar xzvf - && \
|
tar xzvf - && \
|
||||||
cd ta-lib && \
|
cd ta-lib && \
|
||||||
|
@ -224,7 +224,7 @@ class Arguments(object):
|
|||||||
Builds and attaches all subcommands
|
Builds and attaches all subcommands
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
from freqtrade.optimize import backtesting, hyperopt
|
from freqtrade.optimize import backtesting
|
||||||
|
|
||||||
subparsers = self.parser.add_subparsers(dest='subparser')
|
subparsers = self.parser.add_subparsers(dest='subparser')
|
||||||
|
|
||||||
@ -235,10 +235,14 @@ class Arguments(object):
|
|||||||
self.backtesting_options(backtesting_cmd)
|
self.backtesting_options(backtesting_cmd)
|
||||||
|
|
||||||
# Add hyperopt subcommand
|
# Add hyperopt subcommand
|
||||||
|
try:
|
||||||
|
from freqtrade.optimize import hyperopt
|
||||||
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
|
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
|
||||||
hyperopt_cmd.set_defaults(func=hyperopt.start)
|
hyperopt_cmd.set_defaults(func=hyperopt.start)
|
||||||
self.optimizer_shared_options(hyperopt_cmd)
|
self.optimizer_shared_options(hyperopt_cmd)
|
||||||
self.hyperopt_options(hyperopt_cmd)
|
self.hyperopt_options(hyperopt_cmd)
|
||||||
|
except ImportError as e:
|
||||||
|
logging.warn("no hyper opt found - skipping support for it")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_timerange(text: Optional[str]) -> TimeRange:
|
def parse_timerange(text: Optional[str]) -> TimeRange:
|
||||||
@ -340,7 +344,6 @@ class Arguments(object):
|
|||||||
default=None
|
default=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
'--plot-macd',
|
'--plot-macd',
|
||||||
help='Renders a macd chart of the given '
|
help='Renders a macd chart of the given '
|
||||||
@ -383,14 +386,6 @@ class Arguments(object):
|
|||||||
type=int
|
type=int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
self.parser.add_argument(
|
|
||||||
'-db', '--db-url',
|
|
||||||
help='Show trades stored in database.',
|
|
||||||
dest='db_url',
|
|
||||||
default=None
|
|
||||||
)
|
|
||||||
|
|
||||||
def testdata_dl_options(self) -> None:
|
def testdata_dl_options(self) -> None:
|
||||||
"""
|
"""
|
||||||
Parses given arguments for testdata download
|
Parses given arguments for testdata download
|
||||||
|
@ -33,7 +33,7 @@ class FreqtradeBot(object):
|
|||||||
This is from here the bot start its logic.
|
This is from here the bot start its logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: Dict[str, Any])-> None:
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Init all variables and object the bot need to work
|
Init all variables and object the bot need to work
|
||||||
:param config: configuration dict, you can use the Configuration.get_config()
|
:param config: configuration dict, you can use the Configuration.get_config()
|
||||||
@ -253,7 +253,6 @@ class FreqtradeBot(object):
|
|||||||
balance = self.config['bid_strategy']['ask_last_balance']
|
balance = self.config['bid_strategy']['ask_last_balance']
|
||||||
return ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
|
return ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
|
||||||
|
|
||||||
|
|
||||||
def create_trade(self) -> bool:
|
def create_trade(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks the implemented trading indicator(s) for a randomly picked pair,
|
Checks the implemented trading indicator(s) for a randomly picked pair,
|
||||||
@ -440,8 +439,9 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
|||||||
logger.info('Using order book for selling...')
|
logger.info('Using order book for selling...')
|
||||||
orderBook = exchange.get_order_book(trade.pair)
|
orderBook = exchange.get_order_book(trade.pair)
|
||||||
# logger.debug('Order book %s',orderBook)
|
# logger.debug('Order book %s',orderBook)
|
||||||
for i in range(self.config['ask_strategy']['book_order_min'],self.config['ask_strategy']['book_order_max']+1):
|
for i in range(self.config['ask_strategy']['book_order_min'],
|
||||||
sell_rate = orderBook['asks'][i-1][0]
|
self.config['ask_strategy']['book_order_max'] + 1):
|
||||||
|
sell_rate = orderBook['asks'][i - 1][0]
|
||||||
if self.check_sell(trade, sell_rate, buy, sell):
|
if self.check_sell(trade, sell_rate, buy, sell):
|
||||||
return True
|
return True
|
||||||
break
|
break
|
||||||
@ -452,7 +452,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
|||||||
logger.info('Found no sell signals for whitelisted currencies. Trying again..')
|
logger.info('Found no sell signals for whitelisted currencies. Trying again..')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool ) -> bool:
|
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool:
|
||||||
if self.analyze.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell):
|
if self.analyze.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell):
|
||||||
self.execute_sell(trade, sell_rate)
|
self.execute_sell(trade, sell_rate)
|
||||||
return True
|
return True
|
||||||
@ -483,8 +483,11 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
|||||||
continue
|
continue
|
||||||
ordertime = arrow.get(order['datetime']).datetime
|
ordertime = arrow.get(order['datetime']).datetime
|
||||||
|
|
||||||
|
print(order)
|
||||||
# Check if trade is still actually open
|
# Check if trade is still actually open
|
||||||
if (int(order['filled']) == 0) and (order['status']=='open'):
|
# this makes no real sense and causes errors!
|
||||||
|
#
|
||||||
|
# if (filled(int(order['filled']) == 0) and (order['status'] == 'open'):
|
||||||
if order['side'] == 'buy' and ordertime < timeoutthreashold:
|
if order['side'] == 'buy' and ordertime < timeoutthreashold:
|
||||||
self.handle_timedout_limit_buy(trade, order)
|
self.handle_timedout_limit_buy(trade, order)
|
||||||
elif order['side'] == 'sell' and ordertime < timeoutthreashold:
|
elif order['side'] == 'sell' and ordertime < timeoutthreashold:
|
||||||
@ -579,7 +582,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \
|
|||||||
fiat
|
fiat
|
||||||
)
|
)
|
||||||
message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f} {stake}`' \
|
message += f'` ({gain}: {fmt_exp_profit:.2f}%, {profit_trade:.8f} {stake}`' \
|
||||||
f'` / {profit_fiat:.3f} {fiat})`'\
|
f'` / {profit_fiat:.3f} {fiat})`' \
|
||||||
''
|
''
|
||||||
# Because telegram._forcesell does not have the configuration
|
# Because telegram._forcesell does not have the configuration
|
||||||
# Ignore the FIAT value and does not show the stake_currency as well
|
# Ignore the FIAT value and does not show the stake_currency as well
|
||||||
|
@ -31,6 +31,7 @@ class Backtesting(object):
|
|||||||
backtesting = Backtesting(config)
|
backtesting = Backtesting(config)
|
||||||
backtesting.start()
|
backtesting.start()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, config: Dict[str, Any]) -> None:
|
def __init__(self, config: Dict[str, Any]) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
self.analyze = Analyze(self.config)
|
self.analyze = Analyze(self.config)
|
||||||
@ -66,35 +67,41 @@ class Backtesting(object):
|
|||||||
Generates and returns a text table for the given backtest data and the results dataframe
|
Generates and returns a text table for the given backtest data and the results dataframe
|
||||||
:return: pretty printed table with tabulate as str
|
:return: pretty printed table with tabulate as str
|
||||||
"""
|
"""
|
||||||
stake_currency = str(self.config.get('stake_currency'))
|
|
||||||
|
|
||||||
floatfmt = ('s', 'd', '.2f', '.8f', '.1f')
|
floatfmt, headers, tabular_data = self.aggregate(data, results)
|
||||||
|
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
|
||||||
|
|
||||||
|
def aggregate(self, data, results):
|
||||||
|
stake_currency = self.config.get('stake_currency')
|
||||||
|
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.1f')
|
||||||
tabular_data = []
|
tabular_data = []
|
||||||
headers = ['pair', 'buy count', 'avg profit %',
|
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
|
||||||
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
|
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
|
||||||
for pair in data:
|
for pair in data:
|
||||||
result = results[results.currency == pair]
|
result = results[results.currency == pair]
|
||||||
|
print(results)
|
||||||
tabular_data.append([
|
tabular_data.append([
|
||||||
pair,
|
pair,
|
||||||
len(result.index),
|
len(result.index),
|
||||||
result.profit_percent.mean() * 100.0,
|
result.profit_percent.mean() * 100.0,
|
||||||
|
result.profit_percent.sum() * 100.0,
|
||||||
result.profit_BTC.sum(),
|
result.profit_BTC.sum(),
|
||||||
result.duration.mean(),
|
result.duration.mean(),
|
||||||
len(result[result.profit_BTC > 0]),
|
len(result[result.profit_BTC > 0]),
|
||||||
len(result[result.profit_BTC < 0])
|
len(result[result.profit_BTC < 0])
|
||||||
])
|
])
|
||||||
|
|
||||||
# Append Total
|
# Append Total
|
||||||
tabular_data.append([
|
tabular_data.append([
|
||||||
'TOTAL',
|
'TOTAL',
|
||||||
len(results.index),
|
len(results.index),
|
||||||
results.profit_percent.mean() * 100.0,
|
results.profit_percent.mean() * 100.0,
|
||||||
|
results.profit_percent.sum() * 100.0,
|
||||||
results.profit_BTC.sum(),
|
results.profit_BTC.sum(),
|
||||||
results.duration.mean(),
|
results.duration.mean(),
|
||||||
len(results[results.profit_BTC > 0]),
|
len(results[results.profit_BTC > 0]),
|
||||||
len(results[results.profit_BTC < 0])
|
len(results[results.profit_BTC < 0])
|
||||||
])
|
])
|
||||||
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
|
return floatfmt, headers, tabular_data
|
||||||
|
|
||||||
def _get_sell_trade_entry(
|
def _get_sell_trade_entry(
|
||||||
self, pair: str, buy_row: DataFrame,
|
self, pair: str, buy_row: DataFrame,
|
||||||
@ -127,7 +134,9 @@ class Backtesting(object):
|
|||||||
pair,
|
pair,
|
||||||
trade.calc_profit_percent(rate=sell_row.close),
|
trade.calc_profit_percent(rate=sell_row.close),
|
||||||
trade.calc_profit(rate=sell_row.close),
|
trade.calc_profit(rate=sell_row.close),
|
||||||
(sell_row.date - buy_row.date).seconds // 60
|
(sell_row.date - buy_row.date).seconds // 60,
|
||||||
|
buy_row.date,
|
||||||
|
sell_row.date
|
||||||
), \
|
), \
|
||||||
sell_row.date
|
sell_row.date
|
||||||
return None
|
return None
|
||||||
@ -193,6 +202,7 @@ class Backtesting(object):
|
|||||||
if ret:
|
if ret:
|
||||||
row2, trade_entry, next_date = ret
|
row2, trade_entry, next_date = ret
|
||||||
lock_pair_until = next_date
|
lock_pair_until = next_date
|
||||||
|
|
||||||
trades.append(trade_entry)
|
trades.append(trade_entry)
|
||||||
if record:
|
if record:
|
||||||
# Note, need to be json.dump friendly
|
# Note, need to be json.dump friendly
|
||||||
@ -207,10 +217,12 @@ class Backtesting(object):
|
|||||||
if record and record.find('trades') >= 0:
|
if record and record.find('trades') >= 0:
|
||||||
logger.info('Dumping backtest results to %s', recordfilename)
|
logger.info('Dumping backtest results to %s', recordfilename)
|
||||||
file_dump_json(recordfilename, records)
|
file_dump_json(recordfilename, records)
|
||||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
file_dump_json('backtest-result.json', records)
|
||||||
|
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'entry', 'exit']
|
||||||
|
|
||||||
return DataFrame.from_records(trades, columns=labels)
|
return DataFrame.from_records(trades, columns=labels)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self):
|
||||||
"""
|
"""
|
||||||
Run a backtesting end-to-end
|
Run a backtesting end-to-end
|
||||||
:return: None
|
:return: None
|
||||||
@ -284,6 +296,10 @@ class Backtesting(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# return date for data storage
|
||||||
|
table = self.aggregate(data, results)
|
||||||
|
return (results, table)
|
||||||
|
|
||||||
|
|
||||||
def setup_configuration(args: Namespace) -> Dict[str, Any]:
|
def setup_configuration(args: Namespace) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -6,6 +6,7 @@ This module load custom strategies
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
from base64 import urlsafe_b64decode
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Optional, Dict, Type
|
from typing import Optional, Dict, Type
|
||||||
|
|
||||||
@ -64,6 +65,13 @@ class StrategyResolver(object):
|
|||||||
key=lambda t: t[0]))
|
key=lambda t: t[0]))
|
||||||
self.strategy.stoploss = float(self.strategy.stoploss)
|
self.strategy.stoploss = float(self.strategy.stoploss)
|
||||||
|
|
||||||
|
def compile(self, strategy_name: str, strategy_content: str) -> Optional[IStrategy]:
|
||||||
|
temp = Path(tempfile.mkdtemp("freq", "strategy"))
|
||||||
|
temp.joinpath(strategy_name + ".py").write_text(strategy_content)
|
||||||
|
temp.joinpath("__init__.py").touch()
|
||||||
|
|
||||||
|
return self._load_strategy(strategy_name, temp.absolute())
|
||||||
|
|
||||||
def _load_strategy(
|
def _load_strategy(
|
||||||
self, strategy_name: str, extra_dir: Optional[str] = None) -> IStrategy:
|
self, strategy_name: str, extra_dir: Optional[str] = None) -> IStrategy:
|
||||||
"""
|
"""
|
||||||
@ -82,15 +90,16 @@ class StrategyResolver(object):
|
|||||||
# Add extra strategy directory on top of search paths
|
# Add extra strategy directory on top of search paths
|
||||||
abs_paths.insert(0, extra_dir)
|
abs_paths.insert(0, extra_dir)
|
||||||
|
|
||||||
try:
|
# check if the given strategy is provided as name, value pair
|
||||||
# check if given strategy matches an url
|
# where the value is the strategy encoded in base 64
|
||||||
logger.debug("requesting remote strategy from {}".format(strategy_name))
|
if ":" in strategy_name and "http" not in strategy_name:
|
||||||
resp = requests.get(strategy_name, stream=True)
|
strat = strategy_name.split(":")
|
||||||
if resp.status_code == 200:
|
|
||||||
temp = Path(tempfile.mkdtemp("freq", "strategy"))
|
|
||||||
name = os.path.basename(urlparse(strategy_name).path)
|
|
||||||
|
|
||||||
temp.joinpath(name).write_text(resp.text)
|
if len(strat) == 2:
|
||||||
|
temp = Path(tempfile.mkdtemp("freq", "strategy"))
|
||||||
|
name = strat[0] + ".py"
|
||||||
|
|
||||||
|
temp.joinpath(name).write_text(urlsafe_b64decode(strat[1]).decode('utf-8'))
|
||||||
temp.joinpath("__init__.py").touch()
|
temp.joinpath("__init__.py").touch()
|
||||||
|
|
||||||
strategy_name = os.path.splitext(name)[0]
|
strategy_name = os.path.splitext(name)[0]
|
||||||
@ -98,6 +107,28 @@ class StrategyResolver(object):
|
|||||||
# register temp path with the bot
|
# register temp path with the bot
|
||||||
abs_paths.insert(0, temp.absolute())
|
abs_paths.insert(0, temp.absolute())
|
||||||
|
|
||||||
|
# check if given strategy matches an url
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
logger.debug("requesting remote strategy from {}".format(strategy_name))
|
||||||
|
resp = requests.get(strategy_name, stream=True)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
temp = Path(tempfile.mkdtemp("freq", "strategy"))
|
||||||
|
|
||||||
|
if strategy_name.endswith("/code"):
|
||||||
|
strategy_name = strategy_name.replace("/code", "")
|
||||||
|
|
||||||
|
name = os.path.basename(urlparse(strategy_name).path)
|
||||||
|
|
||||||
|
temp.joinpath("{}.py".format(name)).write_text(resp.text)
|
||||||
|
temp.joinpath("__init__.py").touch()
|
||||||
|
|
||||||
|
strategy_name = os.path.splitext(name)[0]
|
||||||
|
|
||||||
|
print("stored downloaded stat at: {}".format(temp))
|
||||||
|
# register temp path with the bot
|
||||||
|
abs_paths.insert(0, temp.absolute())
|
||||||
|
|
||||||
except requests.RequestException:
|
except requests.RequestException:
|
||||||
logger.debug("received error trying to fetch strategy remotely, carry on!")
|
logger.debug("received error trying to fetch strategy remotely, carry on!")
|
||||||
|
|
||||||
|
@ -15,6 +15,10 @@ from freqtrade.analyze import Analyze
|
|||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
|
|
||||||
|
import moto
|
||||||
|
import boto3
|
||||||
|
import os
|
||||||
|
|
||||||
logging.getLogger('').setLevel(logging.INFO)
|
logging.getLogger('').setLevel(logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
@ -523,6 +527,7 @@ def result():
|
|||||||
with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file:
|
with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file:
|
||||||
return Analyze.parse_ticker_dataframe(json.load(data_file))
|
return Analyze.parse_ticker_dataframe(json.load(data_file))
|
||||||
|
|
||||||
|
|
||||||
# FIX:
|
# FIX:
|
||||||
# Create an fixture/function
|
# Create an fixture/function
|
||||||
# that inserts a trade of some type and open-status
|
# that inserts a trade of some type and open-status
|
||||||
|
@ -365,14 +365,10 @@ def test_generate_text_table(default_conf, mocker):
|
|||||||
)
|
)
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
'| pair | buy count | avg profit % | '
|
"""| pair | buy count | avg profit % | cum profit % | total profit BTC | avg duration | profit | loss |
|
||||||
'total profit BTC | avg duration | profit | loss |\n'
|
|:--------|------------:|---------------:|---------------:|-------------------:|---------------:|---------:|-------:|
|
||||||
'|:--------|------------:|---------------:|'
|
| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 |
|
||||||
'-------------------:|---------------:|---------:|-------:|\n'
|
| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 |"""
|
||||||
'| ETH/BTC | 2 | 15.00 | '
|
|
||||||
'0.60000000 | 20.0 | 2 | 0 |\n'
|
|
||||||
'| TOTAL | 2 | 15.00 | '
|
|
||||||
'0.60000000 | 20.0 | 2 | 0 |'
|
|
||||||
)
|
)
|
||||||
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
|
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
|
||||||
|
|
||||||
@ -598,6 +594,7 @@ def test_backtest_record(default_conf, fee, mocker):
|
|||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert len(results) == 3
|
assert len(results) == 3
|
||||||
# Assert file_dump_json was only called once
|
# Assert file_dump_json was only called once
|
||||||
|
print(names)
|
||||||
assert names == ['backtest-result.json']
|
assert names == ['backtest-result.json']
|
||||||
records = records[0]
|
records = records[0]
|
||||||
# Ensure records are of correct type
|
# Ensure records are of correct type
|
||||||
|
@ -28,10 +28,10 @@ def test_load_strategy(result):
|
|||||||
|
|
||||||
def test_load_strategy_from_url(result):
|
def test_load_strategy_from_url(result):
|
||||||
resolver = StrategyResolver()
|
resolver = StrategyResolver()
|
||||||
resolver._load_strategy('https://raw.githubusercontent.com/berlinguyinca'
|
resolver._load_strategy('https://freq.isaac.international/'
|
||||||
'/freqtrade-trading-strategies'
|
'dev/strategies/GBPAQEFGGWCMWVFU34P'
|
||||||
'/master/user_data/strategies/Simple.py')
|
'MVGS4P2NJR4IDFNVI4LTCZAKJAD3JCXUMBI4J/AverageStrategy/code')
|
||||||
assert hasattr(resolver.strategy, 'populate_indicators')
|
assert hasattr(resolver.strategy, 'minimal_roi')
|
||||||
assert 'adx' in resolver.strategy.populate_indicators(result)
|
assert 'adx' in resolver.strategy.populate_indicators(result)
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ def test_scripts_options() -> None:
|
|||||||
arguments = Arguments(['-p', 'ETH/BTC'], '')
|
arguments = Arguments(['-p', 'ETH/BTC'], '')
|
||||||
arguments.scripts_options()
|
arguments.scripts_options()
|
||||||
args = arguments.get_parsed_arg()
|
args = arguments.get_parsed_arg()
|
||||||
|
print(args.pair)
|
||||||
assert args.pair == 'ETH/BTC'
|
assert args.pair == 'ETH/BTC'
|
||||||
|
|
||||||
|
|
||||||
|
BIN
lib/libta_lib.a
Normal file
BIN
lib/libta_lib.a
Normal file
Binary file not shown.
35
lib/libta_lib.la
Executable file
35
lib/libta_lib.la
Executable file
@ -0,0 +1,35 @@
|
|||||||
|
# libta_lib.la - a libtool library file
|
||||||
|
# Generated by ltmain.sh - GNU libtool 1.5.22 Debian 1.5.22-4 (1.1220.2.365 2005/12/18 22:14:06)
|
||||||
|
#
|
||||||
|
# Please DO NOT delete this file!
|
||||||
|
# It is necessary for linking the library.
|
||||||
|
|
||||||
|
# The name that we can dlopen(3).
|
||||||
|
dlname='libta_lib.so.0'
|
||||||
|
|
||||||
|
# Names of this library.
|
||||||
|
library_names='libta_lib.so.0.0.0 libta_lib.so.0 libta_lib.so'
|
||||||
|
|
||||||
|
# The name of the static archive.
|
||||||
|
old_library='libta_lib.a'
|
||||||
|
|
||||||
|
# Libraries that this one depends upon.
|
||||||
|
dependency_libs=' -lpthread -ldl'
|
||||||
|
|
||||||
|
# Version information for libta_lib.
|
||||||
|
current=0
|
||||||
|
age=0
|
||||||
|
revision=0
|
||||||
|
|
||||||
|
# Is this an already installed library?
|
||||||
|
installed=yes
|
||||||
|
|
||||||
|
# Should we warn about portability when linking against -modules?
|
||||||
|
shouldnotlink=no
|
||||||
|
|
||||||
|
# Files to dlopen/dlpreopen
|
||||||
|
dlopen=''
|
||||||
|
dlpreopen=''
|
||||||
|
|
||||||
|
# Directory that this library needs to be installed in:
|
||||||
|
libdir='/usr/local/lib'
|
1
lib/libta_lib.so.0
Symbolic link
1
lib/libta_lib.so.0
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
libta_lib.so.0.0.0
|
BIN
lib/libta_lib.so.0.0.0
Executable file
BIN
lib/libta_lib.so.0.0.0
Executable file
Binary file not shown.
18
requirements-aws.txt
Normal file
18
requirements-aws.txt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
ccxt==1.14.24
|
||||||
|
SQLAlchemy==1.2.7
|
||||||
|
arrow==0.12.1
|
||||||
|
cachetools==2.1.0
|
||||||
|
requests==2.18.4
|
||||||
|
urllib3==1.22
|
||||||
|
wrapt==1.10.11
|
||||||
|
pandas==0.23.0
|
||||||
|
scikit-learn==0.19.1
|
||||||
|
scipy==1.1.0
|
||||||
|
jsonschema==2.6.0
|
||||||
|
numpy==1.14.3
|
||||||
|
TA-Lib==0.4.17
|
||||||
|
git+git://github.com/berlinguyinca/networkx@v1.11
|
||||||
|
tabulate==0.8.2
|
||||||
|
coinmarketcap==5.0.3
|
||||||
|
simplejson==3.15.0
|
||||||
|
boto3
|
@ -17,9 +17,10 @@ pytest-mock==1.10.0
|
|||||||
pytest-cov==2.5.1
|
pytest-cov==2.5.1
|
||||||
hyperopt==0.1
|
hyperopt==0.1
|
||||||
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
|
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
|
||||||
networkx==1.11 # pyup: ignore
|
#networkx==1.11
|
||||||
|
git+git://github.com/berlinguyinca/networkx@v1.11
|
||||||
tabulate==0.8.2
|
tabulate==0.8.2
|
||||||
coinmarketcap==5.0.3
|
coinmarketcap==5.0.3
|
||||||
|
simplejson==3.15.0
|
||||||
# Required for plotting data
|
# Required for plotting data
|
||||||
#plotly==2.3.0
|
#plotly==2.3.0
|
||||||
|
337
serverless.yml
Normal file
337
serverless.yml
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
service: freq
|
||||||
|
|
||||||
|
frameworkVersion: ">=1.1.0 <2.0.0"
|
||||||
|
|
||||||
|
plugins:
|
||||||
|
- serverless-domain-manager
|
||||||
|
- serverless-python-requirements
|
||||||
|
|
||||||
|
############################################################################################
|
||||||
|
# configure out provider and the security guide lines
|
||||||
|
############################################################################################
|
||||||
|
provider:
|
||||||
|
name: aws
|
||||||
|
runtime: python3.6
|
||||||
|
region: us-east-2
|
||||||
|
|
||||||
|
#required permissions
|
||||||
|
iamRoleStatements:
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- dynamodb:*
|
||||||
|
Resource: "*"
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- SNS:*
|
||||||
|
Resource: { "Fn::Join" : [":", ["arn:aws:sns:${self:custom.region}", "*:*" ] ] }
|
||||||
|
- Effect: "Allow"
|
||||||
|
Action:
|
||||||
|
- ecs:RunTask
|
||||||
|
Resource: "*"
|
||||||
|
- Effect: Allow
|
||||||
|
Action:
|
||||||
|
- iam:PassRole
|
||||||
|
Resource: "*"
|
||||||
|
|
||||||
|
memorySize: 128
|
||||||
|
timeout: 90
|
||||||
|
versionFunctions: false
|
||||||
|
|
||||||
|
logRetentionInDays: 3
|
||||||
|
|
||||||
|
#where to store out data, needs to be manually created!
|
||||||
|
deploymentBucket:
|
||||||
|
name: lambdas-freq
|
||||||
|
|
||||||
|
# limit the invocations a bit to avoid overloading the server
|
||||||
|
usagePlan:
|
||||||
|
throttle:
|
||||||
|
burstLimit: 100
|
||||||
|
rateLimit: 50
|
||||||
|
|
||||||
|
############################################################################################
|
||||||
|
#custom configuration settings
|
||||||
|
############################################################################################
|
||||||
|
custom:
|
||||||
|
stage: ${opt:stage, self:provider.stage}
|
||||||
|
region: ${opt:region, self:provider.region}
|
||||||
|
|
||||||
|
snsTopic: "FreqQueue-${self:custom.stage}"
|
||||||
|
snsTradeTopic: "FreqTradeQueue-${self:custom.stage}"
|
||||||
|
|
||||||
|
tradeTable: "FreqTradesTable-${self:custom.stage}"
|
||||||
|
strategyTable: "FreqStrategyTable-${self:custom.stage}"
|
||||||
|
|
||||||
|
###
|
||||||
|
# custom domain management
|
||||||
|
###
|
||||||
|
|
||||||
|
customDomain:
|
||||||
|
basePath: "${self:custom.stage}"
|
||||||
|
domainName: "freq.isaac.international"
|
||||||
|
stage: "${self:custom.stage}"
|
||||||
|
createRoute53Record: true
|
||||||
|
|
||||||
|
pythonRequirements:
|
||||||
|
slim: true
|
||||||
|
invalidateCaches: true
|
||||||
|
dockerizePip: false
|
||||||
|
fileName: requirements-aws.txt
|
||||||
|
noDeploy:
|
||||||
|
- pytest
|
||||||
|
- moto
|
||||||
|
- plotly
|
||||||
|
- boto3
|
||||||
|
- pytest-mock
|
||||||
|
- pytest-cov
|
||||||
|
- pymongo
|
||||||
|
package:
|
||||||
|
exclude:
|
||||||
|
- test/**
|
||||||
|
- node_modules/**
|
||||||
|
- doc/**
|
||||||
|
- scripts/**
|
||||||
|
- bin
|
||||||
|
- freqtrade/tests/**
|
||||||
|
|
||||||
|
############################################################################################
|
||||||
|
# this section defines all lambda function and triggers
|
||||||
|
############################################################################################
|
||||||
|
functions:
|
||||||
|
|
||||||
|
#returns all known strategy names from the server
|
||||||
|
#and if they are private or not
|
||||||
|
strategies:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/strategy.names
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies
|
||||||
|
method: get
|
||||||
|
cors: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
#returns the source code of this given strategy
|
||||||
|
#unless it's private
|
||||||
|
code:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/strategy.code
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies/{user}/{name}/code
|
||||||
|
method: get
|
||||||
|
cors: true
|
||||||
|
integration: lambda
|
||||||
|
request:
|
||||||
|
parameter:
|
||||||
|
paths:
|
||||||
|
user: true
|
||||||
|
name: true
|
||||||
|
response:
|
||||||
|
headers:
|
||||||
|
Content-Type: "'text/plain'"
|
||||||
|
template: $input.path('$')
|
||||||
|
|
||||||
|
environment:
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
# loads the details of the specific strategy
|
||||||
|
get:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/strategy.get
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies/{user}/{name}
|
||||||
|
method: get
|
||||||
|
cors: true
|
||||||
|
request:
|
||||||
|
parameter:
|
||||||
|
paths:
|
||||||
|
user: true
|
||||||
|
name: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
# loads the aggregation report for the given strategy based on different tickers
|
||||||
|
get_aggregate_interval:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/aggregate/strategy.ticker
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies/{user}/{name}/aggregate/ticker
|
||||||
|
method: get
|
||||||
|
cors: true
|
||||||
|
request:
|
||||||
|
parameter:
|
||||||
|
paths:
|
||||||
|
user: true
|
||||||
|
name: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
tradeTable: ${self:custom.tradeTable}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
# loads the aggregation report for the given strategy based on different tickers
|
||||||
|
get_aggregate_timeframe:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/aggregate/strategy.timeframe
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies/{user}/{name}/aggregate/timeframe
|
||||||
|
method: get
|
||||||
|
cors: true
|
||||||
|
request:
|
||||||
|
parameter:
|
||||||
|
paths:
|
||||||
|
user: true
|
||||||
|
name: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
tradeTable: ${self:custom.tradeTable}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
#submits a new strategy to the system
|
||||||
|
submit:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/strategy.submit
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies/submit
|
||||||
|
method: post
|
||||||
|
cors: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
topic: ${self:custom.snsTopic}
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
BASE_URL: ${self:custom.customDomain.domainName}/${self:custom.customDomain.stage}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
#submits a new strategy to the system
|
||||||
|
submit_github:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/strategy.submit_github
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies/submit/github
|
||||||
|
method: post
|
||||||
|
cors: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
topic: ${self:custom.snsTopic}
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
reservedConcurrency: 1
|
||||||
|
|
||||||
|
### TRADE REQUESTS
|
||||||
|
|
||||||
|
# loads all trades for a strategy and it's associated pairs
|
||||||
|
trades:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/trade.get_trades
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: strategies/{user}/{name}/{stake}/{asset}
|
||||||
|
method: get
|
||||||
|
cors: true
|
||||||
|
request:
|
||||||
|
parameter:
|
||||||
|
paths:
|
||||||
|
user: true
|
||||||
|
name: true
|
||||||
|
stake: true
|
||||||
|
asset: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
tradeTable: ${self:custom.tradeTable}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
# submits a new trade to the system
|
||||||
|
trade:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/trade.submit
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: trade
|
||||||
|
method: post
|
||||||
|
cors: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
tradeTopic: ${self:custom.snsTradeTopic}
|
||||||
|
reservedConcurrency: 5
|
||||||
|
|
||||||
|
# query aggregates by day and ticker for all strategies
|
||||||
|
trade-aggregate:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/trade.get_aggregated_trades
|
||||||
|
|
||||||
|
events:
|
||||||
|
- http:
|
||||||
|
path: trades/aggregate/{ticker}/{days}
|
||||||
|
method: get
|
||||||
|
cors: true
|
||||||
|
request:
|
||||||
|
parameter:
|
||||||
|
paths:
|
||||||
|
ticker: true
|
||||||
|
days: true
|
||||||
|
environment:
|
||||||
|
tradeTable: ${self:custom.tradeTable}
|
||||||
|
|
||||||
|
reservedConcurrency: 5
|
||||||
|
### SNS TRIGGERED FUNCTIONS
|
||||||
|
|
||||||
|
# stores the received message in the trade table
|
||||||
|
trade-store:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/trade.store
|
||||||
|
|
||||||
|
events:
|
||||||
|
- sns: ${self:custom.snsTradeTopic}
|
||||||
|
|
||||||
|
environment:
|
||||||
|
tradeTable: ${self:custom.tradeTable}
|
||||||
|
|
||||||
|
reservedConcurrency: 1
|
||||||
|
#backtests the strategy
|
||||||
|
#should be switched to utilze aws fargate instead
|
||||||
|
#and running a container
|
||||||
|
#so that we can evaluate long running tasks
|
||||||
|
backtest:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/backtesting_lambda.backtest
|
||||||
|
|
||||||
|
events:
|
||||||
|
- sns: ${self:custom.snsTopic}
|
||||||
|
|
||||||
|
environment:
|
||||||
|
topic: ${self:custom.snsTopic}
|
||||||
|
tradeTable: ${self:custom.tradeTable}
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
BASE_URL: https://${self:custom.customDomain.domainName}/${self:custom.customDomain.stage}
|
||||||
|
|
||||||
|
reservedConcurrency: 1
|
||||||
|
|
||||||
|
# schedules all registered strategies on a daily base
|
||||||
|
schedule:
|
||||||
|
memorySize: 128
|
||||||
|
handler: freqtrade/aws/backtesting_lambda.cron
|
||||||
|
|
||||||
|
events:
|
||||||
|
- schedule:
|
||||||
|
rate: rate(1440 minutes)
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
environment:
|
||||||
|
topic: ${self:custom.snsTopic}
|
||||||
|
tradeTable: ${self:custom.tradeTable}
|
||||||
|
strategyTable: ${self:custom.strategyTable}
|
||||||
|
|
||||||
|
reservedConcurrency: 1
|
6
setup.py
6
setup.py
@ -19,7 +19,7 @@ setup(name='freqtrade',
|
|||||||
packages=['freqtrade'],
|
packages=['freqtrade'],
|
||||||
scripts=['bin/freqtrade'],
|
scripts=['bin/freqtrade'],
|
||||||
setup_requires=['pytest-runner'],
|
setup_requires=['pytest-runner'],
|
||||||
tests_require=['pytest', 'pytest-mock', 'pytest-cov'],
|
tests_require=['pytest', 'pytest-mock', 'pytest-cov', 'moto'],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'ccxt',
|
'ccxt',
|
||||||
'SQLAlchemy',
|
'SQLAlchemy',
|
||||||
@ -35,7 +35,9 @@ setup(name='freqtrade',
|
|||||||
'TA-Lib',
|
'TA-Lib',
|
||||||
'tabulate',
|
'tabulate',
|
||||||
'cachetools',
|
'cachetools',
|
||||||
'coinmarketcap'
|
'coinmarketcap',
|
||||||
|
'boto3'
|
||||||
|
|
||||||
],
|
],
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
|
Loading…
Reference in New Issue
Block a user