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 diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 0150753..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.8", "3.9", "3.10" ] + python-version: [ "3.9", "3.10", "3.11" ] steps: diff --git a/pyproject.toml b/pyproject.toml index b86d7a0..f8cc48a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,25 +18,24 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Cython", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Topic :: Scientific/Engineering", "Topic :: Software Development :: Libraries", "License :: OSI Approved :: BSD License", ] [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 = "^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] diff --git a/sample/rest.ipynb b/sample/rest.ipynb new file mode 100644 index 0000000..6783168 --- /dev/null +++ b/sample/rest.ipynb @@ -0,0 +1,198 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ec8c83b74991ebc7", + "metadata": { + "collapsed": false + }, + "source": [ + "# Imports, objects, and starting a session\n", + "\n", + "Using v2 sessions by default" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6236766fd0340597", + "metadata": {}, + "outputs": [], + "source": [ + "from trading_ig.rest import IGService\n", + "from trading_ig.config import config\n", + "\n", + "service = IGService(\n", + " config.username,\n", + " config.password,\n", + " config.api_key,\n", + " config.acc_type,\n", + " acc_number=config.acc_number,\n", + ")\n", + "# creating a v2 session\n", + "service.create_session()\n", + "\n", + "# creating a v3 session\n", + "#service.create_session(version=\"3\")" + ] + }, + { + "cell_type": "markdown", + "id": "aa41443cec0fb2d2", + "metadata": { + "collapsed": false + }, + "source": [ + "# Fetching account information" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19f74547fb3174e1", + "metadata": {}, + "outputs": [], + "source": [ + "service.fetch_accounts()" + ] + }, + { + "cell_type": "markdown", + "id": "f3e01ba486501770", + "metadata": { + "collapsed": false + }, + "source": [ + "# Fetching historic prices" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3b72874aaba7a665", + "metadata": {}, + "outputs": [], + "source": [ + "data = service.fetch_historical_prices_by_epic(\n", + " epic=\"IX.D.FTSE.DAILY.IP\",\n", + " resolution=\"D\",\n", + " start_date=\"2024-01-01\",\n", + " end_date=\"2024-01-22\",\n", + " format=service.flat_prices\n", + ")\n", + "\n", + "data[\"prices\"]" + ] + }, + { + "cell_type": "markdown", + "id": "e34db8ecfc649f6e", + "metadata": { + "collapsed": false + }, + "source": [ + "# Save prices to file" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9cd0c0854f0a17c", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "\n", + "save_path = Path('~').expanduser() / \"prices.csv\"\n", + "print(f\"saving historic prices to {save_path}\")\n", + "\n", + "data[\"prices\"].to_csv(save_path, date_format=\"%Y-%m-%dT%H:%M:%S%z\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "d5ecd4aa7bc33dea", + "metadata": { + "collapsed": false + }, + "source": [ + "# Fetch history" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79c0c6ed2ca08a1c", + "metadata": {}, + "outputs": [], + "source": [ + "from datetime import datetime, timedelta\n", + "to_date = datetime.now()\n", + "from_date = to_date - timedelta(days=7)\n", + "\n", + "service.fetch_transaction_history(from_date=from_date, to_date=to_date)" + ] + }, + { + "cell_type": "markdown", + "id": "52e40ae5ef37d3eb", + "metadata": { + "collapsed": false + }, + "source": [ + "# Fetch activity (simple)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a089d1babd98781", + "metadata": {}, + "outputs": [], + "source": [ + "service.fetch_account_activity(from_date=from_date, to_date=to_date)" + ] + }, + { + "cell_type": "markdown", + "id": "5411321a662441be", + "metadata": { + "collapsed": false + }, + "source": [ + "# Fetch activity (detailed)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c31119d129505286", + "metadata": {}, + "outputs": [], + "source": [ + "service.fetch_account_activity(from_date=from_date, to_date=to_date, detailed=True)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.16" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/trading_ig/rest.py b/trading_ig/rest.py index 33cc63e..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 @@ -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): @@ -1637,9 +1642,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=[ @@ -1798,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 @@ -1820,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( @@ -1884,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): 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: