detquantlib 4.0.0__tar.gz → 5.0.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-4.0.0 → detquantlib-5.0.0}/PKG-INFO +1 -1
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/dates/dates.py +4 -65
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/tradable_products/tradable_products.py +83 -24
- {detquantlib-4.0.0 → detquantlib-5.0.0}/pyproject.toml +1 -1
- {detquantlib-4.0.0 → detquantlib-5.0.0}/LICENSE.txt +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/README.md +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/assets/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/assets/battery.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/assets/wind_turbine.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/converters/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/converters/definitions.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/converters/energy.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/converters/helpers.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/converters/price.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/data/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/data/databases/detdatabase.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/data/databases/helpers.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/data/entsoe/entsoe.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/data/sftp/sftp.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/dates/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/figures/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/figures/plotly_figures.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/forecasting/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/forecasting/forecasting.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/outputs/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/outputs/outputs_interface.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/stats/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/stats/data_analysis.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/tradable_products/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/utils/__init__.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/utils/logging.py +0 -0
- {detquantlib-4.0.0 → detquantlib-5.0.0}/detquantlib/utils/utils.py +0 -0
|
@@ -3,16 +3,10 @@ from datetime import datetime, timezone
|
|
|
3
3
|
from typing import Literal
|
|
4
4
|
|
|
5
5
|
# Third-party packages
|
|
6
|
-
import pandas as pd
|
|
7
6
|
from dateutil.relativedelta import *
|
|
8
7
|
|
|
9
8
|
# Specify functions to expose
|
|
10
|
-
__all__ = [
|
|
11
|
-
"count_nr_hours",
|
|
12
|
-
"count_delivery_periods",
|
|
13
|
-
"calc_months_diff",
|
|
14
|
-
"datetime_to_month_code",
|
|
15
|
-
]
|
|
9
|
+
__all__ = ["count_nr_hours", "calc_months_diff", "datetime_to_month_code"]
|
|
16
10
|
|
|
17
11
|
|
|
18
12
|
def count_nr_hours(start_date: datetime, end_date: datetime) -> float:
|
|
@@ -46,61 +40,6 @@ def count_nr_hours(start_date: datetime, end_date: datetime) -> float:
|
|
|
46
40
|
return td.days * 24 + td.seconds / 3600
|
|
47
41
|
|
|
48
42
|
|
|
49
|
-
def count_delivery_periods(
|
|
50
|
-
start_date: datetime,
|
|
51
|
-
end_date: datetime,
|
|
52
|
-
delivery_frequency: str,
|
|
53
|
-
full_periods_only: bool = False,
|
|
54
|
-
timezone: str = None,
|
|
55
|
-
) -> int:
|
|
56
|
-
"""
|
|
57
|
-
Counts the number of delivery periods within a time interval.
|
|
58
|
-
|
|
59
|
-
Note: When the end date is exactly equal to the start of the next delivery period, that next
|
|
60
|
-
period is not included (because it has not yet started). For example, if the end date is
|
|
61
|
-
15-Jan-2025 00:00:00 and the delivery frequency is daily, then the period
|
|
62
|
-
15-Jan-2025 00:00:00 to 16-Jan-2025 00:00:00 is not included.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
start_date: Delivery start date
|
|
66
|
-
end_date: Delivery end date
|
|
67
|
-
delivery_frequency: Delivery frequency, expressed as Pandas offset aliases
|
|
68
|
-
full_periods_only: Indicates whether to count only fully elapsed periods.
|
|
69
|
-
For example, suppose that start date is 15-Jan-2025 00:15:00, end date is
|
|
70
|
-
3-Feb-2025 03:45:00, and delivery frequency is hourly. Then:
|
|
71
|
-
- If full_periods_only=True, the number of periods is 2 (01:00:00-02:00:00 and
|
|
72
|
-
02:00:00-03:00:00), because the hours 00:00:00-01:00:00 and 00:03:00-04:00:00
|
|
73
|
-
are not full.
|
|
74
|
-
- If full_periods_only=False, the number of periods is 4, because incomplete hours
|
|
75
|
-
00:00:00-01:00:00 and 00:03:00-04:00:00 are also included. Note:
|
|
76
|
-
- If end date is 3-Feb-2025 04:00:00, hour 04:00:00-05:00:00 is not included.
|
|
77
|
-
- If end date is 3-Feb-2025 04:00:01, hour 04:00:00-05:00:00 is included.
|
|
78
|
-
timezone: Timezone (needed to account for DST switches)
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
Number of delivery periods in the interval
|
|
82
|
-
"""
|
|
83
|
-
# Convert to pandas timestamp
|
|
84
|
-
start_date = pd.Timestamp(start_date)
|
|
85
|
-
end_date = pd.Timestamp(end_date)
|
|
86
|
-
|
|
87
|
-
if full_periods_only:
|
|
88
|
-
start_date = start_date.ceil(delivery_frequency)
|
|
89
|
-
end_date = end_date.floor(delivery_frequency)
|
|
90
|
-
else:
|
|
91
|
-
start_date = start_date.floor(delivery_frequency)
|
|
92
|
-
|
|
93
|
-
periods = pd.date_range(
|
|
94
|
-
start=start_date,
|
|
95
|
-
end=end_date,
|
|
96
|
-
freq=delivery_frequency,
|
|
97
|
-
inclusive="left",
|
|
98
|
-
tz=timezone,
|
|
99
|
-
)
|
|
100
|
-
nr_periods = len(periods)
|
|
101
|
-
return nr_periods
|
|
102
|
-
|
|
103
|
-
|
|
104
43
|
def calc_months_diff(
|
|
105
44
|
start_date: datetime,
|
|
106
45
|
end_date: datetime,
|
|
@@ -138,8 +77,8 @@ def calc_months_diff(
|
|
|
138
77
|
Month difference between 2 dates
|
|
139
78
|
|
|
140
79
|
Raises:
|
|
141
|
-
ValueError: Raises an error when end_date < start_date
|
|
142
|
-
ValueError: Raises an error when the input argument 'diff_method' is invalid
|
|
80
|
+
ValueError: Raises an error when end_date < start_date.
|
|
81
|
+
ValueError: Raises an error when the input argument 'diff_method' is invalid.
|
|
143
82
|
"""
|
|
144
83
|
# Input validation
|
|
145
84
|
if end_date < start_date and diff_method != "month":
|
|
@@ -185,7 +124,7 @@ def datetime_to_month_code(d: datetime) -> int:
|
|
|
185
124
|
Corresponding month code
|
|
186
125
|
|
|
187
126
|
Raises:
|
|
188
|
-
ValueError: Raises an error if the input datetime is before 1 January 1900
|
|
127
|
+
ValueError: Raises an error if the input datetime is before 1 January 1900.
|
|
189
128
|
"""
|
|
190
129
|
if d < datetime(1900, 1, 1):
|
|
191
130
|
raise ValueError("Input date cannot be before 1 January 1900.")
|
|
@@ -15,6 +15,7 @@ from detquantlib.utils import list_to_str
|
|
|
15
15
|
__all__ = [
|
|
16
16
|
"convert_delivery_start_date_to_maturity",
|
|
17
17
|
"convert_maturity_to_delivery_start_date",
|
|
18
|
+
"count_delivery_periods",
|
|
18
19
|
"tenor_to_pd_offset_alias",
|
|
19
20
|
"tenor_to_nr_hours",
|
|
20
21
|
]
|
|
@@ -70,56 +71,114 @@ def tenor_to_nr_hours(
|
|
|
70
71
|
return nr_hours
|
|
71
72
|
|
|
72
73
|
|
|
74
|
+
def count_delivery_periods(
|
|
75
|
+
start_date: datetime,
|
|
76
|
+
end_date: datetime,
|
|
77
|
+
tenor: str,
|
|
78
|
+
full_periods_only: bool = False,
|
|
79
|
+
tz: str = None,
|
|
80
|
+
) -> int:
|
|
81
|
+
"""
|
|
82
|
+
Counts the number of delivery periods within a time interval.
|
|
83
|
+
|
|
84
|
+
Note: When the end date is exactly equal to the start of the next delivery period, that next
|
|
85
|
+
period is not included (because it has not yet started). For example, if the end date is
|
|
86
|
+
15-Jan-2025 00:00:00 and the delivery frequency is daily, then the period
|
|
87
|
+
15-Jan-2025 00:00:00 to 16-Jan-2025 00:00:00 is not included.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
start_date: Delivery start date
|
|
91
|
+
end_date: Delivery end date
|
|
92
|
+
tenor: Product tenor
|
|
93
|
+
full_periods_only: Indicates whether to count only fully elapsed periods.
|
|
94
|
+
For example, suppose that start date is 15-Jan-2025 00:15:00, end date is
|
|
95
|
+
3-Feb-2025 03:45:00, and delivery frequency is hourly. Then:
|
|
96
|
+
- If full_periods_only=True, the number of periods is 2 (01:00:00-02:00:00 and
|
|
97
|
+
02:00:00-03:00:00), because the hours 00:00:00-01:00:00 and 00:03:00-04:00:00
|
|
98
|
+
are not full.
|
|
99
|
+
- If full_periods_only=False, the number of periods is 4, because incomplete hours
|
|
100
|
+
00:00:00-01:00:00 and 00:03:00-04:00:00 are also included. Note:
|
|
101
|
+
- If end date is 3-Feb-2025 04:00:00, hour 04:00:00-05:00:00 is not included.
|
|
102
|
+
- If end date is 3-Feb-2025 04:00:01, hour 04:00:00-05:00:00 is included.
|
|
103
|
+
tz: Timezone (needed to account for DST switches)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Number of delivery periods in the interval
|
|
107
|
+
"""
|
|
108
|
+
# Convert tenor to offset alias
|
|
109
|
+
freq = tenor_to_pd_offset_alias(tenor)
|
|
110
|
+
|
|
111
|
+
# Convert to pandas timestamp
|
|
112
|
+
start_date = pd.Timestamp(start_date)
|
|
113
|
+
end_date = pd.Timestamp(end_date)
|
|
114
|
+
|
|
115
|
+
if full_periods_only:
|
|
116
|
+
start_date = start_date.ceil(freq)
|
|
117
|
+
end_date = end_date.floor(freq)
|
|
118
|
+
else:
|
|
119
|
+
start_date = start_date.floor(freq)
|
|
120
|
+
|
|
121
|
+
periods = pd.date_range(
|
|
122
|
+
start=start_date,
|
|
123
|
+
end=end_date,
|
|
124
|
+
freq=freq,
|
|
125
|
+
inclusive="left",
|
|
126
|
+
tz=tz,
|
|
127
|
+
)
|
|
128
|
+
nr_periods = len(periods)
|
|
129
|
+
return nr_periods
|
|
130
|
+
|
|
131
|
+
|
|
73
132
|
def convert_delivery_start_date_to_maturity(
|
|
74
133
|
trading_date: datetime,
|
|
75
134
|
delivery_start_date: datetime,
|
|
76
|
-
|
|
135
|
+
tenor: Literal["day", "weekend", "week", "month", "quarter", "year"],
|
|
77
136
|
) -> int:
|
|
78
137
|
"""
|
|
79
138
|
Calculates the number of maturities between the input trading date and the input delivery
|
|
80
|
-
date, based on the input product
|
|
139
|
+
date, based on the input product tenor.
|
|
81
140
|
|
|
82
141
|
Args:
|
|
83
142
|
trading_date: Trading date
|
|
84
143
|
delivery_start_date: Delivery start date
|
|
85
|
-
|
|
144
|
+
tenor: Product tenor (e.g. "month", "quarter", "year")
|
|
86
145
|
|
|
87
146
|
Returns:
|
|
88
147
|
Product maturity
|
|
89
148
|
|
|
90
149
|
Raises:
|
|
91
|
-
ValueError: Raises an error when the input product
|
|
150
|
+
ValueError: Raises an error when the input product tenor is not recognized.
|
|
92
151
|
"""
|
|
93
152
|
# Make input product string lower case only
|
|
94
|
-
|
|
153
|
+
tenor = tenor.lower()
|
|
95
154
|
|
|
96
155
|
# Set trading date and delivery date to midnight
|
|
97
156
|
trading_date = datetime.combine(trading_date, time())
|
|
98
157
|
delivery_start_date = datetime.combine(delivery_start_date, time())
|
|
99
158
|
|
|
100
|
-
if
|
|
159
|
+
if tenor == "day":
|
|
101
160
|
maturity = (delivery_start_date - trading_date).days
|
|
102
161
|
|
|
103
|
-
elif
|
|
162
|
+
elif tenor == "week":
|
|
104
163
|
maturity = math.ceil((delivery_start_date - trading_date).days / 7)
|
|
105
164
|
|
|
106
|
-
elif
|
|
165
|
+
elif tenor == "weekend":
|
|
107
166
|
maturity = math.ceil((delivery_start_date - trading_date).days / 7)
|
|
108
167
|
|
|
109
|
-
elif
|
|
168
|
+
elif tenor == "month":
|
|
110
169
|
maturity = calc_months_diff(
|
|
111
170
|
start_date=trading_date,
|
|
112
171
|
end_date=delivery_start_date,
|
|
113
172
|
diff_method="month",
|
|
114
173
|
)
|
|
115
174
|
|
|
116
|
-
elif
|
|
175
|
+
elif tenor == "quarter":
|
|
117
176
|
trading_quarter_start_date = convert_maturity_to_delivery_start_date(
|
|
118
|
-
trading_date=trading_date, maturity=0,
|
|
177
|
+
trading_date=trading_date, maturity=0, tenor="quarter"
|
|
119
178
|
)
|
|
120
179
|
|
|
121
180
|
delivery_quarter_start_date = convert_maturity_to_delivery_start_date(
|
|
122
|
-
trading_date=delivery_start_date, maturity=0,
|
|
181
|
+
trading_date=delivery_start_date, maturity=0, tenor="quarter"
|
|
123
182
|
)
|
|
124
183
|
|
|
125
184
|
months_diff = calc_months_diff(
|
|
@@ -129,11 +188,11 @@ def convert_delivery_start_date_to_maturity(
|
|
|
129
188
|
)
|
|
130
189
|
maturity = months_diff / 3
|
|
131
190
|
|
|
132
|
-
elif
|
|
191
|
+
elif tenor == "year":
|
|
133
192
|
maturity = delivery_start_date.year - trading_date.year
|
|
134
193
|
|
|
135
194
|
else:
|
|
136
|
-
raise ValueError("Invalid input product
|
|
195
|
+
raise ValueError("Invalid input product tenor.")
|
|
137
196
|
|
|
138
197
|
return maturity
|
|
139
198
|
|
|
@@ -141,42 +200,42 @@ def convert_delivery_start_date_to_maturity(
|
|
|
141
200
|
def convert_maturity_to_delivery_start_date(
|
|
142
201
|
trading_date: datetime,
|
|
143
202
|
maturity: int,
|
|
144
|
-
|
|
203
|
+
tenor: Literal["month", "quarter", "year"],
|
|
145
204
|
) -> datetime:
|
|
146
205
|
"""
|
|
147
|
-
Calculates the delivery start date of the input product, based on the input trading
|
|
148
|
-
and input maturity.
|
|
206
|
+
Calculates the delivery start date of the input product tenor, based on the input trading
|
|
207
|
+
date and input maturity.
|
|
149
208
|
|
|
150
209
|
Args:
|
|
151
210
|
trading_date: Trading date
|
|
152
211
|
maturity: Product maturity
|
|
153
|
-
|
|
212
|
+
tenor: Product tenor (e.g. "month", "quarter", "year")
|
|
154
213
|
|
|
155
214
|
Returns:
|
|
156
215
|
Delivery start date
|
|
157
216
|
|
|
158
217
|
Raises:
|
|
159
|
-
ValueError: Raises an error when the input product
|
|
218
|
+
ValueError: Raises an error when the input product tenor is not recognized.
|
|
160
219
|
"""
|
|
161
220
|
|
|
162
221
|
# Make input product string lower case only
|
|
163
|
-
|
|
222
|
+
tenor = tenor.lower()
|
|
164
223
|
|
|
165
|
-
if
|
|
224
|
+
if tenor == "month":
|
|
166
225
|
month_start_date = datetime(trading_date.year, trading_date.month, 1)
|
|
167
226
|
delivery_start_date = month_start_date + relativedelta(months=maturity)
|
|
168
227
|
|
|
169
|
-
elif
|
|
228
|
+
elif tenor == "quarter":
|
|
170
229
|
quarter = pd.Timestamp(trading_date).quarter
|
|
171
230
|
year_start_date = datetime(trading_date.year, 1, 1)
|
|
172
231
|
quarter_start_date = year_start_date + relativedelta(months=((quarter - 1) * 3))
|
|
173
232
|
delivery_start_date = quarter_start_date + relativedelta(months=(maturity * 3))
|
|
174
233
|
|
|
175
|
-
elif
|
|
234
|
+
elif tenor == "year":
|
|
176
235
|
year_start_date = datetime(trading_date.year, 1, 1)
|
|
177
236
|
delivery_start_date = year_start_date + relativedelta(years=maturity)
|
|
178
237
|
|
|
179
238
|
else:
|
|
180
|
-
raise ValueError("Invalid input product
|
|
239
|
+
raise ValueError("Invalid input product tenor.")
|
|
181
240
|
|
|
182
241
|
return delivery_start_date
|
|
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
|
|
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
|