Skip to content

Commit

Permalink
handle both stop order sides
Browse files Browse the repository at this point in the history
  • Loading branch information
Guillaume De Saint Martin committed Jun 7, 2020
1 parent fde3e9d commit c85838e
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 68 deletions.
4 changes: 2 additions & 2 deletions octobot_trading/data/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,11 @@ def get_profitability(self):
if self.filled_price != 0 and self.created_last_price != 0:
if self.filled_price >= self.created_last_price:
self.order_profitability = 1 - self.filled_price / self.created_last_price
if self.side == TradeOrderSide.SELL:
if self.side is TradeOrderSide.SELL:
self.order_profitability *= -1
else:
self.order_profitability = 1 - self.created_last_price / self.filled_price
if self.side == TradeOrderSide.BUY:
if self.side is TradeOrderSide.BUY:
self.order_profitability *= -1
return self.order_profitability

Expand Down
5 changes: 3 additions & 2 deletions octobot_trading/orders/order_factory.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ cpdef Order create_order_from_raw(Trader trader, dict raw_order)

cpdef Order create_order_instance_from_raw(Trader trader, dict raw_order)

cpdef Order create_order_from_type(Trader trader, object order_type)
cpdef Order create_order_from_type(Trader trader, object order_type, object side=*)

cpdef Order create_order_instance(Trader trader,
object order_type,
Expand All @@ -38,4 +38,5 @@ cpdef Order create_order_instance(Trader trader,
double quantity_filled=*,
double total_cost=*,
double timestamp=*,
object linked_portfolio=*)
object linked_portfolio=*,
object side=*)
13 changes: 9 additions & 4 deletions octobot_trading/orders/order_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ def create_order_instance_from_raw(trader, raw_order):
return order


def create_order_from_type(trader, order_type):
return TraderOrderTypeClasses[order_type](trader)
def create_order_from_type(trader, order_type, side=None):
if side is None:
return TraderOrderTypeClasses[order_type](trader)
return TraderOrderTypeClasses[order_type](trader, side=side)


def create_order_instance(trader,
Expand All @@ -47,9 +49,12 @@ def create_order_instance(trader,
quantity_filled=0.0,
total_cost=0.0,
timestamp=0,
linked_portfolio=None):
linked_portfolio=None,
side=None):
order = create_order_from_type(trader=trader,
order_type=order_type)
order_type=order_type,
side=side
)
order.update(order_type=order_type,
symbol=symbol,
current_price=current_price,
Expand Down
2 changes: 1 addition & 1 deletion octobot_trading/orders/types/limit/limit_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(self, trader, side=TradeOrderSide.BUY):
super().__init__(trader, side)
self.limit_price_hit_event = None
self.wait_for_hit_event_task = None
self.trigger_above = self.side == TradeOrderSide.SELL
self.trigger_above = self.side is TradeOrderSide.SELL

async def update_order_status(self, force_refresh=False):
if not self.trader.simulate and (not self.is_synchronized_with_exchange or force_refresh):
Expand Down
5 changes: 4 additions & 1 deletion octobot_trading/orders/types/limit/stop_loss_limit_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ def __init__(self, trader, side=TradeOrderSide.SELL):

async def on_fill(self):
await super().on_fill()
await self.trader.create_artificial_order(TraderOrderType.SELL_LIMIT, self.symbol, self.origin_stop_price,
await self.trader.create_artificial_order(TraderOrderType.SELL_MARKET
if self.side is TradeOrderSide.SELL
else TraderOrderType.BUY_MARKET,
self.symbol, self.origin_stop_price,
self.origin_quantity,
self.limit_price
if self.limit_price != self.UNINITIALIZED_LIMIT_PRICE else
Expand Down
7 changes: 5 additions & 2 deletions octobot_trading/orders/types/limit/stop_loss_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
class StopLossOrder(LimitOrder):
def __init__(self, trader, side=TradeOrderSide.SELL):
super().__init__(trader, side)
self.trigger_above = False
self.trigger_above = self.side is TradeOrderSide.BUY

async def on_fill(self):
await super().on_fill()
if not self.trader.simulate:
await self.trader.create_artificial_order(TraderOrderType.SELL_MARKET, self.symbol, self.origin_stop_price,
await self.trader.create_artificial_order(TraderOrderType.SELL_MARKET
if self.side is TradeOrderSide.SELL
else TraderOrderType.BUY_MARKET,
self.symbol, self.origin_stop_price,
self.origin_quantity, self.origin_stop_price,
self.linked_portfolio)
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ def __init__(self, trader, side=TradeOrderSide.SELL):

async def on_fill(self):
await super().on_fill()
await self.trader.create_artificial_order(TraderOrderType.SELL_LIMIT, self.symbol, self.origin_stop_price,
await self.trader.create_artificial_order(TraderOrderType.SELL_LIMIT
if self.side is TradeOrderSide.SELL
else TraderOrderType.BUY_LIMIT,
self.symbol, self.origin_stop_price,
self.origin_quantity,
self.limit_price
if self.limit_price != self.UNINITIALIZED_LIMIT_PRICE else
Expand Down
5 changes: 4 additions & 1 deletion octobot_trading/orders/types/limit/take_profit_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def __init__(self, trader, side=TradeOrderSide.SELL):

async def on_fill(self):
await super().on_fill()
await self.trader.create_artificial_order(TraderOrderType.SELL_MARKET, self.symbol, self.origin_stop_price,
await self.trader.create_artificial_order(TraderOrderType.SELL_LIMIT
if self.side is TradeOrderSide.SELL
else TraderOrderType.BUY_LIMIT,
self.symbol, self.origin_stop_price,
self.origin_quantity, self.origin_stop_price,
self.linked_portfolio)
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async def update_order_status(self, force_refresh=False):
if self.trailing_stop_price_hit_event is None:
self.trailing_stop_price_hit_event = self.exchange_manager.exchange_symbols_data.\
get_exchange_symbol_data(self.symbol).price_events_manager.\
add_event(self.origin_price, self.creation_time, self.side == TradeOrderSide.SELL)
add_event(self.origin_price, self.creation_time, self.side is TradeOrderSide.SELL)

if self.wait_for_hit_event_task is None and self.trailing_stop_price_hit_event is not None:
self.wait_for_hit_event_task = asyncio.create_task(self.wait_for_price_hit())
Expand Down
98 changes: 90 additions & 8 deletions tests/data/test_portfolio.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from octobot_trading.constants import CONFIG_SIMULATOR_FEES_MAKER, CONFIG_SIMULATOR_FEES_TAKER, CONFIG_SIMULATOR, \
CONFIG_SIMULATOR_FEES
from octobot_trading.enums import TraderOrderType
from octobot_trading.enums import TraderOrderType, TradeOrderSide
from octobot_trading.orders.types.limit.buy_limit_order import BuyLimitOrder
from octobot_trading.orders.types.limit.sell_limit_order import SellLimitOrder
from octobot_trading.orders.types.limit.stop_loss_order import StopLossOrder
Expand Down Expand Up @@ -298,7 +298,7 @@ async def test_update_portfolio_with_cancelled_orders(backtesting_trader):
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 1000


async def test_update_portfolio_with_stop_loss_orders(backtesting_trader):
async def test_update_portfolio_with_stop_loss_sell_orders(backtesting_trader):
config, exchange_manager, trader = backtesting_trader
portfolio_manager = exchange_manager.exchange_personal_data.portfolio_manager

Expand All @@ -319,7 +319,7 @@ async def test_update_portfolio_with_stop_loss_orders(backtesting_trader):
price=50)

# Test stop loss order
stop_loss = StopLossOrder(trader)
stop_loss = StopLossOrder(trader, side=TradeOrderSide.SELL)
stop_loss.update(order_type=TraderOrderType.STOP_LOSS,
symbol="BTC/USDT",
current_price=60,
Expand Down Expand Up @@ -349,6 +349,57 @@ async def test_update_portfolio_with_stop_loss_orders(backtesting_trader):
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 1240


async def test_update_portfolio_with_stop_loss_buy_orders(backtesting_trader):
config, exchange_manager, trader = backtesting_trader
portfolio_manager = exchange_manager.exchange_personal_data.portfolio_manager

# Test buy order
limit_sell = SellLimitOrder(trader)
limit_sell.update(order_type=TraderOrderType.SELL_LIMIT,
symbol="BTC/USDT",
current_price=90,
quantity=4,
price=90)

# Test buy order
limit_buy = BuyLimitOrder(trader)
limit_buy.update(order_type=TraderOrderType.BUY_LIMIT,
symbol="BTC/USDT",
current_price=50,
quantity=4,
price=50)

# Test stop loss order
stop_loss = StopLossOrder(trader, side=TradeOrderSide.BUY)
stop_loss.update(order_type=TraderOrderType.STOP_LOSS,
symbol="BTC/USDT",
current_price=60,
quantity=4,
price=60)

portfolio_manager.portfolio.update_portfolio_available(stop_loss, True)
portfolio_manager.portfolio.update_portfolio_available(limit_sell, True)
portfolio_manager.portfolio.update_portfolio_available(limit_buy, True)

assert round(portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_AVAILABLE), 1) == 6
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_AVAILABLE) == 800
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 10
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 1000

# cancel limits
portfolio_manager.portfolio.update_portfolio_available(limit_buy, False)
portfolio_manager.portfolio.update_portfolio_available(limit_sell, False)

await fill_limit_or_stop_order(stop_loss)

# filling stop loss
# typical stop loss behavior --> update available before update portfolio
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_AVAILABLE) == 14
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_AVAILABLE) == 760
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 14
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 760


async def test_update_portfolio_with_some_filled_orders(backtesting_trader):
config, exchange_manager, trader = backtesting_trader
portfolio_manager = exchange_manager.exchange_personal_data.portfolio_manager
Expand Down Expand Up @@ -422,8 +473,32 @@ async def test_update_portfolio_with_some_filled_orders(backtesting_trader):
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 10
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 1000

# Test stop loss order
stop_loss_4 = StopLossOrder(trader, side=TradeOrderSide.BUY)
stop_loss_4.update(order_type=TraderOrderType.STOP_LOSS,
symbol="BTC/USDT",
current_price=20,
quantity=4,
price=20)

# Test stop loss order
stop_loss_5 = StopLossOrder(trader, side=TradeOrderSide.BUY)
stop_loss_5.update(order_type=TraderOrderType.STOP_LOSS,
symbol="BTC/USDT",
current_price=200,
quantity=4,
price=20)
portfolio_manager.portfolio.update_portfolio_available(stop_loss_5, True)

# portfolio did not change as stop losses are not affecting available funds
assert round(portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_AVAILABLE), 1) == 3
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_AVAILABLE) == 680
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 10
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 1000

# cancels
portfolio_manager.portfolio.update_portfolio_available(stop_loss_3, False)
portfolio_manager.portfolio.update_portfolio_available(stop_loss_5, False)
portfolio_manager.portfolio.update_portfolio_available(limit_sell_2, False)
portfolio_manager.portfolio.update_portfolio_available(limit_buy, False)

Expand All @@ -438,6 +513,12 @@ async def test_update_portfolio_with_some_filled_orders(backtesting_trader):
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 7
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 1200

await fill_limit_or_stop_order(stop_loss_4)
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_AVAILABLE) == 11
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_AVAILABLE) == 1120
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 11
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL) == 1120


async def test_update_portfolio_with_multiple_filled_orders(backtesting_trader):
config, exchange_manager, trader = backtesting_trader
Expand Down Expand Up @@ -540,7 +621,7 @@ async def test_update_portfolio_with_multiple_filled_orders(backtesting_trader):
price=50)

# Test stop loss order
stop_loss_4 = StopLossOrder(trader)
stop_loss_4 = StopLossOrder(trader, side=TradeOrderSide.BUY)
stop_loss_4.update(order_type=TraderOrderType.STOP_LOSS,
symbol="BTC/USDT",
current_price=45,
Expand All @@ -563,6 +644,7 @@ async def test_update_portfolio_with_multiple_filled_orders(backtesting_trader):
quantity=0.7,
price=9)


portfolio_manager.portfolio.update_portfolio_available(stop_loss_2, True)
portfolio_manager.portfolio.update_portfolio_available(stop_loss_3, True)
portfolio_manager.portfolio.update_portfolio_available(stop_loss_4, True)
Expand Down Expand Up @@ -604,10 +686,10 @@ async def test_update_portfolio_with_multiple_filled_orders(backtesting_trader):
await fill_limit_or_stop_order(limit_buy_5)
await fill_limit_or_stop_order(limit_buy_6)

assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_AVAILABLE) == 12.65448
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_AVAILABLE) == 692.22
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 12.65448
assert round(portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL), 7) == 691.4295063
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_AVAILABLE) == 13.05448
assert portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_AVAILABLE) == 674.22
assert portfolio_manager.portfolio.get_currency_portfolio("BTC", PORTFOLIO_TOTAL) == 13.05448
assert round(portfolio_manager.portfolio.get_currency_portfolio("USDT", PORTFOLIO_TOTAL), 7) == 673.4295063


async def test_update_portfolio_with_multiple_symbols_orders(backtesting_trader):
Expand Down
21 changes: 17 additions & 4 deletions tests/orders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# License along with this library.
import pytest

from octobot_trading.enums import TradeOrderSide
from octobot_trading.orders.types import BuyLimitOrder, SellLimitOrder, SellMarketOrder, BuyMarketOrder, StopLossOrder, \
StopLossLimitOrder, TakeProfitOrder, TakeProfitLimitOrder

Expand Down Expand Up @@ -47,9 +48,15 @@ def sell_market_order(event_loop, simulated_trader):


@pytest.fixture()
def stop_loss_order(event_loop, simulated_trader):
def stop_loss_sell_order(event_loop, simulated_trader):
_, _, trader_instance = simulated_trader
return StopLossOrder(trader_instance)
return StopLossOrder(trader_instance, side=TradeOrderSide.SELL)


@pytest.fixture()
def stop_loss_buy_order(event_loop, simulated_trader):
_, _, trader_instance = simulated_trader
return StopLossOrder(trader_instance, side=TradeOrderSide.BUY)


@pytest.fixture()
Expand All @@ -59,9 +66,15 @@ def stop_loss_limit_order(event_loop, simulated_trader):


@pytest.fixture()
def take_profit_order(event_loop, simulated_trader):
def take_profit_sell_order(event_loop, simulated_trader):
_, _, trader_instance = simulated_trader
return TakeProfitOrder(trader_instance, side=TradeOrderSide.SELL)


@pytest.fixture()
def take_profit_buy_order(event_loop, simulated_trader):
_, _, trader_instance = simulated_trader
return TakeProfitOrder(trader_instance)
return TakeProfitOrder(trader_instance, side=TradeOrderSide.BUY)


@pytest.fixture()
Expand Down

0 comments on commit c85838e

Please sign in to comment.