Merge branch 'master' into dashboard

This commit is contained in:
Saleh Mir
2021-10-11 19:54:20 +02:00
27 changed files with 261 additions and 340 deletions

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"

View File

@@ -200,7 +200,7 @@ config = {
# Below configurations are related to the optimize mode
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
'optimization': {
# sharpe, calmar, sortino, omega
# sharpe, calmar, sortino, omega, serenity, smart sharpe, smart sortino
'ratio': 'sharpe',
},

View File

@@ -12,6 +12,7 @@ class order_statuses:
ACTIVE = 'ACTIVE'
CANCELED = 'CANCELED'
EXECUTED = 'EXECUTED'
PARTIALLY_EXECUTED = 'PARTIALLY EXECUTED'
QUEUED = 'QUEUED'
LIQUIDATED = 'LIQUIDATED'

View File

@@ -269,7 +269,15 @@ def get_strategy_class(strategy_name: str):
from pydoc import locate
if is_unit_testing():
return locate(f'jesse.strategies.{strategy_name}.{strategy_name}')
path = sys.path[0]
# live plugin
if path.endswith('jesse-live'):
strategy_dir = f'tests.strategies.{strategy_name}.{strategy_name}'
# main framework
else:
strategy_dir = f'jesse.strategies.{strategy_name}.{strategy_name}'
return locate(strategy_dir)
else:
return locate(f'strategies.{strategy_name}.{strategy_name}')
@@ -417,6 +425,10 @@ def now_to_timestamp(force_fresh=False) -> int:
return arrow.utcnow().int_timestamp * 1000
def current_1m_candle_timestamp():
return arrow.utcnow().floor('minute').int_timestamp * 1000
def np_ffill(arr: np.ndarray, axis: int = 0) -> np.ndarray:
idx_shape = tuple([slice(None)] + [np.newaxis] * (len(arr.shape) - axis - 1))
idx = np.where(~np.isnan(arr), np.arange(arr.shape[axis])[idx_shape], 0)

View File

@@ -51,15 +51,9 @@ class CompletedTrade(peewee.Model):
"type": self.type,
"entry_price": self.entry_price,
"exit_price": self.exit_price,
"take_profit_at": self.take_profit_at,
"stop_loss_at": self.stop_loss_at,
"qty": self.qty,
"fee": self.fee,
"reward": self.reward,
"size": self.size,
"risk": self.risk,
"risk_percentage": self.risk_percentage,
"R": self.r,
"PNL": self.pnl,
"PNL_percentage": self.pnl_percentage,
"holding_period": self.holding_period,
@@ -79,19 +73,13 @@ class CompletedTrade(peewee.Model):
'type': self.type,
'entry_price': self.entry_price,
'exit_price': self.exit_price,
'take_profit_at': self.take_profit_at,
'stop_loss_at': self.stop_loss_at,
'qty': self.qty,
'opened_at': self.opened_at,
'closed_at': self.closed_at,
'entry_candle_timestamp': self.entry_candle_timestamp,
'exit_candle_timestamp': self.exit_candle_timestamp,
"fee": self.fee,
"reward": self.reward,
"size": self.size,
"risk": self.risk,
"risk_percentage": self.risk_percentage,
"R": self.r,
"PNL": self.pnl,
"PNL_percentage": self.pnl_percentage,
"holding_period": self.holding_period,
@@ -102,31 +90,10 @@ class CompletedTrade(peewee.Model):
trading_fee = jh.get_config(f'env.exchanges.{self.exchange}.fee')
return trading_fee * self.qty * (self.entry_price + self.exit_price)
@property
def reward(self) -> float:
return abs(self.take_profit_at - self.entry_price) * self.qty
@property
def size(self) -> float:
return self.qty * self.entry_price
@property
def risk(self) -> float:
return abs(self.stop_loss_at - self.entry_price) * self.qty
@property
def risk_percentage(self) -> float:
return round((self.risk / self.size) * 100, 2)
@property
def risk_reward_ratio(self) -> float:
return self.reward / self.risk
@property
def r(self) -> float:
"""alias for risk_reward_ratio"""
return self.risk_reward_ratio
@property
def pnl(self) -> float:
"""PNL"""

View File

@@ -6,9 +6,9 @@ import jesse.services.logger as logger
import jesse.services.selectors as selectors
from jesse import sync_publish
from jesse.config import config
from jesse.enums import order_statuses, order_flags
from jesse.services.db import db
from jesse.services.notifier import notify
from jesse.enums import order_statuses, order_flags
class Order(Model):
@@ -34,6 +34,7 @@ class Order(Model):
executed_at = BigIntegerField(null=True)
canceled_at = BigIntegerField(null=True)
role = CharField(null=True)
submitted_via = None
class Meta:
database = db
@@ -132,7 +133,7 @@ class Order(Model):
'executed_at': self.executed_at,
}
def cancel(self) -> None:
def cancel(self, silent=False) -> None:
if self.is_canceled or self.is_executed:
return
@@ -142,23 +143,23 @@ class Order(Model):
if jh.is_live():
self.save()
if jh.is_debuggable('order_cancellation') or jh.is_live():
logger.info(
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
)
if jh.is_live():
sync_publish('order', self.to_dict)
if config['env']['notifications']['events']['cancelled_orders']:
notify(
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
if not silent:
if jh.is_debuggable('order_cancellation') or jh.is_live():
logger.info(
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
)
if jh.is_live():
sync_publish('order', self.to_dict)
if config['env']['notifications']['events']['cancelled_orders']:
notify(
f'CANCELED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
)
# handle exchange balance
e = selectors.get_exchange(self.exchange)
e.on_order_cancellation(self)
def execute(self) -> None:
def execute(self, silent=False) -> None:
if self.is_canceled or self.is_executed:
return
@@ -168,19 +169,19 @@ class Order(Model):
if jh.is_live():
self.save()
# log
if jh.is_debuggable('order_execution') or jh.is_live():
logger.info(
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
)
# notify
if jh.is_live():
sync_publish('order', self.to_dict)
if config['env']['notifications']['events']['executed_orders']:
notify(
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
if not silent:
# log
if jh.is_debuggable('order_execution') or jh.is_live():
logger.info(
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, ${round(self.price, 2)}'
)
# notify
if jh.is_live():
sync_publish('order', self.to_dict)
if config['env']['notifications']['events']['executed_orders']:
notify(
f'EXECUTED order: {self.symbol}, {self.type}, {self.side}, {self.qty}, {round(self.price, 2)}'
)
p = selectors.get_position(self.exchange, self.symbol)

View File

@@ -96,8 +96,6 @@ def store_completed_trade_into_db(completed_trade: CompletedTrade) -> None:
'timeframe': completed_trade.timeframe,
'entry_price': completed_trade.entry_price,
'exit_price': completed_trade.exit_price,
'take_profit_at': completed_trade.take_profit_at,
'stop_loss_at': completed_trade.stop_loss_at,
'qty': completed_trade.qty,
'opened_at': completed_trade.opened_at,
'closed_at': completed_trade.closed_at,

View File

@@ -124,9 +124,18 @@ class Optimizer(Genetics):
elif ratio_config == 'omega':
ratio = training_data['omega_ratio']
ratio_normalized = jh.normalize(ratio, -.5, 5)
elif ratio_config == 'serenity':
ratio = training_data['serenity_index']
ratio_normalized = jh.normalize(ratio, -.5, 15)
elif ratio_config == 'smart sharpe':
ratio = training_data['smart_sharpe']
ratio_normalized = jh.normalize(ratio, -.5, 5)
elif ratio_config == 'smart sortino':
ratio = training_data['smart_sortino']
ratio_normalized = jh.normalize(ratio, -.5, 15)
else:
raise ValueError(
f'The entered ratio configuration `{ratio_config}` for the optimization is unknown. Choose between sharpe, calmar, sortino and omega.')
f'The entered ratio configuration `{ratio_config}` for the optimization is unknown. Choose between sharpe, calmar, sortino, serenity, smart shapre, smart sortino and omega.')
if ratio < 0:
score = 0.0001

View File

@@ -35,7 +35,14 @@ class RouterClass:
# validate strategy
strategy_name = r["strategy"]
if jh.is_unit_testing():
exists = jh.file_exists(f"{sys.path[0]}/jesse/strategies/{strategy_name}/__init__.py")
path = sys.path[0]
# live plugin
if path.endswith('jesse-live'):
strategies_dir = f'{sys.path[0]}/tests/strategies'
# main framework
else:
strategies_dir = f'{sys.path[0]}/jesse/strategies'
exists = jh.file_exists(f"{strategies_dir}/{strategy_name}/__init__.py")
else:
exists = jh.file_exists(f'strategies/{strategy_name}/__init__.py')
if not exists:

View File

@@ -1,8 +1,9 @@
from datetime import datetime, timedelta
from typing import List, Any, Union
import crypto_empyrical
import numpy as np
import pandas as pd
from quantstats import stats
import jesse.helpers as jh
from jesse.store import store
@@ -42,7 +43,7 @@ def routes(routes_arr: list) -> dict:
return array
def trades(trades_list: list, daily_balance: list) -> dict:
def trades(trades_list: list, daily_balance: list, final: bool = True) -> dict:
starting_balance = 0
current_balance = 0
@@ -77,9 +78,6 @@ def trades(trades_list: list, daily_balance: list) -> dict:
largest_winning_trade = 0 if total_winning_trades == 0 else winning_trades['PNL'].max()
win_rate = len(winning_trades) / (len(losing_trades) + len(winning_trades))
max_R = df['R'].max()
min_R = df['R'].min()
mean_R = df['R'].mean()
longs_count = len(df.loc[df['type'] == 'long'])
shorts_count = len(df.loc[df['type'] == 'short'])
longs_percentage = longs_count / (longs_count + shorts_count) * 100
@@ -101,16 +99,38 @@ def trades(trades_list: list, daily_balance: list) -> dict:
gross_profit = winning_trades['PNL'].sum()
gross_loss = losing_trades['PNL'].sum()
daily_returns = pd.Series(daily_balance).pct_change(1).values
max_drawdown = crypto_empyrical.max_drawdown(daily_returns) * 100
annual_return = crypto_empyrical.annual_return(daily_returns) * 100
sharpe_ratio = crypto_empyrical.sharpe_ratio(daily_returns)
calmar_ratio = crypto_empyrical.calmar_ratio(daily_returns)
sortino_ratio = crypto_empyrical.sortino_ratio(daily_returns)
omega_ratio = crypto_empyrical.omega_ratio(daily_returns)
start_date = datetime.fromtimestamp(store.app.starting_time / 1000)
date_list = [start_date + timedelta(days=x) for x in range(len(daily_balance))]
daily_return = pd.DataFrame(daily_balance, index=pd.to_datetime(list(date_list))).pct_change(1)
total_open_trades = store.app.total_open_trades
open_pl = store.app.total_open_pl
max_drawdown = np.nan
annual_return = np.nan
sharpe_ratio = np.nan
calmar_ratio = np.nan
sortino_ratio = np.nan
omega_ratio = np.nan
serenity_index = np.nan
smart_sharpe = np.nan
smart_sortino = np.nan
if len(daily_return) > 2:
max_drawdown = stats.max_drawdown(daily_return).values[0] * 100
annual_return = stats.cagr(daily_return).values[0] * 100
sharpe_ratio = stats.sharpe(daily_return, periods=365).values[0]
calmar_ratio = stats.calmar(daily_return).values[0]
sortino_ratio = stats.sortino(daily_return, periods=365).values[0]
omega_ratio = stats.omega(daily_return, periods=365)
serenity_index = stats.serenity_index(daily_return).values[0]
# As those calculations are slow they are only done for the final report and not at self.metrics in the strategy.
if final:
smart_sharpe = stats.smart_sharpe(daily_return, periods=365).values[0]
smart_sortino = stats.smart_sortino(daily_return, periods=365).values[0]
return {
'total': np.nan if np.isnan(total_completed) else total_completed,
'total_winning_trades': np.nan if np.isnan(total_winning_trades) else total_winning_trades,
@@ -118,9 +138,6 @@ def trades(trades_list: list, daily_balance: list) -> dict:
'starting_balance': np.nan if np.isnan(starting_balance) else starting_balance,
'finishing_balance': np.nan if np.isnan(current_balance) else current_balance,
'win_rate': np.nan if np.isnan(win_rate) else win_rate,
'max_R': np.nan if np.isnan(max_R) else max_R,
'min_R': np.nan if np.isnan(min_R) else min_R,
'mean_R': np.nan if np.isnan(mean_R) else mean_R,
'ratio_avg_win_loss': np.nan if np.isnan(ratio_avg_win_loss) else ratio_avg_win_loss,
'longs_count': np.nan if np.isnan(longs_count) else longs_count,
'longs_percentage': np.nan if np.isnan(longs_percentage) else longs_percentage,
@@ -140,12 +157,15 @@ def trades(trades_list: list, daily_balance: list) -> dict:
'average_losing_holding_period': average_losing_holding_period,
'gross_profit': gross_profit,
'gross_loss': gross_loss,
'max_drawdown': max_drawdown,
'annual_return': annual_return,
'sharpe_ratio': sharpe_ratio,
'calmar_ratio': calmar_ratio,
'sortino_ratio': sortino_ratio,
'omega_ratio': omega_ratio,
'max_drawdown': np.nan if np.isnan(max_drawdown) else max_drawdown,
'annual_return': np.nan if np.isnan(annual_return) else annual_return,
'sharpe_ratio': np.nan if np.isnan(sharpe_ratio) else sharpe_ratio,
'calmar_ratio': np.nan if np.isnan(calmar_ratio) else calmar_ratio,
'sortino_ratio': np.nan if np.isnan(sortino_ratio) else sortino_ratio,
'omega_ratio': np.nan if np.isnan(omega_ratio) else omega_ratio,
'serenity_index': np.nan if np.isnan(serenity_index) else serenity_index,
'smart_sharpe': np.nan if np.isnan(smart_sharpe) else smart_sharpe,
'smart_sortino': np.nan if np.isnan(smart_sortino) else smart_sortino,
'total_open_trades': total_open_trades,
'open_pl': open_pl,
'winning_streak': winning_streak,

View File

@@ -32,8 +32,8 @@ def quantstats_tearsheet(buy_and_hold_returns: pd.Series, study_name: str) -> No
title = f"{modes[mode][1]}{arrow.utcnow().strftime('%d %b, %Y %H:%M:%S')}{study_name}"
try:
qs.reports.html(returns=returns_time_series, trading_year_days=365, benchmark=buy_and_hold_returns, title=title, output=file_path)
qs.reports.html(returns=returns_time_series, periods_per_year=365, benchmark=buy_and_hold_returns, title=title, output=file_path)
except IndexError:
qs.reports.html(returns=returns_time_series, trading_year_days=365, title=title, output=file_path)
qs.reports.html(returns=returns_time_series, periods_per_year=365, title=title, output=file_path)
except:
raise

View File

@@ -10,6 +10,7 @@ from jesse.models import store_candle_into_db
from jesse.services.candle import generate_candle_from_one_minutes
from timeloop import Timeloop
from datetime import timedelta
from jesse.services import logger
class CandlesState:
@@ -37,6 +38,15 @@ class CandlesState:
for c in config['app']['considering_candles']:
exchange, symbol = c[0], c[1]
current_candle = self.get_current_candle(exchange, symbol, '1m')
# TODO: debugging
if current_candle[0] <= 60_000:
logger.error(
'[REPORT TO Saleh for debugging please!] current_candle[0] is being zero but got fixed. more info:\n '
f'current_candle: {current_candle[0]}, {current_candle[1]}, {current_candle[2]}, {current_candle[3]}, {current_candle[4]}, {current_candle[5]}'
)
continue
if jh.now() > current_candle[0] + 60_000:
new_candle = self._generate_empty_candle_from_previous_candle(current_candle)
self.add_candle(new_candle, exchange, symbol, '1m')
@@ -47,6 +57,7 @@ class CandlesState:
def _generate_empty_candle_from_previous_candle(previous_candle: np.ndarray) -> np.ndarray:
new_candle = previous_candle.copy()
new_candle[0] = previous_candle[0] + 60_000
# new candle's open, close, high, and low all equal to previous candle's close
new_candle[1] = previous_candle[2]
new_candle[2] = previous_candle[2]
@@ -169,11 +180,6 @@ class CandlesState:
if jh.now() > current_candle[0] + 60_000:
new_candle = self._generate_empty_candle_from_previous_candle(current_candle)
self.add_candle(new_candle, exchange, symbol, '1m')
# if jh.now() > current_candle[0] + 60_000:
# while jh.now() > current_candle[0] + 60_000:
# new_candle = self._generate_empty_candle_from_previous_candle(current_candle)
# self.add_candle(new_candle, exchange, symbol, '1m')
# current_candle = self.get_current_candle(exchange, symbol, '1m')
# update position's current price
self.update_position(exchange, symbol, trade['price'])
@@ -219,8 +225,24 @@ class CandlesState:
last_candle = self.get_current_candle(exchange, symbol, timeframe)
generate_from_count = int((candle[0] - last_candle[0]) / 60_000)
number_of_candles = len(self.get_candles(exchange, symbol, '1m'))
short_candles = self.get_candles(exchange, symbol, '1m')[-1 - generate_from_count:]
if generate_from_count < 0:
current_1m = self.get_current_candle(exchange, symbol, '1m')
raise ValueError(
f'generate_from_count cannot be negative! '
f'generate_from_count:{generate_from_count}, candle[0]:{candle[0]}, '
f'last_candle[0]:{last_candle[0]}, current_1m:{current_1m[0]}, number_of_candles:{number_of_candles}')
if len(short_candles) == 0:
raise ValueError(
f'No candles were passed. More info:'
f'\nexchange:{exchange}, symbol:{symbol}, timeframe:{timeframe}, generate_from_count:{generate_from_count}'
f'\nlast_candle\'s timestamp: {last_candle[0]}'
f'\ncurrent timestamp: {jh.now()}'
)
# update latest candle
generated_candle = generate_candle_from_one_minutes(
timeframe,

View File

@@ -17,8 +17,11 @@ from jesse.services.broker import Broker
from jesse.store import store
from jesse.services.cache import cached
class Strategy(ABC):
"""The parent strategy class which every strategy must extend"""
"""
The parent strategy class which every strategy must extend. It is the heart of the framework!
"""
def __init__(self) -> None:
self.id = jh.generate_unique_id()
@@ -42,12 +45,9 @@ class Strategy(ABC):
self._stop_loss = None
self.take_profit = None
self._take_profit = None
self._log_take_profit = None
self._log_stop_loss = None
self._open_position_orders = []
self._stop_loss_orders = []
self._take_profit_orders = []
self._close_position_orders = []
self.trade: CompletedTrade = None
self.trades_count = 0
@@ -105,10 +105,8 @@ class Strategy(ABC):
if msg == 'route-open-position':
r.strategy.on_route_open_position(self)
elif msg == 'route-stop-loss':
r.strategy.on_route_stop_loss(self)
elif msg == 'route-take-profit':
r.strategy.on_route_take_profit(self)
elif msg == 'route-close-position':
r.strategy.on_route_close_position(self)
elif msg == 'route-increased-position':
r.strategy.on_route_increased_position(self)
elif msg == 'route-reduced-position':
@@ -133,10 +131,12 @@ class Strategy(ABC):
role = order.role
# if the order's role is CLOSE_POSITION but the position is still open, then it's increase_position order
if role == order_roles.OPEN_POSITION and abs(self.position.qty) != abs(order.qty):
order.role = order_roles.INCREASE_POSITION
role = order_roles.INCREASE_POSITION
# if the order's role is CLOSE_POSITION but the position is still open, then it's reduce_position order
if role == order_roles.CLOSE_POSITION and self.position.is_open:
order.role = order_roles.REDUCE_POSITION
role = order_roles.REDUCE_POSITION
@@ -145,10 +145,8 @@ class Strategy(ABC):
if role == order_roles.OPEN_POSITION:
self._on_open_position(order)
elif role == order_roles.CLOSE_POSITION and order in self._take_profit_orders:
self._on_take_profit(order)
elif role == order_roles.CLOSE_POSITION and order in self._stop_loss_orders:
self._on_stop_loss(order)
elif role == order_roles.CLOSE_POSITION:
self._on_close_position(order)
elif role == order_roles.INCREASE_POSITION:
self._on_increased_position(order)
elif role == order_roles.REDUCE_POSITION:
@@ -191,20 +189,20 @@ class Strategy(ABC):
return
for o in self._buy:
# MARKET order
if abs(o[1] - self.price) < 0.0001:
submitted_order = self.broker.buy_at_market(o[0], order_roles.OPEN_POSITION)
# STOP order
if o[1] > self.price:
elif o[1] > self.price:
submitted_order = self.broker.start_profit_at(sides.BUY, o[0], o[1], order_roles.OPEN_POSITION)
# LIMIT order
elif o[1] < self.price:
submitted_order = self.broker.buy_at(o[0], o[1], order_roles.OPEN_POSITION)
# MARKET order
else:
submitted_order = self.broker.buy_at_market(o[0], order_roles.OPEN_POSITION)
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
if submitted_order:
self._open_position_orders.append(
submitted_order
)
self._open_position_orders.append(submitted_order)
def _prepare_buy(self, make_copies: bool = True) -> None:
if type(self.buy) is np.ndarray:
@@ -243,7 +241,6 @@ class Strategy(ABC):
if make_copies:
self._stop_loss = self.stop_loss.copy()
self._log_stop_loss = self._stop_loss.copy()
def _prepare_take_profit(self, make_copies: bool = True) -> None:
# if it's numpy, then it has already been prepared
@@ -256,7 +253,6 @@ class Strategy(ABC):
if make_copies:
self._take_profit = self.take_profit.copy()
self._log_take_profit = self._take_profit.copy()
def _convert_to_numpy_array(self, arr, name) -> np.ndarray:
if type(arr) is np.ndarray:
@@ -327,17 +323,19 @@ class Strategy(ABC):
return
for o in self._sell:
# MARKET order
if abs(o[1] - self.price) < 0.0001:
submitted_order = self.broker.sell_at_market(o[0], order_roles.OPEN_POSITION)
# STOP order
if o[1] < self.price:
elif o[1] < self.price:
submitted_order = self.broker.start_profit_at(sides.SELL, o[0], o[1], order_roles.OPEN_POSITION)
# LIMIT order
elif o[1] > self.price:
submitted_order = self.broker.sell_at(o[0], o[1], order_roles.OPEN_POSITION)
# MARKET order
else:
submitted_order = self.broker.sell_at_market(o[0], order_roles.OPEN_POSITION)
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
if submitted_order:
for o in self._open_position_orders:
self._open_position_orders.append(submitted_order)
def _execute_filters(self) -> bool:
@@ -404,12 +402,9 @@ class Strategy(ABC):
self._stop_loss = None
self.take_profit = None
self._take_profit = None
self._log_take_profit = None
self._log_stop_loss = None
self._open_position_orders = []
self._stop_loss_orders = []
self._take_profit_orders = []
self._close_positi = []
self.increased_count = 0
self.reduced_count = 0
@@ -468,20 +463,20 @@ class Strategy(ABC):
for o in self._open_position_orders:
if o.is_active or o.is_queued:
self.broker.cancel_order(o.id)
# clean orders array but leave executed ones
self._open_position_orders = [o for o in self._open_position_orders if o.is_executed]
for o in self._buy:
# MARKET order
if abs(o[1] - self.price) < 0.0001:
submitted_order = self.broker.buy_at_market(o[0], order_roles.OPEN_POSITION)
# STOP order
if o[1] > self.price:
elif o[1] > self.price:
submitted_order = self.broker.start_profit_at(sides.BUY, o[0], o[1],
order_roles.OPEN_POSITION)
# LIMIT order
elif o[1] < self.price:
submitted_order = self.broker.buy_at(o[0], o[1], order_roles.OPEN_POSITION)
# MARKET order
else:
submitted_order = self.broker.buy_at_market(o[0], order_roles.OPEN_POSITION)
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
if submitted_order:
self._open_position_orders.append(submitted_order)
@@ -498,21 +493,20 @@ class Strategy(ABC):
for o in self._open_position_orders:
if o.is_active or o.is_queued:
self.broker.cancel_order(o.id)
# clean orders array but leave executed ones
self._open_position_orders = [o for o in self._open_position_orders if o.is_executed]
for o in self._sell:
# MARKET order
if abs(o[1] - self.price) < 0.0001:
submitted_order = self.broker.sell_at_market(o[0], order_roles.OPEN_POSITION)
# STOP order
if o[1] < self.price:
elif o[1] < self.price:
submitted_order = self.broker.start_profit_at(sides.SELL, o[0], o[1],
order_roles.OPEN_POSITION)
# LIMIT order
elif o[1] > self.price:
submitted_order = self.broker.sell_at(o[0], o[1], order_roles.OPEN_POSITION)
# MARKET order
else:
submitted_order = self.broker.sell_at_market(o[0], order_roles.OPEN_POSITION)
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
if submitted_order:
self._open_position_orders.append(submitted_order)
@@ -526,26 +520,20 @@ class Strategy(ABC):
self._take_profit = self.take_profit.copy()
# cancel orders
for o in self._take_profit_orders:
if o.is_active or o.is_queued:
for o in self._close_position_orders:
if o.submitted_via == 'take-profit' and o.is_active or o.is_queued:
self.broker.cancel_order(o.id)
# clean orders array but leave executed ones
self._take_profit_orders = [o for o in self._take_profit_orders if o.is_executed]
self._log_take_profit = []
for s in self._take_profit_orders:
self._log_take_profit.append(
(abs(s.qty), s.price)
)
self._close_position_orders = [o for o in self._close_position_orders if o.is_executed]
for o in self._take_profit:
submitted_order = self.broker.reduce_position_at(
submitted_order: Order = self.broker.reduce_position_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
submitted_order.submitted_via = 'take-profit'
if submitted_order:
self._log_take_profit.append(o)
self._take_profit_orders.append(submitted_order)
self._close_position_orders.append(submitted_order)
if self.position.is_open and self.stop_loss is not None:
self._validate_stop_loss()
@@ -557,26 +545,20 @@ class Strategy(ABC):
self._stop_loss = self.stop_loss.copy()
# cancel orders
for o in self._stop_loss_orders:
if o.is_active or o.is_queued:
for o in self._close_position_orders:
if o.submitted_via == 'stop-loss' and o.is_active or o.is_queued:
self.broker.cancel_order(o.id)
# clean orders array but leave executed ones
self._stop_loss_orders = [o for o in self._stop_loss_orders if o.is_executed]
self._log_stop_loss = []
for s in self._stop_loss_orders:
self._log_stop_loss.append(
(abs(s.qty), s.price)
)
self._close_position_orders = [o for o in self._close_position_orders if o.is_executed]
for o in self._stop_loss:
submitted_order = self.broker.reduce_position_at(
submitted_order: Order = self.broker.reduce_position_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
submitted_order.submitted_via = 'stop-loss'
if submitted_order:
self._log_stop_loss.append(o)
self._stop_loss_orders.append(submitted_order)
self._close_position_orders.append(submitted_order)
except TypeError:
raise exceptions.InvalidStrategy(
'Something odd is going on within your strategy causing a TypeError exception. '
@@ -619,7 +601,7 @@ class Strategy(ABC):
logger.info('Maximum allowed trades in test-drive mode is reached')
return
if self._open_position_orders != [] and self.is_close and self.should_cancel():
if len(self._open_position_orders) and self.is_close and self.should_cancel():
self._execute_cancel()
# make sure order cancellation response is received via WS
@@ -631,13 +613,13 @@ class Strategy(ABC):
if store.orders.count_active_orders(self.exchange, self.symbol) == 0:
break
logger.info('sleeping 0.2 more seconds...')
logger.info('sleeping 0.2 more seconds until cancellation is over...')
sleep(0.2)
# If it's still not cancelled, something is wrong. Handle cancellation failure
if store.orders.count_active_orders(self.exchange, self.symbol) != 0:
raise exceptions.ExchangeNotResponding(
'The exchange did not respond as expected'
'The exchange did not respond as expected for order cancellation'
)
if self.position.is_open:
@@ -679,13 +661,14 @@ class Strategy(ABC):
)
# submit take-profit
submitted_order = self.broker.reduce_position_at(
submitted_order: Order = self.broker.reduce_position_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
if submitted_order:
self._take_profit_orders.append(submitted_order)
submitted_order.submitted_via = 'take-profit'
self._close_position_orders.append(submitted_order)
if self.stop_loss is not None:
for o in self._stop_loss:
@@ -702,13 +685,14 @@ class Strategy(ABC):
)
# submit stop-loss
submitted_order = self.broker.stop_loss_at(
submitted_order: Order = self.broker.stop_loss_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
if submitted_order:
self._stop_loss_orders.append(submitted_order)
submitted_order.submitted_via = 'stop-loss'
self._close_position_orders.append(submitted_order)
self._open_position_orders = []
self.on_open_position(order)
@@ -720,38 +704,22 @@ class Strategy(ABC):
"""
pass
def _on_stop_loss(self, order: Order) -> None:
if not jh.should_execute_silently() or jh.is_debugging():
logger.info('Stop-loss has been executed.')
self._broadcast('route-stop-loss')
self._execute_cancel()
self.on_stop_loss(order)
self._detect_and_handle_entry_and_exit_modifications()
def on_stop_loss(self, order) -> None:
def on_close_position(self, order) -> None:
"""
What should happen after the stop-loss order has been executed
What should happen after the open position order has been executed
"""
pass
def _on_take_profit(self, order: Order) -> None:
def _on_close_position(self, order: Order):
if not jh.should_execute_silently() or jh.is_debugging():
logger.info("Take-profit order has been executed.")
logger.info("A closing order has been executed")
self._broadcast('route-take-profit')
self._broadcast('route-close-position')
self._execute_cancel()
self.on_take_profit(order)
self.on_close_position(order)
self._detect_and_handle_entry_and_exit_modifications()
def on_take_profit(self, order) -> None:
"""
What should happen after the take-profit order is executed.
"""
pass
def _on_increased_position(self, order: Order) -> None:
self.increased_count += 1
@@ -799,12 +767,7 @@ class Strategy(ABC):
"""
pass
def on_route_stop_loss(self, strategy) -> None:
"""used when trading multiple routes that related
"""
pass
def on_route_take_profit(self, strategy) -> None:
def on_route_close_position(self, strategy) -> None:
"""used when trading multiple routes that related
Arguments:
@@ -886,7 +849,7 @@ class Strategy(ABC):
)
return
if self._open_position_orders:
if len(self._open_position_orders):
self._execute_cancel()
logger.info('Canceled open-position orders because we reached the end of the backtest session.')
@@ -1015,7 +978,7 @@ class Strategy(ABC):
"""
if self.trades_count not in self._cached_metrics:
self._cached_metrics[self.trades_count] = metrics.trades(
store.completed_trades.trades, store.app.daily_balance
store.completed_trades.trades, store.app.daily_balance, final=False
)
return self._cached_metrics[self.trades_count]
@@ -1079,54 +1042,33 @@ class Strategy(ABC):
self.trade.exit_candle_timestamp = self.current_candle[0]
self.trade.orders.append(order)
# calculate average stop-loss price
sum_price = 0
sum_qty = 0
if self._log_stop_loss is not None:
for l in self._log_stop_loss:
sum_qty += abs(l[0])
sum_price += abs(l[0]) * l[1]
self.trade.stop_loss_at = sum_price / sum_qty
else:
self.trade.stop_loss_at = np.nan
# calculate average take-profit price
sum_price = 0
sum_qty = 0
if self._log_take_profit is not None:
for l in self._log_take_profit:
sum_qty += abs(l[0])
sum_price += abs(l[0]) * l[1]
self.trade.take_profit_at = sum_price / sum_qty
else:
self.trade.take_profit_at = np.nan
# calculate average entry_price price
sum_price = 0
sum_qty = 0
for l in self.trade.orders:
if not l.is_executed:
for trade_order in self.trade.orders:
if not trade_order.is_executed:
continue
if jh.side_to_type(l.side) != self.trade.type:
if jh.side_to_type(trade_order.side) != self.trade.type:
continue
sum_qty += abs(l.qty)
sum_price += abs(l.qty) * l.price
sum_qty += abs(trade_order.qty)
sum_price += abs(trade_order.qty) * trade_order.price
self.trade.entry_price = sum_price / sum_qty
# calculate average exit_price
sum_price = 0
sum_qty = 0
for l in self.trade.orders:
if not l.is_executed:
for trade_order in self.trade.orders:
if not trade_order.is_executed:
continue
if jh.side_to_type(l.side) == self.trade.type:
if jh.side_to_type(trade_order.side) == self.trade.type:
continue
sum_qty += abs(l.qty)
sum_price += abs(l.qty) * l.price
sum_qty += abs(trade_order.qty)
sum_price += abs(trade_order.qty) * trade_order.price
self.trade.exit_price = sum_price / sum_qty
self.trade.closed_at = jh.now_to_timestamp()
@@ -1242,7 +1184,8 @@ class Strategy(ABC):
def liquidation_price(self) -> float:
return self.position.liquidation_price
def log(self, msg: str, log_type: str = 'info') -> None:
@staticmethod
def log(msg: str, log_type: str = 'info') -> None:
msg = str(msg)
if log_type == 'info':

View File

@@ -1,7 +1,7 @@
from jesse.strategies import Strategy
# test_on_route_take_profit part 1 - BTC-USD
# test_on_route_close_position part 1 - BTC-USD
class Test23(Strategy):
def should_long(self):
# buy on market at first candle, close when on_route_take_profit event is fired
@@ -20,9 +20,5 @@ class Test23(Strategy):
def should_cancel(self):
return False
def on_route_take_profit(self, strategy):
"""
:param strategy:
"""
def on_route_close_position(self, strategy) -> None:
self.take_profit = 1, self.price

View File

@@ -1,7 +1,7 @@
from jesse.strategies import Strategy
# test_on_route_take_profit part 2 - ETH-USD
# test_on_route_close_position part 2 - ETH-USD
class Test24(Strategy):
def should_long(self):
return self.price == 10

View File

@@ -1,7 +1,7 @@
from jesse.strategies import Strategy
# test_on_route_stop_loss part 1 - BTC-USD
# test_on_route_close_position part 1 - BTC-USD
class Test25(Strategy):
def should_long(self):
# buy on market at first candle, close when on_route_stop_loss event is fired
@@ -20,9 +20,5 @@ class Test25(Strategy):
def should_cancel(self):
return False
def on_route_stop_loss(self, strategy):
"""
:param strategy:
"""
def on_route_close_position(self, strategy):
self.take_profit = 1, self.price

View File

@@ -1,7 +1,7 @@
from jesse.strategies import Strategy
# test_on_route_stop_loss part 2 - ETH-USD
# test_on_route_close_position part 2 - ETH-USD
class Test26(Strategy):
def should_long(self):
return False

View File

@@ -3,10 +3,6 @@ from jesse.strategies import Strategy
# test_on_route_increased_position_and_on_route_reduced_position_and_strategy_vars part 1 - BTC-USD
class Test29(Strategy):
"""
"""
def __init__(self) -> None:
super().__init__()
@@ -28,28 +24,16 @@ class Test29(Strategy):
self.stop_loss = 1, self.price + 10
def on_route_increased_position(self, strategy):
"""
:param strategy:
"""
# setting it to True means we'll open a position on NEXT candle
self.vars['should_long'] = True
def on_route_reduced_position(self, strategy):
"""
:param strategy:
"""
# setting it to True means we'll open a position on NEXT candle
self.vars['should_short'] = True
def should_cancel(self):
return False
def on_take_profit(self, order):
self.vars['should_long'] = False
self.vars['should_short'] = False
def on_stop_loss(self, order):
def on_close_position(self, order):
self.vars['should_long'] = False
self.vars['should_short'] = False

View File

@@ -1,5 +1,4 @@
import jesse.helpers as jh
from jesse.models import Order
from jesse.strategies import Strategy
@@ -21,7 +20,7 @@ class TestCompletedTradeAfterExitingTrade(Strategy):
def should_cancel(self):
return False
def on_take_profit(self, order: Order):
def on_close_position(self, order):
assert self.trades_count == 1
trade = self.trades[0]
@@ -34,8 +33,6 @@ class TestCompletedTradeAfterExitingTrade(Strategy):
assert trade.timeframe == '1m'
assert trade.entry_price == 10
assert trade.exit_price == 12
assert trade.take_profit_at == 12
assert trade.stop_loss_at == 5
assert trade.qty == 10
assert trade.opened_at == 1552309906171.0
assert trade.closed_at == 1552310026171.0

View File

@@ -0,0 +1,22 @@
from jesse.strategies import Strategy
class TestMarketOrderForLowPriceDifference(Strategy):
def on_open_position(self, order):
assert order.type == 'MARKET'
def should_long(self) -> bool:
return self.index == 0
def should_short(self) -> bool:
return False
def go_long(self):
# current-price: 1
self.buy = 1, 1.00001
def go_short(self):
pass
def should_cancel(self):
return False

View File

@@ -21,7 +21,7 @@ class TestMetrics1(Strategy):
def should_cancel(self):
return False
def on_take_profit(self, order):
def on_close_position(self, order):
assert self.metrics['total'] == 1
assert self.metrics['starting_balance'] == 10000
assert self.metrics['finishing_balance'] == 10050

1
jesse/version.py Normal file
View File

@@ -0,0 +1 @@
__version__ = '0.27.10'

View File

@@ -1,7 +1,6 @@
arrow==1.1.1
arrow==1.2.0
blinker==1.4
click==8.0.1
crypto-empyrical==1.0.4
click==8.0.2
matplotlib==3.4.3
mplfinance==0.12.7a17
newtulipy==0.4.6
@@ -10,12 +9,13 @@ numpy_groupies==0.9.14
pandas==1.3.3
peewee==3.14.4
psycopg2-binary==2.9.1
pydash==5.0.2
pydash==5.1.0
pytest==6.2.5
PyWavelets==1.1.1
quantstats==0.0.42
requests==2.26.0
scipy==1.7.1
statsmodels==0.12.2
statsmodels==0.13.0
TA-Lib==0.4.21
tabulate==0.8.9
timeloop==1.0.2

View File

@@ -1,13 +1,13 @@
from setuptools import setup, find_packages
VERSION = '0.27.3'
# also change in version.py
VERSION = '0.27.10'
DESCRIPTION = "A trading framework for cryptocurrencies"
REQUIRED_PACKAGES = [
'arrow',
'blinker',
'Click',
'crypto_empyrical',
'matplotlib',
'newtulipy',
'numpy',

View File

@@ -144,7 +144,6 @@ def test_r():
'opened_at': jh.now_to_timestamp(),
'closed_at': jh.now_to_timestamp()
})
assert trade.risk_reward_ratio == 2
def test_risk_percentage():
@@ -163,7 +162,6 @@ def test_risk_percentage():
'opened_at': jh.now_to_timestamp(),
'closed_at': jh.now_to_timestamp()
})
assert trade.risk_percentage == round((((10 - 5) / 1) * 10), 2)
def test_trade_size():

View File

@@ -39,10 +39,8 @@ def test_can_handle_multiple_entry_orders_too_close_to_each_other():
t: CompletedTrade = store.completed_trades.trades[0]
assert t.type == 'long'
assert t.stop_loss_at == 0.4
assert t.entry_price == (1.1 + 1.2 + 1.3 + 1.4) / 4
assert t.exit_price == 3
assert t.take_profit_at == 3
# 4 entry + 1 exit
assert len(t.orders) == 5
# last order is closing order
@@ -62,10 +60,8 @@ def test_conflicting_orders():
t: CompletedTrade = store.completed_trades.trades[0]
assert t.type == 'long'
assert t.stop_loss_at == 1.0
assert t.entry_price == (1.1 + 1.11) / 2
assert t.exit_price == (1.2 + 1.3) / 2
assert t.take_profit_at == (1.2 + 1.3) / 2
def test_conflicting_orders_2():
@@ -76,13 +72,9 @@ def test_conflicting_orders_2():
t: CompletedTrade = store.completed_trades.trades[0]
assert t.entry_price == 2.5
assert t.take_profit_at == 2.6
assert t.stop_loss_at == 2.4
assert t.exit_price == 2.6
#
# def test_can_handle_not_correctly_sorted_multiple_orders():
# set_up([
# (exchanges.SANDBOX, 'BTC-USDT', timeframes.MINUTE_1, 'Test35'),
@@ -95,7 +87,5 @@ def test_conflicting_orders_2():
# t: CompletedTrade = store.completed_trades.trades[0]
#
# assert t.type == 'long'
# assert t.stop_loss_at == 0.4
# assert t.entry_price == (1.1 + 1.2 + 1.3 + 1.4) / 4
# assert t.exit_price == 3
# assert t.take_profit_at == 3

View File

@@ -35,7 +35,6 @@ def test_average_take_profit_and_average_stop_loss():
assert t1.type == 'long'
assert t1.entry_price == 1
assert t1.exit_price == 3.5
assert t1.take_profit_at == 3.5
assert t1.qty == 2
t2: CompletedTrade = store.completed_trades.trades[1]
@@ -160,8 +159,6 @@ def test_increasing_position_size_after_opening():
assert t1.type == 'long'
assert t1.entry_price == (7 + 10) / 2
assert t1.exit_price == 15
assert t1.take_profit_at == 15
assert t1.stop_loss_at == 5
assert t1.qty == 2
assert t1.fee == 0
@@ -190,8 +187,6 @@ def test_is_smart_enough_to_open_positions_via_market_orders():
assert t1.type == 'long'
assert t1.entry_price == 129.23
assert t1.exit_price == 128.35
assert t1.take_profit_at == 131.29
assert t1.stop_loss_at == 128.35
assert t1.qty == 10.204
assert t1.fee == 0
assert t1.opened_at == 1547201100000 + 60000
@@ -204,8 +199,6 @@ def test_is_smart_enough_to_open_positions_via_market_orders():
assert t2.type == 'short'
assert t2.entry_price == 128.01
assert t2.exit_price == 126.58
assert t2.take_profit_at == 126.58
assert t2.stop_loss_at == 129.52
assert t2.qty == 10
assert t2.fee == 0
assert t2.opened_at == 1547203560000 + 60000
@@ -238,8 +231,6 @@ def test_is_smart_enough_to_open_positions_via_stop_orders():
assert t1.type == 'long'
assert t1.entry_price == 129.33
assert t1.exit_price == 128.35
assert t1.take_profit_at == 131.29
assert t1.stop_loss_at == 128.35
assert t1.qty == 10.204
assert t1.fee == 0
assert t1.opened_at == 1547201100000 + 60000
@@ -252,8 +243,6 @@ def test_is_smart_enough_to_open_positions_via_stop_orders():
assert t2.type == 'short'
assert t2.entry_price == 128.05
assert t2.exit_price == 126.58
assert t2.take_profit_at == 126.58
assert t2.stop_loss_at == 129.52
assert t2.qty == 10
assert t2.fee == 0
assert t2.opened_at == 1547203560000 + 60000
@@ -273,14 +262,10 @@ def test_liquidate():
assert t1.type == 'long'
assert t1.entry_price == 1
assert t1.exit_price == 11
assert t1.take_profit_at == 11
assert np.isnan(t1.stop_loss_at)
assert t2.type == 'short'
assert t2.entry_price == 21
assert t2.exit_price == 41
assert t2.stop_loss_at == 41
assert np.isnan(t2.take_profit_at)
def test_modifying_stop_loss_after_part_of_position_is_already_reduced_with_stop_loss():
@@ -309,8 +294,6 @@ def test_modifying_stop_loss_after_part_of_position_is_already_reduced_with_stop
assert t1.type == 'long'
assert t1.entry_price == 7
assert t1.exit_price == (4 * 2 + 6) / 3
assert t1.take_profit_at == 13
assert t1.stop_loss_at == (4 * 2 + 6) / 3
assert t1.qty == 1.5
assert t1.fee == 0
@@ -323,8 +306,6 @@ def test_modifying_take_profit_after_opening_position():
assert t1.type == 'long'
assert t1.entry_price == 7
assert t1.exit_price == 16
assert t1.take_profit_at == 16
assert t1.stop_loss_at == 5
assert t1.qty == 1.5
assert t1.fee == 0
@@ -337,8 +318,6 @@ def test_modifying_take_profit_after_part_of_position_is_already_reduced_with_pr
assert t1.type == 'long'
assert t1.entry_price == 7
assert t1.exit_price == (16 * 2 + 11) / 3
assert t1.take_profit_at == (16 * 2 + 11) / 3
assert t1.stop_loss_at == 5
assert t1.qty == 1.5
assert t1.fee == 0
@@ -410,8 +389,6 @@ def test_on_reduced_position():
assert t1.type == 'long'
assert t1.entry_price == 7
assert t1.exit_price == 13
assert t1.take_profit_at == 13
assert t1.stop_loss_at == 5
assert t1.qty == 2
assert t1.fee == 0
@@ -425,9 +402,7 @@ def test_on_route_canceled():
assert t1.type == 'long'
assert t1.entry_price == 101
assert t1.exit_price == 120
assert t1.take_profit_at == 120
assert t1.qty == 1
assert np.isnan(t1.stop_loss_at)
def test_on_route_increased_position_and_on_route_reduced_position_and_strategy_vars():
@@ -444,17 +419,13 @@ def test_on_route_increased_position_and_on_route_reduced_position_and_strategy_
assert t1.type == 'long'
assert t1.entry_price == 121
assert t1.exit_price == 131
assert t1.take_profit_at == 131
assert t1.qty == 1
assert np.isnan(t1.stop_loss_at)
assert t2.symbol == 'BTC-USDT'
assert t2.type == 'short'
assert t2.entry_price == 151
assert t2.exit_price == 161
assert t2.stop_loss_at == 161
assert t2.qty == 1
assert np.isnan(t2.take_profit_at)
assert t3.symbol == 'ETH-USDT'
assert t3.type == 'long'
@@ -462,9 +433,7 @@ def test_on_route_increased_position_and_on_route_reduced_position_and_strategy_
assert t3.entry_price == 15
# (50 + 70) / 2
assert t3.exit_price == 60
assert t3.take_profit_at == 60
assert t3.qty == 2
assert np.isnan(t3.stop_loss_at)
def test_on_route_open_position():
@@ -477,17 +446,13 @@ def test_on_route_open_position():
assert t1.type == 'long'
assert t1.entry_price == 101
assert t1.exit_price == 110
assert t1.take_profit_at == 110
assert t1.qty == 1
assert np.isnan(t1.stop_loss_at)
assert t2.symbol == 'ETH-USDT'
assert t2.type == 'long'
assert t2.entry_price == 10
assert t2.exit_price == 20
assert t2.take_profit_at == 20
assert t2.qty == 1
assert np.isnan(t2.stop_loss_at)
def test_on_route_stop_loss():
@@ -500,17 +465,13 @@ def test_on_route_stop_loss():
assert t2.type == 'long'
assert t2.entry_price == 101
assert t2.exit_price == 120
assert t2.take_profit_at == 120
assert t2.qty == 1
assert np.isnan(t2.stop_loss_at)
assert t1.symbol == 'ETH-USDT'
assert t1.type == 'short'
assert t1.entry_price == 10
assert t1.exit_price == 20
assert t1.stop_loss_at == 20
assert t1.qty == 1
assert np.isnan(t1.take_profit_at)
def test_on_route_take_profit():
@@ -523,17 +484,13 @@ def test_on_route_take_profit():
assert t2.type == 'long'
assert t2.entry_price == 101
assert t2.exit_price == 120
assert t2.take_profit_at == 120
assert t2.qty == 1
assert np.isnan(t2.stop_loss_at)
assert t1.symbol == 'ETH-USDT'
assert t1.type == 'long'
assert t1.entry_price == 10
assert t1.exit_price == 20
assert t1.take_profit_at == 20
assert t1.qty == 1
assert np.isnan(t1.stop_loss_at)
def test_opening_position_in_multiple_points():
@@ -544,8 +501,6 @@ def test_opening_position_in_multiple_points():
assert t1.type == 'long'
assert t1.entry_price == (7 + 9 + 11) / 3
assert t1.exit_price == 15
assert t1.take_profit_at == 15
assert t1.stop_loss_at == 5
assert t1.qty == 1.5
assert t1.fee == 0
@@ -558,8 +513,6 @@ def test_reducing_position_size_after_opening():
assert t1.type == 'long'
assert t1.entry_price == 7
assert t1.exit_price == (15 + 10) / 2
assert t1.take_profit_at == (15 + 10) / 2
assert t1.stop_loss_at == 5
assert t1.qty == 2
assert t1.fee == 0
@@ -573,9 +526,7 @@ def test_shared_vars():
assert t1.type == 'long'
assert t1.entry_price == 11
assert t1.exit_price == 21
assert t1.take_profit_at == 21
assert t1.qty == 1
assert np.isnan(t1.stop_loss_at)
def test_should_buy_and_execute_buy():
@@ -671,8 +622,6 @@ def test_stop_loss_at_multiple_points():
assert t1.type == 'short'
assert t1.entry_price == 3
assert t1.exit_price == (6 + 5 + 4) / 3
assert t1.take_profit_at == 1
assert t1.stop_loss_at == (6 + 5 + 4) / 3
assert t1.qty == 1.5
assert t1.fee == 0
@@ -704,8 +653,6 @@ def test_taking_profit_at_multiple_points():
assert t1.type == 'long'
assert t1.entry_price == 7
assert t1.exit_price == (15 + 13 + 11) / 3
assert t1.take_profit_at == (15 + 13 + 11) / 3
assert t1.stop_loss_at == 5
assert t1.qty == 1.5
assert t1.fee == 0
assert t1.holding_period == 8 * 60
@@ -767,8 +714,6 @@ def test_updating_stop_loss_and_take_profit_after_opening_the_position():
assert t1.type == 'long'
assert t1.entry_price == 129.23
assert t1.exit_price == 128.98
assert t1.take_profit_at == 131.29
assert t1.stop_loss_at == 128.98
assert t1.qty == 10.204
assert t1.fee == 0
assert t1.opened_at == 1547201100000 + 60000
@@ -781,8 +726,6 @@ def test_updating_stop_loss_and_take_profit_after_opening_the_position():
assert t2.type == 'short'
assert t2.entry_price == 128.01
assert t2.exit_price == 127.66
assert t2.take_profit_at == 127.66
assert t2.stop_loss_at == 129.52
assert t2.qty == 10
assert t2.fee == 0
assert t2.opened_at == 1547203560000 + 60000
@@ -853,6 +796,11 @@ def test_log_method():
assert store.logs.info[1]['message'] == 'test info log'
assert store.logs.errors[0]['message'] == 'test error log'
def test_using_market_order_for_low_price_difference():
single_route_backtest('TestMarketOrderForLowPriceDifference')
# TODO: implement liquidation in backtest mode for cross mode
# def test_liquidation_in_cross_mode_for_short_trades():
# single_route_backtest(
@@ -887,5 +835,3 @@ def test_log_method():
# assert len(store.completed_trades.trades) == 1
# assert t.qty == 1.5
# assert t.entry_price == 5.123
# assert t.take_profit_at == 10.12
# assert t.stop_loss_at == 1.123