Skip to content

Conversation

@LawiK974
Copy link
Contributor

@LawiK974 LawiK974 commented Sep 14, 2025

Bug fix: Properly handle buy_to_close and sell_to_close orders in backtesting position tracking

Issue

When using buy_to_close orders to close short positions in backtesting, the positions were not being properly tracked due to a mismatch in order side handling. The issue occurred because _process_crypto_quote() was checking for "buy" string directly, but our order sides could be "buy_to_close" or "buy_to_open".

Changes

  • Modified _process_filled_order() to normalize order sides before processing:
    • buy_to_close and buy_to_open are normalized to BUY
    • sell_to_close and sell_to_open are normalized to SELL

Impact

This fixes position tracking in backtesting, particularly for :

  • Closing short positions with buy_to_close orders
  • Preventing "ghost" positions that appeared to remain open after being closed
  • Ensuring accurate position quantities in the backtesting stats

Example

Before the fix, a short position closed with buy_to_close would show :

# trades.csv
time,strategy,identifier,symbol,side,type,status,multiplier,time_in_force,asset.strike,asset.multiplier,asset.asset_type,price,filled_quantity,trade_cost
2024-09-03 00:00:00-04:00,MLTrader,b8dc6ee9e20942448cb0465b68d20fa0,ETH-USD,sell,market,new,1,gtc,0.0,1,crypto,,,
2024-09-03 00:00:00-04:00,MLTrader,b8dc6ee9e20942448cb0465b68d20fa0,ETH-USD,sell,market,fill,1,gtc,0.0,1,crypto,2516.0,1.9879515138228698,0.0
2024-09-03 20:00:00-04:00,MLTrader,98a7fd29651140d8b4b27d883c89ae4e,ETH-USD,buy_to_close,stop,canceled,1,day,0.0,1,crypto,,,
2024-09-03 20:00:00-04:00,MLTrader,a01b76650e0748beb72fc75f1ed718d1,ETH-USD,buy_to_close,limit,fill,1,day,0.0,1,crypto,2425.28,1.9879515138228698,0.0

# stats.csv
2024-09-03 20:00:00-04:00,1,0,1.0,10354.621912727398,15175.96096017173,"[{'asset': ETH-USD, 'quantity': -1.9879515138228698}]",0.005466791649808789
2024-09-03 21:00:00-04:00,2,0,1.0,10469.923100529128,19997.30000761606,"[{'asset': ETH-USD, 'quantity': -3.9759030276457397}]",0.011135238811569526  # position remains and is doubled !

Testing

  • Verified with both short and long position sequences
  • Tested with market and limit orders using take_profit_price and stop_loss_price

Description by Korbit AI

What change is being made?

Convert buy_to_open/buy_to_close orders to BUY and sell_to_open/sell_to_close orders to SELL when processing filled orders in the backtester, to align filled orders with correct position updates.

Why are these changes being made?

To fix incorrect backtest position tracking caused by misinterpreting buy_to_* and sell_to_* fills when no broker updates positions.

Is this description stale? Ask me to generate a new description by commenting /korbit-generate-pr-description

Copy link
Contributor

@korbit-ai korbit-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review by Korbit AI

Korbit automatically attempts to detect when you fix issues in new commits.
Category Issue Status
Documentation Missing business context in side conversion comments ▹ view 🧠 Not in standard
Files scanned
File Path Reviewed
lumibot/backtesting/backtesting_broker.py

Explore our documentation to understand the languages and file types we support and the files we ignore.

Check out our docs on how you can make Korbit work best for you and your team.

Loving Korbit!? Share us on LinkedIn Reddit and X

Comment on lines +451 to +456
# If the order has a "buy_to_open" or "buy_to_close" side, then we should change it to "buy"
if order.is_buy_order():
order.side = Order.OrderSide.BUY
# If the order has a "sell_to_open" or "sell_to_close" side, then we should change it to "sell"
if order.is_sell_order():
order.side = Order.OrderSide.SELL

This comment was marked as resolved.

Copy link
Contributor

@grzesir grzesir Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not sure this is a good idea, this will return less data back to the user, no? it sounds like we will be pretending that buy_to_open order and buy_to_close would be the same thing. maybe im wrong, but we should be preserving those values. perhaps we can do this transformation somewhere else where do not have to modify the data

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You raise a good point about preserving order type information. Let's explore alternatives that maintain order specificity while addressing the position tracking issue. Perhaps we could introduce a separate field for normalized sides used in position calculations, keeping the original order type intact. This would preserve data fidelity while ensuring consistent position tracking. What are your thoughts on this approach?

Comment on lines +451 to +456
# If the order has a "buy_to_open" or "buy_to_close" side, then we should change it to "buy"
if order.is_buy_order():
order.side = Order.OrderSide.BUY
# If the order has a "sell_to_open" or "sell_to_close" side, then we should change it to "sell"
if order.is_sell_order():
order.side = Order.OrderSide.SELL
Copy link
Contributor

@grzesir grzesir Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im not sure this is a good idea, this will return less data back to the user, no? it sounds like we will be pretending that buy_to_open order and buy_to_close would be the same thing. maybe im wrong, but we should be preserving those values. perhaps we can do this transformation somewhere else where do not have to modify the data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants