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 |
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 |
ref |
str |
reference of this instance
(see |
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 |
|
required |
Returns:
Type | Description |
---|---|
TradeClose |
updated |
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 |
|
required |
Returns:
Type | Description |
---|---|
Trade |
updated |
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