Skip to content

Commit

Permalink
add new example strategy, position logic
Browse files Browse the repository at this point in the history
  • Loading branch information
robswc committed Mar 20, 2023
1 parent d890142 commit 00eff21
Show file tree
Hide file tree
Showing 4 changed files with 23 additions and 6 deletions.
14 changes: 11 additions & 3 deletions app/components/backtest/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def get_effect(position: Position, order: Order):
else:
return 'increase'


class BacktestResult(BaseModel):
pnl: float
wl_ratio: float
Expand All @@ -35,12 +36,16 @@ class BacktestResult(BaseModel):
positions: List[Position]
orders: List[Order]


class Backtest:
def __init__(self, data, strategy):
self.data = data
self.strategy = strategy
self.result = None

def _get_orders_with_filled_timestamp(self, orders):
return [o for o in orders if o.filled_timestamp is not None]

def _sort_orders(self, orders: List[Order]):
return sorted(orders, key=lambda x: x.timestamp)

Expand All @@ -50,15 +55,13 @@ def test(self):
positions = self.strategy.positions.all()
orders = self.strategy.orders.all()


# use concurrent futures to test orders in parallel
logger.debug(f'Testing {len(positions)} positions in parallel...')
with concurrent.futures.ThreadPoolExecutor() as executor:
for p in positions:
executor.submit(p.test, self.data)
logger.debug(f'Finished testing {len(positions)} positions in parallel.')


all_position_orders = []
for p in positions:
all_position_orders += p.orders
Expand All @@ -73,6 +76,11 @@ def test(self):
else:
wl_ratio = round(winning_trades / losing_trades, 2)

# filter and sort orders
result_orders = self._get_orders_with_filled_timestamp(orders + all_position_orders)
# sort ascending by timestamp
result_orders = self._sort_orders(result_orders)

# create backtest result
self.result = BacktestResult(
pnl=sum([position.pnl for position in positions]),
Expand All @@ -81,5 +89,5 @@ def test(self):
winning_trades=winning_trades,
losing_trades=losing_trades,
positions=positions,
orders=orders + all_position_orders,
orders=result_orders,
)
4 changes: 4 additions & 0 deletions app/components/orders/position.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ def handle_order(self, order: Union[Order, StopOrder, LimitOrder], ohlc: 'OHLC'
self.opened_timestamp = order.timestamp if self.opened_timestamp is None else self.opened_timestamp
self.closed = self.size == 0

# if order is missing filled timestamp, set it to the order timestamp
if order.filled_timestamp is None:
order.filled_timestamp = order.timestamp


class BracketPosition(Position):
pass
5 changes: 5 additions & 0 deletions app/components/orders/position_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ def __init__(self, strategy: 'BaseStrategy'):
self._strategy = strategy
self.positions: List[Position] = []

def add(self, position: Position):
"""Adds a position to the manager"""
# TODO: add validation
self.positions.append(position)

def open(self, order_type: str, side: str, quantity: int):
"""Opens a new position"""

Expand Down
6 changes: 3 additions & 3 deletions app/storage/strategies/examples/sma_cross_over_advanced.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ def check_for_crossover(self):
side='sell',
qty=100,
symbol=self.symbol.symbol,
limit_price=self.data.close + 5,
limit_price=self.data.close + 1,
)
stop_loss = StopOrder(
side='sell',
qty=100,
symbol=self.symbol.symbol,
stop_price=self.data.close - 5,
stop_price=self.data.close - 1,
)
p = Position(orders=[open_order, take_profit, stop_loss])
self.positions.append(p)
self.positions.add(p)

@after
def create_plots(self):
Expand Down

0 comments on commit 00eff21

Please sign in to comment.