diff --git a/.gitignore b/.gitignore index bc7d30f3b..472f079a5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,8 +26,8 @@ dist/ downloads/ eggs/ .eggs/ -lib/ -lib64/ +#lib/ +#lib64/ parts/ sdist/ var/ diff --git a/Dockerfile b/Dockerfile index afafd93c1..62003930c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM python:3.6.5-slim-stretch # 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 | \ tar xzvf - && \ cd ta-lib && \ diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index d5e9c929f..9c342141f 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -224,7 +224,7 @@ class Arguments(object): Builds and attaches all subcommands :return: None """ - from freqtrade.optimize import backtesting, hyperopt + from freqtrade.optimize import backtesting subparsers = self.parser.add_subparsers(dest='subparser') @@ -235,10 +235,14 @@ class Arguments(object): self.backtesting_options(backtesting_cmd) # Add hyperopt subcommand - hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module') - hyperopt_cmd.set_defaults(func=hyperopt.start) - self.optimizer_shared_options(hyperopt_cmd) - self.hyperopt_options(hyperopt_cmd) + try: + from freqtrade.optimize import hyperopt + hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module') + hyperopt_cmd.set_defaults(func=hyperopt.start) + self.optimizer_shared_options(hyperopt_cmd) + self.hyperopt_options(hyperopt_cmd) + except ImportError as e: + logging.warn("no hyper opt found - skipping support for it") @staticmethod def parse_timerange(text: Optional[str]) -> TimeRange: @@ -340,7 +344,6 @@ class Arguments(object): default=None ) - self.parser.add_argument( '--plot-macd', help='Renders a macd chart of the given ' @@ -383,14 +386,6 @@ class Arguments(object): 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: """ Parses given arguments for testdata download diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 1997d67c6..56d6e9c81 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -33,7 +33,7 @@ class FreqtradeBot(object): 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 :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'] return ticker['ask'] + balance * (ticker['last'] - ticker['ask']) - def create_trade(self) -> bool: """ Checks the implemented trading indicator(s) for a randomly picked pair, @@ -440,19 +439,20 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ logger.info('Using order book for selling...') orderBook = exchange.get_order_book(trade.pair) # 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): - sell_rate = orderBook['asks'][i-1][0] + for i in range(self.config['ask_strategy']['book_order_min'], + self.config['ask_strategy']['book_order_max'] + 1): + sell_rate = orderBook['asks'][i - 1][0] if self.check_sell(trade, sell_rate, buy, sell): return True break else: if self.check_sell(trade, sell_rate, buy, sell): return True - + logger.info('Found no sell signals for whitelisted currencies. Trying again..') 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): self.execute_sell(trade, sell_rate) return True @@ -483,12 +483,15 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ continue ordertime = arrow.get(order['datetime']).datetime + print(order) # Check if trade is still actually open - if (int(order['filled']) == 0) and (order['status']=='open'): - if order['side'] == 'buy' and ordertime < timeoutthreashold: - self.handle_timedout_limit_buy(trade, order) - elif order['side'] == 'sell' and ordertime < timeoutthreashold: - self.handle_timedout_limit_sell(trade, order) +# 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: + self.handle_timedout_limit_buy(trade, order) + elif order['side'] == 'sell' and ordertime < timeoutthreashold: + self.handle_timedout_limit_sell(trade, order) # FIX: 20180110, why is cancel.order unconditionally here, whereas # it is conditionally called in the @@ -579,7 +582,7 @@ with limit `{buy_limit:.8f} ({stake_amount:.6f} \ fiat ) 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 # Ignore the FIAT value and does not show the stake_currency as well diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 713f23ecc..d2ecf0829 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -31,6 +31,7 @@ class Backtesting(object): backtesting = Backtesting(config) backtesting.start() """ + def __init__(self, config: Dict[str, Any]) -> None: self.config = config self.analyze = Analyze(self.config) @@ -59,42 +60,48 @@ class Backtesting(object): for frame in data.values() ] return min(timeframe, key=operator.itemgetter(0))[0], \ - max(timeframe, key=operator.itemgetter(1))[1] + max(timeframe, key=operator.itemgetter(1))[1] def _generate_text_table(self, data: Dict[str, Dict], results: DataFrame) -> str: """ Generates and returns a text table for the given backtest data and the results dataframe :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 = [] - headers = ['pair', 'buy count', 'avg profit %', + headers = ['pair', 'buy count', 'avg profit %', 'cum profit %', 'total profit ' + stake_currency, 'avg duration', 'profit', 'loss'] for pair in data: result = results[results.currency == pair] + print(results) tabular_data.append([ pair, len(result.index), result.profit_percent.mean() * 100.0, + result.profit_percent.sum() * 100.0, result.profit_BTC.sum(), result.duration.mean(), len(result[result.profit_BTC > 0]), len(result[result.profit_BTC < 0]) ]) - # Append Total tabular_data.append([ 'TOTAL', len(results.index), results.profit_percent.mean() * 100.0, + results.profit_percent.sum() * 100.0, results.profit_BTC.sum(), results.duration.mean(), 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( self, pair: str, buy_row: DataFrame, @@ -127,7 +134,9 @@ class Backtesting(object): pair, trade.calc_profit_percent(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 return None @@ -193,6 +202,7 @@ class Backtesting(object): if ret: row2, trade_entry, next_date = ret lock_pair_until = next_date + trades.append(trade_entry) if record: # Note, need to be json.dump friendly @@ -207,10 +217,12 @@ class Backtesting(object): if record and record.find('trades') >= 0: logger.info('Dumping backtest results to %s', recordfilename) 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) - def start(self) -> None: + def start(self): """ Run a backtesting end-to-end :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]: """ diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 0609b50f2..acb4258c1 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -6,6 +6,7 @@ This module load custom strategies import importlib.util import inspect import logging +from base64 import urlsafe_b64decode from collections import OrderedDict from typing import Optional, Dict, Type @@ -64,6 +65,13 @@ class StrategyResolver(object): key=lambda t: t[0])) 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( 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 abs_paths.insert(0, extra_dir) - try: - # check if given strategy matches an url - 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")) - name = os.path.basename(urlparse(strategy_name).path) + # check if the given strategy is provided as name, value pair + # where the value is the strategy encoded in base 64 + if ":" in strategy_name and "http" not in strategy_name: + strat = strategy_name.split(":") - 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() strategy_name = os.path.splitext(name)[0] @@ -98,8 +107,30 @@ class StrategyResolver(object): # register temp path with the bot abs_paths.insert(0, temp.absolute()) - except requests.RequestException: - logger.debug("received error trying to fetch strategy remotely, carry on!") + # 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: + logger.debug("received error trying to fetch strategy remotely, carry on!") for path in abs_paths: strategy = self._search_strategy(path, strategy_name) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index fd4421d22..a4325dcef 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -15,6 +15,10 @@ from freqtrade.analyze import Analyze from freqtrade import constants from freqtrade.freqtradebot import FreqtradeBot +import moto +import boto3 +import os + logging.getLogger('').setLevel(logging.INFO) @@ -523,6 +527,7 @@ def result(): with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file: return Analyze.parse_ticker_dataframe(json.load(data_file)) + # FIX: # Create an fixture/function # that inserts a trade of some type and open-status diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 6f59d112d..429a0309a 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -365,14 +365,10 @@ def test_generate_text_table(default_conf, mocker): ) result_str = ( - '| pair | buy count | avg profit % | ' - 'total profit BTC | avg duration | profit | loss |\n' - '|:--------|------------:|---------------:|' - '-------------------:|---------------:|---------:|-------:|\n' - '| ETH/BTC | 2 | 15.00 | ' - '0.60000000 | 20.0 | 2 | 0 |\n' - '| TOTAL | 2 | 15.00 | ' - '0.60000000 | 20.0 | 2 | 0 |' +"""| pair | buy count | avg profit % | cum profit % | total profit BTC | avg duration | profit | loss | +|:--------|------------:|---------------:|---------------:|-------------------:|---------------:|---------:|-------:| +| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 | +| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 |""" ) 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) assert len(results) == 3 # Assert file_dump_json was only called once + print(names) assert names == ['backtest-result.json'] records = records[0] # Ensure records are of correct type diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index e1965baf6..2a8580292 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -28,10 +28,10 @@ def test_load_strategy(result): def test_load_strategy_from_url(result): resolver = StrategyResolver() - resolver._load_strategy('https://raw.githubusercontent.com/berlinguyinca' - '/freqtrade-trading-strategies' - '/master/user_data/strategies/Simple.py') - assert hasattr(resolver.strategy, 'populate_indicators') + resolver._load_strategy('https://freq.isaac.international/' + 'dev/strategies/GBPAQEFGGWCMWVFU34P' + 'MVGS4P2NJR4IDFNVI4LTCZAKJAD3JCXUMBI4J/AverageStrategy/code') + assert hasattr(resolver.strategy, 'minimal_roi') assert 'adx' in resolver.strategy.populate_indicators(result) diff --git a/freqtrade/tests/test_arguments.py b/freqtrade/tests/test_arguments.py index 8a41e3379..b94cddd31 100644 --- a/freqtrade/tests/test_arguments.py +++ b/freqtrade/tests/test_arguments.py @@ -63,6 +63,7 @@ def test_scripts_options() -> None: arguments = Arguments(['-p', 'ETH/BTC'], '') arguments.scripts_options() args = arguments.get_parsed_arg() + print(args.pair) assert args.pair == 'ETH/BTC' diff --git a/lib/libta_lib.a b/lib/libta_lib.a new file mode 100644 index 000000000..5adbbfdfb Binary files /dev/null and b/lib/libta_lib.a differ diff --git a/lib/libta_lib.la b/lib/libta_lib.la new file mode 100755 index 000000000..a02606b41 --- /dev/null +++ b/lib/libta_lib.la @@ -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' diff --git a/lib/libta_lib.so.0 b/lib/libta_lib.so.0 new file mode 120000 index 000000000..f1c7fea7f --- /dev/null +++ b/lib/libta_lib.so.0 @@ -0,0 +1 @@ +libta_lib.so.0.0.0 \ No newline at end of file diff --git a/lib/libta_lib.so.0.0.0 b/lib/libta_lib.so.0.0.0 new file mode 100755 index 000000000..fffce0226 Binary files /dev/null and b/lib/libta_lib.so.0.0.0 differ diff --git a/requirements-aws.txt b/requirements-aws.txt new file mode 100644 index 000000000..3e5dbca8d --- /dev/null +++ b/requirements-aws.txt @@ -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 diff --git a/requirements.txt b/requirements.txt index 2272d47b8..96e4855b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,9 +17,10 @@ pytest-mock==1.10.0 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 # pyup: ignore +#networkx==1.11 +git+git://github.com/berlinguyinca/networkx@v1.11 tabulate==0.8.2 coinmarketcap==5.0.3 - +simplejson==3.15.0 # Required for plotting data #plotly==2.3.0 diff --git a/serverless.yml b/serverless.yml new file mode 100644 index 000000000..798187488 --- /dev/null +++ b/serverless.yml @@ -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 diff --git a/setup.py b/setup.py index 34cf42535..c12d56949 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup(name='freqtrade', packages=['freqtrade'], scripts=['bin/freqtrade'], setup_requires=['pytest-runner'], - tests_require=['pytest', 'pytest-mock', 'pytest-cov'], + tests_require=['pytest', 'pytest-mock', 'pytest-cov', 'moto'], install_requires=[ 'ccxt', 'SQLAlchemy', @@ -35,7 +35,9 @@ setup(name='freqtrade', 'TA-Lib', 'tabulate', 'cachetools', - 'coinmarketcap' + 'coinmarketcap', + 'boto3' + ], include_package_data=True, zip_safe=False,