detquantlib 3.5.1__tar.gz → 3.7.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.5.1 → detquantlib-3.7.0}/PKG-INFO +1 -1
  2. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/data/databases/detdatabase.py +317 -27
  3. {detquantlib-3.5.1 → detquantlib-3.7.0}/pyproject.toml +1 -1
  4. {detquantlib-3.5.1 → detquantlib-3.7.0}/LICENSE.txt +0 -0
  5. {detquantlib-3.5.1 → detquantlib-3.7.0}/README.md +0 -0
  6. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/__init__.py +0 -0
  7. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/data/__init__.py +0 -0
  8. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/data/entsoe/entsoe.py +0 -0
  9. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/data/sftp/sftp.py +0 -0
  10. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/dates/__init__.py +0 -0
  11. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/dates/dates.py +0 -0
  12. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/figures/__init__.py +0 -0
  13. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/figures/plotly_figures.py +0 -0
  14. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/outputs/__init__.py +0 -0
  15. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/outputs/outputs_interface.py +0 -0
  16. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/stats/__init__.py +0 -0
  17. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/stats/data_analysis.py +0 -0
  18. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/tradable_products/__init__.py +0 -0
  19. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/tradable_products/tradable_products.py +0 -0
  20. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/utils/__init__.py +0 -0
  21. {detquantlib-3.5.1 → detquantlib-3.7.0}/detquantlib/utils/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: detquantlib
3
- Version: 3.5.1
3
+ Version: 3.7.0
4
4
  Summary: An internal library containing functions and classes that can be used across Quant models.
5
5
  Author: DET
6
6
  Requires-Python: >=3.10,<4.0
@@ -402,8 +402,7 @@ class DetDatabase:
402
402
 
403
403
  # Convert end trading date to end delivery date
404
404
  if end_trading_date is not None:
405
- end_trading_date = pd.Timestamp(end_trading_date).floor("D")
406
- end_delivery_date = end_trading_date + relativedelta(days=1)
405
+ end_trading_date = pd.Timestamp(end_trading_date).floor("D") + relativedelta(days=1)
407
406
 
408
407
  # Convert end date to UTC and string
409
408
  end_delivery_date = end_delivery_date.replace(tzinfo=ZoneInfo(timezone))
@@ -666,10 +665,7 @@ class DetDatabase:
666
665
  return commodity_info
667
666
 
668
667
  def load_account_positions(
669
- self,
670
- start_trading_date: datetime,
671
- end_trading_date: datetime,
672
- columns: list = None,
668
+ self, start_trading_date: datetime, end_trading_date: datetime, columns: list = None
673
669
  ) -> pd.DataFrame:
674
670
  """
675
671
  Loads account positions from the database, over a user-defined range of trading dates.
@@ -697,6 +693,7 @@ class DetDatabase:
697
693
  columns_str = f"[{'], ['.join(columns)}]"
698
694
 
699
695
  # Convert dates from datetime to string
696
+ end_trading_date = pd.Timestamp(end_trading_date).floor("D") + relativedelta(days=1)
700
697
  start_trading_date_str = start_trading_date.strftime("%Y-%m-%d")
701
698
  end_trading_date_str = end_trading_date.strftime("%Y-%m-%d")
702
699
 
@@ -704,8 +701,8 @@ class DetDatabase:
704
701
  table = DetDatabaseDefinitions.DEFINITIONS["table_name_account_position"]
705
702
  query = (
706
703
  f"SELECT {columns_str} FROM {table} "
707
- f"WHERE CAST ([InsertionTimestamp] AS DATE) BETWEEN '{start_trading_date_str}' AND "
708
- f"'{end_trading_date_str}'"
704
+ f"WHERE InsertionTimestamp>='{start_trading_date_str}' "
705
+ f"AND InsertionTimestamp<'{end_trading_date_str}'"
709
706
  )
710
707
 
711
708
  # Query db
@@ -717,25 +714,20 @@ class DetDatabase:
717
714
  if df.empty:
718
715
  raise ValueError("No account position data found for user-defined inputs.")
719
716
 
720
- # Sort data
721
- df.sort_values(
722
- by=["InsertionTimestamp"],
723
- axis=0,
724
- ascending=True,
725
- inplace=True,
726
- ignore_index=True,
727
- )
728
-
729
- # Convert dates from datetime.date to pd.Timestamp
730
- df["InsertionTimestamp"] = pd.DatetimeIndex(df["InsertionTimestamp"])
717
+ # Sort data and convert dates from datetime.date to pd.Timestamp
718
+ if "InsertionTimestamp" in df.columns:
719
+ df.sort_values(
720
+ by=["InsertionTimestamp"],
721
+ axis=0,
722
+ ascending=True,
723
+ inplace=True,
724
+ ignore_index=True,
725
+ )
726
+ df["InsertionTimestamp"] = pd.DatetimeIndex(df["InsertionTimestamp"])
731
727
 
732
728
  return df
733
729
 
734
- def load_instruments(
735
- self,
736
- identifiers: list,
737
- columns: list = None,
738
- ) -> pd.DataFrame:
730
+ def load_instruments(self, identifiers: list, columns: list = None) -> pd.DataFrame:
739
731
  """
740
732
  Loads instrument data based on identifier (of account positions) from the database.
741
733
 
@@ -828,9 +820,9 @@ class DetDatabase:
828
820
  table = DetDatabaseDefinitions.DEFINITIONS["table_name_eex_eod_price"]
829
821
  query = (
830
822
  f"SELECT {columns_str} FROM {table} "
831
- f"WHERE [Product] LIKE '{product_code}' "
832
- f"AND CAST ([TradingDate] AS DATE) BETWEEN '{start_trading_date_str}' AND "
833
- f"'{end_trading_date_str}'"
823
+ f"WHERE Product LIKE '{product_code}' "
824
+ f"AND TradingDate>='{start_trading_date_str}' "
825
+ f"AND TradingDate<='{end_trading_date_str}'"
834
826
  )
835
827
 
836
828
  # Query db
@@ -862,6 +854,301 @@ class DetDatabase:
862
854
 
863
855
  return df
864
856
 
857
+ def load_forecast_customer_volume(
858
+ self,
859
+ profile: str,
860
+ forecast_date: datetime,
861
+ start_delivery_date: datetime,
862
+ end_delivery_date: datetime,
863
+ local_timezone: str,
864
+ timezone_aware_dates: bool = False,
865
+ columns: list = None,
866
+ ) -> pd.DataFrame:
867
+ """
868
+ Loads customer volume forecasts from the database.
869
+
870
+ Args:
871
+ profile: Customer/profile name.
872
+ Note on the 'Portfolio' and 'Portfolioweekend' profiles:
873
+ - Profile names 'Portfolio' and 'Portfolioweekend' refer to the same set of
874
+ connection points.
875
+ - In the database, the profile name is set to 'Portfolio' when the forecast date
876
+ is between Monday and Friday.
877
+ - In the database, the profile name is set to 'Portfolioweekend' when the forecast
878
+ date is Saturday or Sunday.
879
+ - When calling this method, the input argument 'profile' can be set to
880
+ 'PortfolioAll' to automatically let the method switch between 'Portfolio' and
881
+ 'Portfolioweekend', based on the used-input forecast date.
882
+ forecast_date: Date on which customer volume forecast is generated.
883
+ start_delivery_date: First delivery date included.
884
+ end_delivery_date: Last delivery date included.
885
+ local_timezone: Local timezone (needed to account for DST switches).
886
+ timezone_aware_dates: If true, returns all dates as timezone-aware. Otherwise, returns
887
+ them as timezone-naive.
888
+ columns: Requested database table columns. Set columns=["*"] (i.e. as list) to get all
889
+ columns.
890
+
891
+ Returns:
892
+ Dataframe containing customer volume forecasts.
893
+
894
+ Raises:
895
+ ValueError: Raises an error if no volume forecast data is found for user inputs.
896
+ """
897
+ # Convert profile if set to "PortfolioAll"
898
+ if profile == "PortfolioAll":
899
+ if forecast_date.weekday() > 4:
900
+ profile = "Portfolioweekend"
901
+ else:
902
+ profile = "Portfolio"
903
+
904
+ # Convert start delivery date from local timezone to UTC and string
905
+ start_delivery_date = start_delivery_date.replace(tzinfo=ZoneInfo(local_timezone))
906
+ start_delivery_date = start_delivery_date.astimezone(ZoneInfo("UTC"))
907
+ start_date_str = start_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
908
+
909
+ # Convert end delivery date form local timezone to UTC and string
910
+ end_delivery_date = pd.Timestamp(end_delivery_date).floor("D") + relativedelta(days=1)
911
+ end_delivery_date = end_delivery_date.replace(tzinfo=ZoneInfo(local_timezone))
912
+ end_delivery_date = end_delivery_date.astimezone(ZoneInfo("UTC"))
913
+ end_date_str = end_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
914
+
915
+ # Set default column values
916
+ if columns is None:
917
+ columns = ["*"]
918
+
919
+ # Convert columns from list to string
920
+ if len(columns) == 1:
921
+ columns_str = str(columns[0])
922
+ else:
923
+ columns_str = f"[{'], ['.join(columns)}]"
924
+
925
+ # Convert dates from datetime to string
926
+ forecast_date_str = forecast_date.strftime("%Y-%m-%d")
927
+
928
+ # Create query
929
+ table = DetDatabaseDefinitions.DEFINITIONS["table_name_forecast_customer_volume"]
930
+ query = (
931
+ f"SELECT {columns_str} FROM {table} "
932
+ f"WHERE Profile='{profile}' "
933
+ f"AND ForecastDate='{forecast_date_str}' "
934
+ f"AND Datetime>='{start_date_str}' "
935
+ f"AND Datetime<'{end_date_str}'"
936
+ )
937
+
938
+ # Query db
939
+ self.open_connection()
940
+ df = self.query_db(query)
941
+ self.close_connection()
942
+
943
+ # Assert data
944
+ if df.empty:
945
+ raise ValueError("No volume forecast data found for user-defined inputs.")
946
+
947
+ # Sort data
948
+ sort_cols = ["Profile", "ForecastDate", "Datetime"]
949
+ sort_cols = [c for c in sort_cols if c in df.columns]
950
+ if len(sort_cols) > 0:
951
+ df.sort_values(
952
+ by=sort_cols,
953
+ axis=0,
954
+ ascending=True,
955
+ inplace=True,
956
+ ignore_index=True,
957
+ )
958
+
959
+ # Localize, convert and set timezone-(un)aware datetimes
960
+ cols_date = ["ForecastDate", "Datetime", "InsertionTimestamp"]
961
+ for c in cols_date:
962
+ if c in df.columns:
963
+ # Convert from datetime to pandas timestamp
964
+ df[c] = pd.DatetimeIndex(df[c])
965
+
966
+ # Convert to timezone-aware dates
967
+ if c == "ForecastDate":
968
+ df[c] = df[c].dt.tz_localize(local_timezone)
969
+ else:
970
+ df[c] = df[c].dt.tz_localize("UTC")
971
+ df[c] = df[c].dt.tz_convert(local_timezone)
972
+
973
+ if not timezone_aware_dates:
974
+ df[c] = df[c].dt.tz_localize(None)
975
+
976
+ # Rescale column values
977
+ df["kWh"] = df["kWh"] / 1000
978
+
979
+ # Rename columns
980
+ df = df.rename(columns={"kWh": "Volume(MWh)", "Datetime": "DeliveryStart"})
981
+
982
+ return df
983
+
984
+ def load_customer_day_ahead_auction_bids(
985
+ self,
986
+ client_id: str,
987
+ start_delivery_date: datetime,
988
+ end_delivery_date: datetime,
989
+ local_timezone: str,
990
+ timezone_aware_dates: bool = False,
991
+ columns: list = None,
992
+ ) -> pd.DataFrame:
993
+ """
994
+ Loads customer day-ahead auction bids (volumes and limit prices) from the database.
995
+
996
+ Args:
997
+ client_id: Client ID
998
+ start_delivery_date: First delivery date included
999
+ end_delivery_date: Last delivery date included
1000
+ local_timezone: Local timezone (needed to account for DST switches)
1001
+ timezone_aware_dates: If true, returns all dates as timezone-aware. Otherwise, returns
1002
+ them as timezone-naive
1003
+ columns: Requested database table columns. Set columns=["*"] (i.e. as list) to get all
1004
+ columns
1005
+
1006
+ Returns:
1007
+ Dataframe containing customer day-ahead auction bids
1008
+
1009
+ Raises:
1010
+ ValueError: Raises an error if no data is found for user inputs
1011
+ """
1012
+ # Convert start delivery date from local timezone to UTC and string
1013
+ start_delivery_date = start_delivery_date.replace(tzinfo=ZoneInfo(local_timezone))
1014
+ start_delivery_date = start_delivery_date.astimezone(ZoneInfo("UTC"))
1015
+ start_date_str = start_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
1016
+
1017
+ # Convert end delivery date form local timezone to UTC and string
1018
+ end_delivery_date = pd.Timestamp(end_delivery_date).floor("D") + relativedelta(days=1)
1019
+ end_delivery_date = end_delivery_date.replace(tzinfo=ZoneInfo(local_timezone))
1020
+ end_delivery_date = end_delivery_date.astimezone(ZoneInfo("UTC"))
1021
+ end_date_str = end_delivery_date.strftime("%Y-%m-%d %H:%M:%S")
1022
+
1023
+ # Set default column values
1024
+ if columns is None:
1025
+ columns = ["*"]
1026
+
1027
+ # Convert columns from list to string
1028
+ if len(columns) == 1:
1029
+ columns_str = str(columns[0])
1030
+ else:
1031
+ columns_str = f"[{'], ['.join(columns)}]"
1032
+
1033
+ # Create query
1034
+ table = DetDatabaseDefinitions.DEFINITIONS["table_name_customer_day_ahead_auction_bids"]
1035
+ query = (
1036
+ f"SELECT {columns_str} FROM {table} "
1037
+ f"WHERE ClientId='{client_id}' "
1038
+ f"AND DeliveryStart>='{start_date_str}' "
1039
+ f"AND DeliveryStart<'{end_date_str}'"
1040
+ )
1041
+
1042
+ # Query db
1043
+ self.open_connection()
1044
+ df = self.query_db(query)
1045
+ self.close_connection()
1046
+
1047
+ # Assert data
1048
+ if df.empty:
1049
+ raise ValueError("No data found for user-defined inputs.")
1050
+
1051
+ # Sort data
1052
+ sort_cols = ["ClientId", "InsertionTimestamp", "DeliveryStart"]
1053
+ sort_cols = [c for c in sort_cols if c in df.columns]
1054
+ if len(sort_cols) > 0:
1055
+ df.sort_values(
1056
+ by=sort_cols,
1057
+ axis=0,
1058
+ ascending=True,
1059
+ inplace=True,
1060
+ ignore_index=True,
1061
+ )
1062
+
1063
+ # Localize, convert and set timezone-(un)aware datetimes
1064
+ cols_date = ["DeliveryStart", "DeliveryEnd", "InsertionTimestamp"]
1065
+ for c in cols_date:
1066
+ if c in df.columns:
1067
+ # Convert from datetime to pandas timestamp
1068
+ df[c] = pd.DatetimeIndex(df[c])
1069
+
1070
+ # Convert to timezone-aware dates
1071
+ if c == "InsertionTimestamp":
1072
+ df[c] = df[c].dt.tz_localize(local_timezone)
1073
+ else:
1074
+ df[c] = df[c].dt.tz_localize("UTC")
1075
+ df[c] = df[c].dt.tz_convert(local_timezone)
1076
+
1077
+ if not timezone_aware_dates:
1078
+ df[c] = df[c].dt.tz_localize(None)
1079
+
1080
+ return df
1081
+
1082
+ def load_clients(self, columns: list = None, conditions: str = None) -> pd.DataFrame:
1083
+ """
1084
+ General method to load data from the database's client table.
1085
+
1086
+ Args:
1087
+ columns: Requested database table columns. Set columns=["*"] (i.e. as list) to get
1088
+ all columns.
1089
+ conditions: Optional conditions to add to SQL query. E.g. "WHERE Type='Supplier'".
1090
+
1091
+ Returns:
1092
+ Table data
1093
+ """
1094
+ # Set default column values
1095
+ if columns is None:
1096
+ columns = ["*"]
1097
+
1098
+ # Convert columns from list to string
1099
+ if len(columns) == 1:
1100
+ columns_str = str(columns[0])
1101
+ else:
1102
+ columns_str = f"[{'], ['.join(columns)}]"
1103
+
1104
+ # Create query
1105
+ table = DetDatabaseDefinitions.DEFINITIONS["table_name_client"]
1106
+ query = f"SELECT {columns_str} FROM {table} {conditions}"
1107
+
1108
+ # Query db
1109
+ self.open_connection()
1110
+ df = self.query_db(query)
1111
+ self.close_connection()
1112
+
1113
+ return df
1114
+
1115
+ def get_client_info(self, filter_column: str, filter_value: str, info_columns: list) -> dict:
1116
+ """
1117
+ Finds information related to a specific, user-defined client.
1118
+
1119
+ Args:
1120
+ filter_column: Column used to filter data for one specific client
1121
+ filter_value: Value used to filter data for one specific client
1122
+ info_columns: Columns containing the requested information
1123
+
1124
+ Returns:
1125
+ A dictionary containing the requested information
1126
+
1127
+ Raises:
1128
+ ValueError: Raises an error if match with input filter value is not unique
1129
+ ValueError: Raises an error if the input filter value is not found
1130
+ """
1131
+ # Get client information for user-defined filtering criteria
1132
+ condition = f"WHERE {filter_column}='{filter_value}'"
1133
+ client_info = self.load_clients(columns=info_columns, conditions=condition)
1134
+
1135
+ # Validate response
1136
+ if client_info.shape[0] > 1:
1137
+ raise ValueError(f"More than one match found for {filter_column}={filter_value}.")
1138
+
1139
+ elif client_info.shape[0] == 0:
1140
+ available_values = self.load_clients(columns=[filter_column])
1141
+ available_values_str = ", ".join(f"'{x}'" for x in available_values[filter_column])
1142
+ raise ValueError(
1143
+ f"Value {filter_value} not found in column '{filter_column}'. Available values: "
1144
+ f"{available_values_str}."
1145
+ )
1146
+
1147
+ # Convert dataframe row to dict
1148
+ client_info = client_info.loc[0, :].to_dict()
1149
+
1150
+ return client_info
1151
+
865
1152
 
866
1153
  class DetDatabaseDefinitions:
867
1154
  """A class containing some hard-coded definitions related to the DET database."""
@@ -874,4 +1161,7 @@ class DetDatabaseDefinitions:
874
1161
  table_name_account_position="[TT].[AccountPosition]",
875
1162
  table_name_instruments="[TT].[Instrument]",
876
1163
  table_name_eex_eod_price="[EEX].[EODPrice]",
1164
+ table_name_forecast_customer_volume="[DISP].[ForecastGold]",
1165
+ table_name_customer_day_ahead_auction_bids="[TRADE].[ClientBid]",
1166
+ table_name_client="[META].[Client]",
877
1167
  )
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "detquantlib"
3
- version = "3.5.1"
3
+ version = "3.7.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"
File without changes
File without changes