Custom_Orders - Demo.
Early demonstration of Strategy controlling Order Type on the Exchange, Stop / Stop_limit / Market / limit etc The stake The price to submit Demo allows multiple orders to be placed, so ladding buys down to a fib Or buying and setting stop-loss Or Take profit in stages etc. Included a strategy demo with stubs for limit, market, take_profit and stop_limit orders Obviously very early code. Submitted as buys, stops etc are working / showing in GDAX. and we have hte order IDs to update trade table.
This commit is contained in:
parent
1044d15b17
commit
6748e897a5
296
freqtrade/custom_orders_strat.py
Normal file
296
freqtrade/custom_orders_strat.py
Normal file
@ -0,0 +1,296 @@
|
||||
# --- Do not remove these libs ---
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
from pandas import DataFrame
|
||||
import talib.abstract as ta
|
||||
|
||||
from datetime import datetime
|
||||
import time
|
||||
import timeit
|
||||
|
||||
|
||||
# --------------------------------
|
||||
class customorders(IStrategy):
|
||||
"""
|
||||
author@: Creslin
|
||||
|
||||
"""
|
||||
minimal_roi = {
|
||||
"0": 0.20
|
||||
}
|
||||
# Optimal stoploss designed for the strategy
|
||||
stoploss = -0.01
|
||||
|
||||
# Optimal ticker interval for the strategy
|
||||
ticker_interval = '5m'
|
||||
|
||||
def get_ticker_indicator(self):
|
||||
return int(self.ticker_interval[:-1])
|
||||
|
||||
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
|
||||
#
|
||||
|
||||
# dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
|
||||
time.sleep(1)
|
||||
|
||||
return dataframe
|
||||
|
||||
# # # If using Berlins expose pairs to strategy
|
||||
# def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
#
|
||||
# # RSI
|
||||
# dataframe['rsi'] = ta.RSI(dataframe, timeperiod=7)
|
||||
#
|
||||
# return dataframe
|
||||
|
||||
#####
|
||||
# EXAMPLE Strat - buys every even minute sells every odd
|
||||
####
|
||||
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||
if int(datetime.now().strftime('%M')) % 2 == 0: #buy = 1 every even minute
|
||||
print('----------------we are in strategy', datetime.now().time())
|
||||
b: float = 9999999.99
|
||||
print( "buy = 1")
|
||||
else:
|
||||
b = 0.00000001
|
||||
dataframe.loc[
|
||||
(
|
||||
# L-RSI When below 0.01 / vfi -26
|
||||
#(dataframe['lrsi'] < 0.5) &
|
||||
#(dataframe['vfi'] < -4)
|
||||
(dataframe['close'] < b )
|
||||
),
|
||||
'buy'] = 1
|
||||
return dataframe
|
||||
|
||||
#####
|
||||
# EXAMPLE Strat - buys every even minute sells every odd
|
||||
####
|
||||
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
|
||||
if int(datetime.now().strftime('%M'))% 2 != 0: ## sell = 1 every ODD minute
|
||||
print('----------------we are in strategy', datetime.now().time())
|
||||
s: float = 9999999.99
|
||||
print("sell = 1")
|
||||
else:
|
||||
s = 0.00000001
|
||||
dataframe.loc[
|
||||
(
|
||||
# vfi 0.99
|
||||
#(dataframe['vfi'] > 0.41)
|
||||
(dataframe['close'] < s )
|
||||
),
|
||||
'sell'] = 1
|
||||
return dataframe
|
||||
|
||||
def stop_stops_plugin(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
stop_stops_pligin. To stop a buy where X trades from Y have stopped out.
|
||||
i.e to remove a pair from being bought that is on a losing streak.
|
||||
|
||||
Only processed if buy=1
|
||||
"""
|
||||
"""
|
||||
Called before a trade executes if exists,
|
||||
:param dataframe:
|
||||
:param pair:
|
||||
:return:
|
||||
"""
|
||||
|
||||
from freqtrade.persistence import Trade
|
||||
import pandas as pd
|
||||
df = dataframe
|
||||
|
||||
# Only check buys, else return DF untouched
|
||||
|
||||
if df.iloc[-1]['buy'] == 1:
|
||||
|
||||
print("STOP_STOPS STOP_STOPS STOP_STOPS STOP_STOP STOP_STOPS STOP_STOP STOP_STOPS")
|
||||
|
||||
from pandas import set_option
|
||||
set_option('display.max_rows', 2000)
|
||||
set_option('display.max_columns', 8)
|
||||
|
||||
def closed_pair_trades(pair):
|
||||
"""
|
||||
To return list of closed trades for a pair
|
||||
enriched with open/closd dates, profit, and stake
|
||||
|
||||
:param pair:
|
||||
:return: df_c pair open, close, profit, stake
|
||||
"""
|
||||
pair = pair
|
||||
df_c = pd.DataFrame(columns=['pair', 'profit', 'open_date', 'close_date', 'stake'])
|
||||
|
||||
pair_trades = Trade.query.filter(Trade.pair.is_(pair)).all()
|
||||
for pair_trade in pair_trades:
|
||||
if pair_trade.is_open == False:
|
||||
# print("closed trade for", pair, "closed with ", pair_trade.close_profit )
|
||||
|
||||
p = pair
|
||||
pcp = pair_trade.close_profit
|
||||
od = pair_trade.open_date
|
||||
cd = pair_trade.close_date
|
||||
sa = pair_trade.stake_amount
|
||||
|
||||
df_c = df_c.append([{'pair':p, 'profit':pcp, 'open_date':od,
|
||||
'close_date':cd, 'stake':sa}], ignore_index=True)
|
||||
return df_c
|
||||
|
||||
def has_past_perf(df_c, lost_the_last=1, out_of=1):
|
||||
"""
|
||||
Return if pair lost_the_last X trades out_of Y trades
|
||||
:param df_c: dataframe of closed trades for the pair
|
||||
:param lost_the_last: number of bad trades to look for
|
||||
:param out_of: within the last number over trades closed
|
||||
:return: bool
|
||||
"""
|
||||
lost_the_last =lost_the_last
|
||||
out_of = out_of
|
||||
df_c = df_c
|
||||
|
||||
df_c_tail = df_c.tail(out_of)
|
||||
lost_from_last_count = df_c_tail[df_c_tail['profit'] < 0].shapec
|
||||
if lost_from_last_count[0] >= lost_the_last:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# load this pairs closed trades into a df
|
||||
# df_c = closed_pair_trades(pair=pair)
|
||||
# print("df_c", df_c)
|
||||
|
||||
# Get bool if on pairs past profitable trades count, cancel the buy if limit hit
|
||||
# past_perf = has_past_perf(df_c, lost_the_last=9, out_of=10)
|
||||
# if past_perf:
|
||||
# #set buy to 0
|
||||
# print("STOP STOP SETTING BUY TO ZERO ")
|
||||
# df.loc['buy'] = 0
|
||||
|
||||
dataframe = df
|
||||
return dataframe
|
||||
|
||||
#def money_mgt_plugin(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
def money_mgt_plugin(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
pre_trade_mgt.
|
||||
To allow money mamagement and or other checks to be put against dataframe
|
||||
with buy or sell set to "1"
|
||||
|
||||
Returns a list of custom order trades.
|
||||
Each custom order to include, above the needed fields for exchange
|
||||
|
||||
dict['order_type'] = 'a_open_limit'
|
||||
|
||||
order_types are later ordered and executed in alphabetical order.
|
||||
order to enter market are executed before stops and take profits are played in.
|
||||
Supported types are:
|
||||
- a_open_limit # enter market with limit order
|
||||
- a_open_market # enter market with market order
|
||||
- b_stop_limit # set a stop_limit order
|
||||
- b_stop_market # set a stop_market order
|
||||
- c_take_limit # set a take profit limit order
|
||||
- c_take_market # set a take profit market order
|
||||
|
||||
Multipe orders can be processed but an asset may only have the sum of its open value
|
||||
in either stop or take profit orders.
|
||||
|
||||
i.e we can open a $500 for 500 tokens position and have any combination of
|
||||
stop or take profit orders at any prices that COMBINED do not break 500 tokens.
|
||||
|
||||
"one" common scenario may be an 'a_open_limit' or 'a_open_market' order
|
||||
and 'b_stop_limit' or 'b_stop_market' order to protect capital.
|
||||
|
||||
"""
|
||||
print("MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT MONEY_MGT")
|
||||
df = dataframe
|
||||
|
||||
# If buy = 1 and Exchange is GDAX
|
||||
if df.iloc[-1]['buy'] == 1:
|
||||
exchange = self.config['exchange']['name']
|
||||
|
||||
# GDAX Stop-Limit Order
|
||||
if exchange == "gdax":
|
||||
|
||||
custom_orders = []
|
||||
limit = {}
|
||||
take_profit = {}
|
||||
|
||||
# Take profit Market example "b_take_market"
|
||||
# open market order example "a_open_market"
|
||||
|
||||
# Open limit order example
|
||||
amount = 0.5
|
||||
limit_price = df.iloc[-1]['close'] * 1.05 # pay 5% for testing order are made
|
||||
|
||||
limit = {}
|
||||
limit["order_type"] = "a_open_limit"
|
||||
limit["symbol"] = pair
|
||||
limit["type"] = "limit"
|
||||
limit["side"] = "buy"
|
||||
limit["amount"] = amount
|
||||
limit["price"] = limit_price
|
||||
params: dict = {}
|
||||
limit["params"] = params
|
||||
|
||||
custom_orders.append(limit)
|
||||
|
||||
# Market Example
|
||||
market_amount = 1
|
||||
|
||||
market = {}
|
||||
market_price = 1.05 # pay 5% for testing order are made
|
||||
market["order_type"] = "a_open_market"
|
||||
market["symbol"] = pair
|
||||
market["type"] = "market"
|
||||
market["side"] = "buy"
|
||||
market["amount"] = market_amount
|
||||
params: dict = {}
|
||||
market["params"] = params
|
||||
|
||||
custom_orders.append(market)
|
||||
|
||||
# Stop Limit order
|
||||
stop_limit = {}
|
||||
stop_limit_amount = 0.01
|
||||
stop_limit_price = df.iloc[-1]['close'] * 0.90 # take 10% hard code for testing
|
||||
stop_price = df.iloc[-1]['close'] * 0.95 # trigger at 7% loss, hard code for testing
|
||||
|
||||
stop_limit['order_type'] = 'b_stop_limit'
|
||||
stop_limit['symbol'] = pair
|
||||
stop_limit['type'] = 'limit'
|
||||
stop_limit['side'] = 'sell'
|
||||
stop_limit['amount'] = stop_limit_amount
|
||||
stop_limit['price'] = stop_limit_price
|
||||
params: dict = {}
|
||||
params['stop'] = 'loss'
|
||||
params['stop_price'] = stop_price
|
||||
stop_limit['params'] = params
|
||||
|
||||
custom_orders.append(stop_limit)
|
||||
|
||||
# Take profit Limit example
|
||||
take_profit_limit_price = df.iloc[-1]['close'] * 1.2 # take_proft at 20% - for demo
|
||||
take_profit_amount = 0.01
|
||||
|
||||
take_profit["order_type"] = "c_take_profit_limit"
|
||||
take_profit["order_template"] = (pair, amount, take_profit_limit_price)
|
||||
take_profit["symbol"] = pair
|
||||
take_profit["type"] = "limit"
|
||||
take_profit["side"] = "sell"
|
||||
take_profit["amount"] = take_profit_amount
|
||||
take_profit["price"] = take_profit_limit_price
|
||||
take_profit_params: dict = {}
|
||||
take_profit["params"] = take_profit_params
|
||||
|
||||
# custom_orders.append(take_profit)
|
||||
|
||||
#todo Add library of trade templates for binance, polo, gdax...
|
||||
#todo Code to check have all the fields and sense check
|
||||
|
||||
# Cancel Legacy buy/sell signals if there are custom orders.
|
||||
# Set buy / sell = 0 if we have custom orders
|
||||
dataframe.loc['buy'] = 0
|
||||
dataframe.loc['sell'] = 0
|
||||
|
||||
return dataframe, custom_orders
|
||||
|
||||
return dataframe, None
|
@ -91,7 +91,7 @@ class Exchange(object):
|
||||
'secret': exchange_config.get('secret'),
|
||||
'password': exchange_config.get('password'),
|
||||
'uid': exchange_config.get('uid', ''),
|
||||
'enableRateLimit': True,
|
||||
'enableRateLimit': exchange_config.get('ccxt_rate_limit', True),
|
||||
})
|
||||
except (KeyError, AttributeError):
|
||||
raise OperationalException(f'Exchange {name} is not supported')
|
||||
@ -185,6 +185,79 @@ class Exchange(object):
|
||||
price = ceil(big_price) / pow(10, symbol_prec)
|
||||
return price
|
||||
|
||||
def submit_custom_order(self, symbol, type, side, amount, price=None, params={}):
|
||||
pair = symbol
|
||||
rate = price
|
||||
try:
|
||||
|
||||
return self._api.createOrder(symbol, type, side, amount, price, params)
|
||||
|
||||
except ccxt.InsufficientFunds as e:
|
||||
raise DependencyException(
|
||||
f'Insufficient funds to create limit buy order on market {pair}.'
|
||||
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
|
||||
f'Message: {e}')
|
||||
|
||||
except ccxt.InvalidOrder as e:
|
||||
raise DependencyException(
|
||||
f'Could not create limit buy order on market {pair}.'
|
||||
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
|
||||
f'Message: {e}')
|
||||
|
||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||
raise TemporaryError(
|
||||
f'Could not place buy order due to {e.__class__.__name__}. Message: {e}')
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e)
|
||||
|
||||
def customOrders(self, custom_orders):
|
||||
'''
|
||||
To process custom orders.
|
||||
These are a list of dicts of orders created in strategy.
|
||||
|
||||
:param custom_orders:
|
||||
:return:
|
||||
'''
|
||||
sorted_custom_orders = sorted(custom_orders, key=lambda k: k['order_type'])
|
||||
|
||||
for order in sorted_custom_orders:
|
||||
order_type = order.get('order_type') if order.get('order_type') else ""
|
||||
symbol = order.get('symbol') if order.get('symbol') else ""
|
||||
type = order.get('type') if order.get('type') else ""
|
||||
side = order.get('side') if order.get('side') else ""
|
||||
amount = order.get('amount') if order.get('amount') else None
|
||||
price = order.get('price') if order.get('price') else None
|
||||
stop = order.get('stop') if order.get('stop') else ""
|
||||
stop_price = order.get('stop_price') if order.get('stop_price') else None
|
||||
params = order.get('params') if order.get('params') else {}
|
||||
|
||||
# Set the precision for amount and price(rate) as accepted by the exchange
|
||||
if amount:
|
||||
float(amount)
|
||||
amount = self.symbol_amount_prec(symbol, amount)
|
||||
float(amount)
|
||||
|
||||
if price:
|
||||
float(price)
|
||||
price = self.symbol_price_prec(symbol, price)
|
||||
float(price)
|
||||
|
||||
if params.get('stop_price'):
|
||||
stop_price = params['stop_price']
|
||||
float(stop_price)
|
||||
params['stop_price'] = self.symbol_price_prec(symbol, stop_price)
|
||||
float(stop_price)
|
||||
|
||||
# destroy the params dict if its empty, not to pass to CCXT
|
||||
if not params:
|
||||
params = ""
|
||||
|
||||
# create_order(self, symbol, type, side, amount, price=None, params={})
|
||||
print(symbol, type, side, amount, price, params)
|
||||
custom_order_id = self.submit_custom_order(symbol=symbol, type=type, side=side,
|
||||
amount=amount, price=price, params=params)
|
||||
print("custom order_id is", custom_order_id)
|
||||
|
||||
def buy(self, pair: str, rate: float, amount: float) -> Dict:
|
||||
if self._conf['dry_run']:
|
||||
order_id = f'dry_run_buy_{randint(0, 10**6)}'
|
||||
|
@ -330,14 +330,15 @@ class FreqtradeBot(object):
|
||||
|
||||
# Pick pair based on buy signals
|
||||
for _pair in whitelist:
|
||||
thistory = self.exchange.get_ticker_history(_pair, interval)
|
||||
(buy, sell) = self.strategy.get_signal(_pair, interval, thistory)
|
||||
(buy, sell, custom_orders) = self.strategy.get_signal(self.exchange, _pair, interval)
|
||||
|
||||
if buy and not sell:
|
||||
return self.execute_buy(_pair, stake_amount)
|
||||
return self.execute_buy(_pair, stake_amount, custom_orders)
|
||||
if custom_orders:
|
||||
return self.execute_buy(_pair, stake_amount, custom_orders)
|
||||
return False
|
||||
|
||||
def execute_buy(self, pair: str, stake_amount: float) -> bool:
|
||||
def execute_buy(self, pair: str, stake_amount: float, custom_orders: list) -> bool:
|
||||
"""
|
||||
Executes a limit buy for the given pair
|
||||
:param pair: pair for which we want to create a LIMIT_BUY
|
||||
@ -352,6 +353,13 @@ class FreqtradeBot(object):
|
||||
buy_limit = self.get_target_bid(self.exchange.get_ticker(pair))
|
||||
|
||||
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit)
|
||||
|
||||
# if custom trades are set over_ride stake_amount test
|
||||
|
||||
if custom_orders:
|
||||
# todo condider how or if to do this for limit, market, stop cutom_orders.
|
||||
logger.info("bypassing stake amount test for custom_orders")
|
||||
else:
|
||||
if min_stake_amount is not None and min_stake_amount > stake_amount:
|
||||
logger.warning(
|
||||
f'Can\'t open a new trade for {pair_s}: stake amount'
|
||||
@ -359,6 +367,14 @@ class FreqtradeBot(object):
|
||||
)
|
||||
return False
|
||||
|
||||
# Call exchange to make order or customOrder
|
||||
if custom_orders:
|
||||
# Call custom_oders in exchange/__init__.py
|
||||
logger.info("Calling customOrders in exchange")
|
||||
order_ids = self.exchange.customOrders(custom_orders)
|
||||
|
||||
else:
|
||||
|
||||
amount = stake_amount / buy_limit
|
||||
|
||||
order_id = self.exchange.buy(pair, buy_limit, amount)['id']
|
||||
@ -393,6 +409,8 @@ class FreqtradeBot(object):
|
||||
Trade.session.flush()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def process_maybe_execute_buy(self) -> bool:
|
||||
"""
|
||||
Tries to execute a buy trade in a safe way
|
||||
@ -497,9 +515,8 @@ class FreqtradeBot(object):
|
||||
(buy, sell) = (False, False)
|
||||
experimental = self.config.get('experimental', {})
|
||||
if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'):
|
||||
ticker = self.exchange.get_ticker_history(trade.pair, self.strategy.ticker_interval)
|
||||
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval,
|
||||
ticker)
|
||||
(buy, sell, custom_orders) = self.strategy.get_signal(self.exchange,
|
||||
trade.pair, self.strategy.ticker_interval)
|
||||
|
||||
should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell)
|
||||
if should_sell.sell_flag:
|
||||
|
@ -14,6 +14,7 @@ from pandas import DataFrame
|
||||
|
||||
from freqtrade import constants
|
||||
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.persistence import Trade
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -74,29 +75,29 @@ class IStrategy(ABC):
|
||||
self.config = config
|
||||
|
||||
@abstractmethod
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def populate_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
Populate indicators that will be used in the Buy and Sell strategy
|
||||
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:param pair: Pair currently analyzed
|
||||
:return: a Dataframe with all mandatory indicators for the strategies
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def populate_buy_trend(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the buy signal for the given dataframe
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:param pair: Pair currently analyzed
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def populate_sell_trend(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the sell signal for the given dataframe
|
||||
:param dataframe: DataFrame
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:param pair: Pair currently analyzed
|
||||
:return: DataFrame with sell column
|
||||
"""
|
||||
|
||||
@ -106,31 +107,51 @@ class IStrategy(ABC):
|
||||
"""
|
||||
return self.__class__.__name__
|
||||
|
||||
def analyze_ticker(self, ticker_history: List[Dict], metadata: dict) -> DataFrame:
|
||||
def analyze_ticker(self, ticker_history: List[Dict], pair: str) -> DataFrame:
|
||||
"""
|
||||
Parses the given ticker history and returns a populated DataFrame
|
||||
add several TA indicators and buy signal to it
|
||||
:return DataFrame with ticker data and indicator data
|
||||
:return custom_orders list of dicts containing any custom trades from strategy
|
||||
"""
|
||||
dataframe = parse_ticker_dataframe(ticker_history)
|
||||
dataframe = self.advise_indicators(dataframe, metadata)
|
||||
dataframe = self.advise_buy(dataframe, metadata)
|
||||
dataframe = self.advise_sell(dataframe, metadata)
|
||||
return dataframe
|
||||
dataframe = self.advise_indicators(dataframe, pair)
|
||||
dataframe = self.advise_buy(dataframe, pair)
|
||||
dataframe = self.advise_sell(dataframe, pair)
|
||||
|
||||
def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
|
||||
## Strategy Plugin stop_stops_mgt' if in strategy.
|
||||
# Analyzes stop-count of past X from Y trades, cancels BUY if True
|
||||
if hasattr(self, 'stop_stops_plugin'):
|
||||
dataframe = self.stop_stops_plugin(dataframe, pair)
|
||||
|
||||
## Strategy Plugin 'win_rate' if in strategy.
|
||||
# Calls micro backslap on proposed buy=1 pair. If winrate is poor, cancels BUY
|
||||
if hasattr(self, 'win_rate_plugin'):
|
||||
dataframe = self.win_rate_plugin(dataframe, pair)
|
||||
|
||||
## Strategy Plugin 'money_mgt' if in strategy.
|
||||
# Money management for any order in buy. Determines stake size, stop-loss, take-profit
|
||||
# Returns dataframe, custom_orders (list of dicts)
|
||||
if hasattr(self, 'money_mgt_plugin'):
|
||||
dataframe, custom_orders = self.money_mgt_plugin(dataframe, pair)
|
||||
|
||||
return dataframe, custom_orders
|
||||
|
||||
def get_signal(self, exchange: Exchange, pair: str, interval: str) -> Tuple[bool, bool]:
|
||||
"""
|
||||
Calculates current signal based several technical analysis indicators
|
||||
:param pair: pair in format ANT/BTC
|
||||
:param interval: Interval to use (in min)
|
||||
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
|
||||
:return: (Buy, Sell, custom_orders) A bool-tuple indicating buy/sell signal,
|
||||
and any custom_orders
|
||||
"""
|
||||
ticker_hist = exchange.get_ticker_history(pair, interval)
|
||||
if not ticker_hist:
|
||||
logger.warning('Empty ticker history for pair %s', pair)
|
||||
return False, False
|
||||
|
||||
try:
|
||||
dataframe = self.analyze_ticker(ticker_hist, {'pair': pair})
|
||||
dataframe, custom_orders = self.analyze_ticker(ticker_hist, pair)
|
||||
except ValueError as error:
|
||||
logger.warning(
|
||||
'Unable to analyze ticker for pair %s: %s',
|
||||
@ -155,13 +176,13 @@ class IStrategy(ABC):
|
||||
# Check if dataframe is out of date
|
||||
signal_date = arrow.get(latest['date'])
|
||||
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
|
||||
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
|
||||
logger.warning(
|
||||
'Outdated history for pair %s. Last tick is %s minutes old',
|
||||
pair,
|
||||
(arrow.utcnow() - signal_date).seconds // 60
|
||||
)
|
||||
return False, False
|
||||
# if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
|
||||
# logger.warning(
|
||||
# 'Outdated history for pair %s. Last tick is %s minutes old',
|
||||
# pair,
|
||||
# (arrow.utcnow() - signal_date).seconds // 60
|
||||
# )
|
||||
# return False, False
|
||||
|
||||
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
|
||||
logger.debug(
|
||||
@ -171,7 +192,7 @@ class IStrategy(ABC):
|
||||
str(buy),
|
||||
str(sell)
|
||||
)
|
||||
return buy, sell
|
||||
return buy, sell, custom_orders
|
||||
|
||||
def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
|
||||
sell: bool) -> SellCheckTuple:
|
||||
@ -212,7 +233,6 @@ class IStrategy(ABC):
|
||||
"""
|
||||
Based on current profit of the trade and configured (trailing) stoploss,
|
||||
decides to sell or not
|
||||
:param current_profit: current profit in percent
|
||||
"""
|
||||
|
||||
trailing_stop = self.config.get('trailing_stop', False)
|
||||
@ -240,15 +260,12 @@ class IStrategy(ABC):
|
||||
# check if we have a special stop loss for positive condition
|
||||
# and if profit is positive
|
||||
stop_loss_value = self.stoploss
|
||||
sl_offset = self.config.get('trailing_stop_positive_offset', 0.0)
|
||||
|
||||
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
|
||||
if 'trailing_stop_positive' in self.config and current_profit > 0:
|
||||
|
||||
# Ignore mypy error check in configuration that this is a float
|
||||
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
|
||||
logger.debug(f"using positive stop loss mode: {stop_loss_value} "
|
||||
f"with offset {sl_offset:.4g} "
|
||||
f"since we have profit {current_profit:.4f}%")
|
||||
f"since we have profit {current_profit}")
|
||||
|
||||
trade.adjust_stop_loss(current_rate, stop_loss_value)
|
||||
|
||||
@ -275,15 +292,15 @@ class IStrategy(ABC):
|
||||
"""
|
||||
Creates a dataframe and populates indicators for given ticker data
|
||||
"""
|
||||
return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), {'pair': pair})
|
||||
return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), pair)
|
||||
for pair, pair_data in tickerdata.items()}
|
||||
|
||||
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def advise_indicators(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
Populate indicators that will be used in the Buy and Sell strategy
|
||||
This method should not be overridden.
|
||||
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
|
||||
:param metadata: Additional information, like the currently traded pair
|
||||
:param pair: The currently traded pair
|
||||
:return: a Dataframe with all mandatory indicators for the strategies
|
||||
"""
|
||||
if self._populate_fun_len == 2:
|
||||
@ -291,14 +308,14 @@ class IStrategy(ABC):
|
||||
"the current function headers!", DeprecationWarning)
|
||||
return self.populate_indicators(dataframe) # type: ignore
|
||||
else:
|
||||
return self.populate_indicators(dataframe, metadata)
|
||||
return self.populate_indicators(dataframe, pair)
|
||||
|
||||
def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def advise_buy(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the buy signal for the given dataframe
|
||||
This method should not be overridden.
|
||||
:param dataframe: DataFrame
|
||||
:param pair: Additional information, like the currently traded pair
|
||||
:param pair: The currently traded pair
|
||||
:return: DataFrame with buy column
|
||||
"""
|
||||
if self._buy_fun_len == 2:
|
||||
@ -306,14 +323,14 @@ class IStrategy(ABC):
|
||||
"the current function headers!", DeprecationWarning)
|
||||
return self.populate_buy_trend(dataframe) # type: ignore
|
||||
else:
|
||||
return self.populate_buy_trend(dataframe, metadata)
|
||||
return self.populate_buy_trend(dataframe, pair)
|
||||
|
||||
def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
def advise_sell(self, dataframe: DataFrame, pair: str) -> DataFrame:
|
||||
"""
|
||||
Based on TA indicators, populates the sell signal for the given dataframe
|
||||
This method should not be overridden.
|
||||
:param dataframe: DataFrame
|
||||
:param pair: Additional information, like the currently traded pair
|
||||
:param pair: The currently traded pair
|
||||
:return: DataFrame with sell column
|
||||
"""
|
||||
if self._sell_fun_len == 2:
|
||||
@ -321,4 +338,4 @@ class IStrategy(ABC):
|
||||
"the current function headers!", DeprecationWarning)
|
||||
return self.populate_sell_trend(dataframe) # type: ignore
|
||||
else:
|
||||
return self.populate_sell_trend(dataframe, metadata)
|
||||
return self.populate_sell_trend(dataframe, pair)
|
||||
|
Loading…
Reference in New Issue
Block a user