detquantlib 3.9.0__tar.gz → 3.10.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.
Files changed (21) hide show
  1. {detquantlib-3.9.0 → detquantlib-3.10.0}/PKG-INFO +2 -2
  2. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/data/databases/detdatabase.py +73 -90
  3. {detquantlib-3.9.0 → detquantlib-3.10.0}/pyproject.toml +2 -2
  4. {detquantlib-3.9.0 → detquantlib-3.10.0}/LICENSE.txt +0 -0
  5. {detquantlib-3.9.0 → detquantlib-3.10.0}/README.md +0 -0
  6. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/__init__.py +0 -0
  7. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/data/__init__.py +0 -0
  8. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/data/entsoe/entsoe.py +0 -0
  9. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/data/sftp/sftp.py +0 -0
  10. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/dates/__init__.py +0 -0
  11. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/dates/dates.py +0 -0
  12. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/figures/__init__.py +0 -0
  13. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/figures/plotly_figures.py +0 -0
  14. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/outputs/__init__.py +0 -0
  15. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/outputs/outputs_interface.py +0 -0
  16. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/stats/__init__.py +0 -0
  17. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/stats/data_analysis.py +0 -0
  18. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/tradable_products/__init__.py +0 -0
  19. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/tradable_products/tradable_products.py +0 -0
  20. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/utils/__init__.py +0 -0
  21. {detquantlib-3.9.0 → detquantlib-3.10.0}/detquantlib/utils/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: detquantlib
3
- Version: 3.9.0
3
+ Version: 3.10.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
@@ -15,9 +15,9 @@ Requires-Dist: numpy (>=2.2.6,<3.0.0)
15
15
  Requires-Dist: pandas (>=2.3.3,<3.0.0)
16
16
  Requires-Dist: paramiko (>=4.0.0,<5.0.0)
17
17
  Requires-Dist: plotly (>=6.3.1,<7.0.0)
18
- Requires-Dist: pyodbc (>=5.2.0,<6.0.0)
19
18
  Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
20
19
  Requires-Dist: scipy (>=1.15.2,<2.0.0)
20
+ Requires-Dist: sqlalchemy (>=2.0.43,<3.0.0)
21
21
  Project-URL: Repository, https://github.com/Dynamic-Energy-Trading/detquantlib
22
22
  Description-Content-Type: text/markdown
23
23
 
@@ -1,13 +1,13 @@
1
1
  # Python built-in packages
2
2
  import os
3
- import warnings
4
3
  from datetime import datetime
4
+ from urllib.parse import quote_plus
5
5
  from zoneinfo import ZoneInfo
6
6
 
7
7
  # Third-party packages
8
8
  import pandas as pd
9
- import pyodbc
10
9
  from dateutil.relativedelta import *
10
+ from sqlalchemy import Engine, create_engine, text
11
11
 
12
12
 
13
13
  class DetDatabase:
@@ -15,36 +15,34 @@ class DetDatabase:
15
15
  A class to easily interact with the DET database, including fetching and processing data.
16
16
  """
17
17
 
18
- def __init__(
19
- self,
20
- connection: pyodbc.Connection = None,
21
- driver: str = "{ODBC Driver 18 for SQL Server}",
22
- ):
18
+ def __init__(self, engine: Engine = None, driver: str = "ODBC Driver 18 for SQL Server"):
23
19
  """
24
20
  Constructor method.
25
21
 
26
22
  Args:
27
- connection: Database connection object. This argument does not have to be passed
28
- when creating the object. It can be set after the object has been created, using
29
- the open_connection() method.
23
+ engine: Instance of SQLAlchemy engine. If None, the instance is automatically
24
+ initialized.
30
25
  driver: ODBC driver
26
+ """
27
+ # Only allow object creation if mandatory environment variables exist
28
+ DetDatabase.check_environment_variables()
29
+
30
+ self.driver = driver
31
+ self.engine = engine if engine is not None else self.initialize_engine()
32
+
33
+ @staticmethod
34
+ def check_environment_variables():
35
+ """
36
+ Checks if environment variables needed by the class are defined.
31
37
 
32
38
  Raises:
33
39
  EnvironmentError: Raises an error if environment variables are not defined
34
40
  """
35
- self.connection = connection
36
- self.driver = driver
37
-
38
- # Check if environment variables needed by the class are defined
39
41
  required_env_vars = [
40
- dict(name="DET_DB_NAME", value=None, description="DET database name"),
41
- dict(name="DET_DB_SERVER", value=None, description="DET database server name"),
42
- dict(
43
- name="DET_DB_USERNAME", value=None, description="Username to connect to database"
44
- ),
45
- dict(
46
- name="DET_DB_PASSWORD", value=None, description="Password to connect to database"
47
- ),
42
+ dict(name="DET_DB_NAME", value=None, description="Database name"),
43
+ dict(name="DET_DB_SERVER", value=None, description="Server name"),
44
+ dict(name="DET_DB_USERNAME", value=None, description="Username"),
45
+ dict(name="DET_DB_PASSWORD", value=None, description="Password"),
48
46
  ]
49
47
  available_env_vars = os.environ
50
48
  for d in required_env_vars:
@@ -57,21 +55,28 @@ class DetDatabase:
57
55
  f"(description: '{d['description']}') not found."
58
56
  )
59
57
 
60
- def open_connection(self):
61
- """Opens a connection to the database."""
58
+ def initialize_engine(self) -> Engine:
59
+ """
60
+ Initializes the SQLAlchemy engine.
61
+
62
+ Returns:
63
+ Instance of SQLAlchemy engine
64
+ """
62
65
  # Create the connection string
63
66
  connection_str = (
64
67
  f"DRIVER={self.driver};"
65
68
  f"SERVER={os.getenv('DET_DB_SERVER')};"
66
69
  f"DATABASE={os.getenv('DET_DB_NAME')};"
67
70
  f"UID={os.getenv('DET_DB_USERNAME')};"
68
- f"PWD={os.getenv('DET_DB_PASSWORD')}"
71
+ f"PWD={quote_plus(os.getenv('DET_DB_PASSWORD'))}"
69
72
  )
70
- self.connection = pyodbc.connect(connection_str)
73
+ url = quote_plus(connection_str)
74
+ engine = create_engine(f"mssql+pyodbc:///?odbc_connect={url}")
75
+ return engine
71
76
 
72
- def close_connection(self):
73
- """Closes the connection to the database."""
74
- self.connection.close()
77
+ def terminate_engine(self):
78
+ """Disconnects the SQLAlchemy engine and releases all underlying resources."""
79
+ self.engine.dispose()
75
80
 
76
81
  def query_db(self, query: str) -> pd.DataFrame:
77
82
  """
@@ -86,17 +91,12 @@ class DetDatabase:
86
91
  Raises:
87
92
  Exception: Raises an error if the SQL query fails
88
93
  """
89
- with warnings.catch_warnings():
90
- # Pandas UserWarning returned when using pandas with pyodbc. Disable warning
91
- # temporarily for the SQL query.
92
- warnings.simplefilter("ignore", category=UserWarning)
93
-
94
- try:
95
- df = pd.read_sql(query, self.connection)
96
- except Exception as e:
97
- # If query fails, close connection before raising the error
98
- self.close_connection()
99
- raise
94
+ try:
95
+ df = pd.read_sql_query(query, con=self.engine)
96
+ except Exception as e:
97
+ # If query fails, close connection before raising the error
98
+ self.terminate_engine()
99
+ raise
100
100
 
101
101
  return df
102
102
 
@@ -229,7 +229,7 @@ class DetDatabase:
229
229
  end_delivery_date = end_delivery_date.astimezone(ZoneInfo("UTC"))
230
230
  end_date_str = end_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
231
231
 
232
- # Create query
232
+ # Query db
233
233
  table = DetDatabase.get_table_name("entsoe_day_ahead_spot_price")
234
234
  query = (
235
235
  f"SELECT {columns_str} FROM {table} "
@@ -237,11 +237,7 @@ class DetDatabase:
237
237
  f"AND [DateTime(UTC)]>='{start_date_str}' "
238
238
  f"AND [DateTime(UTC)]<'{end_date_str}' "
239
239
  )
240
-
241
- # Query db
242
- self.open_connection()
243
240
  df = self.query_db(query)
244
- self.close_connection()
245
241
 
246
242
  if df.empty:
247
243
  raise ValueError("No price data found for user-defined inputs.")
@@ -428,7 +424,7 @@ class DetDatabase:
428
424
  end_delivery_date = end_delivery_date.astimezone(ZoneInfo("UTC"))
429
425
  end_date_str = end_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
430
426
 
431
- # Create query
427
+ # Query db
432
428
  table = DetDatabase.get_table_name("entsoe_imbalance_price")
433
429
  query = (
434
430
  f"SELECT {columns_str} FROM {table} "
@@ -436,11 +432,7 @@ class DetDatabase:
436
432
  f"AND [DateTime(UTC)]>='{start_date_str}' "
437
433
  f"AND [DateTime(UTC)]<'{end_date_str}' "
438
434
  )
439
-
440
- # Query db
441
- self.open_connection()
442
435
  df = self.query_db(query)
443
- self.close_connection()
444
436
 
445
437
  if df.empty:
446
438
  raise ValueError("No price data found for user-defined inputs.")
@@ -561,7 +553,7 @@ class DetDatabase:
561
553
  start_trading_date_str = start_trading_date.strftime("%Y-%m-%d")
562
554
  end_trading_date_str = end_trading_date.strftime("%Y-%m-%d")
563
555
 
564
- # Create query
556
+ # Query db
565
557
  table = DetDatabase.get_table_name("futures_tt")
566
558
  query = (
567
559
  f"SELECT {columns_str} FROM {table} "
@@ -571,11 +563,7 @@ class DetDatabase:
571
563
  f"AND Tenor IN {tenors_str} "
572
564
  f"AND DeliveryType='{delivery_type}'"
573
565
  )
574
-
575
- # Query db
576
- self.open_connection()
577
566
  df = self.query_db(query)
578
- self.close_connection()
579
567
 
580
568
  if df.empty:
581
569
  raise ValueError("No price data found for user-defined inputs.")
@@ -633,14 +621,10 @@ class DetDatabase:
633
621
  else:
634
622
  columns_str = f"[{'], ['.join(columns)}]"
635
623
 
636
- # Create query
624
+ # Query db
637
625
  table = DetDatabase.get_table_name("commodity")
638
626
  query = f"SELECT {columns_str} FROM {table} {conditions}"
639
-
640
- # Query db
641
- self.open_connection()
642
627
  df = self.query_db(query)
643
- self.close_connection()
644
628
 
645
629
  return df
646
630
 
@@ -716,18 +700,14 @@ class DetDatabase:
716
700
  start_trading_date_str = start_trading_date.strftime("%Y-%m-%d")
717
701
  end_trading_date_str = end_trading_date.strftime("%Y-%m-%d")
718
702
 
719
- # Create query
703
+ # Query db
720
704
  table = DetDatabase.get_table_name("account_position")
721
705
  query = (
722
706
  f"SELECT {columns_str} FROM {table} "
723
707
  f"WHERE InsertionTimestamp>='{start_trading_date_str}' "
724
708
  f"AND InsertionTimestamp<'{end_trading_date_str}'"
725
709
  )
726
-
727
- # Query db
728
- self.open_connection()
729
710
  df = self.query_db(query)
730
- self.close_connection()
731
711
 
732
712
  # Assert data
733
713
  if df.empty:
@@ -774,14 +754,10 @@ class DetDatabase:
774
754
  # Convert tenors from list to string
775
755
  identifiers_str = ", ".join(f"'{i}'" for i in identifiers)
776
756
 
777
- # Create query
757
+ # Query db
778
758
  table = DetDatabase.get_table_name("instrument")
779
759
  query = f"SELECT {columns_str} FROM {table} WHERE [id] IN ({identifiers_str})"
780
-
781
- # Query db
782
- self.open_connection()
783
760
  df = self.query_db(query)
784
- self.close_connection()
785
761
 
786
762
  # Assert data
787
763
  if df.empty:
@@ -835,7 +811,7 @@ class DetDatabase:
835
811
  start_trading_date_str = start_trading_date.strftime("%Y-%m-%d")
836
812
  end_trading_date_str = end_trading_date.strftime("%Y-%m-%d")
837
813
 
838
- # Create query
814
+ # Query db
839
815
  table = DetDatabase.get_table_name("futures_eex")
840
816
  query = (
841
817
  f"SELECT {columns_str} FROM {table} "
@@ -843,11 +819,7 @@ class DetDatabase:
843
819
  f"AND TradingDate>='{start_trading_date_str}' "
844
820
  f"AND TradingDate<='{end_trading_date_str}'"
845
821
  )
846
-
847
- # Query db
848
- self.open_connection()
849
822
  df = self.query_db(query)
850
- self.close_connection()
851
823
 
852
824
  # Assert data
853
825
  if df.empty:
@@ -944,7 +916,7 @@ class DetDatabase:
944
916
  # Convert dates from datetime to string
945
917
  forecast_date_str = forecast_date.strftime("%Y-%m-%d")
946
918
 
947
- # Create query
919
+ # Query db
948
920
  table = DetDatabase.get_table_name("client_volume_forecast")
949
921
  query = (
950
922
  f"SELECT {columns_str} FROM {table} "
@@ -953,11 +925,7 @@ class DetDatabase:
953
925
  f"AND Datetime>='{start_date_str}' "
954
926
  f"AND Datetime<'{end_date_str}'"
955
927
  )
956
-
957
- # Query db
958
- self.open_connection()
959
928
  df = self.query_db(query)
960
- self.close_connection()
961
929
 
962
930
  # Assert data
963
931
  if df.empty:
@@ -1049,7 +1017,7 @@ class DetDatabase:
1049
1017
  else:
1050
1018
  columns_str = f"[{'], ['.join(columns)}]"
1051
1019
 
1052
- # Create query
1020
+ # Query db
1053
1021
  table = DetDatabase.get_table_name("client_day_ahead_auction_bids")
1054
1022
  query = (
1055
1023
  f"SELECT {columns_str} FROM {table} "
@@ -1057,11 +1025,7 @@ class DetDatabase:
1057
1025
  f"AND DeliveryStart>='{start_date_str}' "
1058
1026
  f"AND DeliveryStart<'{end_date_str}'"
1059
1027
  )
1060
-
1061
- # Query db
1062
- self.open_connection()
1063
1028
  df = self.query_db(query)
1064
- self.close_connection()
1065
1029
 
1066
1030
  # Assert data
1067
1031
  if df.empty:
@@ -1120,14 +1084,10 @@ class DetDatabase:
1120
1084
  else:
1121
1085
  columns_str = f"[{'], ['.join(columns)}]"
1122
1086
 
1123
- # Create query
1087
+ # Query db
1124
1088
  table = DetDatabase.get_table_name("client")
1125
1089
  query = f"SELECT {columns_str} FROM {table} {conditions}"
1126
-
1127
- # Query db
1128
- self.open_connection()
1129
1090
  df = self.query_db(query)
1130
- self.close_connection()
1131
1091
 
1132
1092
  return df
1133
1093
 
@@ -1168,6 +1128,29 @@ class DetDatabase:
1168
1128
 
1169
1129
  return client_info
1170
1130
 
1131
+ def add_table(self, df: pd.DataFrame, table_name: str, schema: str):
1132
+ """
1133
+ Creates a new database table and populates it with the user-input data.
1134
+
1135
+ Args:
1136
+ df: Data used to populate the database table
1137
+ table_name: Table name
1138
+ schema: Schema
1139
+ """
1140
+ df.to_sql(name=table_name, schema=schema, con=self.engine, if_exists="fail", index=False)
1141
+
1142
+ def remove_table(self, table_name: str, schema: str):
1143
+ """
1144
+ Deletes a database table.
1145
+
1146
+ Args:
1147
+ table_name: Table name
1148
+ schema: Schema
1149
+ """
1150
+ with self.engine.connect() as conn:
1151
+ conn.execute(text(f"DROP TABLE IF EXISTS [{schema}].[{table_name}]"))
1152
+ conn.commit()
1153
+
1171
1154
 
1172
1155
  class DetDatabaseDefinitions:
1173
1156
  """A class containing some hard-coded definitions related to the DET database."""
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "detquantlib"
3
- version = "3.9.0"
3
+ version = "3.10.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"
@@ -15,9 +15,9 @@ paramiko = "^4.0.0"
15
15
  numpy = "^2.2.6"
16
16
  pandas = "^2.3.3"
17
17
  plotly = "^6.3.1"
18
- pyodbc = "^5.2.0"
19
18
  python-dotenv = "^1.1.0"
20
19
  scipy = "^1.15.2"
20
+ sqlalchemy = "^2.0.43"
21
21
 
22
22
  [tool.poetry.group.dev.dependencies]
23
23
  toml = "^0.10.2"
File without changes
File without changes