detquantlib 2.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- detquantlib/__init__.py +1 -0
- detquantlib/data/databases/detdatabase.py +535 -0
- detquantlib/data/entsoe/entsoe.py +174 -0
- detquantlib/data/sftp/sftp.py +89 -0
- detquantlib/dates/dates.py +154 -0
- detquantlib/figures/plotly_figures.py +82 -0
- detquantlib/stats/data_analysis.py +143 -0
- detquantlib/tradable_products/tradable_products.py +117 -0
- detquantlib/utils/utils.py +11 -0
- detquantlib-2.0.4.dist-info/LICENSE.txt +3 -0
- detquantlib-2.0.4.dist-info/METADATA +153 -0
- detquantlib-2.0.4.dist-info/RECORD +13 -0
- detquantlib-2.0.4.dist-info/WHEEL +4 -0
detquantlib/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Empty file required by poetry. Do not delete.
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
# Python built-in packages
|
|
2
|
+
import warnings
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from zoneinfo import ZoneInfo
|
|
5
|
+
|
|
6
|
+
# Third-party packages
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import pyodbc
|
|
9
|
+
from dateutil.relativedelta import *
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DetDatabase:
|
|
13
|
+
"""
|
|
14
|
+
A class to easily interact with the DET database, including fetching and processing data.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
username: str,
|
|
20
|
+
password: str,
|
|
21
|
+
server: str,
|
|
22
|
+
database: str,
|
|
23
|
+
connection: pyodbc.Connection = None,
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Constructor method.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
username: Database username
|
|
30
|
+
password: Database password
|
|
31
|
+
server: Database server name
|
|
32
|
+
database: Database name
|
|
33
|
+
connection: Database connection object. This argument does not have to be passed
|
|
34
|
+
when creating the object. It can be set after the object has been created, using
|
|
35
|
+
the open_connection() method.
|
|
36
|
+
"""
|
|
37
|
+
self.username = username
|
|
38
|
+
self.password = password
|
|
39
|
+
self.server = server
|
|
40
|
+
self.database = database
|
|
41
|
+
self.connection = connection
|
|
42
|
+
|
|
43
|
+
# Define the ODBC driver
|
|
44
|
+
self.driver = "{ODBC Driver 18 for SQL Server}"
|
|
45
|
+
|
|
46
|
+
def open_connection(self):
|
|
47
|
+
"""Opens a connection to the database."""
|
|
48
|
+
# Create the connection string
|
|
49
|
+
connection_str = (
|
|
50
|
+
f"DRIVER={self.driver};"
|
|
51
|
+
f"SERVER={self.server};"
|
|
52
|
+
f"DATABASE={self.database};"
|
|
53
|
+
f"UID={self.username};"
|
|
54
|
+
f"PWD={self.password}"
|
|
55
|
+
)
|
|
56
|
+
self.connection = pyodbc.connect(connection_str)
|
|
57
|
+
|
|
58
|
+
def close_connection(self):
|
|
59
|
+
"""Closes the connection to the database."""
|
|
60
|
+
self.connection.close()
|
|
61
|
+
|
|
62
|
+
def query_db(self, query: str) -> pd.DataFrame:
|
|
63
|
+
"""
|
|
64
|
+
Short utility method to make an SQL query to the database.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
query: SQL query
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Dataframe containing the queried data
|
|
71
|
+
|
|
72
|
+
Raises:
|
|
73
|
+
Exception: Raises an error if the SQL query fails
|
|
74
|
+
"""
|
|
75
|
+
with warnings.catch_warnings():
|
|
76
|
+
# Pandas UserWarning returned when using pandas with pyodbc. Disable warning
|
|
77
|
+
# temporarily for the SQL query.
|
|
78
|
+
warnings.simplefilter("ignore", category=UserWarning)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
df = pd.read_sql(query, self.connection)
|
|
82
|
+
except Exception as e:
|
|
83
|
+
# If query fails, close connection before raising the error
|
|
84
|
+
self.close_connection()
|
|
85
|
+
raise
|
|
86
|
+
|
|
87
|
+
return df
|
|
88
|
+
|
|
89
|
+
def load_entsoe_day_ahead_spot_prices(
|
|
90
|
+
self,
|
|
91
|
+
map_code: str,
|
|
92
|
+
timezone: str,
|
|
93
|
+
start_trading_date: datetime = None,
|
|
94
|
+
end_trading_date: datetime = None,
|
|
95
|
+
start_delivery_date: datetime = None,
|
|
96
|
+
end_delivery_date: datetime = None,
|
|
97
|
+
columns: list = None,
|
|
98
|
+
process_data: bool = True,
|
|
99
|
+
) -> pd.DataFrame:
|
|
100
|
+
"""
|
|
101
|
+
Loads entsoe day-ahead spot prices from the database.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
map_code: Map code of the power country/region
|
|
105
|
+
start_trading_date: Start trading date
|
|
106
|
+
end_trading_date: End trading date
|
|
107
|
+
Note: The user should provide either 'start_trading_date' and 'end_trading_date',
|
|
108
|
+
or 'start_delivery_date' and 'end_delivery_date'.
|
|
109
|
+
start_delivery_date: Delivery start date. The start datetime is included in the
|
|
110
|
+
filtering (i.e. delivery dates >= start_date).
|
|
111
|
+
end_delivery_date: Delivery end date. The end datetime is excluded from the filtering
|
|
112
|
+
(i.e. delivery dates < end_date).
|
|
113
|
+
Note: The user should provide either 'start_trading_date' and 'end_trading_date',
|
|
114
|
+
or 'start_delivery_date' and 'end_delivery_date'.
|
|
115
|
+
timezone: Timezone of the power country/region. This argument is important because
|
|
116
|
+
ENTSOE provides all prices in the UTC timezone. We first convert the dates from
|
|
117
|
+
UTC to the local timezone, and then filter for the requested delivery period.
|
|
118
|
+
columns: Requested database table columns. Set columns=["*"] (i.e. as list) to get
|
|
119
|
+
all columns.
|
|
120
|
+
process_data: Indicates if data should be processed convert to standardized format
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Dataframe containing day-ahead spot prices
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
ValueError: Raises an error when input arguments 'columns' and 'process_data' are
|
|
127
|
+
not compatible
|
|
128
|
+
ValueError: Raises an error when the combination of trading dates and delivery dates
|
|
129
|
+
is not valid.
|
|
130
|
+
"""
|
|
131
|
+
# Input validation
|
|
132
|
+
if process_data and columns is not None:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
"Input argument 'process_data' can only be true if input argument 'columns' "
|
|
135
|
+
"is None."
|
|
136
|
+
)
|
|
137
|
+
if not (
|
|
138
|
+
start_trading_date is not None
|
|
139
|
+
and end_trading_date is not None
|
|
140
|
+
and start_delivery_date is None
|
|
141
|
+
and end_delivery_date is None
|
|
142
|
+
) and not (
|
|
143
|
+
start_trading_date is None
|
|
144
|
+
and end_trading_date is None
|
|
145
|
+
and start_delivery_date is not None
|
|
146
|
+
and end_delivery_date is not None
|
|
147
|
+
):
|
|
148
|
+
raise ValueError(
|
|
149
|
+
"Either 'start_trading_date' and 'end_trading_date', or 'start_delivery_date' "
|
|
150
|
+
"and 'end_delivery_date' should be provided."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Set default column values
|
|
154
|
+
if columns is None:
|
|
155
|
+
columns = ["DateTime(UTC)", "MapCode", "Price(Currency/MWh)", "Currency"]
|
|
156
|
+
|
|
157
|
+
# Always add delivery date column
|
|
158
|
+
if "DateTime(UTC)" not in columns and columns != ["*"]:
|
|
159
|
+
columns.append("DateTime(UTC)")
|
|
160
|
+
|
|
161
|
+
# Convert columns from list to string
|
|
162
|
+
if len(columns) == 1:
|
|
163
|
+
columns_str = str(columns[0])
|
|
164
|
+
else:
|
|
165
|
+
columns_str = f"[{'], ['.join(columns)}]"
|
|
166
|
+
|
|
167
|
+
# Convert start trading date to start delivery date
|
|
168
|
+
if start_trading_date is not None:
|
|
169
|
+
start_trading_date = pd.Timestamp(start_trading_date).floor("D")
|
|
170
|
+
start_delivery_date = start_trading_date + relativedelta(days=1)
|
|
171
|
+
|
|
172
|
+
# Convert start delivery date from local timezone to UTC and string
|
|
173
|
+
start_delivery_date = start_delivery_date.replace(tzinfo=ZoneInfo(timezone))
|
|
174
|
+
start_delivery_date = start_delivery_date.astimezone(ZoneInfo("UTC"))
|
|
175
|
+
start_date_str = start_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
176
|
+
|
|
177
|
+
# Convert end trading date to end delivery date
|
|
178
|
+
if end_trading_date is not None:
|
|
179
|
+
end_trading_date = pd.Timestamp(end_trading_date).floor("D")
|
|
180
|
+
end_delivery_date = end_trading_date + relativedelta(days=2)
|
|
181
|
+
|
|
182
|
+
# Convert end date to UTC and string
|
|
183
|
+
end_delivery_date = end_delivery_date.replace(tzinfo=ZoneInfo(timezone))
|
|
184
|
+
end_delivery_date = end_delivery_date.astimezone(ZoneInfo("UTC"))
|
|
185
|
+
end_date_str = end_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
186
|
+
|
|
187
|
+
# Create query
|
|
188
|
+
table = DetDatabaseDefinitions.DEFINITIONS["table_name_entsoe_day_ahead_spot_price"]
|
|
189
|
+
query = (
|
|
190
|
+
f"SELECT {columns_str} FROM {table} "
|
|
191
|
+
f"WHERE MapCode='{map_code}' "
|
|
192
|
+
f"AND [DateTime(UTC)]>='{start_date_str}' "
|
|
193
|
+
f"AND [DateTime(UTC)]<'{end_date_str}' "
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Query db
|
|
197
|
+
self.open_connection()
|
|
198
|
+
df = self.query_db(query)
|
|
199
|
+
self.close_connection()
|
|
200
|
+
|
|
201
|
+
# Sort data by delivery date
|
|
202
|
+
df.sort_values(
|
|
203
|
+
by=["DateTime(UTC)"], axis=0, ascending=True, inplace=True, ignore_index=True
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Add column with delivery date expressed in local timezone
|
|
207
|
+
datetime_column_name = f"DateTime({timezone})"
|
|
208
|
+
df[datetime_column_name] = df["DateTime(UTC)"].dt.tz_localize("UTC")
|
|
209
|
+
df[datetime_column_name] = df[datetime_column_name].dt.tz_convert(timezone)
|
|
210
|
+
df[datetime_column_name] = df[datetime_column_name].dt.tz_localize(None)
|
|
211
|
+
|
|
212
|
+
# Process raw data and convert it to standardized format
|
|
213
|
+
if process_data:
|
|
214
|
+
df = DetDatabase.process_day_ahead_spot_prices(df, timezone)
|
|
215
|
+
|
|
216
|
+
return df
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def process_day_ahead_spot_prices(df_in: pd.DataFrame, timezone: str) -> pd.DataFrame:
|
|
220
|
+
"""
|
|
221
|
+
Processes day-ahead spot prices and converts from ENTSOE format to standardized format.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
df_in: Dataframe containing day-ahead spot prices
|
|
225
|
+
timezone: Timezone of the power country/region
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Processed dataframe containing day-ahead spot prices
|
|
229
|
+
"""
|
|
230
|
+
map_code_to_commodity_mapper = dict(
|
|
231
|
+
NL={"commodity_name": "DutchPower", "product_code": "Q0B"}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Initialize output dataframe
|
|
235
|
+
df_out = pd.DataFrame()
|
|
236
|
+
|
|
237
|
+
# Set commodity name
|
|
238
|
+
commodity_name = [
|
|
239
|
+
map_code_to_commodity_mapper[mc]["commodity_name"] for mc in df_in["MapCode"]
|
|
240
|
+
]
|
|
241
|
+
df_out["CommodityName"] = commodity_name
|
|
242
|
+
|
|
243
|
+
# Set product code
|
|
244
|
+
product_code = [
|
|
245
|
+
map_code_to_commodity_mapper[mc]["product_code"] for mc in df_in["MapCode"]
|
|
246
|
+
]
|
|
247
|
+
df_out["ProductCode"] = product_code
|
|
248
|
+
|
|
249
|
+
# Set trading date
|
|
250
|
+
trading_date = [d - relativedelta(days=1, hour=0) for d in df_in[f"DateTime({timezone})"]]
|
|
251
|
+
df_out["TradingDate"] = trading_date
|
|
252
|
+
|
|
253
|
+
# Set delivery start date
|
|
254
|
+
df_out["DeliveryStart"] = df_in[f"DateTime({timezone})"].values
|
|
255
|
+
|
|
256
|
+
# Set delivery end date
|
|
257
|
+
delivery_end = [d + relativedelta(hours=1) for d in df_in[f"DateTime({timezone})"]]
|
|
258
|
+
df_out["DeliveryEnd"] = delivery_end
|
|
259
|
+
|
|
260
|
+
# Set tenor
|
|
261
|
+
df_out["Tenor"] = "Spot"
|
|
262
|
+
|
|
263
|
+
# Set price
|
|
264
|
+
df_out["Price"] = df_in["Price(Currency/MWh)"].values
|
|
265
|
+
|
|
266
|
+
return df_out
|
|
267
|
+
|
|
268
|
+
def load_entsoe_imbalance_prices(
|
|
269
|
+
self,
|
|
270
|
+
map_code: str,
|
|
271
|
+
timezone: str,
|
|
272
|
+
start_trading_date: datetime = None,
|
|
273
|
+
end_trading_date: datetime = None,
|
|
274
|
+
start_delivery_date: datetime = None,
|
|
275
|
+
end_delivery_date: datetime = None,
|
|
276
|
+
columns: list = None,
|
|
277
|
+
process_data: bool = True,
|
|
278
|
+
) -> pd.DataFrame:
|
|
279
|
+
"""
|
|
280
|
+
Loads entsoe imbalance prices from the database.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
map_code: Map code of the power country/region
|
|
284
|
+
timezone: Timezone of the power country/region. This argument is important because
|
|
285
|
+
ENTSOE provides all prices in the UTC timezone. We first convert the dates from
|
|
286
|
+
UTC to the local timezone, and then filter for the requested delivery period.
|
|
287
|
+
start_trading_date: Start trading date
|
|
288
|
+
end_trading_date: End trading date
|
|
289
|
+
Note: The user should provide either 'start_trading_date' and 'end_trading_date',
|
|
290
|
+
or 'start_delivery_date' and 'end_delivery_date'.
|
|
291
|
+
start_delivery_date: Delivery start date. The start datetime is included in the
|
|
292
|
+
filtering (i.e. delivery dates >= start_date).
|
|
293
|
+
end_delivery_date: Delivery end date. The end datetime is excluded from the filtering
|
|
294
|
+
(i.e. delivery dates < end_date).
|
|
295
|
+
Note: The user should provide either 'start_trading_date' and 'end_trading_date',
|
|
296
|
+
or 'start_delivery_date' and 'end_delivery_date'.
|
|
297
|
+
columns: Requested database table columns. Set columns=["*"] (i.e. as list) to get
|
|
298
|
+
all columns.
|
|
299
|
+
process_data: Indicates if data should be processed convert to standardized format
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Dataframe containing imbalance prices
|
|
303
|
+
|
|
304
|
+
Raises:
|
|
305
|
+
ValueError: Raises an error when input arguments 'columns' and 'process_data' are
|
|
306
|
+
not compatible
|
|
307
|
+
ValueError: Raises an error when the combination of trading dates and delivery dates
|
|
308
|
+
is not valid.
|
|
309
|
+
"""
|
|
310
|
+
# Input validation
|
|
311
|
+
if process_data and columns is not None:
|
|
312
|
+
raise ValueError(
|
|
313
|
+
"Input argument 'process_data' can only be true if input argument 'columns' "
|
|
314
|
+
"is None."
|
|
315
|
+
)
|
|
316
|
+
if not (
|
|
317
|
+
start_trading_date is not None
|
|
318
|
+
and end_trading_date is not None
|
|
319
|
+
and start_delivery_date is None
|
|
320
|
+
and end_delivery_date is None
|
|
321
|
+
) and not (
|
|
322
|
+
start_trading_date is None
|
|
323
|
+
and end_trading_date is None
|
|
324
|
+
and start_delivery_date is not None
|
|
325
|
+
and end_delivery_date is not None
|
|
326
|
+
):
|
|
327
|
+
raise ValueError(
|
|
328
|
+
"Either 'start_trading_date' and 'end_trading_date', or 'start_delivery_date' "
|
|
329
|
+
"and 'end_delivery_date' should be provided."
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
# Set default column values
|
|
333
|
+
if columns is None:
|
|
334
|
+
columns = [
|
|
335
|
+
"DateTime(UTC)",
|
|
336
|
+
"MapCode",
|
|
337
|
+
"PositiveImbalancePrice",
|
|
338
|
+
"NegativeImbalancePrice",
|
|
339
|
+
"Currency",
|
|
340
|
+
]
|
|
341
|
+
|
|
342
|
+
# Always add delivery date column
|
|
343
|
+
if "DateTime(UTC)" not in columns and columns != ["*"]:
|
|
344
|
+
columns.append("DateTime(UTC)")
|
|
345
|
+
|
|
346
|
+
# Convert columns from list to string
|
|
347
|
+
if len(columns) == 1:
|
|
348
|
+
columns_str = str(columns[0])
|
|
349
|
+
else:
|
|
350
|
+
columns_str = f"[{'], ['.join(columns)}]"
|
|
351
|
+
|
|
352
|
+
# Convert start trading date to start delivery date
|
|
353
|
+
if start_trading_date is not None:
|
|
354
|
+
start_delivery_date = pd.Timestamp(start_trading_date).floor("D")
|
|
355
|
+
|
|
356
|
+
# Convert start delivery date from local timezone to UTC and string
|
|
357
|
+
start_delivery_date = start_delivery_date.replace(tzinfo=ZoneInfo(timezone))
|
|
358
|
+
start_delivery_date = start_delivery_date.astimezone(ZoneInfo("UTC"))
|
|
359
|
+
start_date_str = start_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
360
|
+
|
|
361
|
+
# Convert end trading date to end delivery date
|
|
362
|
+
if end_trading_date is not None:
|
|
363
|
+
end_trading_date = pd.Timestamp(end_trading_date).floor("D")
|
|
364
|
+
end_delivery_date = end_trading_date + relativedelta(days=1)
|
|
365
|
+
|
|
366
|
+
# Convert end date to UTC and string
|
|
367
|
+
end_delivery_date = end_delivery_date.replace(tzinfo=ZoneInfo(timezone))
|
|
368
|
+
end_delivery_date = end_delivery_date.astimezone(ZoneInfo("UTC"))
|
|
369
|
+
end_date_str = end_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
|
|
370
|
+
|
|
371
|
+
# Create query
|
|
372
|
+
table = DetDatabaseDefinitions.DEFINITIONS["table_name_entsoe_imbalance_price"]
|
|
373
|
+
query = (
|
|
374
|
+
f"SELECT {columns_str} FROM {table} "
|
|
375
|
+
f"WHERE MapCode='{map_code}' "
|
|
376
|
+
f"AND [DateTime(UTC)]>='{start_date_str}' "
|
|
377
|
+
f"AND [DateTime(UTC)]<'{end_date_str}' "
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Query db
|
|
381
|
+
self.open_connection()
|
|
382
|
+
df = self.query_db(query)
|
|
383
|
+
self.close_connection()
|
|
384
|
+
|
|
385
|
+
# Sort data by delivery date
|
|
386
|
+
df.sort_values(
|
|
387
|
+
by=["DateTime(UTC)"], axis=0, ascending=True, inplace=True, ignore_index=True
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Add column with delivery date expressed in local timezone
|
|
391
|
+
datetime_column_name = f"DateTime({timezone})"
|
|
392
|
+
df[datetime_column_name] = df["DateTime(UTC)"].dt.tz_localize("UTC")
|
|
393
|
+
df[datetime_column_name] = df[datetime_column_name].dt.tz_convert(timezone)
|
|
394
|
+
df[datetime_column_name] = df[datetime_column_name].dt.tz_localize(None)
|
|
395
|
+
|
|
396
|
+
# Process raw data and convert it to standardized format
|
|
397
|
+
if process_data:
|
|
398
|
+
df = DetDatabase.process_imbalance_prices(df, timezone)
|
|
399
|
+
|
|
400
|
+
return df
|
|
401
|
+
|
|
402
|
+
@staticmethod
|
|
403
|
+
def process_imbalance_prices(df_in: pd.DataFrame, timezone: str) -> pd.DataFrame:
|
|
404
|
+
"""
|
|
405
|
+
Processes imbalance prices and converts from ENTSOE format to standardized format.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
df_in: Dataframe containing imbalance prices
|
|
409
|
+
timezone: Timezone of the power country/region
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Processed dataframe containing imbalance prices
|
|
413
|
+
"""
|
|
414
|
+
map_code_to_commodity_mapper = dict(
|
|
415
|
+
NL={"commodity_name": "DutchPower", "product_code": "Q0B"}
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
# Initialize output dataframe
|
|
419
|
+
df_out = pd.DataFrame()
|
|
420
|
+
|
|
421
|
+
# Set commodity name
|
|
422
|
+
commodity_name = [
|
|
423
|
+
map_code_to_commodity_mapper[mc]["commodity_name"] for mc in df_in["MapCode"]
|
|
424
|
+
]
|
|
425
|
+
df_out["CommodityName"] = commodity_name
|
|
426
|
+
|
|
427
|
+
# Set product code
|
|
428
|
+
product_code = [
|
|
429
|
+
map_code_to_commodity_mapper[mc]["product_code"] for mc in df_in["MapCode"]
|
|
430
|
+
]
|
|
431
|
+
df_out["ProductCode"] = product_code
|
|
432
|
+
|
|
433
|
+
# Set trading date
|
|
434
|
+
df_out["TradingDate"] = df_in[f"DateTime({timezone})"].dt.floor("D").values
|
|
435
|
+
|
|
436
|
+
# Set delivery start date
|
|
437
|
+
df_out["DeliveryStart"] = df_in[f"DateTime({timezone})"].values
|
|
438
|
+
|
|
439
|
+
# Set delivery end date
|
|
440
|
+
delivery_end = [d + relativedelta(minutes=15) for d in df_in[f"DateTime({timezone})"]]
|
|
441
|
+
df_out["DeliveryEnd"] = delivery_end
|
|
442
|
+
|
|
443
|
+
# Set tenor
|
|
444
|
+
df_out["Tenor"] = "Imbalance"
|
|
445
|
+
|
|
446
|
+
# Set price
|
|
447
|
+
df_out["PositiveImbalancePrice"] = df_in["PositiveImbalancePrice"].values
|
|
448
|
+
df_out["NegativeImbalancePrice"] = df_in["NegativeImbalancePrice"].values
|
|
449
|
+
|
|
450
|
+
return df_out
|
|
451
|
+
|
|
452
|
+
def load_futures_eod_settlement_prices(
|
|
453
|
+
self,
|
|
454
|
+
commodity_name: str,
|
|
455
|
+
start_trading_date: datetime,
|
|
456
|
+
end_trading_date: datetime,
|
|
457
|
+
tenors: list,
|
|
458
|
+
delivery_type: str,
|
|
459
|
+
columns: list = None,
|
|
460
|
+
) -> pd.DataFrame:
|
|
461
|
+
"""
|
|
462
|
+
Loads futures end-of-day settlement prices from the database, over a user-defined range
|
|
463
|
+
of trading dates.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
commodity_name: Commodity name
|
|
467
|
+
start_trading_date: Start trading date
|
|
468
|
+
end_trading_date: End trading date
|
|
469
|
+
tenors: Product tenors (e.g. "Month", "Quarter", "Year")
|
|
470
|
+
delivery_type: Delivery type
|
|
471
|
+
columns: Requested database table columns. Set columns=["*"] (i.e. as list) to get
|
|
472
|
+
all columns.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
Dataframe containing futures end-of-day settlement prices
|
|
476
|
+
"""
|
|
477
|
+
# Set default column values
|
|
478
|
+
if columns is None:
|
|
479
|
+
columns = ["*"]
|
|
480
|
+
|
|
481
|
+
# Convert columns from list to string
|
|
482
|
+
if len(columns) == 1:
|
|
483
|
+
columns_str = str(columns[0])
|
|
484
|
+
else:
|
|
485
|
+
columns_str = f"[{'], ['.join(columns)}]"
|
|
486
|
+
|
|
487
|
+
# Convert tenors from list to string
|
|
488
|
+
tenors_str = f"({' ,'.join([repr(item) for item in tenors])})"
|
|
489
|
+
|
|
490
|
+
# Convert dates from datetime to string
|
|
491
|
+
start_trading_date_str = start_trading_date.strftime("%Y-%m-%d")
|
|
492
|
+
end_trading_date_str = end_trading_date.strftime("%Y-%m-%d")
|
|
493
|
+
|
|
494
|
+
# Create query
|
|
495
|
+
table = DetDatabaseDefinitions.DEFINITIONS["table_name_futures_eod_settlement_price"]
|
|
496
|
+
query = (
|
|
497
|
+
f"SELECT {columns_str} FROM {table} "
|
|
498
|
+
f"WHERE CommodityName='{commodity_name}' "
|
|
499
|
+
f"AND TradingDate>='{start_trading_date_str}' "
|
|
500
|
+
f"AND TradingDate<='{end_trading_date_str}' "
|
|
501
|
+
f"AND Tenor IN {tenors_str} "
|
|
502
|
+
f"AND DeliveryType='{delivery_type}'"
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Query db
|
|
506
|
+
self.open_connection()
|
|
507
|
+
df = self.query_db(query)
|
|
508
|
+
self.close_connection()
|
|
509
|
+
|
|
510
|
+
# Sort data
|
|
511
|
+
df.sort_values(
|
|
512
|
+
by=["TradingDate", "DeliveryStart", "DeliveryEnd"],
|
|
513
|
+
axis=0,
|
|
514
|
+
ascending=True,
|
|
515
|
+
inplace=True,
|
|
516
|
+
ignore_index=True,
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
# Convert dates from datetime.date to pd.Timestamp
|
|
520
|
+
df["TradingDate"] = pd.DatetimeIndex(df["TradingDate"])
|
|
521
|
+
df["DeliveryStart"] = pd.DatetimeIndex(df["DeliveryStart"])
|
|
522
|
+
df["DeliveryEnd"] = pd.DatetimeIndex(df["DeliveryEnd"])
|
|
523
|
+
|
|
524
|
+
return df
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
class DetDatabaseDefinitions:
|
|
528
|
+
"""A class containing some hard-coded definitions related to the DET database."""
|
|
529
|
+
|
|
530
|
+
DEFINITIONS = dict(
|
|
531
|
+
driver="{ODBC Driver 18 for SQL Server}",
|
|
532
|
+
table_name_entsoe_day_ahead_spot_price="[ENTSOE].[DayAheadSpotPrice]",
|
|
533
|
+
table_name_entsoe_imbalance_price="[ENTSOE].[ImbalancePrice]",
|
|
534
|
+
table_name_futures_eod_settlement_price="[FUT].[EODSettlementPrice]",
|
|
535
|
+
)
|