Skip to content

Commit

Permalink
fetch_deal_by_deal_reference() can now optionally attempt to return t…
Browse files Browse the repository at this point in the history
…he correct dealId when updating or closing a position. Fix for ig-python#320
  • Loading branch information
bug-or-feature committed Jan 16, 2024
1 parent 5dadeb7 commit b5cc7fe
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 6 deletions.
65 changes: 65 additions & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,71 @@ def test_create_open_position(self, ig_service: IGService):
assert close_result["dealStatus"] == "ACCEPTED"
assert close_result["reason"] == "SUCCESS"

def test_create_open_position_correcting_close_id(self, ig_service: IGService):
epic = "IX.D.FTSE.DAILY.IP"
market_info = ig_service.fetch_market_by_epic(epic)
status = market_info.snapshot.marketStatus
min_bet = market_info.dealingRules.minDealSize.value
bid = market_info.snapshot.bid
offer = market_info.snapshot.offer
if status != "TRADEABLE":
pytest.skip("Skipping open position test, market not open")

open_result = ig_service.create_open_position(
epic=epic,
direction="BUY",
currency_code="GBP",
order_type="MARKET",
expiry="DFB",
force_open="false",
guaranteed_stop="false",
size=min_bet,
level=None,
limit_level=None,
limit_distance=None,
quote_id=None,
stop_distance=None,
stop_level=None,
trailing_stop=None,
trailing_stop_increment=None,
)
open_id = open_result["dealId"]
assert open_result["dealStatus"] == "ACCEPTED"
assert open_result["reason"] == "SUCCESS"
assert open_result["status"] == "OPEN"
time.sleep(10)

update_result = ig_service.update_open_position(
offer * 1.5,
bid * 0.5,
open_result["dealId"],
fix_response=True,
)
update_id = update_result["dealId"]
assert update_result["dealStatus"] == "ACCEPTED"
assert update_result["reason"] == "SUCCESS"
assert update_result["status"] == "AMENDED"
assert open_id != update_id
time.sleep(10)

close_result = ig_service.close_open_position(
deal_id=open_result["dealId"],
direction="SELL",
epic=None,
expiry="DFB",
level=None,
order_type="MARKET",
quote_id=None,
size=0.5,
session=None,
fix_response=True,
)
close_id = close_result["dealId"]
assert close_result["dealStatus"] == "ACCEPTED"
assert close_result["reason"] == "SUCCESS"
assert close_result["status"] == "CLOSED"
assert open_id != close_id

def test_create_working_order(self, ig_service: IGService):
epic = "CS.D.GBPUSD.TODAY.IP"
market_info = ig_service.fetch_market_by_epic(epic)
Expand Down
74 changes: 68 additions & 6 deletions trading_ig/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from urllib.parse import urlparse, parse_qs

from datetime import timedelta, datetime
from .utils import _HAS_PANDAS, _HAS_MUNCH
from .utils import _HAS_PANDAS, _HAS_MUNCH, parse_utc
from .utils import (
conv_resol,
conv_datetime,
Expand Down Expand Up @@ -925,8 +925,28 @@ def fetch_transaction_history(

# -------- DEALING -------- #

def fetch_deal_by_deal_reference(self, deal_reference, session=None):
"""Returns a deal confirmation for the given deal reference"""
def fetch_deal_by_deal_reference(
self, deal_reference, session=None, fix_deal_id=False
):
"""
Returns a deal confirmation for the given deal reference.
There is a bug in IG's implementation of this function. When updating and
closing positions, this function responds with the dealId of the opening deal
instead. If the `fix_deal_id` param is set to True, we will attempt to rectify
this, by looking up the deal using the '/history/activity' endpoint, and
replacing the opening dealId with the correct one.
:param deal_reference: deal reference code
:type deal_reference: str
:param session: session object. Optional
:type session: Session
:param fix_deal_id: if true, attempt to update the response with the correct
dealId
:type fix_deal_id: bool
:return: details of the deal
:rtype: dict
"""
self.non_trading_rate_limit_pause_or_pass()
version = "1"
params = {}
Expand All @@ -941,6 +961,10 @@ def fetch_deal_by_deal_reference(self, deal_reference, session=None):
else:
break
data = self.parse_response(response.text)

if fix_deal_id:
data = self._fix_deal_id(data)

return data

def fetch_open_position_by_deal_id(self, deal_id, session=None):
Expand Down Expand Up @@ -1056,6 +1080,7 @@ def close_open_position(
size,
session=None,
time_in_force=None,
fix_response=False,
):
"""Closes one or more OTC positions"""
self.trading_rate_limit_pause_or_pass()
Expand All @@ -1078,7 +1103,9 @@ def close_open_position(

if response.status_code == 200:
deal_reference = json.loads(response.text)["dealReference"]
return self.fetch_deal_by_deal_reference(deal_reference)
return self.fetch_deal_by_deal_reference(
deal_reference, fix_deal_id=fix_response
)
else:
raise IGException(response.text)

Expand All @@ -1102,6 +1129,7 @@ def create_open_position(
trailing_stop_increment,
session=None,
time_in_force=None,
fix_response=False,
):
"""Creates an OTC position"""
self.trading_rate_limit_pause_or_pass()
Expand Down Expand Up @@ -1134,7 +1162,9 @@ def create_open_position(

if response.status_code == 200:
deal_reference = json.loads(response.text)["dealReference"]
return self.fetch_deal_by_deal_reference(deal_reference)
return self.fetch_deal_by_deal_reference(
deal_reference, fix_deal_id=fix_response
)
else:
raise IGException(response.text)

Expand All @@ -1149,6 +1179,7 @@ def update_open_position(
trailing_stop_increment=None,
session=None,
version="2",
fix_response=False,
):
"""Updates an OTC position"""
self.trading_rate_limit_pause_or_pass()
Expand All @@ -1173,7 +1204,9 @@ def update_open_position(

if response.status_code == 200:
deal_reference = json.loads(response.text)["dealReference"]
return self.fetch_deal_by_deal_reference(deal_reference)
return self.fetch_deal_by_deal_reference(
deal_reference, fix_deal_id=fix_response
)
else:
raise IGException(response.text)

Expand Down Expand Up @@ -1388,6 +1421,35 @@ def update_working_order(
else:
raise IGException(response.text)

def _fix_deal_id(self, result):
try:
deal_date = parse_utc(result["date"])
except Exception: # noqa
logger.warning(f"Failed to convert date: {result['date']}")
return result

search_filter = (
f"channel==PUBLIC_WEB_API;"
f"type==POSITION,type==EDIT_STOP_AND_LIMIT;"
f"epic=={result['epic']}"
)

for i in range(5):
df = self.fetch_account_activity(
from_date=deal_date - timedelta(seconds=1),
to_date=deal_date + timedelta(seconds=1),
fiql_filter=search_filter,
)
if len(df) != 1:
logger.info("Recent activity not found, retrying...")
time.sleep(0.5)
else:
deal_id = df.iloc[0]["dealId"]
result["dealId"] = deal_id
break

return result

# -------- END -------- #

# -------- MARKETS -------- #
Expand Down

0 comments on commit b5cc7fe

Please sign in to comment.