From 23e6220d347f71812ad6e452c09e9f6c75c235ab Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Tue, 7 Jan 2025 21:50:59 +0000 Subject: [PATCH 01/11] variable pandas version per python version --- pyproject.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b86d7a0..f0ba193 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,12 @@ requests-cache = "^0.5" six = "^1.15" lightstreamer-client-lib = "^1.0.3" -pandas = { version = "^1", optional = true } +pandas = [ + {version = "^2.1", python = ">=3.9", optional = true}, + {version = "^1.5", python = ">=3.8,<3.9", optional = true}, + {version = "^1.3", python = ">=3.7.1,<3.8", optional = true}, + {version = "^1.1", python = ">=3.6.1,<3.7.1", optional = true}, +] munch = { version = "^2.5", optional = true } tenacity = {version = "^8", optional = true} From f6db636388dfb430cf4728249442aa1198264d23 Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Tue, 7 Jan 2025 22:31:48 +0000 Subject: [PATCH 02/11] handle UTC dateformat for newer pandas --- trading_ig/rest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/trading_ig/rest.py b/trading_ig/rest.py index 33cc63e..926252f 100644 --- a/trading_ig/rest.py +++ b/trading_ig/rest.py @@ -1637,9 +1637,11 @@ def flat_prices(self, prices, version): if version == "3": df = df.set_index("snapshotTimeUTC") df = df.drop(columns=["snapshotTime"]) + date_format = "%Y-%m-%dT%H:%M:%S" else: df = df.set_index("snapshotTime") - df.index = pd.to_datetime(df.index, format=DATE_FORMATS[int(version)]) + date_format = DATE_FORMATS[int(version)] + df.index = pd.to_datetime(df.index, format=date_format) df.index.name = "DateTime" df = df.drop( columns=[ From cd0ab8cf07611ac19a9bbe811f6e4dbf5f37b537 Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:16:05 +0000 Subject: [PATCH 03/11] drop 3.8, add 3.11, 3.12, 3.13 --- .github/workflows/unit-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 0150753..1c4886b 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.8", "3.9", "3.10" ] + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] steps: From db643afb8b7542e496f4aef13bd06c3eda81a4df Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:16:26 +0000 Subject: [PATCH 04/11] run against 3.10 --- .github/workflows/integration-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index a64f450..8455dfd 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -15,10 +15,10 @@ jobs: - uses: actions/checkout@v4 - - name: Set up Python 3.8 + - name: Set up Python 3.10 uses: actions/setup-python@v5 with: - python-version: 3.8 + python-version: 3.10 - name: Install Poetry uses: abatilo/actions-poetry@v2 From 117a844011617ce70094934ba5251ee3fdbaa263 Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:17:02 +0000 Subject: [PATCH 05/11] supress deprecation warnings --- trading_ig/utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/trading_ig/utils.py b/trading_ig/utils.py index f006aaa..5cc25e2 100644 --- a/trading_ig/utils.py +++ b/trading_ig/utils.py @@ -46,13 +46,13 @@ def conv_resol(resolution): to_offset("10Min"): "MINUTE_10", to_offset("15Min"): "MINUTE_15", to_offset("30Min"): "MINUTE_30", - to_offset("1H"): "HOUR", - to_offset("2H"): "HOUR_2", - to_offset("3H"): "HOUR_3", - to_offset("4H"): "HOUR_4", + to_offset("1h"): "HOUR", + to_offset("2h"): "HOUR_2", + to_offset("3h"): "HOUR_3", + to_offset("4h"): "HOUR_4", to_offset("D"): "DAY", to_offset("W"): "WEEK", - to_offset("M"): "MONTH", + to_offset("ME"): "MONTH", } offset = to_offset(resolution) if offset in d: From 26eab06b9cc0b99e9370ccbc4f4d4befeb197509 Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:19:07 +0000 Subject: [PATCH 06/11] better way to replace None with NaN in price data --- trading_ig/rest.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/trading_ig/rest.py b/trading_ig/rest.py index 926252f..c13ebd6 100644 --- a/trading_ig/rest.py +++ b/trading_ig/rest.py @@ -1613,6 +1613,11 @@ def cols(typ): keys.append("last") df2 = pd.concat(data, axis=1, keys=keys) + + # force all object columns to be numeric, NaN if error + for col in df2.select_dtypes(include=['object']).columns: + df2[col] = pd.to_numeric(df2[col], errors='coerce') + return df2 def flat_prices(self, prices, version): @@ -1800,7 +1805,6 @@ def fetch_historical_prices_by_epic( format = self.format_prices if self.return_dataframe: data["prices"] = format(data["prices"], version) - data["prices"] = data["prices"].fillna(value=np.nan) self.log_allowance(data["metadata"]) return data @@ -1822,7 +1826,6 @@ def fetch_historical_prices_by_epic_and_num_points( format = self.format_prices if self.return_dataframe: data["prices"] = format(data["prices"], version) - data["prices"] = data["prices"].fillna(value=np.nan) return data def fetch_historical_prices_by_epic_and_date_range( @@ -1886,7 +1889,6 @@ def fetch_historical_prices_by_epic_and_date_range( format = self.format_prices if self.return_dataframe: data["prices"] = format(data["prices"], version) - data["prices"] = data["prices"].fillna(value=np.nan) return data def log_allowance(self, data): From e7297c3497dc8b205005da127e2305cbe03f776a Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:24:58 +0000 Subject: [PATCH 07/11] min python 3.9 --- pyproject.toml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f0ba193..12cb1ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,20 +28,15 @@ classifiers = [ ] [tool.poetry.dependencies] -python = ">=3.7,<4.0" +python = ">=3.9,<4.0" requests = "^2.24" pycryptodome = "^3.9" requests-cache = "^0.5" six = "^1.15" lightstreamer-client-lib = "^1.0.3" -pandas = [ - {version = "^2.1", python = ">=3.9", optional = true}, - {version = "^1.5", python = ">=3.8,<3.9", optional = true}, - {version = "^1.3", python = ">=3.7.1,<3.8", optional = true}, - {version = "^1.1", python = ">=3.6.1,<3.7.1", optional = true}, -] -munch = { version = "^2.5", optional = true } +pandas = {version = "^2", optional = true} +munch = {version = "^2.5", optional = true} tenacity = {version = "^8", optional = true} [tool.poetry.extras] From 7c1092ab425c1904b8ac39942c6219e15f73bd86 Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:25:14 +0000 Subject: [PATCH 08/11] python 3.9 - 3.11 --- .github/workflows/unit-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 1c4886b..2a724fc 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.9", "3.10", "3.11" ] steps: From 7a5840227cb0488b384e5b01d8d0e028581054a3 Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:28:39 +0000 Subject: [PATCH 09/11] black --- trading_ig/rest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trading_ig/rest.py b/trading_ig/rest.py index c13ebd6..d6834eb 100644 --- a/trading_ig/rest.py +++ b/trading_ig/rest.py @@ -1615,8 +1615,8 @@ def cols(typ): df2 = pd.concat(data, axis=1, keys=keys) # force all object columns to be numeric, NaN if error - for col in df2.select_dtypes(include=['object']).columns: - df2[col] = pd.to_numeric(df2[col], errors='coerce') + for col in df2.select_dtypes(include=["object"]).columns: + df2[col] = pd.to_numeric(df2[col], errors="coerce") return df2 From 3c930a3321950870e2c64a069b2d710fa4d0721c Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Wed, 8 Jan 2025 13:33:14 +0000 Subject: [PATCH 10/11] flake8 --- trading_ig/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trading_ig/rest.py b/trading_ig/rest.py index d6834eb..c9e2758 100644 --- a/trading_ig/rest.py +++ b/trading_ig/rest.py @@ -33,7 +33,7 @@ from .utils import munchify if _HAS_PANDAS: - from .utils import pd, np + from .utils import pd from pandas import json_normalize from threading import Thread From 2b7b81642d3b7b515ca72d07e5cbbde6c827c664 Mon Sep 17 00:00:00 2001 From: Andy Geach Date: Mon, 1 Dec 2025 11:16:25 +0000 Subject: [PATCH 11/11] skip tests where munch needed if not imported --- tests/test_integration.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_integration.py b/tests/test_integration.py index b697b37..a372404 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -18,6 +18,12 @@ stop_after_attempt, ) +try: + import munch # noqa: F401 + + munch_installed = True +except ImportError: + munch_installed = False RETRYABLE = (ApiExceededException, TokenInvalidException) @@ -417,6 +423,7 @@ def test_fetch_market_by_epic(self, ig_service: IGService): response = ig_service.fetch_market_by_epic("CS.D.EURUSD.MINI.IP") assert isinstance(response, dict) + @pytest.mark.skipif(not munch_installed, reason="Requires munch") def test_fetch_markets_by_epics(self, ig_service: IGService): markets_list = ig_service.fetch_markets_by_epics( "IX.D.SPTRD.MONTH1.IP,IX.D.FTSE.DAILY.IP", version="1" @@ -663,6 +670,7 @@ def test_fetch_historical_prices_by_epic_mid(self, ig_service: IGService): assert prices.shape[0] == 5 assert prices.shape[1] == 5 + @pytest.mark.skipif(not munch_installed, reason="Requires munch") def test_create_open_position(self, ig_service: IGService): epic = "IX.D.FTSE.DAILY.IP" market_info = ig_service.fetch_market_by_epic(epic) @@ -728,6 +736,7 @@ def test_create_open_position(self, ig_service: IGService): assert close_result["dealStatus"] == "ACCEPTED" assert close_result["reason"] == "SUCCESS" + @pytest.mark.skipif(not munch_installed, reason="Requires munch") def test_create_working_order(self, ig_service: IGService): epic = "CS.D.GBPUSD.TODAY.IP" market_info = ig_service.fetch_market_by_epic(epic) @@ -761,6 +770,7 @@ def test_create_working_order(self, ig_service: IGService): assert delete_result["dealStatus"] == "ACCEPTED" assert delete_result["reason"] == "SUCCESS" + @pytest.mark.skipif(not munch_installed, reason="Requires munch") def test_create_working_order_guaranteed_stop_loss(self, ig_service: IGService): epic = "CS.D.GBPUSD.TODAY.IP" market_info = ig_service.fetch_market_by_epic(epic)