refactor order execution and event firing

This commit is contained in:
Saleh Mir
2022-03-03 08:51:54 +01:00
parent 25f1076629
commit 92269df6b8
16 changed files with 104 additions and 195 deletions

View File

@@ -42,21 +42,6 @@ class colors:
BLACK = 'black'
class order_roles:
OPEN_POSITION = 'OPEN POSITION'
CLOSE_POSITION = 'CLOSE POSITION'
INCREASE_POSITION = 'INCREASE POSITION'
REDUCE_POSITION = 'REDUCE POSITION'
class order_flags:
OCO = 'OCO'
POST_ONLY = 'PostOnly'
CLOSE = 'Close'
HIDDEN = 'Hidden'
REDUCE_ONLY = 'ReduceOnly'
class order_types:
MARKET = 'MARKET'
LIMIT = 'LIMIT'

View File

@@ -8,15 +8,15 @@ class Exchange(ABC):
"""
@abstractmethod
def market_order(self, symbol: str, qty: float, current_price: float, side: str, role: str, flags: list) -> Order:
def market_order(self, symbol: str, qty: float, current_price: float, side: str, reduce_only: bool) -> Order:
pass
@abstractmethod
def limit_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def limit_order(self, symbol: str, qty: float, price: float, side: str, reduce_only: bool) -> Order:
pass
@abstractmethod
def stop_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def stop_order(self, symbol: str, qty: float, price: float, side: str, reduce_only: bool) -> Order:
pass
@abstractmethod
@@ -27,10 +27,6 @@ class Exchange(ABC):
def cancel_order(self, symbol: str, order_id: str) -> None:
pass
@abstractmethod
def get_exec_inst(self, flags: list) -> Union[str, None]:
pass
@abstractmethod
def _fetch_precisions(self) -> None:
pass

View File

@@ -5,22 +5,22 @@ from jesse.models import Order
from jesse.store import store
from typing import Union
class Sandbox(Exchange):
def __init__(self, name='Sandbox'):
super().__init__()
self.name = name
def market_order(self, symbol: str, qty: float, current_price: float, side: str, role: str, flags: list) -> Order:
def market_order(self, symbol: str, qty: float, current_price: float, side: str, reduce_only: bool) -> Order:
order = Order({
'id': jh.generate_unique_id(),
'symbol': symbol,
'exchange': self.name,
'side': side,
'type': order_types.MARKET,
'flag': self.get_exec_inst(flags),
'reduce_only': reduce_only,
'qty': jh.prepare_qty(qty, side),
'price': current_price,
'role': role
})
store.orders.add_order(order)
@@ -29,34 +29,32 @@ class Sandbox(Exchange):
return order
def limit_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def limit_order(self, symbol: str, qty: float, price: float, side: str, reduce_only: bool) -> Order:
order = Order({
'id': jh.generate_unique_id(),
'symbol': symbol,
'exchange': self.name,
'side': side,
'type': order_types.LIMIT,
'flag': self.get_exec_inst(flags),
'reduce_only': reduce_only,
'qty': jh.prepare_qty(qty, side),
'price': price,
'role': role
})
store.orders.add_order(order)
return order
def stop_order(self, symbol: str, qty: float, price: float, side: str, role: str, flags: list) -> Order:
def stop_order(self, symbol: str, qty: float, price: float, side: str, reduce_only: bool) -> Order:
order = Order({
'id': jh.generate_unique_id(),
'symbol': symbol,
'exchange': self.name,
'side': side,
'type': order_types.STOP,
'flag': self.get_exec_inst(flags),
'reduce_only': reduce_only,
'qty': jh.prepare_qty(qty, side),
'price': price,
'role': role
})
store.orders.add_order(order)
@@ -76,10 +74,5 @@ class Sandbox(Exchange):
def cancel_order(self, symbol: str, order_id: str) -> None:
store.orders.get_order_by_id(self.name, symbol, order_id).cancel()
def get_exec_inst(self, flags: list) -> Union[str, None]:
if flags:
return flags[0]
return None
def _fetch_precisions(self) -> None:
pass

View File

@@ -146,7 +146,7 @@ class FuturesExchange(Exchange):
base_asset = jh.base_asset(order.symbol)
# make sure we don't spend more than we're allowed considering current allowed leverage
if (order.type != order_types.MARKET or skip_market_order) and not order.is_reduce_only:
if (order.type != order_types.MARKET or skip_market_order) and not order.reduce_only:
order_size = abs(order.qty * order.price)
remaining_margin = self.available_margin()
if order_size > remaining_margin:
@@ -161,7 +161,7 @@ class FuturesExchange(Exchange):
self.available_assets[base_asset] += order.qty
if not order.is_reduce_only:
if not order.reduce_only:
if order.side == sides.BUY:
self.buy_orders[base_asset].append(np.array([order.qty, order.price]))
else:
@@ -176,7 +176,7 @@ class FuturesExchange(Exchange):
if order.type == order_types.MARKET:
self.on_order_submission(order, skip_market_order=False)
if not order.is_reduce_only:
if not order.reduce_only:
if order.side == sides.BUY:
# find and set order to [0, 0] (same as removing it)
for index, item in enumerate(self.buy_orders[base_asset]):
@@ -198,7 +198,7 @@ class FuturesExchange(Exchange):
self.available_assets[base_asset] -= order.qty
# self.available_assets[quote_asset] += order.qty * order.price
if not order.is_reduce_only:
if not order.reduce_only:
if order.side == sides.BUY:
# find and set order to [0, 0] (same as removing it)
for index, item in enumerate(self.buy_orders[base_asset]):

View File

@@ -6,7 +6,7 @@ import jesse.services.selectors as selectors
from jesse import sync_publish
from jesse.config import config
from jesse.services.notifier import notify
from jesse.enums import order_statuses, order_flags
from jesse.enums import order_statuses
from jesse.services.db import database
@@ -28,14 +28,14 @@ class Order(Model):
exchange = CharField()
side = CharField()
type = CharField()
flag = CharField(null=True)
reduce_only = BooleanField()
qty = FloatField()
filled_qty = FloatField(default=0)
price = FloatField(null=True)
status = CharField(default=order_statuses.ACTIVE)
created_at = BigIntegerField()
executed_at = BigIntegerField(null=True)
canceled_at = BigIntegerField(null=True)
role = CharField(null=True)
submitted_via = None
class Meta:
@@ -120,14 +120,6 @@ class Order(Model):
def is_partially_filled(self) -> bool:
return self.status == order_statuses.PARTIALLY_FILLED
@property
def is_reduce_only(self) -> bool:
return self.flag == order_flags.REDUCE_ONLY
@property
def is_close(self) -> bool:
return self.flag == order_flags.CLOSE
@property
def is_stop_loss(self):
return self.submitted_via == 'stop-loss'
@@ -146,6 +138,7 @@ class Order(Model):
'side': self.side,
'type': self.type,
'qty': self.qty,
'filled_qty': self.filled_qty,
'price': self.price,
'flag': self.flag,
'status': self.status,
@@ -215,9 +208,6 @@ class Order(Model):
e = selectors.get_exchange(self.exchange)
e.on_order_execution(self)
def update_from_stream(self, data: dict) -> None:
pass
# if database is open, create the table
if database.is_open():

View File

@@ -352,7 +352,7 @@ class Position:
self._mutating_close(price)
# order increases the size of the position
elif self.qty * qty > 0:
if order.is_reduce_only:
if order.reduce_only:
logger.info('Did not increase position because order is a reduce_only order')
else:
self._increase(qty, price)
@@ -361,7 +361,7 @@ class Position:
# if size of the order is big enough to both close the
# position AND open it on the opposite side
if abs(qty) > abs(self.qty):
if order.is_reduce_only:
if order.reduce_only:
logger.info(
f'Executed order is bigger than the current position size but it is a reduce_only order so it just closes it. Order QTY: {qty}, Position QTY: {self.qty}')
self._mutating_close(price)
@@ -381,14 +381,18 @@ class Position:
"""
Used for updating the position from the WS stream (only for live trading)
"""
before_qty = abs(self.qty)
after_qty = abs(data['qty'])
self.entry_price = data['entry_price']
self._liquidation_price = data['liquidation_price']
self.qty = data['qty']
# if opening position
if self.is_close and data['qty'] != 0:
if before_qty == 0 and after_qty != 0:
self.opened_at = jh.now_to_timestamp()
self._open()
# if closing position
if self.is_open and data['qty'] == 0:
elif after_qty != 0 and before_qty == 0:
self.closed_at = jh.now_to_timestamp()
self._close()
self.qty = data['qty']

View File

@@ -122,14 +122,14 @@ def store_order_into_db(order) -> None:
'exchange': order.exchange,
'side': order.side,
'type': order.type,
'flag': order.flag,
'reduce_only': order.reduce_only,
'qty': order.qty,
'filled_qty': order.filled_qty,
'price': order.price,
'status': order.status,
'created_at': order.created_at,
'executed_at': order.executed_at,
'canceled_at': order.canceled_at,
'role': order.role,
}
def async_save() -> None:

View File

@@ -12,7 +12,7 @@ import jesse.services.required_candles as required_candles
import jesse.services.selectors as selectors
from jesse import exceptions
from jesse.config import config
from jesse.enums import timeframes, order_types, order_roles, order_flags
from jesse.enums import timeframes, order_types
from jesse.models import Candle, Order, Position
from jesse.modes.utils import save_daily_portfolio_balance
from jesse.routes import router
@@ -450,10 +450,9 @@ def _check_for_liquidations(candle: np.ndarray, exchange: str, symbol: str) -> N
'exchange': exchange,
'side': closing_order_side,
'type': order_types.MARKET,
'flag': order_flags.REDUCE_ONLY,
'reduce_only': True,
'qty': jh.prepare_qty(p.qty, closing_order_side),
'price': p.bankruptcy_price,
'role': order_roles.CLOSE_POSITION
'price': p.bankruptcy_price
})
store.orders.add_order(order)

View File

@@ -49,13 +49,12 @@ class API:
qty: float,
current_price: float,
side: str,
role: str,
flags: list
reduce_only: bool
) -> Union[Order, None]:
if exchange not in self.drivers:
logger.info(f'Exchange "{exchange}" driver not initiated yet. Trying again in the next candle')
return None
return self.drivers[exchange].market_order(symbol, qty, current_price, side, role, flags)
return self.drivers[exchange].market_order(symbol, qty, current_price, side, reduce_only)
def limit_order(
self,
@@ -64,13 +63,12 @@ class API:
qty: float,
price: float,
side: str,
role: str,
flags: list
reduce_only: bool
) -> Union[Order, None]:
if exchange not in self.drivers:
logger.info(f'Exchange "{exchange}" driver not initiated yet. Trying again in the next candle')
return None
return self.drivers[exchange].limit_order(symbol, qty, price, side, role, flags)
return self.drivers[exchange].limit_order(symbol, qty, price, side, reduce_only)
def stop_order(
self, exchange: str,
@@ -78,13 +76,12 @@ class API:
qty: float,
price: float,
side: str,
role: str,
flags: list
reduce_only: bool
) -> Union[Order, None]:
if exchange not in self.drivers:
logger.info(f'Exchange "{exchange}" driver not initiated yet. Trying again in the next candle')
return None
return self.drivers[exchange].stop_order(symbol, qty, price, side, role, flags)
return self.drivers[exchange].stop_order(symbol, qty, price, side, reduce_only)
def cancel_all_orders(self, exchange: str, symbol: str) -> bool:
if exchange not in self.drivers:

View File

@@ -1,7 +1,7 @@
from typing import Union
import jesse.helpers as jh
from jesse.enums import sides, order_flags
from jesse.enums import sides
from jesse.exceptions import OrderNotAllowed, InvalidStrategy
from jesse.models import Order
from jesse.models import Position
@@ -21,7 +21,7 @@ class Broker:
if qty == 0:
raise InvalidStrategy('qty cannot be 0')
def sell_at_market(self, qty: float, role: str = None) -> Union[Order, None]:
def sell_at_market(self, qty: float) -> Union[Order, None]:
self._validate_qty(qty)
return self.api.market_order(
@@ -30,10 +30,10 @@ class Broker:
abs(qty),
self.position.current_price,
sides.SELL,
role, []
reduce_only=False
)
def sell_at(self, qty: float, price: float, role: str = None) -> Union[Order, None]:
def sell_at(self, qty: float, price: float) -> Union[Order, None]:
self._validate_qty(qty)
if price < 0:
@@ -45,11 +45,10 @@ class Broker:
abs(qty),
price,
sides.SELL,
role,
[]
reduce_only=False
)
def buy_at_market(self, qty: float, role: str = None) -> Union[Order, None]:
def buy_at_market(self, qty: float) -> Union[Order, None]:
self._validate_qty(qty)
return self.api.market_order(
@@ -58,11 +57,10 @@ class Broker:
abs(qty),
self.position.current_price,
sides.BUY,
role,
[]
reduce_only=False
)
def buy_at(self, qty: float, price: float, role: str = None) -> Union[Order, None]:
def buy_at(self, qty: float, price: float) -> Union[Order, None]:
self._validate_qty(qty)
if price < 0:
@@ -74,11 +72,10 @@ class Broker:
abs(qty),
price,
sides.BUY,
role,
[]
reduce_only=False
)
def reduce_position_at(self, qty: float, price: float, role: str = None) -> Union[Order, None]:
def reduce_position_at(self, qty: float, price: float) -> Union[Order, None]:
self._validate_qty(qty)
qty = abs(qty)
@@ -102,8 +99,7 @@ class Broker:
qty,
price,
side,
role,
[order_flags.REDUCE_ONLY]
reduce_only=True
)
elif (side == 'sell' and self.position.type == 'long' and price > self.position.current_price) or (
@@ -114,8 +110,7 @@ class Broker:
qty,
price,
side,
role,
[order_flags.REDUCE_ONLY]
reduce_only=True
)
elif (side == 'sell' and self.position.type == 'long' and price < self.position.current_price) or (
side == 'buy' and self.position.type == 'short' and price > self.position.current_price):
@@ -125,13 +120,12 @@ class Broker:
abs(qty),
price,
side,
role,
[order_flags.REDUCE_ONLY]
reduce_only=True
)
else:
raise OrderNotAllowed("This order doesn't seem to be for reducing the position.")
def start_profit_at(self, side: str, qty: float, price: float, role: str = None) -> Union[Order, None]:
def start_profit_at(self, side: str, qty: float, price: float) -> Union[Order, None]:
self._validate_qty(qty)
if price < 0:
@@ -152,8 +146,7 @@ class Broker:
abs(qty),
price,
side,
role,
[]
reduce_only=False
)
def cancel_all_orders(self) -> bool:

View File

@@ -3,15 +3,13 @@ from time import sleep
from typing import List, Dict
import numpy as np
import pydash
import jesse.helpers as jh
import jesse.services.logger as logger
import jesse.services.selectors as selectors
from jesse import exceptions
from jesse.enums import sides, trade_types, order_roles
from jesse.enums import sides
from jesse.models import CompletedTrade, Order, Route, FuturesExchange, SpotExchange, Position
from jesse.models.utils import store_completed_trade_into_db, store_order_into_db
from jesse.services import metrics
from jesse.services.broker import Broker
from jesse.store import store
@@ -119,38 +117,34 @@ class Strategy(ABC):
def _on_updated_position(self, order: Order) -> None:
"""
Handles the after-effect of the executed order
Note that it assumes that the position has already been affected
by the executed order.
Arguments:
order {Order} -- the executed order object
Handles the after-effect of the executed order to execute strategy
events. Note that it assumes that the position has already
been affected by the executed order.
"""
# in live-mode, sometimes order-update effects and new execution has overlaps, so:
self._is_handling_updated_order = True
role = order.role
# this is the last executed order, and had its effect on
# the position. We need to know what its effect was:
before_qty = self.position.qty - order.qty
after_qty = self.position.qty
# if the order's role is CLOSE_POSITION but the position qty is not the same as this order's qty,
# then it's increase_position order (because the position was already open before this)
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
if role == order_roles.OPEN_POSITION:
# call the relevant strategy event handler:
# if opening position
if before_qty == 0 and after_qty != 0:
self._on_open_position(order)
elif role == order_roles.CLOSE_POSITION:
# if closing position
elif before_qty != 0 and after_qty == 0:
self._on_close_position(order)
elif role == order_roles.INCREASE_POSITION:
# if increasing position size
elif abs(after_qty) > abs(before_qty):
self._on_increased_position(order)
elif role == order_roles.REDUCE_POSITION:
# if reducing position size
elif abs(after_qty) < abs(before_qty):
self._on_reduced_position(order)
else:
pass
self._is_handling_updated_order = False
@@ -194,13 +188,13 @@ class Strategy(ABC):
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)
submitted_order = self.broker.buy_at_market(o[0])
# STOP order
elif o[1] > self.price:
submitted_order = self.broker.start_profit_at(sides.BUY, o[0], o[1], order_roles.OPEN_POSITION)
submitted_order = self.broker.start_profit_at(sides.BUY, o[0], o[1])
# LIMIT order
elif o[1] < self.price:
submitted_order = self.broker.buy_at(o[0], o[1], order_roles.OPEN_POSITION)
submitted_order = self.broker.buy_at(o[0], o[1])
else:
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
@@ -234,13 +228,13 @@ class Strategy(ABC):
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)
submitted_order = self.broker.sell_at_market(o[0])
# STOP order
elif o[1] < self.price:
submitted_order = self.broker.start_profit_at(sides.SELL, o[0], o[1], order_roles.OPEN_POSITION)
submitted_order = self.broker.start_profit_at(sides.SELL, o[0], o[1])
# LIMIT order
elif o[1] > self.price:
submitted_order = self.broker.sell_at(o[0], o[1], order_roles.OPEN_POSITION)
submitted_order = self.broker.sell_at(o[0], o[1])
else:
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
@@ -482,14 +476,13 @@ class Strategy(ABC):
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)
submitted_order = self.broker.buy_at_market(o[0])
# STOP order
elif o[1] > self.price:
submitted_order = self.broker.start_profit_at(sides.BUY, o[0], o[1],
order_roles.OPEN_POSITION)
submitted_order = self.broker.start_profit_at(sides.BUY, o[0], o[1])
# LIMIT order
elif o[1] < self.price:
submitted_order = self.broker.buy_at(o[0], o[1], order_roles.OPEN_POSITION)
submitted_order = self.broker.buy_at(o[0], o[1])
else:
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
@@ -512,14 +505,13 @@ class Strategy(ABC):
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)
submitted_order = self.broker.sell_at_market(o[0])
# STOP order
elif o[1] < self.price:
submitted_order = self.broker.start_profit_at(sides.SELL, o[0], o[1],
order_roles.OPEN_POSITION)
submitted_order = self.broker.start_profit_at(sides.SELL, o[0], o[1])
# LIMIT order
elif o[1] > self.price:
submitted_order = self.broker.sell_at(o[0], o[1], order_roles.OPEN_POSITION)
submitted_order = self.broker.sell_at(o[0], o[1])
else:
raise ValueError(f'Invalid order price: o[1]:{o[1]}, self.price:{self.price}')
@@ -541,11 +533,7 @@ class Strategy(ABC):
# remove canceled orders to optimize the loop
self._exit_orders = [o for o in self._exit_orders if not o.is_canceled]
for o in self._take_profit:
submitted_order: Order = self.broker.reduce_position_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
submitted_order: Order = self.broker.reduce_position_at(o[0], o[1])
if submitted_order:
submitted_order.submitted_via = 'take-profit'
self._exit_orders.append(submitted_order)
@@ -566,11 +554,7 @@ class Strategy(ABC):
# remove canceled orders to optimize the loop
self._exit_orders = [o for o in self._exit_orders if not o.is_canceled]
for o in self._stop_loss:
submitted_order: Order = self.broker.reduce_position_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
submitted_order: Order = self.broker.reduce_position_at(o[0], o[1])
if submitted_order:
submitted_order.submitted_via = 'stop-loss'
self._exit_orders.append(submitted_order)
@@ -668,19 +652,15 @@ class Strategy(ABC):
for o in self._take_profit:
# validation: make sure take-profit will exit with profit, if not, close the position
if self.is_long and o[1] <= self.position.entry_price:
submitted_order: Order = self.broker.sell_at_market(o[0], order_roles.CLOSE_POSITION)
submitted_order: Order = self.broker.sell_at_market(o[0])
logger.info(
'The take-profit is below entry-price for long position, so it will be replaced with a market order instead')
elif self.is_short and o[1] >= self.position.entry_price:
submitted_order: Order = self.broker.buy_at_market(o[0], order_roles.CLOSE_POSITION)
submitted_order: Order = self.broker.buy_at_market(o[0])
logger.info(
'The take-profit is above entry-price for a short position, so it will be replaced with a market order instead')
else:
submitted_order: Order = self.broker.reduce_position_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
submitted_order: Order = self.broker.reduce_position_at(o[0], o[1])
if submitted_order:
submitted_order.submitted_via = 'take-profit'
@@ -690,17 +670,13 @@ class Strategy(ABC):
for o in self._stop_loss:
# validation: make sure stop-loss will exit with profit, if not, close the position
if self.is_long and o[1] >= self.position.entry_price:
submitted_order: Order = self.broker.sell_at_market(o[0], order_roles.CLOSE_POSITION)
submitted_order: Order = self.broker.sell_at_market(o[0])
logger.info('The stop-loss is above entry-price for long position, so it will be replaced with a market order instead')
elif self.is_short and o[1] <= self.position.entry_price:
submitted_order: Order = self.broker.buy_at_market(o[0], order_roles.CLOSE_POSITION)
submitted_order: Order = self.broker.buy_at_market(o[0])
logger.info('The stop-loss is below entry-price for a short position, so it will be replaced with a market order instead')
else:
submitted_order: Order = self.broker.reduce_position_at(
o[0],
o[1],
order_roles.CLOSE_POSITION
)
submitted_order: Order = self.broker.reduce_position_at(o[0], o[1])
if submitted_order:
submitted_order.submitted_via = 'stop-loss'
@@ -851,9 +827,7 @@ class Strategy(ABC):
f"Closed open {self.exchange}-{self.symbol} position at {self.position.current_price} with PNL: {round(self.position.pnl, 4)}({round(self.position.pnl_percentage, 2)}%) because we reached the end of the backtest session."
)
# fake a closing (market) order so that the calculations would be correct
self.broker.reduce_position_at(
self.position.qty, self.position.current_price, order_roles.CLOSE_POSITION
)
self.broker.reduce_position_at(self.position.qty, self.position.current_price)
return
if len(self._entry_orders):

View File

@@ -1,4 +1,5 @@
from jesse.strategies import Strategy
import jesse.helpers as jh
# test_is_smart_enough_to_open_positions_via_market_orders
@@ -26,6 +27,3 @@ class Test05(Strategy):
def should_cancel(self):
return False
def filters(self):
return []

View File

View File

@@ -2,7 +2,7 @@ import pytest
import jesse.services.selectors as selectors
from jesse.config import config, reset_config
from jesse.enums import exchanges, timeframes, order_types, order_flags, order_roles
from jesse.enums import exchanges, timeframes, order_types
from jesse.exceptions import InvalidStrategy, NegativeBalance, OrderNotAllowed
from jesse.models import Position, Exchange
from jesse.routes import router
@@ -169,7 +169,7 @@ def test_opening_and_closing_position_with_stop():
assert exchange.available_margin() == 1000
assert exchange.wallet_balance() == 1000
# open position
open_position_order = broker.start_profit_at('buy', 1, 60, order_roles.OPEN_POSITION)
open_position_order = broker.start_profit_at('buy', 1, 60)
open_position_order.execute()
position.current_price = 60
assert position.is_open is True
@@ -180,14 +180,14 @@ def test_opening_and_closing_position_with_stop():
assert exchange.available_margin() == 940
# submit stop-loss order
stop_loss_order = broker.reduce_position_at(1, 40, order_roles.CLOSE_POSITION)
assert stop_loss_order.flag == order_flags.REDUCE_ONLY
stop_loss_order = broker.reduce_position_at(1, 40)
assert stop_loss_order.reduce_only is True
# balance should NOT have changed
assert exchange.assets['USDT'] == 1000
assert exchange.wallet_balance() == 1000
# submit take-profit order also
take_profit_order = broker.reduce_position_at(1, 80, order_roles.CLOSE_POSITION)
assert take_profit_order.flag == order_flags.REDUCE_ONLY
take_profit_order = broker.reduce_position_at(1, 80)
assert take_profit_order.reduce_only is True
assert exchange.assets['USDT'] == 1000
# execute stop order
@@ -259,7 +259,7 @@ def test_stop_loss():
assert order.price == 40
assert order.qty == -1
assert order.side == 'sell'
assert order.flag == order_flags.REDUCE_ONLY
assert order.reduce_only is True
# balance should NOT have changed
assert exchange.available_margin() == 950
assert exchange.wallet_balance() == 1000

View File

@@ -1,6 +1,6 @@
import jesse.helpers as jh
from jesse.config import reset_config
from jesse.enums import exchanges, order_roles
from jesse.enums import exchanges
from jesse.factories import candles_from_close_prices
from jesse.models import CompletedTrade
from jesse.routes import router
@@ -42,13 +42,6 @@ def test_can_handle_multiple_entry_orders_too_close_to_each_other():
assert t.exit_price == 3
# 4 entry + 1 exit
assert len(t.orders) == 5
# last order is closing order
assert t.orders[-1].role == order_roles.CLOSE_POSITION
# first order must be opening order
assert t.orders[0].role == order_roles.OPEN_POSITION
# second order must be increasing order
assert t.orders[1].role == order_roles.INCREASE_POSITION
assert t.orders[2].role == order_roles.INCREASE_POSITION
def test_conflicting_orders():

View File

@@ -7,7 +7,7 @@ import jesse.helpers as jh
import jesse.services.selectors as selectors
from jesse import exceptions
from jesse.config import reset_config
from jesse.enums import exchanges, timeframes, order_roles, order_types
from jesse.enums import exchanges, timeframes, order_types
from jesse.factories import range_candles, candles_from_close_prices
from jesse.models import CompletedTrade
from jesse.models import Order
@@ -91,11 +91,8 @@ def test_can_perform_backtest_with_multiple_routes():
assert o.price == s.candles[0][2]
assert o.created_at == short_candles[4][0] + 60_000
assert o.is_executed is True
assert s.orders[0].role == order_roles.OPEN_POSITION
assert s.orders[0].type == order_types.MARKET
assert s.orders[2].role == order_roles.CLOSE_POSITION
assert s.orders[2].type == order_types.STOP
assert s.orders[1].role == order_roles.CLOSE_POSITION
assert s.orders[1].type == order_types.LIMIT
assert s.trade is None
assert len(store.completed_trades.trades) == 2
@@ -347,16 +344,12 @@ def test_multiple_routes_can_communicate_with_each_other():
assert len(s.orders) == 1
# assert that the order got canceled
assert o.is_canceled is True
assert s.orders[0].role == order_roles.OPEN_POSITION
assert s.orders[0].type == order_types.LIMIT
elif r.strategy.trades_count == 1:
assert len(s.orders) == 3
assert o.is_executed is True
assert s.orders[0].role == order_roles.OPEN_POSITION
assert s.orders[0].type == order_types.LIMIT
assert s.orders[2].role == order_roles.CLOSE_POSITION
assert s.orders[2].type == order_types.STOP
assert s.orders[1].role == order_roles.CLOSE_POSITION
assert s.orders[1].type == order_types.LIMIT
@@ -551,9 +544,6 @@ def test_should_buy_and_execute_buy():
assert o.price == s.candles[0][2]
assert o.created_at == short_candles[4][0] + 60_000
assert o.is_executed is True
assert s.orders[1].role == order_roles.CLOSE_POSITION
assert s.orders[2].role == order_roles.CLOSE_POSITION
assert s.orders[0].role == order_roles.OPEN_POSITION
assert s.trade is None
trade: CompletedTrade = store.completed_trades.trades[0]
assert trade.type == 'long'
@@ -597,9 +587,6 @@ def test_should_sell_and_execute_sell():
assert o.price == s.candles[0][2]
assert o.created_at == short_candles[4][0] + 60_000
assert o.is_executed is True
assert s.orders[1].role == order_roles.CLOSE_POSITION
assert s.orders[2].role == order_roles.CLOSE_POSITION
assert s.orders[0].role == order_roles.OPEN_POSITION
assert s.trade is None
assert len(store.completed_trades.trades) == 1
assert store.completed_trades.trades[0].type == 'short'