Trader
Overview¶
A trader is responsible for creating orders and managing the risk. It can create orders based on the signals it receives that are created by a Strategy, but it can also create orders based on the latest version of the account.
API¶
The Trader API has 1 single method called create_orders that needs to be implemented:
class MyTrader(Trader):
def create_orders(self, signals: list[Signal], event: Event, account: Account) -> list[Order]:
...Some of the typical logic found in a trader:
looks at the incoming signals to see what potential orders to create
looks at open positions to see how to size for exit orders
looks at buying power to see how much to allocate for an entry order
looks at open orders to see if there is a conflict
look at open positions to manage bad performing assets (risk management)
SimpleTrader¶
The SimpleTrader is the default trader implementation in roboquant.
As the name suggests, it implements a simple set of rules. This makes it easier to understand what is going on, although not suitable for all use cases.
Key characteristics:
Configurable number of max open positions.
The buying power is equally allocated over the remaining free positions.
A position will only be opened or closed, never increased or decreased.
FlexTrader¶
FlexTrader uses a percentage of the equity to determine the desired order sizes. So if your equity grows during a back test, so does the average order size.
Some of the features:
support for fractional order sizes
support for minimum and maximum order values (% of equity)
support for limiting position sizes (% of equity)
support for increase and decrease of position sizes
extensive logging of the applied rules
can be subclasses to change behavior
configurable order limit calculation
Custom Trader¶
If you have custom risk policies, you’ll have to implement a custom trader. It requires a lot of testing to see if all edge cases are handled.
A very basic and naive implementation would look something like this:
class MyTrader(Trader):
def create_orders(self, signals: list[Signal], event: Event, account: Account) -> list[Order]:
orders = []
for signal in signals:
asset = signal.asset
if price := event.get_price(asset):
if signal.is_buy:
order = Order(signal.asset, 1, price)
else:
order = Order(signal.asset, -1, price)
orders.append(order)
return ordersBut this handles none of the challenges:
How much should I allocate to a new order
What to do with signals if there is already an open order for that signal
What to do with open positions that have increasing unrealized losses (or profit)
Am I not generating too many orders (especially a concern for higher frequency price-data)
Below is an example how risk management could be implemented for open positions that are loosing too much money.
def close_loosing_positions(account: Account) -> list[Order]:
orders = []
for pos in account.positions:
amt = pos.unrealized_pnl().convert_to(USD, account.last_update)
if amt < -1000:
order = pos.close_order()
orders.append(order)
return orders