Skip to content

TradeProvider

Backtests

For backtests there is no need to define a Trade Provider. On creation the Epic object automatically uses a TradeProviderBacktests that set every open trade status to confirmed.

Live trade provider

In order to call your custom trade provider, you have to instanciate a BaseTradeProvider.

import arrow

from estrade import BaseTradeProvider, Epic, Tick
from estrade.enums import TradeDirection, TransactionStatus


class MyTradeProvider(BaseTradeProvider):
    def open_trade_request(self, trade):
        # call your api to open a new trade
        # eg: response = requests.get("http://my_provider.com/open_trade", params={
        #     "epic": trade.epic.ref,
        #     "direction": trade.direction,
        #     "quantity": trade.quantity,
        #     ...
        # })

        # update trade with your provider response
        trade.meta["provider_id"] = 123
        trade.status = TransactionStatus.PENDING

        return trade

    def close_trade_request(self, trade_close):
        # call your api to close a trade.
        # eg: response = requests.get("http://my_provider.com/close_trade", params={
        #     "epic": trade_close.trade.epic.ref,
        #     "quantity": trade_close.quantity,
        #     ...
        # })

        # update trade with your provider response
        trade_close.meta["provider_close_id"] = 123
        trade_close.status = TransactionStatus.REQUIRED

        return trade_close


def test_trade_provider_custom():
    # GIVEN an instance of our custom trade provider attached to an Epic
    trade_provider = MyTradeProvider()
    epic = Epic(ref="MY_EPIC", trade_provider=trade_provider)

    # Add a tick to the epic
    tick = Tick(datetime=arrow.utcnow(), bid=99, ask=101)
    epic.on_new_tick(tick)

    # WHEN I create a new trade add open it with the Trade Provider
    trade = epic.open_trade(direction=TradeDirection.SELL, quantity=2)

    # THEN a new trade is created
    assert len(epic.trade_provider.trades) == 1

    # THEN the trade attribute were updated by the trade provider
    trade_in_provider = epic.trade_provider.trades[0]
    assert trade_in_provider.status == TransactionStatus.PENDING
    assert trade_in_provider.meta["provider_id"] == 123

    # WHEN I close the trade
    epic.close_trade(trade=trade, quantity=1)

    # THEN a close of one quantity was created on the opened trade
    assert len(trade_in_provider.closes) == 1

    # THEN the trade close attributes where set
    trade_close_in_provider = trade_in_provider.closes[0]
    assert trade_close_in_provider.status == TransactionStatus.REQUIRED
    assert trade_close_in_provider.meta["provider_close_id"] == 123

TradeProvider Base Class

Abstract object to create, update and close Trades.

Attributes:

Name Type Description
trades List[estrade.trade.Trade]

List of Trades managed by this instance.

threads List[threading.Thread]

List of opened Threads (used to call provider)

default_transaction_status estrade.enums.TransactionStatus

default status to assign to new received Trades .

ref str

reference of this instance (see estrade.mixins.ref.RefMixin)

is_alive: bool property readonly

Check if some threads are still running.

Returns:

Type Description
bool

Are some threads still running?

opened_trades: List[Trade] property readonly

List all opened trades for this instance.

Returns:

Type Description
List[Trade]

List of opened trades

ref: str inherited property writable

Return ref of current instance.

Returns:

Type Description
str

reference of current instance.

__init__(self, ref=None) special

Create a new TradeProvider.

Parameters:

Name Type Description Default
ref Optional[str]

trade provider identifier.

None
Source code in estrade/trade_provider.py
def __init__(self, ref: Optional[str] = None) -> None:
    """
    Create a new TradeProvider.

    Arguments:
        ref: trade provider identifier.
    """
    self.trades: List["Trade"] = []
    self.threads: List[threading.Thread] = []
    self.default_transaction_status = TransactionStatus.REQUIRED
    RefMixin.__init__(self, ref)

    logger.info("New trade provider created.")

close_trade(self, trade_close)

Close a trade.

Parameters:

Name Type Description Default
trade_close TradeClose

Close to register

required
Source code in estrade/trade_provider.py
def close_trade(self, trade_close: "TradeClose") -> None:
    """
    Close a trade.

    Arguments:
        trade_close: Close to register
    """
    thr = threading.Thread(
        target=self.close_trade_request,
        args=(),
        kwargs={"trade_close": trade_close},
    )
    self.threads.append(thr)
    thr.start()

close_trade_request(self, trade_close)

Call an external Trade Provider to close a Trade.

Note

after sending request to provider, it is recommened to update the TradeClose object:

  • trade_close.status to set to pending or confirmed.
  • trade_close.meta to update with the provider transaction details.

Parameters:

Name Type Description Default
trade_close TradeClose

TradeClose instance.

required

Returns:

Type Description
TradeClose

updated TradeClose instance

Source code in estrade/trade_provider.py
def close_trade_request(self, trade_close: "TradeClose") -> "TradeClose":
    """
    Call an external Trade Provider to close a Trade.

    !!! note
        after sending request to provider, it is recommened to update the
        TradeClose object:

         - `trade_close.status` to set to pending or confirmed.
         - `trade_close.meta` to update with the provider transaction details.

    Arguments:
        trade_close: [`TradeClose`][estrade.trade.TradeClose] instance.

    Returns:
        updated [`TradeClose`][estrade.trade.TradeClose] instance
    """
    raise NotImplementedError()

open_trade(self, trade)

Open a trade.

Create a thread to perform the provider request (so the thread request does not bock the rest of the program)

Parameters:

Name Type Description Default
trade Trade

New trade to create.

required
Source code in estrade/trade_provider.py
def open_trade(self, trade: "Trade") -> None:
    """
    Open a trade.

    Create a thread to perform the provider request (so the thread request does
    not bock the rest of the program)

    Arguments:
        trade: New trade to create.
    """
    self.trades.append(trade)
    if trade.strategy:
        trade.strategy.trades.append(trade)

    thr = threading.Thread(
        target=self.open_trade_request,
        args=(),
        kwargs={"trade": trade},
    )
    self.threads.append(thr)
    thr.start()

open_trade_request(self, trade)

Call an external Trade Provider to open a new Trade.

Note

after sending request to provider, it is recommened to update the trade object:

  • trade.status to set to pending or confirmed.
  • trade.meta to update with the provider transaction details.

Parameters:

Name Type Description Default
trade Trade

Trade instance to open

required

Returns:

Type Description
Trade

updated Trade instance

Source code in estrade/trade_provider.py
def open_trade_request(self, trade: "Trade") -> "Trade":
    """
    Call an external Trade Provider to open a new Trade.

    !!! note
        after sending request to provider, it is recommened to update the
        trade object:

         - `trade.status` to set to pending or confirmed.
         - `trade.meta` to update with the provider transaction details.

    Arguments:
        trade: [`Trade`][estrade.trade.Trade] instance to open

    Returns:
        updated [`Trade`][estrade.trade.Trade] instance
    """
    raise NotImplementedError()

TradeProviderBacktests Class

Trade Provider that does not perform any external call to open/close trades.

is_alive: bool inherited property readonly

Check if some threads are still running.

Returns:

Type Description
bool

Are some threads still running?

opened_trades: List[Trade] inherited property readonly

List all opened trades for this instance.

Returns:

Type Description
List[Trade]

List of opened trades

ref: str inherited property writable

Return ref of current instance.

Returns:

Type Description
str

reference of current instance.

close_trade(self, trade_close) inherited

Close a trade.

Parameters:

Name Type Description Default
trade_close TradeClose

Close to register

required
Source code in estrade/trade_provider.py
def close_trade(self, trade_close: "TradeClose") -> None:
    """
    Close a trade.

    Arguments:
        trade_close: Close to register
    """
    thr = threading.Thread(
        target=self.close_trade_request,
        args=(),
        kwargs={"trade_close": trade_close},
    )
    self.threads.append(thr)
    thr.start()

close_trade_request(self, trade_close)

Automatically set trade_close as confirmed.

Parameters:

Name Type Description Default
trade_close TradeClose

close to perform on trade.

required
Source code in estrade/trade_provider.py
def close_trade_request(self, trade_close: "TradeClose") -> "TradeClose":
    """
    Automatically set trade_close as confirmed.

    Arguments:
        trade_close: close to perform on trade.
    """
    trade_close.status = TransactionStatus.CONFIRMED
    return trade_close

open_trade(self, trade)

Open a trade in backtests.

Warning

This function checks if a trade is currently opened in the opposite direction. In this case, the currently opened trade is partially closed with the input trade quantity.

Parameters:

Name Type Description Default
trade Trade

New trade to open

required
Source code in estrade/trade_provider.py
def open_trade(self, trade: "Trade") -> None:
    """
    Open a trade in backtests.

    !!! warning
        This function checks if a trade is currently opened in the opposite
        direction. In this case, the currently opened trade is partially closed
        with the input trade quantity.

    Arguments:
        trade: New trade to open

    """
    opposite_direction = (
        TradeDirection.BUY
        if trade.direction == TradeDirection.SELL
        else TradeDirection.SELL
    )

    for open_trade in self.opened_trades:
        if open_trade.direction == opposite_direction:
            logger.info(
                "A trade was opened when another is still open in "
                "the opposite direction."
            )
            if open_trade.strategy != trade.strategy:
                logger.info(
                    "As both trades does not belongs to the same strategy, "
                    "the existing trade will not be closed. Be warned that in "
                    "a live environement, this behaviour will not be applied."
                )
                continue
            logger.warning("It will be closed before opening a new position")
            quantities_to_close = min(
                open_trade.opened_quantities, trade.open_quantity
            )
            trade_close = open_trade.close_from_epic(quantity=quantities_to_close)
            self.close_trade(trade_close=trade_close)

            trade.open_quantity -= quantities_to_close

    # if after decrement, the trade to open has a zero quantity, stop
    if trade.open_quantity <= 0:
        logger.warning("Do not open trade.")
        return

    BaseTradeProvider.open_trade(self, trade)

open_trade_request(self, trade)

Automatically set trade as confirmed.

Parameters:

Name Type Description Default
trade Trade

trade to open.

required
Source code in estrade/trade_provider.py
def open_trade_request(self, trade: "Trade") -> "Trade":
    """
    Automatically set trade as confirmed.

    Arguments:
        trade: trade to open.
    """
    trade.status = TransactionStatus.CONFIRMED
    return trade