detquantlib 3.12.0__tar.gz → 3.14.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.12.0 → detquantlib-3.14.0}/PKG-INFO +10 -1
- {detquantlib-3.12.0 → detquantlib-3.14.0}/README.md +9 -0
- detquantlib-3.14.0/detquantlib/converters/__init__.py +2 -0
- detquantlib-3.14.0/detquantlib/converters/definitions.py +24 -0
- detquantlib-3.14.0/detquantlib/converters/energy.py +140 -0
- detquantlib-3.14.0/detquantlib/converters/helpers.py +67 -0
- detquantlib-3.14.0/detquantlib/converters/price.py +107 -0
- detquantlib-3.14.0/detquantlib/data/__init__.py +6 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/data/databases/detdatabase.py +34 -48
- detquantlib-3.14.0/detquantlib/data/databases/helpers.py +40 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/dates/dates.py +40 -1
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/forecasting/forecasting.py +3 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/tradable_products/tradable_products.py +60 -1
- detquantlib-3.14.0/detquantlib/utils/__init__.py +2 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/utils/logging.py +4 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/utils/utils.py +8 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/pyproject.toml +1 -1
- detquantlib-3.12.0/detquantlib/data/__init__.py +0 -5
- detquantlib-3.12.0/detquantlib/utils/__init__.py +0 -1
- {detquantlib-3.12.0 → detquantlib-3.14.0}/LICENSE.txt +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/__init__.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/data/entsoe/entsoe.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/data/sftp/sftp.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/dates/__init__.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/figures/__init__.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/figures/plotly_figures.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/forecasting/__init__.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/outputs/__init__.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/outputs/outputs_interface.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/stats/__init__.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/stats/data_analysis.py +0 -0
- {detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/tradable_products/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detquantlib
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.14.0
|
|
4
4
|
Summary: An internal library containing functions and classes that can be used across Quant models.
|
|
5
5
|
License-File: LICENSE.txt
|
|
6
6
|
Author: DET
|
|
@@ -99,6 +99,15 @@ Classes:
|
|
|
99
99
|
- `from detquantlib.data import Sftp`
|
|
100
100
|
- `from detquantlib.data.sftp.sftp import Sftp`
|
|
101
101
|
|
|
102
|
+
Functions:
|
|
103
|
+
|
|
104
|
+
- `list_to_sql_columns`:
|
|
105
|
+
- `from detquantlib.data import list_to_sql_columns`
|
|
106
|
+
- `from detquantlib.data.databases.helpers import list_to_sql_columns`
|
|
107
|
+
- `list_to_sql_set`:
|
|
108
|
+
- `from detquantlib.data import list_to_sql_set`
|
|
109
|
+
- `from detquantlib.data.databases.helpers import list_to_sql_set`
|
|
110
|
+
|
|
102
111
|
<!-- END EXPOSED SYMBOLS AUTO-GENERATED -->
|
|
103
112
|
|
|
104
113
|
## Configuration
|
|
@@ -73,6 +73,15 @@ Classes:
|
|
|
73
73
|
- `from detquantlib.data import Sftp`
|
|
74
74
|
- `from detquantlib.data.sftp.sftp import Sftp`
|
|
75
75
|
|
|
76
|
+
Functions:
|
|
77
|
+
|
|
78
|
+
- `list_to_sql_columns`:
|
|
79
|
+
- `from detquantlib.data import list_to_sql_columns`
|
|
80
|
+
- `from detquantlib.data.databases.helpers import list_to_sql_columns`
|
|
81
|
+
- `list_to_sql_set`:
|
|
82
|
+
- `from detquantlib.data import list_to_sql_set`
|
|
83
|
+
- `from detquantlib.data.databases.helpers import list_to_sql_set`
|
|
84
|
+
|
|
76
85
|
<!-- END EXPOSED SYMBOLS AUTO-GENERATED -->
|
|
77
86
|
|
|
78
87
|
## Configuration
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class PowerUnits:
|
|
2
|
+
"""A class that contains hard-coded information about power units."""
|
|
3
|
+
|
|
4
|
+
CONVERSIONS = [
|
|
5
|
+
{"unit": "W", "value_in_base_unit": 1},
|
|
6
|
+
{"unit": "kW", "value_in_base_unit": 1e3},
|
|
7
|
+
{"unit": "MW", "value_in_base_unit": 1e6},
|
|
8
|
+
{"unit": "GW", "value_in_base_unit": 1e9},
|
|
9
|
+
{"unit": "TW", "value_in_base_unit": 1e12},
|
|
10
|
+
{"unit": "MJ/s", "value_in_base_unit": 1e6},
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EnergyUnits:
|
|
15
|
+
"""A class that contains hard-coded information about energy units."""
|
|
16
|
+
|
|
17
|
+
CONVERSIONS = [
|
|
18
|
+
{"unit": "Wh", "value_in_base_unit": 1},
|
|
19
|
+
{"unit": "kWh", "value_in_base_unit": 1e3},
|
|
20
|
+
{"unit": "MWh", "value_in_base_unit": 1e6},
|
|
21
|
+
{"unit": "GWh", "value_in_base_unit": 1e9},
|
|
22
|
+
{"unit": "TWh", "value_in_base_unit": 1e12},
|
|
23
|
+
{"unit": "MJ", "value_in_base_unit": 1e6 / 3600},
|
|
24
|
+
]
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Python built-in packages
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
# Third-party packages
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
# Internal modules
|
|
8
|
+
from detquantlib.converters.definitions import EnergyUnits, PowerUnits
|
|
9
|
+
from detquantlib.converters.helpers import convert_unit_same_cat
|
|
10
|
+
from detquantlib.dates import count_nr_hours
|
|
11
|
+
from detquantlib.tradable_products import tenor_to_nr_hours
|
|
12
|
+
|
|
13
|
+
# Specify functions to expose
|
|
14
|
+
__all__ = ["convert_power", "convert_energy", "power_to_energy", "energy_to_power"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def convert_power(value_in: float | np.ndarray, unit_in: str, unit_out: str) -> float | np.ndarray:
|
|
18
|
+
"""
|
|
19
|
+
Converts power from one unit to another.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
value_in: Power quantity
|
|
23
|
+
unit_in: Unit of input power quantity
|
|
24
|
+
unit_out: Unit of output power quantity
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Converted power quantity
|
|
28
|
+
"""
|
|
29
|
+
return convert_unit_same_cat(
|
|
30
|
+
conversions=PowerUnits.CONVERSIONS, value_in=value_in, unit_in=unit_in, unit_out=unit_out
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def convert_energy(
|
|
35
|
+
value_in: float | np.ndarray, unit_in: str, unit_out: str
|
|
36
|
+
) -> float | np.ndarray:
|
|
37
|
+
"""
|
|
38
|
+
Converts energy from one unit to another.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
value_in: Energy quantity
|
|
42
|
+
unit_in: Unit of input energy quantity
|
|
43
|
+
unit_out: Unit of output energy quantity
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Converted energy quantity
|
|
47
|
+
"""
|
|
48
|
+
return convert_unit_same_cat(
|
|
49
|
+
conversions=EnergyUnits.CONVERSIONS, value_in=value_in, unit_in=unit_in, unit_out=unit_out
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def power_to_energy(
|
|
54
|
+
power: float | np.ndarray,
|
|
55
|
+
power_unit: str,
|
|
56
|
+
energy_unit: str,
|
|
57
|
+
tenor: str = None,
|
|
58
|
+
delivery_start: datetime = None,
|
|
59
|
+
delivery_end: datetime = None,
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Converts a power quantity into its corresponding energy quantity.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
power: Power quantity
|
|
66
|
+
power_unit: Unit of input power quantity
|
|
67
|
+
energy_unit: Unit of output energy quantity
|
|
68
|
+
tenor: (Optional) Product tenor. If not provided, delivery_start and delivery_end must be
|
|
69
|
+
provided.
|
|
70
|
+
delivery_start: (Optional) Delivery start date. Only needed if tenor is not provided,
|
|
71
|
+
or if tenor > 1 hour to account for DST switches.
|
|
72
|
+
delivery_end: (Optional) Delivery end date. Only needed if tenor is not provided, or
|
|
73
|
+
if tenor > 1 hour to account for DST switches.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Energy quantity
|
|
77
|
+
"""
|
|
78
|
+
# Count number of hours in delivery period
|
|
79
|
+
if tenor is None:
|
|
80
|
+
nr_hours = count_nr_hours(start_date=delivery_start, end_date=delivery_end)
|
|
81
|
+
else:
|
|
82
|
+
nr_hours = tenor_to_nr_hours(
|
|
83
|
+
tenor=tenor, delivery_start=delivery_start, delivery_end=delivery_end
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Convert power from input unit to W
|
|
87
|
+
power_w = convert_power(value_in=power, unit_in=power_unit, unit_out="w")
|
|
88
|
+
|
|
89
|
+
# Convert power (W) to energy (Wh)
|
|
90
|
+
energy_wh = power_w * nr_hours
|
|
91
|
+
|
|
92
|
+
# Convert energy from Wh to output unit
|
|
93
|
+
energy = convert_energy(value_in=energy_wh, unit_in="wh", unit_out=energy_unit)
|
|
94
|
+
|
|
95
|
+
return energy
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def energy_to_power(
|
|
99
|
+
energy: float | np.ndarray,
|
|
100
|
+
energy_unit: str,
|
|
101
|
+
power_unit: str,
|
|
102
|
+
tenor: str = None,
|
|
103
|
+
delivery_start: datetime = None,
|
|
104
|
+
delivery_end: datetime = None,
|
|
105
|
+
):
|
|
106
|
+
"""
|
|
107
|
+
Converts an energy quantity into its corresponding power quantity.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
energy: Energy quantity
|
|
111
|
+
energy_unit: Unit of input energy quantity
|
|
112
|
+
power_unit: Unit of output power quantity
|
|
113
|
+
tenor: (Optional) Product tenor. If not provided, delivery_start and delivery_end must be
|
|
114
|
+
provided.
|
|
115
|
+
delivery_start: (Optional) Delivery start date. Only needed if tenor is not provided,
|
|
116
|
+
or if tenor > 1 hour to account for DST switches.
|
|
117
|
+
delivery_end: (Optional) Delivery end date. Only needed if tenor is not provided, or
|
|
118
|
+
if tenor > 1 hour to account for DST switches.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Power quantity
|
|
122
|
+
"""
|
|
123
|
+
# Count number of hours in delivery period
|
|
124
|
+
if tenor is None:
|
|
125
|
+
nr_hours = count_nr_hours(start_date=delivery_start, end_date=delivery_end)
|
|
126
|
+
else:
|
|
127
|
+
nr_hours = tenor_to_nr_hours(
|
|
128
|
+
tenor=tenor, delivery_start=delivery_start, delivery_end=delivery_end
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Convert energy from input unit to Wh
|
|
132
|
+
energy_wh = convert_energy(value_in=energy, unit_in=energy_unit, unit_out="wh")
|
|
133
|
+
|
|
134
|
+
# Convert energy (Wh) to power (W)
|
|
135
|
+
power_w = energy_wh / nr_hours
|
|
136
|
+
|
|
137
|
+
# Convert power from W to output unit
|
|
138
|
+
power = convert_power(value_in=power_w, unit_in="w", unit_out=power_unit)
|
|
139
|
+
|
|
140
|
+
return power
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Third-party packages
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
# Internal modules
|
|
5
|
+
from detquantlib.utils import list_to_str
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_base_unit(conversions: list[dict]) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Gets the base unit of a given unit group.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
conversions: List of conversion dicts of a given unit group
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Base unit
|
|
17
|
+
"""
|
|
18
|
+
return next((d for d in conversions if d["value_in_base_unit"] == 1))["unit"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_unit_dict(unit: str, conversions: list[dict]) -> dict:
|
|
22
|
+
"""
|
|
23
|
+
Gets the unit conversion dict of a given unit.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
unit: Unit
|
|
27
|
+
conversions: List of conversion dicts of a given unit group
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Conversion dict of the input unit
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: Raises an error if the input unit is not supported.
|
|
34
|
+
"""
|
|
35
|
+
unit_dict = next((d for d in conversions if d["unit"].lower() == unit.lower()), None)
|
|
36
|
+
if unit_dict is None:
|
|
37
|
+
units_str = list_to_str([d["unit"] for d in conversions])
|
|
38
|
+
raise ValueError(f"Unsupported unit '{unit}'. Accepted values: {units_str}.")
|
|
39
|
+
return unit_dict
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def convert_unit_same_cat(
|
|
43
|
+
conversions: list[dict], value_in: float | np.ndarray, unit_in: str, unit_out: str
|
|
44
|
+
) -> float | np.ndarray:
|
|
45
|
+
"""
|
|
46
|
+
Converts a quantity from a given unit to another unit of the same unit group.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
conversions: List of conversion dicts of a given unit group
|
|
50
|
+
value_in: Quantity to convert
|
|
51
|
+
unit_in: Unit of the input quantity
|
|
52
|
+
unit_out: Unit of the output quantity
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Converted quantity
|
|
56
|
+
"""
|
|
57
|
+
# Get unit dicts
|
|
58
|
+
unit_dict_in = get_unit_dict(unit_in, conversions)
|
|
59
|
+
unit_dict_out = get_unit_dict(unit_out, conversions)
|
|
60
|
+
|
|
61
|
+
# Convert input value to base unit
|
|
62
|
+
value_base = value_in * unit_dict_in["value_in_base_unit"]
|
|
63
|
+
|
|
64
|
+
# Convert base value to output unit
|
|
65
|
+
value_out = value_base / unit_dict_out["value_in_base_unit"]
|
|
66
|
+
|
|
67
|
+
return value_out
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Third-party packages
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
# Internal modules
|
|
5
|
+
from detquantlib.converters.definitions import EnergyUnits
|
|
6
|
+
from detquantlib.converters.helpers import convert_unit_same_cat
|
|
7
|
+
|
|
8
|
+
# Specify functions to expose
|
|
9
|
+
__all__ = ["convert_price", "flag_cents"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def convert_price(
|
|
13
|
+
price_in: float | np.ndarray,
|
|
14
|
+
currency_in: str,
|
|
15
|
+
unit_in: str,
|
|
16
|
+
currency_out: str,
|
|
17
|
+
unit_out: str,
|
|
18
|
+
commodity_category: str,
|
|
19
|
+
) -> float | np.ndarray:
|
|
20
|
+
"""
|
|
21
|
+
Converts a price from a given currency and volume unit to another.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
price_in: Price
|
|
25
|
+
currency_in: Currency of input price
|
|
26
|
+
unit_in: Volume unit of current price
|
|
27
|
+
currency_out: Currency of output price
|
|
28
|
+
unit_out: Volume unit of output price
|
|
29
|
+
commodity_category: Commodity category
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Converted price
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
ValueError: Raises an error if the commodity category is not supported.
|
|
36
|
+
ValueError: Raises an error if the currency conversion is not supported.
|
|
37
|
+
"""
|
|
38
|
+
# Split currency and cents indicator
|
|
39
|
+
currency_pre_in, is_cent_in = flag_cents(currency_in)
|
|
40
|
+
currency_pre_out, is_cent_out = flag_cents(currency_out)
|
|
41
|
+
|
|
42
|
+
# Get unit converters
|
|
43
|
+
match commodity_category.lower():
|
|
44
|
+
case "energy":
|
|
45
|
+
conversions = EnergyUnits.CONVERSIONS
|
|
46
|
+
case _:
|
|
47
|
+
raise ValueError(f"Commodity category '{commodity_category}' is not supported.")
|
|
48
|
+
|
|
49
|
+
# 1. Convert price based on currency
|
|
50
|
+
if currency_pre_in.lower() != currency_pre_out.lower():
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"Price conversion from currency prefix '{currency_pre_in}' to currency prefix "
|
|
53
|
+
f"'{currency_pre_out}' is not supported."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# 2. Convert price based on units
|
|
57
|
+
unit_factor = convert_unit_same_cat(
|
|
58
|
+
conversions=conversions, value_in=1, unit_in=unit_in, unit_out=unit_out
|
|
59
|
+
)
|
|
60
|
+
price_out = price_in / unit_factor
|
|
61
|
+
|
|
62
|
+
# 3. Convert price based on cents indicators
|
|
63
|
+
if is_cent_in and not is_cent_out:
|
|
64
|
+
price_out /= 100
|
|
65
|
+
elif not is_cent_in and is_cent_out:
|
|
66
|
+
price_out *= 100
|
|
67
|
+
|
|
68
|
+
return price_out
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def flag_cents(currency_str: str) -> (str, bool):
|
|
72
|
+
"""
|
|
73
|
+
Checks if the input currency is expressed in cents or not.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
currency_str: Currency string (e.g. "EUR", "EUR_CENTS")
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
tuple:
|
|
80
|
+
- Currency, excluding cents flag (e.g. "EUR")
|
|
81
|
+
- Cents flag, given as Boolean
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
ValueError: Raises an error if the suffix of the currency string is not recognized.
|
|
85
|
+
ValueError: Raises an error if the currency string could not be parsed.
|
|
86
|
+
"""
|
|
87
|
+
# Split currency and cents flag
|
|
88
|
+
currency_list = currency_str.split("_")
|
|
89
|
+
|
|
90
|
+
currency = currency_list[0]
|
|
91
|
+
if len(currency_list) == 1:
|
|
92
|
+
is_cent = False
|
|
93
|
+
elif len(currency_list) == 2:
|
|
94
|
+
if currency_list[1].lower() == "cents":
|
|
95
|
+
is_cent = True
|
|
96
|
+
else:
|
|
97
|
+
raise ValueError(
|
|
98
|
+
f"Suffix '{currency_list[1]}' of currency string '{currency_str}' not recognized "
|
|
99
|
+
f"as valid cents indicator."
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"Currency string '{currency_str}' could not be parsed into currency and cents "
|
|
104
|
+
f"indicator."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return currency, is_cent
|
|
@@ -9,21 +9,36 @@ import pandas as pd
|
|
|
9
9
|
from dateutil.relativedelta import *
|
|
10
10
|
from sqlalchemy import Engine, create_engine, text
|
|
11
11
|
|
|
12
|
+
# Internal modules
|
|
13
|
+
from detquantlib.converters import convert_energy
|
|
14
|
+
from detquantlib.data.databases.helpers import list_to_sql_columns, list_to_sql_set
|
|
15
|
+
from detquantlib.utils import list_to_str
|
|
16
|
+
|
|
12
17
|
|
|
13
18
|
class DetDatabase:
|
|
14
19
|
"""
|
|
15
20
|
A class to easily interact with the DET database, including fetching and processing data.
|
|
16
21
|
"""
|
|
17
22
|
|
|
18
|
-
def __init__(
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
db_name: str = None,
|
|
26
|
+
engine: Engine = None,
|
|
27
|
+
driver: str = "ODBC Driver 18 for SQL Server",
|
|
28
|
+
):
|
|
19
29
|
"""
|
|
20
30
|
Constructor method.
|
|
21
31
|
|
|
22
32
|
Args:
|
|
33
|
+
db_name: Database name. If None, the name is fetched from environment variables.
|
|
23
34
|
engine: Instance of SQLAlchemy engine. If None, the instance is automatically
|
|
24
35
|
initialized.
|
|
25
36
|
driver: ODBC driver
|
|
26
37
|
"""
|
|
38
|
+
# Store database name if it is provided
|
|
39
|
+
if db_name:
|
|
40
|
+
os.environ["DET_DB_NAME"] = db_name
|
|
41
|
+
|
|
27
42
|
# Only allow object creation if mandatory environment variables exist
|
|
28
43
|
DetDatabase.check_environment_variables()
|
|
29
44
|
|
|
@@ -48,7 +63,7 @@ class DetDatabase:
|
|
|
48
63
|
for d in required_env_vars:
|
|
49
64
|
if d["name"] not in available_env_vars:
|
|
50
65
|
required_env_vars_names = [x["name"] for x in required_env_vars]
|
|
51
|
-
required_env_vars_str =
|
|
66
|
+
required_env_vars_str = list_to_str(required_env_vars_names)
|
|
52
67
|
raise EnvironmentError(
|
|
53
68
|
f"The DetDatabase class requires the following environment variables: "
|
|
54
69
|
f"{required_env_vars_str}. Environment variable '{d['name']}' "
|
|
@@ -198,10 +213,7 @@ class DetDatabase:
|
|
|
198
213
|
columns.append("DateTime(UTC)")
|
|
199
214
|
|
|
200
215
|
# Convert columns from list to string
|
|
201
|
-
|
|
202
|
-
columns_str = str(columns[0])
|
|
203
|
-
else:
|
|
204
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
216
|
+
columns_str = list_to_sql_columns(columns)
|
|
205
217
|
|
|
206
218
|
# Get commodity information (map code and local timezone)
|
|
207
219
|
# Note: The local timezone is important because ENTSOE provides all prices in the UTC
|
|
@@ -415,10 +427,7 @@ class DetDatabase:
|
|
|
415
427
|
columns.append("DateTime(UTC)")
|
|
416
428
|
|
|
417
429
|
# Convert columns from list to string
|
|
418
|
-
|
|
419
|
-
columns_str = str(columns[0])
|
|
420
|
-
else:
|
|
421
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
430
|
+
columns_str = list_to_sql_columns(columns)
|
|
422
431
|
|
|
423
432
|
# Get commodity information (map code and local timezone)
|
|
424
433
|
# Note: The local timezone is important because ENTSOE provides all prices in the UTC
|
|
@@ -567,13 +576,10 @@ class DetDatabase:
|
|
|
567
576
|
columns = ["*"]
|
|
568
577
|
|
|
569
578
|
# Convert columns from list to string
|
|
570
|
-
|
|
571
|
-
columns_str = str(columns[0])
|
|
572
|
-
else:
|
|
573
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
579
|
+
columns_str = list_to_sql_columns(columns)
|
|
574
580
|
|
|
575
581
|
# Convert tenors from list to string
|
|
576
|
-
tenors_str =
|
|
582
|
+
tenors_str = list_to_sql_set(tenors)
|
|
577
583
|
|
|
578
584
|
# Convert dates from datetime to string
|
|
579
585
|
start_trading_date_str = start_trading_date.strftime("%Y-%m-%d")
|
|
@@ -639,10 +645,7 @@ class DetDatabase:
|
|
|
639
645
|
columns = ["*"]
|
|
640
646
|
|
|
641
647
|
# Convert columns from list to string
|
|
642
|
-
|
|
643
|
-
columns_str = str(columns[0])
|
|
644
|
-
else:
|
|
645
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
648
|
+
columns_str = list_to_sql_columns(columns)
|
|
646
649
|
|
|
647
650
|
# Query db
|
|
648
651
|
table = DetDatabase.get_table_name("commodity")
|
|
@@ -679,7 +682,7 @@ class DetDatabase:
|
|
|
679
682
|
|
|
680
683
|
elif commodity_info.shape[0] == 0:
|
|
681
684
|
available_values = self.load_commodities(columns=[filter_column])
|
|
682
|
-
available_values_str =
|
|
685
|
+
available_values_str = list_to_str(available_values[filter_column])
|
|
683
686
|
raise ValueError(
|
|
684
687
|
f"Value {filter_value} not found in column '{filter_column}'. Available values: "
|
|
685
688
|
f"{available_values_str}."
|
|
@@ -713,10 +716,7 @@ class DetDatabase:
|
|
|
713
716
|
columns = ["*"]
|
|
714
717
|
|
|
715
718
|
# Convert columns from list to string
|
|
716
|
-
|
|
717
|
-
columns_str = str(columns[0])
|
|
718
|
-
else:
|
|
719
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
719
|
+
columns_str = list_to_sql_columns(columns)
|
|
720
720
|
|
|
721
721
|
# Convert dates from datetime to string
|
|
722
722
|
end_trading_date = pd.Timestamp(end_trading_date).floor("D") + relativedelta(days=1)
|
|
@@ -769,13 +769,10 @@ class DetDatabase:
|
|
|
769
769
|
columns = ["*"]
|
|
770
770
|
|
|
771
771
|
# Convert columns from list to string
|
|
772
|
-
|
|
773
|
-
columns_str = str(columns[0])
|
|
774
|
-
else:
|
|
775
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
772
|
+
columns_str = list_to_sql_columns(columns)
|
|
776
773
|
|
|
777
774
|
# Convert tenors from list to string
|
|
778
|
-
identifiers_str =
|
|
775
|
+
identifiers_str = list_to_sql_set(identifiers)
|
|
779
776
|
|
|
780
777
|
# Query db
|
|
781
778
|
table = DetDatabase.get_table_name("instrument")
|
|
@@ -819,10 +816,7 @@ class DetDatabase:
|
|
|
819
816
|
columns = ["*"]
|
|
820
817
|
|
|
821
818
|
# Convert columns from list to string
|
|
822
|
-
|
|
823
|
-
columns_str = f"[{columns[0]}]"
|
|
824
|
-
else:
|
|
825
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
819
|
+
columns_str = list_to_sql_columns(columns)
|
|
826
820
|
|
|
827
821
|
# Convert dates from datetime to string
|
|
828
822
|
start_trading_date_str = start_trading_date.strftime("%Y-%m-%d")
|
|
@@ -922,10 +916,7 @@ class DetDatabase:
|
|
|
922
916
|
columns = ["*"]
|
|
923
917
|
|
|
924
918
|
# Convert columns from list to string
|
|
925
|
-
|
|
926
|
-
columns_str = str(columns[0])
|
|
927
|
-
else:
|
|
928
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
919
|
+
columns_str = list_to_sql_columns(columns)
|
|
929
920
|
|
|
930
921
|
# Convert dates from datetime to string
|
|
931
922
|
forecast_date_str = forecast_date.strftime("%Y-%m-%d")
|
|
@@ -975,10 +966,11 @@ class DetDatabase:
|
|
|
975
966
|
df[c] = df[c].dt.tz_localize(None)
|
|
976
967
|
|
|
977
968
|
# Rescale column values
|
|
978
|
-
df["
|
|
969
|
+
df["Volume(MWh)"] = convert_energy(df["kWh"], unit_in="kWh", unit_out="MWh")
|
|
970
|
+
df.drop(columns="kWh", inplace=True)
|
|
979
971
|
|
|
980
972
|
# Rename columns
|
|
981
|
-
df = df.rename(columns={"
|
|
973
|
+
df = df.rename(columns={"Datetime": "DeliveryStart"})
|
|
982
974
|
|
|
983
975
|
return df
|
|
984
976
|
|
|
@@ -1026,10 +1018,7 @@ class DetDatabase:
|
|
|
1026
1018
|
columns = ["*"]
|
|
1027
1019
|
|
|
1028
1020
|
# Convert columns from list to string
|
|
1029
|
-
|
|
1030
|
-
columns_str = str(columns[0])
|
|
1031
|
-
else:
|
|
1032
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
1021
|
+
columns_str = list_to_sql_columns(columns)
|
|
1033
1022
|
|
|
1034
1023
|
# Query db
|
|
1035
1024
|
table = DetDatabase.get_table_name("client_day_ahead_auction_bids")
|
|
@@ -1093,10 +1082,7 @@ class DetDatabase:
|
|
|
1093
1082
|
columns = ["*"]
|
|
1094
1083
|
|
|
1095
1084
|
# Convert columns from list to string
|
|
1096
|
-
|
|
1097
|
-
columns_str = str(columns[0])
|
|
1098
|
-
else:
|
|
1099
|
-
columns_str = f"[{'], ['.join(columns)}]"
|
|
1085
|
+
columns_str = list_to_sql_columns(columns)
|
|
1100
1086
|
|
|
1101
1087
|
# Query db
|
|
1102
1088
|
table = DetDatabase.get_table_name("client")
|
|
@@ -1131,7 +1117,7 @@ class DetDatabase:
|
|
|
1131
1117
|
|
|
1132
1118
|
elif client_info.shape[0] == 0:
|
|
1133
1119
|
available_values = self.load_clients(columns=[filter_column])
|
|
1134
|
-
available_values_str =
|
|
1120
|
+
available_values_str = list_to_str(available_values[filter_column])
|
|
1135
1121
|
raise ValueError(
|
|
1136
1122
|
f"Value {filter_value} not found in column '{filter_column}'. Available values: "
|
|
1137
1123
|
f"{available_values_str}."
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Internal modules
|
|
2
|
+
from detquantlib.utils import list_to_str
|
|
3
|
+
|
|
4
|
+
# Specify functions to expose
|
|
5
|
+
__all__ = ["list_to_sql_set", "list_to_sql_columns"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def list_to_sql_set(list_in: list[str]) -> str:
|
|
9
|
+
"""
|
|
10
|
+
Converts a list of the form ["a", "b", "c"] into a string of the form "('a', 'b', 'c')".
|
|
11
|
+
This string can be used in SQL queries applying the IN operator to filter data. Example:
|
|
12
|
+
>> "SELECT * FROM my_table WHERE my_column IN ('a', 'b', 'c')").
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
list_in: Input list of values
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Set of values reformatted in SQL string format
|
|
19
|
+
"""
|
|
20
|
+
return "(" + list_to_str(list_in) + ")"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def list_to_sql_columns(list_in: list[str]) -> str:
|
|
24
|
+
"""
|
|
25
|
+
Converts a list of the form ["a", "b", "c"] into a string of the form "[a], [b], [c]".
|
|
26
|
+
This string can be used in SQL queries to select specific columns. Example:
|
|
27
|
+
>> "SELECT [a], [b], [c] FROM my_table
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
list_in: Input list of values
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
String containing reformatted values
|
|
34
|
+
"""
|
|
35
|
+
# Convert columns to string
|
|
36
|
+
if len(list_in) == 1:
|
|
37
|
+
columns = str(list_in[0])
|
|
38
|
+
else:
|
|
39
|
+
columns = f"[{'], ['.join(list_in)}]"
|
|
40
|
+
return columns
|
|
@@ -1,11 +1,50 @@
|
|
|
1
1
|
# Python built-in packages
|
|
2
|
-
from datetime import datetime
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
3
|
from typing import Literal
|
|
4
4
|
|
|
5
5
|
# Third-party packages
|
|
6
6
|
import pandas as pd
|
|
7
7
|
from dateutil.relativedelta import *
|
|
8
8
|
|
|
9
|
+
# Specify functions to expose
|
|
10
|
+
__all__ = [
|
|
11
|
+
"count_nr_hours",
|
|
12
|
+
"count_delivery_periods",
|
|
13
|
+
"calc_months_diff",
|
|
14
|
+
"datetime_to_month_code",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def count_nr_hours(start_date: datetime, end_date: datetime) -> float:
|
|
19
|
+
"""
|
|
20
|
+
Counts the number of hours in a given time interval.
|
|
21
|
+
|
|
22
|
+
Note:
|
|
23
|
+
- datetime.timedelta does not take into account timezone-awareness. As such, it does
|
|
24
|
+
not take into account DST switches in the hours count.
|
|
25
|
+
- To correct for this, we first convert the dates to UTC, and then count the number of
|
|
26
|
+
hours.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
start_date: Start date
|
|
30
|
+
end_date: End date
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Number of hours in the given time interval
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def _convert_to_utc(dt: datetime) -> datetime:
|
|
37
|
+
utc = timezone.utc
|
|
38
|
+
return dt.replace(tzinfo=timezone.utc) if dt.tzinfo is None else dt.astimezone(tz=utc)
|
|
39
|
+
|
|
40
|
+
# Convert dates to UTC
|
|
41
|
+
start_date = _convert_to_utc(start_date)
|
|
42
|
+
end_date = _convert_to_utc(end_date)
|
|
43
|
+
|
|
44
|
+
# Calculate nr hours
|
|
45
|
+
td = end_date - start_date
|
|
46
|
+
return td.days * 24 + td.seconds / 3600
|
|
47
|
+
|
|
9
48
|
|
|
10
49
|
def count_delivery_periods(
|
|
11
50
|
start_date: datetime,
|
|
@@ -5,6 +5,9 @@ from datetime import datetime
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
|
+
# Specify functions to expose
|
|
9
|
+
__all__ = ["forecast_knife_strategy"]
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
def forecast_knife_strategy(
|
|
10
13
|
dates: list[datetime] | list[pd.Timestamp] | pd.DatetimeIndex, values: list | np.ndarray
|
{detquantlib-3.12.0 → detquantlib-3.14.0}/detquantlib/tradable_products/tradable_products.py
RENAMED
|
@@ -8,7 +8,66 @@ import pandas as pd
|
|
|
8
8
|
from dateutil.relativedelta import *
|
|
9
9
|
|
|
10
10
|
# Internal modules
|
|
11
|
-
from detquantlib.dates
|
|
11
|
+
from detquantlib.dates import calc_months_diff, count_nr_hours
|
|
12
|
+
from detquantlib.utils import list_to_str
|
|
13
|
+
|
|
14
|
+
# Specify functions to expose
|
|
15
|
+
__all__ = [
|
|
16
|
+
"convert_delivery_start_date_to_maturity",
|
|
17
|
+
"convert_maturity_to_delivery_start_date",
|
|
18
|
+
"tenor_to_pd_offset_alias",
|
|
19
|
+
"tenor_to_nr_hours",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def tenor_to_pd_offset_alias(tenor: str) -> str:
|
|
24
|
+
"""A short helper function mapping a product tenor to the corresponding pandas offset alias."""
|
|
25
|
+
tenor = tenor.upper()
|
|
26
|
+
mapper = dict(QUARTERHOUR="15min", HALFHOUR="30min", HOUR="h", DAY="D", WEEK="W")
|
|
27
|
+
if tenor not in mapper.keys():
|
|
28
|
+
tenors_str = list_to_str(list(mapper.keys()))
|
|
29
|
+
raise ValueError(f"Input tenor '{tenor}' is not supported. Accepted values: {tenors_str}.")
|
|
30
|
+
return mapper[tenor]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def tenor_to_nr_hours(
|
|
34
|
+
tenor: str, delivery_start: datetime = None, delivery_end: datetime = None
|
|
35
|
+
) -> float:
|
|
36
|
+
"""
|
|
37
|
+
Counts the number of hours in a given tenor.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
tenor: Tenor
|
|
41
|
+
delivery_start: Delivery start date (only used if tenor > 1 hour, to account for DST
|
|
42
|
+
switches)
|
|
43
|
+
delivery_end: Delivery end date (only used if tenor > 1 hour, to account for DST switches)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Number of hours in tenor
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValueError: Raises an error if the tenor delivery period is flexible and delivery dates
|
|
50
|
+
are not provided.
|
|
51
|
+
ValueError: Raises an error if he input tenor is not supported.
|
|
52
|
+
"""
|
|
53
|
+
tenor = tenor.upper()
|
|
54
|
+
if tenor == "QUARTERHOUR":
|
|
55
|
+
nr_hours = 0.25
|
|
56
|
+
elif tenor == "HALFHOUR":
|
|
57
|
+
nr_hours = 0.5
|
|
58
|
+
elif tenor == "HOUR":
|
|
59
|
+
nr_hours = 1.0
|
|
60
|
+
elif tenor in ["DAY", "WEEK", "WEEKEND", "MONTH", "QUARTER", "YEAR"]:
|
|
61
|
+
if delivery_start and delivery_end:
|
|
62
|
+
nr_hours = count_nr_hours(delivery_start, delivery_end)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Missing input(s) 'delivery_start' and/or 'delivery_end'. They must be provided "
|
|
66
|
+
f"for tenor '{tenor}', to account for DST switches and leap years."
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
raise ValueError(f"Input tenor '{tenor}' is not supported.")
|
|
70
|
+
return nr_hours
|
|
12
71
|
|
|
13
72
|
|
|
14
73
|
def convert_delivery_start_date_to_maturity(
|
|
@@ -8,6 +8,9 @@ from types import TracebackType
|
|
|
8
8
|
# Third-party packages
|
|
9
9
|
from colorlog import ColoredFormatter
|
|
10
10
|
|
|
11
|
+
# Specify functions to expose
|
|
12
|
+
__all__ = ["setup_logger"]
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
def setup_logger(log_dir: Path):
|
|
13
16
|
"""
|
|
@@ -18,6 +21,7 @@ def setup_logger(log_dir: Path):
|
|
|
18
21
|
"""
|
|
19
22
|
# Clear existing handlers to avoid duplicate logs
|
|
20
23
|
for handler in logging.root.handlers[:]:
|
|
24
|
+
handler.close()
|
|
21
25
|
logging.root.removeHandler(handler)
|
|
22
26
|
|
|
23
27
|
# Console handler with colors
|
|
@@ -4,6 +4,9 @@ from datetime import datetime
|
|
|
4
4
|
# Third-party packages
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
|
+
# Specify functions to expose
|
|
8
|
+
__all__ = ["add_log", "list_to_str", "divide_with_nan"]
|
|
9
|
+
|
|
7
10
|
|
|
8
11
|
def add_log(message: str):
|
|
9
12
|
"""
|
|
@@ -15,6 +18,11 @@ def add_log(message: str):
|
|
|
15
18
|
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {message}")
|
|
16
19
|
|
|
17
20
|
|
|
21
|
+
def list_to_str(lt: list[str | int | float | bool]) -> str:
|
|
22
|
+
"""Converts a list of the form [a, b, c] into a string of the form "'a', 'b', 'c'"."""
|
|
23
|
+
return ", ".join(f"'{x}'" for x in lt)
|
|
24
|
+
|
|
25
|
+
|
|
18
26
|
def divide_with_nan(
|
|
19
27
|
numerator: np.array, denominator: np.array, nan=np.nan, posinf=np.nan, neginf=np.nan
|
|
20
28
|
) -> np.array:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .utils import *
|
|
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
|