Chavithra / degiro-connector

This is yet another library to access Degiro's API.
BSD 3-Clause "New" or "Revised" License
220 stars 48 forks source link

Request to improve the docu of the Trading API timeout (section 3.8) by adding an example how to handle timeout exceptions #64

Closed funnel20 closed 2 years ago

funnel20 commented 2 years ago

We build a simple script:

  1. The script is started 1 hour before market opening, to analyse the pre-market prices.
  2. At start of the script, the Trading API is started via connect().
  3. During this 1 hour period, there is no activity on the Trading API.
  4. After market opening, when the strategy detects a BUY opportunity , it sends a check_order() to the Trading API.

So the first BUY order in step 4, starts at least 1 hour after connecting the Trading API. As expected, it did throw a TimeoutError exception.

We're looking for a solution. The docu states:

  1. The Trading API connection has a timeout of "around" 30 minutes.
  2. Every time you do an operation using the Trading API connection the timeout resets.

Analysis

Where comes "around 30 minutes" from? Digging into the source code, I discovered that the Trading API uses a nested timeout property in the ModelConnection object. It's set to constant timeouts.TRADING_TIMEOUT which is defined at 1800 seconds. This is exactly 30 minutes, not "around".

How to handle the timeout exception and restore the connection? Of course a not so robust/pretty work-around would be to schedule a recurring call to for instance get_update(), to keep the session alive. However, it would be more appropriate to detect the TimeoutError exception and act upon it.

Since I've discovered the ModelConnection object, I can prototype by changing the timeout to 2 seconds, and create an order 3 seconds later:

# SETUP TRADING API
trading_api = TradingAPI(
    credentials=credentials,
    connection_storage=ModelConnection(
        timeout=2,
    )
)

# Connect:
trading_api.connect()

# Wait 3 seconds to let the session expire:
time.sleep(3)

# SETUP ORDER
order = Order(
    action=Order.Action.BUY,
    order_type=Order.OrderType.LIMIT,
    price=10,
    product_id=71981,
    size=1,
    time_type=Order.TimeType.GOOD_TILL_DAY,
)

# FETCH CHECKING_RESPONSE
checking_response = trading_api.check_order(order=order)

This raises indeed a TimeoutError exception:

Traceback (most recent call last):
  File "/Users/me/broker.py", line 499, in <module>
    checking_response = trading_api.check_order(order=order)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/degiro_connector/core/abstracts/abstract_action.py", line 72, in __call__
    return self.call(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/degiro_connector/trading/actions/action_check_order.py", line 168, in call
    session_id = connection_storage.session_id
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/wrapt/decorators.py", line 526, in _synchronized_wrapper
    return wrapped(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/degiro_connector/core/models/model_connection.py", line 30, in session_id
    raise TimeoutError("Connection has probably expired.")
TimeoutError: Connection has probably expired.

It appears that a proper solution is to add an exception handler to each call to the Trader API. In case of an exception it will call connect() again, followed by a repeat of the original call. So the last row of the sample code:

# FETCH CHECKING_RESPONSE
checking_response = trading_api.check_order(order=order)

Is replaced by:

try:
    checking_response = trading_api.check_order(order=order)
except TimeoutError:
    logging.warning("TradingAPI session did timeout, reconnecting for new session ID...")
    trading_api.connect()
    checking_response = trading_api.check_order(order=order)

This shows the following in the log, while the order is checked successfully:

WARNING - TradingAPI session did timeout, reconnecting for new session ID...
INFO - get_session_id:response_dict: {'isPassCodeEnabled': False, 'locale': 'nl_NL', 'redirectUrl': 'https://trader.degiro.nl/trader/', 'sessionId': '2BADBBEF3****', 'status': 0, 'statusText': 'success'}
INFO - confirmation_id: "053df7cf-****"
response_datetime {
  seconds: 1643801134
  nanos: 715765000
}

Can we adjust/extend the timeout? Why is timeouts.TRADING_TIMEOUT set to 1800? Is this a necessity for DeGiro, or can we increase it? To for example 36000 (10 hours) so we will not encounter TimeoutError exceptions during a trading day.

After we can come up with proper answers, I'm happy to update the docu.

Chavithra commented 2 years ago

30MIN TIMEOUT You can try by running some scripts with a timers. I have estimated 15sec and 30min were close enough timeouts last time I have checked : for the Quotecast and Trading.

RECONNECT Yes that is the purpose for which I have raised this exception. I am using it like that : to catch it on time and reconnect without generating errors on API side.

PRE-MARKET PRICES Which function from Trading API are you using to get pre-market prices ? How come it doesn’t refresh the timeout ? I am suprised.

Thanks

funnel20 commented 2 years ago

Hi @Chavithra Thanks, for the confirmation. Regarding Pre-market prices, we use Yahoo Finance as we did not found any frequent updates from DeGiro QuoteCast API.

I'll make a PR to update the docu to reflect:

funnel20 commented 2 years ago

@Chavithra PR is ready

funnel20 commented 2 years ago

Closed by PR #65