{ "cells": [ { "cell_type": "raw", "metadata": {}, "source": [ "This notebook was generated on 17 April 2020. Vortexa is constantly improving the quality of our data and models, and consequently some historical data points may change causing future runs of the notebook to yield different results.\n", "\n", "Note that this notebook fetches and analyses public data from the website of U.S. Energy Information Admistration (EIA, www.eia.gov). Vortexa is not responsible for any third-party content.\n", "\n", "The version of Vortexa SDK used to generate this notebook was:\n", "\n", "vortexasdk-0.14.2\n", "\n", "The following packages were installed to run this notebook:\n", "\n", "xlrd==1.2.0\n", "matplotlib==3.2.1\n", "statsmodels==0.11.1" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import warnings\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from datetime import datetime\n", "from statsmodels.tsa.stattools import grangercausalitytests\n", "from vortexasdk import CargoTimeSeries, Products, Geographies\n", "\n", "warnings.filterwarnings(\"ignore\")\n", "plt.rcParams['figure.figsize'] = (15, 10)\n", "plt.rcParams.update({'font.size': 14})\n", "pd.options.display.max_columns = None" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Crude Floating Storage and its relation to Prices" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The oil markets are in turmoil. Demand for crude oil has collapsed due to the COVID-19 pandemic, and supply has surged due to the Russia-Saudi Arabia price war, causing an unprecedented oil glut that is rapidly filling land-based storage. [Vortexa data](https://analytics.vortexa.com/?activeTemplateId=b6c73502e93045b90c7fd05620f24b5d936695bb&page=Storage&tabId=e151735d-c29a-49e8-bceb-a7c35ef0d047&unit=t) shows that global crude oil floating storage has been steadily increasing since mid-March, suggesting that storage demand has started spilling over to seaborne storage.\n", "\n", "In this Notebook we analyse Vortexa's crude oil floating storage data (retrieved using our [Python SDK](https://github.com/VorTECHsa/python-sdk)) and its relationship to crude oil prices. Floating storage is directly determined by oil prices and market dynamics; however, it is not known whether floating storage may be predictive of oil prices in the future. In other words, can floating storage be used as a trading signal for crude oil? Here we apply Granger causality analysis on floating storage and price data, and for the first time demonstrate that floating storage may have significant predictive value for spot crude oil prices 2-3 months into the future." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Fetching and Visualising the Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Vortexa global crude oil floating storage data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For this analysis, we will look at the 4-year period ending in 31 January 2020, before the COVID-19 pandemic had a significant impact on floating storage." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Defining date range for historical analysis\n", "# NB: Vortexa data is currently available from 2016-01-01 with a maximum date range of 4 years per query \n", "START_DATE = datetime(2016, 1, 31)\n", "END_DATE = datetime(2020, 1, 31)\n", "DATE_RANGE = pd.date_range(START_DATE, END_DATE)\n", "\n", "# Define floating storage unit (t = metric tonnes, b = barrels)\n", "UNIT = 't'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We use the [CargoTimeSeries](https://vortechsa.github.io/python-sdk/endpoints/cargo_timeseries/) endpoint to download floating storage data from Vortexa. The function below makes an API query to retrieve the time series of global crude floating storage. The only filters set in this query are the required start and end time, the activity (set to `storing_state` for floating storage), and the product (set to Crude/Condensates; similar results are obtained if only Crude is selected, as Condensate floating storage volumes are low). The frequency of the time series is set to daily (the most granular available), and unit to metric tonnes (`t`; for barrels use `b`). Importantly, `disable_geographic_exclusion_rules` is set to `True` so that intra-country movements are included in the aggregation. Unless this parameter is set the default behaviour is to ignore cargo movements that load and discharge in the same country. Finally, we do not set `filter_storage_locations` so that the query will return the total amount of crude stored globally. " ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def fetch_global_crude_floating_storage_timeseries(start_date, end_date, unit='t'):\n", " \n", " # Find Crude/Condensates ID, ensuring its uniqueness\n", " crude_and_condensates = [p.id for p in Products().search('crude').to_list() if p.name=='Crude/Condensates']\n", " assert len(crude_and_condensates) == 1\n", " \n", " # make Vortexa API query\n", " # NB: disable_geographic_exclusion_rules is set to True to include intra-movements\n", " df_fs = CargoTimeSeries().search(timeseries_frequency='day',\n", " timeseries_unit=unit,\n", " disable_geographic_exclusion_rules=True,\n", " filter_products=crude_and_condensates,\n", " filter_activity=\"storing_state\",\n", " filter_time_min=start_date,\n", " filter_time_max=end_date).to_df()\n", "\n", " # rename columns\n", " df_fs = df_fs.rename(columns={'key': 'date',\n", " 'value': unit,\n", " 'count': 'number_of_cargo_movements'})\n", " \n", " # remove time zone from timestamp\n", " df_fs['date'] = pd.to_datetime(df_fs['date']).dt.tz_localize(None)\n", " \n", " return df_fs" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The function above makes the API query and returns a Pandas DataFrame with the time series data. Vessels in floating storage are considered to be in cargo movements, and CargoTimeSeries() will return a count of cargo movements satisfying the filters and their total volume. When the function is run, the SDK prints some log messages confirming the parameters used in the query. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2020-04-17 05:36:37,978 vortexasdk.operations — INFO — Searching Products with params: {'term': ['crude'], 'ids': [], 'product_parent': [], 'allowTopLevelProducts': True}\n", "2020-04-17 05:36:37,981 vortexasdk.client — INFO — Creating new VortexaClient\n", "2020-04-17 05:36:38,262 vortexasdk.client — INFO — 18 Results to retreive. Sending 1 post requests in parallel using 6 threads.\n", "2020-04-17 05:36:38,667 vortexasdk.operations — INFO — Searching CargoTimeSeries with params: {'filter_activity': 'storing_state', 'filter_time_min': '2016-01-31T00:00:00.000Z', 'filter_time_max': '2020-01-31T00:00:00.000Z', 'timeseries_activity_time_span_min': None, 'timeseries_activity_time_span_max': None, 'filter_charterers': [], 'filter_owners': [], 'filter_products': ['54af755a090118dcf9b0724c9a4e9f14745c26165385ffa7f1445bc768f06f11'], 'filter_vessels': [], 'filter_destinations': [], 'filter_origins': [], 'filter_storage_locations': [], 'filter_ship_to_ship_locations': [], 'filter_waypoints': [], 'disable_geographic_exclusion_rules': True, 'timeseries_frequency': 'day', 'timeseries_unit': 't', 'timeseries_activity': 'storing_state', 'size': 500}\n" ] }, { "data": { "text/html": [ "
\n", " | date | \n", "t | \n", "number_of_cargo_movements | \n", "
---|---|---|---|
0 | \n", "2016-01-31 | \n", "7436278 | \n", "108 | \n", "
1 | \n", "2016-02-01 | \n", "7841026 | \n", "107 | \n", "
2 | \n", "2016-02-02 | \n", "7555497 | \n", "114 | \n", "
3 | \n", "2016-02-03 | \n", "8065912 | \n", "120 | \n", "
4 | \n", "2016-02-04 | \n", "8329186 | \n", "118 | \n", "
... | \n", "... | \n", "... | \n", "... | \n", "
1457 | \n", "2020-01-27 | \n", "7919126 | \n", "118 | \n", "
1458 | \n", "2020-01-28 | \n", "8155308 | \n", "121 | \n", "
1459 | \n", "2020-01-29 | \n", "8501078 | \n", "124 | \n", "
1460 | \n", "2020-01-30 | \n", "8081172 | \n", "117 | \n", "
1461 | \n", "2020-01-31 | \n", "6827855 | \n", "100 | \n", "
1462 rows × 3 columns
\n", "\n", " | Cushing, OK WTI Spot Price FOB (Dollars per Barrel) | \n", "Europe Brent Spot Price FOB (Dollars per Barrel) | \n", "
---|---|---|
2016-01-31 | \n", "33.66 | \n", "33.14 | \n", "
2016-02-01 | \n", "31.62 | \n", "32.45 | \n", "
2016-02-02 | \n", "29.90 | \n", "30.98 | \n", "
2016-02-03 | \n", "32.29 | \n", "32.38 | \n", "
2016-02-04 | \n", "31.63 | \n", "32.76 | \n", "
... | \n", "... | \n", "... | \n", "
2020-01-27 | \n", "53.09 | \n", "58.54 | \n", "
2020-01-28 | \n", "53.33 | \n", "59.37 | \n", "
2020-01-29 | \n", "53.29 | \n", "59.46 | \n", "
2020-01-30 | \n", "52.19 | \n", "57.72 | \n", "
2020-01-31 | \n", "51.58 | \n", "57.77 | \n", "
1462 rows × 2 columns
\n", "\n", " | Cushing, OK Crude Oil Future Contract 1 (Dollars per Barrel) | \n", "Cushing, OK Crude Oil Future Contract 2 (Dollars per Barrel) | \n", "Cushing, OK Crude Oil Future Contract 3 (Dollars per Barrel) | \n", "Cushing, OK Crude Oil Future Contract 4 (Dollars per Barrel) | \n", "
---|---|---|---|---|
2016-01-31 | \n", "33.62 | \n", "35.28 | \n", "36.78 | \n", "38.00 | \n", "
2016-02-01 | \n", "31.62 | \n", "33.36 | \n", "34.93 | \n", "36.23 | \n", "
2016-02-02 | \n", "29.88 | \n", "31.61 | \n", "33.24 | \n", "34.63 | \n", "
2016-02-03 | \n", "32.28 | \n", "33.86 | \n", "35.39 | \n", "36.67 | \n", "
2016-02-04 | \n", "31.72 | \n", "33.38 | \n", "34.95 | \n", "36.30 | \n", "
... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
2020-01-27 | \n", "53.14 | \n", "53.18 | \n", "53.13 | \n", "52.97 | \n", "
2020-01-28 | \n", "53.48 | \n", "53.52 | \n", "53.48 | \n", "53.33 | \n", "
2020-01-29 | \n", "53.33 | \n", "53.41 | \n", "53.42 | \n", "53.31 | \n", "
2020-01-30 | \n", "52.14 | \n", "52.23 | \n", "52.27 | \n", "52.23 | \n", "
2020-01-31 | \n", "51.56 | \n", "51.68 | \n", "51.77 | \n", "51.80 | \n", "
1462 rows × 4 columns
\n", "\n", " | Cushing, OK Crude Oil Future Contract 1 (Dollars per Barrel) | \n", "Cushing, OK Crude Oil Future Contract 2 (Dollars per Barrel) | \n", "Cushing, OK Crude Oil Future Contract 3 (Dollars per Barrel) | \n", "Cushing, OK Crude Oil Future Contract 4 (Dollars per Barrel) | \n", "
---|---|---|---|---|
2016-01-31 | \n", "-0.04 | \n", "1.62 | \n", "3.12 | \n", "4.34 | \n", "
2016-02-01 | \n", "0.00 | \n", "1.74 | \n", "3.31 | \n", "4.61 | \n", "
2016-02-02 | \n", "-0.02 | \n", "1.71 | \n", "3.34 | \n", "4.73 | \n", "
2016-02-03 | \n", "-0.01 | \n", "1.57 | \n", "3.10 | \n", "4.38 | \n", "
2016-02-04 | \n", "0.09 | \n", "1.75 | \n", "3.32 | \n", "4.67 | \n", "
... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
2020-01-27 | \n", "0.05 | \n", "0.09 | \n", "0.04 | \n", "-0.12 | \n", "
2020-01-28 | \n", "0.15 | \n", "0.19 | \n", "0.15 | \n", "0.00 | \n", "
2020-01-29 | \n", "0.04 | \n", "0.12 | \n", "0.13 | \n", "0.02 | \n", "
2020-01-30 | \n", "-0.05 | \n", "0.04 | \n", "0.08 | \n", "0.04 | \n", "
2020-01-31 | \n", "-0.02 | \n", "0.10 | \n", "0.19 | \n", "0.22 | \n", "
1462 rows × 4 columns
\n", "