detquantlib 3.10.3__tar.gz → 3.11.0__tar.gz
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-3.10.3 → detquantlib-3.11.0}/PKG-INFO +1 -1
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/data/databases/detdatabase.py +44 -30
- detquantlib-3.11.0/detquantlib/forecasting/__init__.py +1 -0
- detquantlib-3.11.0/detquantlib/forecasting/forecasting.py +59 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/tradable_products/tradable_products.py +0 -1
- detquantlib-3.11.0/detquantlib/utils/utils.py +38 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/pyproject.toml +5 -1
- detquantlib-3.10.3/detquantlib/utils/utils.py +0 -11
- {detquantlib-3.10.3 → detquantlib-3.11.0}/LICENSE.txt +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/README.md +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/__init__.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/data/__init__.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/data/entsoe/entsoe.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/data/sftp/sftp.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/dates/__init__.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/dates/dates.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/figures/__init__.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/figures/plotly_figures.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/outputs/__init__.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/outputs/outputs_interface.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/stats/__init__.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/stats/data_analysis.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/tradable_products/__init__.py +0 -0
- {detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/utils/__init__.py +0 -0
|
@@ -185,7 +185,13 @@ class DetDatabase:
|
|
|
185
185
|
|
|
186
186
|
# Set default column values
|
|
187
187
|
if columns is None:
|
|
188
|
-
columns = [
|
|
188
|
+
columns = [
|
|
189
|
+
"DateTime(UTC)",
|
|
190
|
+
"ResolutionCode",
|
|
191
|
+
"MapCode",
|
|
192
|
+
"Price(Currency/MWh)",
|
|
193
|
+
"Currency",
|
|
194
|
+
]
|
|
189
195
|
|
|
190
196
|
# Always add delivery date column
|
|
191
197
|
if "DateTime(UTC)" not in columns and columns != ["*"]:
|
|
@@ -248,17 +254,21 @@ class DetDatabase:
|
|
|
248
254
|
)
|
|
249
255
|
|
|
250
256
|
# Convert date columns to timezone-aware dates
|
|
251
|
-
|
|
252
|
-
for c in
|
|
257
|
+
cols_date = ["DateTime(UTC)", "UpdateTime(UTC)", "InsertionTimestamp"]
|
|
258
|
+
for c in cols_date:
|
|
253
259
|
if c in df.columns:
|
|
254
260
|
df[c] = df[c].dt.tz_localize("UTC")
|
|
255
261
|
|
|
256
262
|
# Add column with delivery date expressed in local timezone
|
|
257
|
-
|
|
263
|
+
col_local_date = f"DateTime({timezone})"
|
|
264
|
+
cols_date.append(col_local_date)
|
|
265
|
+
loc = df.columns.get_loc("DateTime(UTC)") + 1
|
|
266
|
+
df.insert(
|
|
267
|
+
loc=loc, column=col_local_date, value=df["DateTime(UTC)"].dt.tz_convert(timezone)
|
|
268
|
+
)
|
|
258
269
|
|
|
259
270
|
if not timezone_aware_dates:
|
|
260
|
-
|
|
261
|
-
for c in cols_date_all:
|
|
271
|
+
for c in cols_date:
|
|
262
272
|
if c in df.columns:
|
|
263
273
|
df[c] = df[c].dt.tz_localize(None)
|
|
264
274
|
|
|
@@ -282,6 +292,9 @@ class DetDatabase:
|
|
|
282
292
|
|
|
283
293
|
Returns:
|
|
284
294
|
Processed dataframe containing day-ahead spot prices
|
|
295
|
+
|
|
296
|
+
Raises:
|
|
297
|
+
ValueError: Raises an error if the resolution code is not supported.
|
|
285
298
|
"""
|
|
286
299
|
df_in.reset_index(drop=True, inplace=True)
|
|
287
300
|
|
|
@@ -292,14 +305,27 @@ class DetDatabase:
|
|
|
292
305
|
df_out["CommodityName"] = [commodity_name] * df_in.shape[0]
|
|
293
306
|
|
|
294
307
|
# Set trading date
|
|
295
|
-
|
|
308
|
+
col_date = f"DateTime({timezone})"
|
|
309
|
+
trading_date = [d.normalize() - relativedelta(days=1) for d in df_in[col_date]]
|
|
296
310
|
df_out["TradingDate"] = trading_date
|
|
297
311
|
|
|
298
312
|
# Set delivery start date
|
|
299
|
-
df_out["DeliveryStart"] = df_in[
|
|
313
|
+
df_out["DeliveryStart"] = df_in[col_date]
|
|
300
314
|
|
|
301
315
|
# Set delivery end date
|
|
302
|
-
delivery_end =
|
|
316
|
+
delivery_end = list()
|
|
317
|
+
for i in df_in.index:
|
|
318
|
+
start_date = df_out.loc[i, "DeliveryStart"]
|
|
319
|
+
offset = df_in.loc[i, "ResolutionCode"]
|
|
320
|
+
match offset:
|
|
321
|
+
case "PT60M":
|
|
322
|
+
offset = 60
|
|
323
|
+
case "PT15M":
|
|
324
|
+
offset = 15
|
|
325
|
+
case _:
|
|
326
|
+
raise ValueError("Resolution not supported.")
|
|
327
|
+
end_date = start_date + relativedelta(minutes=offset)
|
|
328
|
+
delivery_end.append(end_date)
|
|
303
329
|
df_out["DeliveryEnd"] = delivery_end
|
|
304
330
|
|
|
305
331
|
# Set tenor
|
|
@@ -569,13 +595,10 @@ class DetDatabase:
|
|
|
569
595
|
raise ValueError("No price data found for user-defined inputs.")
|
|
570
596
|
|
|
571
597
|
# Sort data
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
ascending=True,
|
|
576
|
-
inplace=True,
|
|
577
|
-
ignore_index=True,
|
|
578
|
-
)
|
|
598
|
+
cols_sort = ["TradingDate", "DeliveryStart", "DeliveryEnd"]
|
|
599
|
+
cols_sort = [c for c in cols_sort if c in df.columns]
|
|
600
|
+
if len(cols_sort) > 0:
|
|
601
|
+
df.sort_values(by=cols_sort, axis=0, ascending=True, inplace=True, ignore_index=True)
|
|
579
602
|
|
|
580
603
|
# Convert dates from datetime.date to pd.Timestamp
|
|
581
604
|
cols_date = ["TradingDate", "DeliveryStart", "DeliveryEnd"]
|
|
@@ -794,16 +817,10 @@ class DetDatabase:
|
|
|
794
817
|
# Set default column values
|
|
795
818
|
if columns is None:
|
|
796
819
|
columns = ["*"]
|
|
797
|
-
else:
|
|
798
|
-
# Assert required columns
|
|
799
|
-
columns = list(
|
|
800
|
-
set(columns)
|
|
801
|
-
| {"TradingDate", "Product", "Delivery Start", "Delivery End", "Settlement Price"}
|
|
802
|
-
)
|
|
803
820
|
|
|
804
821
|
# Convert columns from list to string
|
|
805
822
|
if len(columns) == 1:
|
|
806
|
-
columns_str =
|
|
823
|
+
columns_str = f"[{columns[0]}]"
|
|
807
824
|
else:
|
|
808
825
|
columns_str = f"[{'], ['.join(columns)}]"
|
|
809
826
|
|
|
@@ -826,13 +843,10 @@ class DetDatabase:
|
|
|
826
843
|
raise ValueError("No price data found for user-defined inputs.")
|
|
827
844
|
|
|
828
845
|
# Sort data
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
ascending=True,
|
|
833
|
-
inplace=True,
|
|
834
|
-
ignore_index=True,
|
|
835
|
-
)
|
|
846
|
+
cols_sort = ["TradingDate", "Delivery Start", "Delivery End"]
|
|
847
|
+
cols_sort = [c for c in cols_sort if c in df.columns]
|
|
848
|
+
if len(cols_sort) > 0:
|
|
849
|
+
df.sort_values(by=cols_sort, axis=0, ascending=True, inplace=True, ignore_index=True)
|
|
836
850
|
|
|
837
851
|
# Convert dates from datetime.date to pd.Timestamp
|
|
838
852
|
cols_date = ["TradingDate", "Delivery Start", "Delivery End"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .forecasting import *
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Python built-in packages
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
# Third-party packages
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def forecast_knife_strategy(
|
|
10
|
+
dates: list[datetime] | list[pd.Timestamp] | pd.DatetimeIndex, values: list | np.ndarray
|
|
11
|
+
) -> np.ndarray:
|
|
12
|
+
"""
|
|
13
|
+
Generates a forecast using the knife strategy (i.e. random walk).
|
|
14
|
+
|
|
15
|
+
The knife strategy works as follows:
|
|
16
|
+
- Set the forecasted value at time t equal to the observed value on the most recent
|
|
17
|
+
previous date that has the same time (hour, minute, and second) and same day type
|
|
18
|
+
(Mon-Fri, Sat, or Sun).
|
|
19
|
+
- If there is no previous date with matching time and day type (i.e. very first observation
|
|
20
|
+
for a given time and day type), the strategy sets the forecasted value at time t equal
|
|
21
|
+
to the observed value at time t.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
dates: Delivery dates
|
|
25
|
+
values: Observed values
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Forecasted values
|
|
29
|
+
"""
|
|
30
|
+
# Make sure input dates are stored as pd.DatetimeIndex and values as np.array
|
|
31
|
+
dates = pd.DatetimeIndex(dates)
|
|
32
|
+
values = np.array(values)
|
|
33
|
+
|
|
34
|
+
# Get times
|
|
35
|
+
hours = dates.hour
|
|
36
|
+
minutes = dates.minute
|
|
37
|
+
seconds = dates.second
|
|
38
|
+
|
|
39
|
+
# Get weekday types (1 = Mon-Fri, 2 = Sat, 3 = Sun)
|
|
40
|
+
weekdays = dates.weekday
|
|
41
|
+
day_types = np.zeros_like(weekdays)
|
|
42
|
+
day_types[weekdays < 5] = 1
|
|
43
|
+
day_types[weekdays == 5] = 2
|
|
44
|
+
day_types[weekdays == 6] = 3
|
|
45
|
+
|
|
46
|
+
# Get forecasted values
|
|
47
|
+
# Note: The logic below has been designed to optimize code speed, and works as follows:
|
|
48
|
+
# - 1. Get all the unique pairs of time and day type in the input dataset.
|
|
49
|
+
# - 2. For each pair:
|
|
50
|
+
# - 2.1. Get all the corresponding observed data.
|
|
51
|
+
# - 2.2. Set the forecast as: data[0], data[0], data[1], data[2], ..., data[N-1]
|
|
52
|
+
fc_values = np.zeros_like(values)
|
|
53
|
+
pairs = np.column_stack([hours, minutes, seconds, day_types])
|
|
54
|
+
for hh, mm, ss, dt in np.unique(pairs, axis=0):
|
|
55
|
+
idx = (hours == hh) & (minutes == mm) & (seconds == ss) & (day_types == dt)
|
|
56
|
+
i_values = values[idx]
|
|
57
|
+
fc_values[idx] = np.insert(i_values[:-1], 0, i_values[0])
|
|
58
|
+
|
|
59
|
+
return fc_values
|
{detquantlib-3.10.3 → detquantlib-3.11.0}/detquantlib/tradable_products/tradable_products.py
RENAMED
|
@@ -29,7 +29,6 @@ def convert_delivery_start_date_to_maturity(
|
|
|
29
29
|
Product maturity
|
|
30
30
|
|
|
31
31
|
Raises:
|
|
32
|
-
ValueError: Raises an error when the delivery start date is older than the trading date
|
|
33
32
|
ValueError: Raises an error when the input product type is not recognized
|
|
34
33
|
"""
|
|
35
34
|
# Make input product string lower case only
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Python built-in packages
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
# Third-party packages
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def add_log(message: str):
|
|
9
|
+
"""
|
|
10
|
+
Short helper function to print log messages, including time stamps.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
message: Message to be printed
|
|
14
|
+
"""
|
|
15
|
+
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def divide_with_nan(
|
|
19
|
+
numerator: np.array, denominator: np.array, nan=np.nan, posinf=np.nan, neginf=np.nan
|
|
20
|
+
) -> np.array:
|
|
21
|
+
"""
|
|
22
|
+
A short utility function to perform an element-wise division of two arrays and replace
|
|
23
|
+
potential NaN, inf, and -inf results with user-defined values.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
numerator: Numerator array
|
|
27
|
+
denominator: Denominator array
|
|
28
|
+
nan: Value to use to replace NaN results
|
|
29
|
+
posinf: Value to use to replace positive inf results
|
|
30
|
+
neginf: Value to use to replace negative inf results
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Quotient array
|
|
34
|
+
"""
|
|
35
|
+
quotient = np.nan_to_num(
|
|
36
|
+
x=np.divide(numerator, denominator), nan=nan, posinf=posinf, neginf=neginf
|
|
37
|
+
)
|
|
38
|
+
return quotient
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "detquantlib"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.11.0"
|
|
4
4
|
description = "An internal library containing functions and classes that can be used across Quant models."
|
|
5
5
|
authors = ["DET"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -37,6 +37,10 @@ md-toc = "^9.0.0"
|
|
|
37
37
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
38
38
|
build-backend = "poetry.core.masonry.api"
|
|
39
39
|
|
|
40
|
+
[tool.pytest.ini_options]
|
|
41
|
+
testpaths = ["tests"] # Tells pytest to look for tests only under the tests/ folder
|
|
42
|
+
pythonpath = ["."] # Adds project root (.) to sys.path, so import detquantlib works anywhere
|
|
43
|
+
|
|
40
44
|
[tool.isort]
|
|
41
45
|
multi_line_output = 3
|
|
42
46
|
include_trailing_comma = true
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def add_log(message: str):
|
|
5
|
-
"""
|
|
6
|
-
Short helper function to print log messages, including time stamps.
|
|
7
|
-
|
|
8
|
-
Args:
|
|
9
|
-
message: Message to be printed
|
|
10
|
-
"""
|
|
11
|
-
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|