Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not understanding IB open positions and closing them #47

Open
weklund opened this issue Feb 7, 2025 · 8 comments
Open

Not understanding IB open positions and closing them #47

weklund opened this issue Feb 7, 2025 · 8 comments

Comments

@weklund
Copy link

weklund commented Feb 7, 2025

Not technically an ibind issue, but this repo seems to be the best brain trust for understanding the API, outside of spamming IB customer support. Hoping I can get some extra context 😁

My question is 2 part:

  1. How do I go about 'closing' an open position?
  2. Does an API response change based on a particular reason?

1. How do I go about 'closing' an open position?

I'm going through and following the example given in the place order file here
I see the response from using client.place_order() from it

{'order_id': '<MY_ORDER_ID>', 'local_order_id': '<MY_LOCAL_ORDER_ID>', 'order_status': 'PreSubmitted', 'encrypt_mes
sage': '1'}

Cool.

So I can double check when the order is filled that it's open with client.positions()

[
    {
        "acctId": "XXXXXXXXX",
        "conid": 672387468,
        "contractDesc": "MNQ      MAR2025",
        "position": 2.0,
        "mktPrice": 21851.8769531,
        "mktValue": 87407.51,
        "currency": "USD",
        "avgCost": 43704.12,
        "avgPrice": 21852.06,
        "realizedPnl": -428.48,
        "unrealizedPnl": -0.73,
        "exchs": null,
        "expiry": null,
        "putOrCall": null,
        "multiplier": null,
        "strike": 0.0,
        "exerciseStyle": null,
        "conExchMap": [],
        "assetClass": "FUT",
        "undConid": 0,
        "model": ""
    }
]

Now I want to close this position. Am I making another order that takes the opposite side and same size?

for position in open_positions:
    if position['conid'] == expected_conid:
        conid = position['conid']
        quantity = position['position']
        side = 'SELL' if quantity > 0 else 'BUY'  # Determine the side to close the position

        order_tag="umcanICloseThis"
        order_type="MKT"

        order_request = make_order_request(
           conid=conid,
           side=side,
           quantity=abs(quantity),
           order_type=order_type,
           acct_id=account_id,
           coid=order_tag
        )

        answers = {
            QuestionType.PRICE_PERCENTAGE_CONSTRAINT: True,
            QuestionType.ORDER_VALUE_LIMIT: True,
            "Unforeseen new question": True,
        }

        # Submit the order
        client.place_order(order_request, answers, account_id)
 

Both open and close orders I made were MKT, but the close orders stayed unfulfilled for a really long time so I feel like I'm missing something.

2. Does an API response change based on a particular reason?

After I opened the position, everytime I called to the status via client.positions() I would get this response:

{
  "acctId": "XXXXXXXXX",
  "conid": 672387468,
  "contractDesc": "MNQ      MAR2025",
  "position": 2.0,
  "mktPrice": 21770.4296875,
  "mktValue": 87081.72,
  "currency": "USD",
  "avgCost": 43536.12,
  "avgPrice": 21768.06,
  "realizedPnl": 0.0,
  "unrealizedPnl": 9.48,
  "exchs": null,
  "expiry": null,
  "putOrCall": null,
  "multiplier": null,
  "strike": 0.0,
  "exerciseStyle": null,
  "conExchMap": [],
  "assetClass": "FUT",
  "undConid": 0,
  "model": ""
}

But other times I'll make the exact same call seconds later but get a different response type

{
  "acctId": "XXXXXXXXX",
  "conid": 672387468,
  "contractDesc": "MNQ      MAR2025",
  "position": 2.0,
  "mktPrice": 21770.4296875,
  "mktValue": 87081.72,
  "currency": "USD",
  "avgCost": 43536.12,
  "avgPrice": 21768.06,
  "realizedPnl": 0.0,
  "unrealizedPnl": 9.48,
  "exchs": null,
  "expiry": "20250321",
  "putOrCall": null,
  "multiplier": 2.0,
  "strike": "0",
  "exerciseStyle": null,
  "conExchMap": [],
  "assetClass": "FUT",
  "undConid": 362687422,
  "model": "",
  "baseMktValue": 69636.34671149254,
  "baseMktPrice": 17409.08642797731,
  "baseAvgCost": 34814.38293585777,
  "baseAvgPrice": 17407.191467928886,
  "baseRealizedPnl": 0.0,
  "baseUnrealizedPnl": 7.580839776992798,
  "incrementRules": [
    {
      "lowerEdge": 0.0,
      "increment": 0.25
    }
  ],
  "displayRule": {
    "magnification": 0,
    "displayRuleStep": [
      {
        "decimalDigits": 2,
        "lowerEdge": 0.0,
        "wholeDigits": 4
      }
    ]
  },
  "time": 13,
  "chineseName": "微型E-迷你纳斯达克100指数",
  "allExchanges": "CME",
  "listingExchange": "CME",
  "countryCode": "US",
  "name": "Micro E-Mini Nasdaq-100 Index",
  "lastTradingDay": "20250321",
  "group": null,
  "sector": null,
  "sectorGroup": null,
  "ticker": "MNQ",
  "type": "",
  "undComp": "Micro E-Mini Nasdaq-100 Index",
  "undSym": "MNQ",
  "underExchange": "CME",
  "hasOptions": true,
  "fullName": "MNQ Mar21'25",
  "isEventContract": false,
  "pageSize": 100
}

Is this common? Is there anything I can do to get consistent API behavior?

@salsasepp
Copy link

I can only comment on your 2nd question:

After I opened the position, everytime I called to the status via client.positions() I would get this response:
But other times I'll make the exact same call seconds later but get a different response type
Is this common? Is there anything I can do to get consistent API behavior?

I was wondering the same: See IBKR docs on the endpoint here: https://www.interactivebrokers.com/campus/ibkr-api-page/webapi-ref/#tag/Trading-Portfolio/paths/~1portfolio~1%7BaccountId%7D~1positions~1%7BpageId%7D/get

There is a parameter named waitForSecDef: "Forcing to wait for all security definition to be received. If false, position may not have secDef portion". This parameter is not used by client.positions() and presumably defaults to false. Your 2nd longer response has just this additional secDef information. It seems repeated positions() calls will include secDef info eventually.

See also position2(), which is not mentioned in IBKR docs. Never tried it.

@Voyz
Copy link
Owner

Voyz commented Feb 7, 2025

hey @weklund great questions. @salsasepp gave you a great explanation for the second - do try the position2 endpoint, it's meant to avoid IBKR server caching, - so let me address the first:

Now I want to close this position. Am I making another order that takes the opposite side and same size?

That's correct. 'Closing' a position isn't actually an action that one can take explicitly, as far as I know no trading platform exposes a close_position endpoint. It's more an intent to get out of whatever investment type you were making. I'm trying to avoid simply saying 'sell' because in different instrument types closing a position could mean different things.

In a simple case your example is correct - closing a position equals making one or more orders that will sell all your currently held positions of a stock.

Note that you could close a position using several orders, not just one, eg, over a course of a day - the intent to close all is still there. And contrarily, note that simply selling a portion - but not all - of your stock would probably be referred to as 'reducing' a position rather than 'closing' it.

Both open and close orders I made were MKT, but the close orders stayed unfulfilled for a really long time so I feel like I'm missing something.

Did you happen to submit these when the markets were closed? Eg. see NYSE trading hours: https://www.nyse.com/markets/hours-calendars

@weklund
Copy link
Author

weklund commented Feb 8, 2025

I was wondering the same: See IBKR docs on the endpoint here: https://www.interactivebrokers.com/campus/ibkr-api-page/webapi-ref/#tag/Trading-Portfolio/paths/~1portfolio~1%7BaccountId%7D~1positions~1%7BpageId%7D/get

@salsasepp That helps thank you! wow I've been looking at the wrong docs.... s/web-ref/webapi-ref/g gets you a completely different documentation! I've been looking at https://www.interactivebrokers.com/campus/ibkr-api-page/web-api/

Did you happen to submit these when the markets were closed? Eg. see NYSE trading hours: https://www.nyse.com/markets/hours-calendars

@Voyz I'm doing Futures exclusively but during after hours trading, and on paper account. Maybe it's harder for paper accounts to simulate order books after hours? Will try again during normal market hours and follow up.

@Voyz
Copy link
Owner

Voyz commented Feb 9, 2025

I'd imagine the after hours would be the cause of your issues here. For futures the concept of closing position is pretty much the same as for stocks.

@weklund
Copy link
Author

weklund commented Feb 12, 2025

Haven't tried position2 but I was able to open and close single orders. Closing this. Thanks for the help @Voyz @salsasepp :D

@weklund weklund closed this as completed Feb 12, 2025
@Voyz
Copy link
Owner

Voyz commented Feb 12, 2025

Glad to hear it worked! 👏 Do try position2 though, it skips the IBKR caching which should reduce one breakage point in your system.

@mmarihart
Copy link

@weklund @Voyz
Fyi: there's an undocumented parameter to 'iserver/account/{account_id}/orders' to close an existing position.

order_data = make_order_request(
            conid=symbol,
            side=action,
            quantity=quantity,
            order_type=order_type,
            acct_id=account_id,
            parent_id=parent_id,
            coid=order_id,
            price=price,
        )
        
order_data['isClose'] = True

You still need to know which direction the order has to take, however, one important difference is, that if there are OCO orders (or generally other orders for the given symbol?) they can be cancelled automatically.
For this you get the following question on order submission:

['<html>You are attempting to close a position that has open orders.<br><br>Would you like to cancel all open orders and then place new closing order?</html>']

This is how the web trading in the client portal does it, because it has the extra feature of closing an exisiting position.

@Voyz
Copy link
Owner

Voyz commented Feb 26, 2025

Wow @mmarihart that is super useful! How did you find about this endpoint? I'll add this parameter 🙌

@Voyz Voyz reopened this Mar 9, 2025
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

No branches or pull requests

4 participants