trade-database-manager 0.0.1.dev4__tar.gz → 0.0.3.dev1__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 (27) hide show
  1. {trade_database_manager-0.0.1.dev4/trade_database_manager.egg-info → trade_database_manager-0.0.3.dev1}/PKG-INFO +1 -1
  2. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/pyproject.toml +1 -1
  3. trade_database_manager-0.0.3.dev1/trade_database_manager/core/kdb/__init__.py +5 -0
  4. trade_database_manager-0.0.3.dev1/trade_database_manager/core/kdb/kdbmanager.py +150 -0
  5. trade_database_manager-0.0.3.dev1/trade_database_manager/core/sql/__init__.py +5 -0
  6. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/core/sql/sqlmanager.py +49 -11
  7. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/manager/metadata_sql.py +6 -8
  8. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1/trade_database_manager.egg-info}/PKG-INFO +1 -1
  9. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager.egg-info/SOURCES.txt +1 -1
  10. trade_database_manager-0.0.1.dev4/setup.py +0 -3
  11. trade_database_manager-0.0.1.dev4/trade_database_manager/core/kdb/__init__.py +0 -0
  12. trade_database_manager-0.0.1.dev4/trade_database_manager/core/sql/__init__.py +0 -0
  13. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/LICENSE +0 -0
  14. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/README.md +0 -0
  15. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/setup.cfg +0 -0
  16. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/__init__.py +0 -0
  17. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/config.py +0 -0
  18. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/core/__init__.py +0 -0
  19. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/core/sql/sqlreader.py +0 -0
  20. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/core/sql/sqlwriter.py +0 -0
  21. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/core/typedefs.py +0 -0
  22. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/manager/__init__.py +0 -0
  23. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/manager/fields_data_type.py +0 -0
  24. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager/manager/typedefs.py +0 -0
  25. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager.egg-info/dependency_links.txt +0 -0
  26. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager.egg-info/requires.txt +0 -0
  27. {trade_database_manager-0.0.1.dev4 → trade_database_manager-0.0.3.dev1}/trade_database_manager.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trade_database_manager
3
- Version: 0.0.1.dev4
3
+ Version: 0.0.3.dev1
4
4
  Summary: A wrapper of kdb and sql for convenient trade data management.
5
5
  Author-email: "Y.Q. Cui" <qianyun210603@hotmail.com>
6
6
  Classifier: Operating System :: POSIX :: Linux
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trade_database_manager"
7
- version = "0.0.1.dev4"
7
+ version = "0.0.3.dev1"
8
8
  description = "A wrapper of kdb and sql for convenient trade data management."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/4/15 20:28
3
+ # @Author : YQ Tsui
4
+ # @File : __init__.py
5
+ # @Purpose :
@@ -0,0 +1,150 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/4/27 12:26
3
+ # @Author : YQ Tsui
4
+ # @File : kdbmanager.py
5
+ # @Purpose :
6
+ import os.path
7
+ import pandas as pd
8
+ import pykx
9
+ from ...config import CONFIG
10
+
11
+
12
+ class KdbManager:
13
+
14
+ _instance = None
15
+
16
+ @classmethod
17
+ def instance(cls):
18
+ if cls._instance is None:
19
+ cls._instance = cls()
20
+ cls._instance.__init__()
21
+ return cls._instance
22
+
23
+ def __init__(self):
24
+ self.host = CONFIG["kdbhost"]
25
+ self.port = CONFIG["kdbport"]
26
+ # self.username = CONFIG["username"]
27
+ # self.password = CONFIG["password"]
28
+ self.username = ""
29
+ self.password = ""
30
+ # print(self.host, self.port, self.username, self.password)
31
+
32
+ def path_exists(self, path: str):
33
+ """
34
+ Checks if a path exists in the kdb database.
35
+
36
+ :param path: The path to check.
37
+ :type path: str
38
+ """
39
+ with pykx.QConnection(self.host, self.port, username=self.username, password=self.password) as conn:
40
+ return bool(conn(f".path.exists `:{path}"))
41
+
42
+ def create_folder(self, path: str):
43
+ """
44
+ Creates a folder in the kdb database.
45
+
46
+ :param path: The path of the folder.
47
+ :type path: str
48
+ """
49
+ with pykx.QConnection(self.host, self.port, username=self.username, password=self.password) as conn:
50
+ conn(f".path.mkdir \"{path}\"")
51
+
52
+ def write(self, table_name: str, data: pd.DataFrame, path: str = "", splayed: bool = False):
53
+ """
54
+ Writes data to the kdb database.
55
+
56
+ :param table_name: The table name in the kdb database.
57
+ :type table_name: str
58
+ :param data: The data to be written.
59
+ :type data: pd.DataFrame
60
+ :param path: The path of data
61
+ :type path: str
62
+ :param splayed: Whether the table is splayed.
63
+ :type splayed: bool
64
+ """
65
+
66
+ real_path = os.path.join(path, table_name) if path else table_name
67
+ if splayed:
68
+ real_path += "/"
69
+ with pykx.QConnection(self.host, self.port, username=self.username, password=self.password) as conn:
70
+ conn(f"{{`:{real_path}/ set x}}", data)
71
+
72
+ def write_partitioned(self, table_name: str, data: pd.DataFrame, path: str = "", partition_func=None, key_column=None):
73
+ """
74
+ Writes data to the kdb database with partitioning.
75
+
76
+ :param table_name: The table name in the kdb database.
77
+ :type table_name: str
78
+ :param data: The data to be written.
79
+ :type data: pd.DataFrame
80
+ :param path: The path of data
81
+ :type path: str
82
+ :param partition_func: calculate the partition bucket from datetime column
83
+ :type partition_func: callable
84
+ :param key_column: Column which is sorted to server as key.
85
+ :type key_column: str
86
+ """
87
+ if isinstance(data.index, pd.DatetimeIndex):
88
+ data.reset_index(inplace=True, names=["datetime"])
89
+ assert "datetime" in data.columns, "datetime column not found"
90
+ data.sort_values(by="datetime", inplace=True)
91
+ if key_column is not None and key_column in data.columns:
92
+ with pykx.QConnection(host=self.host, port=self.port, username=self.username, password=self.password) as conn:
93
+ for bucket, df in data.groupby(partition_func(data["datetime"])):
94
+ conn(f'{{`{table_name} set x; .partable.createOrAppend[`:{path};{bucket};`{key_column};`{table_name}]}}', df.reset_index(drop=True))
95
+ else:
96
+ with pykx.QConnection(host=self.host, port=self.port, username=self.username, password=self.password) as conn:
97
+ for bucket, df in data.groupby(by=partition_func(data["datetime"])):
98
+ conn(f'{{`{table_name} set x;.Q.dpt[`:{path};{bucket};`{table_name}]}}', df.reset_index(drop=True))
99
+
100
+ def read_partitioned(self, table_name: str, path: str = "", fields=None, start_time=None, end_time=None, partition_func=None,
101
+ other_conditions=None):
102
+ """
103
+ Reads data from the kdb database with partitioning.
104
+
105
+ :param table_name: The table name in the kdb database.
106
+ :type table_name: str
107
+ :param path: The path of data
108
+ :type path: str
109
+ :param start_time: The start time of the data.
110
+ :type start_time: pd.Timestamp
111
+ :param end_time: The end time of the data.
112
+ :type end_time: pd.Timestamp
113
+ :param partition_func: calculate the partition bucket from datetime column
114
+ :type partition_func: callable
115
+ :param other_conditions: Other conditions for the query.
116
+ :type other_conditions: str
117
+ """
118
+ time_format = "%Y.%m.%dD%H:%M:%S.%f"
119
+ start_time_str = start_time.strftime(time_format) if start_time is not None else None
120
+ end_time_str = end_time.strftime(time_format) if end_time is not None else None
121
+ if isinstance(fields, (bytes, str)):
122
+ fields = [fields]
123
+ select_clause = f"select from {table_name}" if fields is None else f"select {','.join(fields)} from {table_name}"
124
+ where_cond = ""
125
+ if start_time_str is not None and end_time_str is not None:
126
+ where_cond += f"datetime within ({start_time_str};{end_time_str})"
127
+ if partition_func is not None:
128
+ where_cond = f"int in {' '.join(str(x) for x in range(partition_func(start_time), partition_func(end_time) + 1))}" + ',' + where_cond
129
+ elif start_time_str is not None:
130
+ where_cond += f"datetime>={start_time_str}"
131
+ if partition_func is not None:
132
+ where_cond = f"int>={partition_func(start_time)}" + ',' + where_cond
133
+ elif end_time_str is not None:
134
+ where_cond += f"datetime<={end_time_str}"
135
+ if partition_func is not None:
136
+ where_cond = f"int<= {partition_func(end_time)}" + ',' + where_cond
137
+ if other_conditions is not None:
138
+ where_cond = other_conditions if where_cond == "" else where_cond + ',' + other_conditions
139
+ if where_cond:
140
+ where_cond = " where " + where_cond
141
+ final_query = select_clause + where_cond
142
+
143
+ with pykx.QConnection(host=self.host, port=self.port, username=self.username, password=self.password) as conn:
144
+ conn("`currpath__ set .path.pwd[]") # save current path to currpath__ as following command will change the path
145
+ try:
146
+ conn(f"\\l {path}") # load the path
147
+ q_table = conn(final_query)
148
+ return q_table.pd().set_index("datetime")
149
+ finally:
150
+ conn('system "cd ", currpath__')
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/4/15 20:28
3
+ # @Author : YQ Tsui
4
+ # @File : __init__.py
5
+ # @Purpose :
@@ -1,10 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/4/15 20:28
3
+ # @Author : YQ Tsui
4
+ # @File : sqlmanager.py
5
+ # @Purpose : Core SQL operations
6
+
1
7
  import re
2
8
  from collections.abc import Container
3
9
  from functools import partial, reduce
4
10
  from typing import Any, Literal, Sequence, Type, Union
5
11
 
6
12
  import pandas as pd
7
- from sqlalchemy import REAL, Index, Integer, MetaData, String, Table, create_engine, inspect, select, sql, text
13
+ from sqlalchemy import DOUBLE_PRECISION, Index, Integer, MetaData, String, Table, create_engine, inspect, select, sql, text
8
14
  from sqlalchemy.dialects.postgresql import insert
9
15
 
10
16
  from ...config import CONFIG
@@ -107,7 +113,8 @@ class SqlManager:
107
113
  self.add_index(table_name, column, unique=False)
108
114
  return num_rows
109
115
 
110
- def _convert_to_sqlalchemy_type(self, column_type: Type, **kwargs):
116
+ @staticmethod
117
+ def _convert_to_sqlalchemy_type(column_type: Type, **kwargs):
111
118
  if isinstance(column_type, type):
112
119
  column_type = column_type.__name__
113
120
  if column_type == "str":
@@ -115,10 +122,10 @@ class SqlManager:
115
122
  if column_type == "int":
116
123
  return Integer()
117
124
  if column_type == "float":
118
- return REAL()
125
+ return DOUBLE_PRECISION()
119
126
  raise ValueError(f"Unsupported column type {column_type}")
120
127
 
121
- def insert_column(self, table_name: str, column_name: str, column_type: Union[str, Type], type_kwargs: dict = None):
128
+ def insert_column(self, table_name: str, column_name: str, column_type: str):
122
129
  """
123
130
  Inserts a new column into a table.
124
131
 
@@ -126,13 +133,10 @@ class SqlManager:
126
133
  :type table_name: str
127
134
  :param column_name: The name of the new column.
128
135
  :type column_name: str
129
- :param column_type: The data type of the new column. It can be a string or a Python type.
130
- :type column_type: Union[str, Type]
131
- :param type_kwargs: Additional keyword arguments for the data type. Defaults to None.
132
- :type type_kwargs: dict, optional
136
+ :param column_type: The data type of the new column. It is a string representation of the data type in PostgreSQL.
137
+ :type column_type: str
133
138
  """
134
- type_kwargs = type_kwargs or {}
135
- sql_code = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {str(self._convert_to_sqlalchemy_type(column_type, **type_kwargs))}"
139
+ sql_code = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}"
136
140
  self._execute(sql_code)
137
141
 
138
142
  def delete_column(self, table_name: str, column_name: str):
@@ -250,7 +254,6 @@ class SqlManager:
250
254
 
251
255
  # cannot use pandas.read_sql here as it discards timezone info
252
256
  res = self._execute(stmt)
253
- pd.read_sql_query()
254
257
  return pd.DataFrame(res.fetchall(), columns=res.keys())
255
258
 
256
259
  def read_data_across_tables(
@@ -310,3 +313,38 @@ class SqlManager:
310
313
  # cannot use pandas.read_sql here as it discards timezone info
311
314
  res = self._execute(stmt)
312
315
  return pd.DataFrame(res.fetchall(), columns=res.keys())
316
+
317
+ def create_ts_table(self, table_name: str, time_column: str, table_columns: dict[str, str],
318
+ time_chunk_interval: str = "1 year", other_chunkers: dict[str, int] = None):
319
+ """
320
+ Creates a table with a timestamp column.
321
+
322
+ :param table_name: The name of the table to create.
323
+ :type table_name: str
324
+ :param time_column: The name of the timestamp column.
325
+ :type time_column: str
326
+ :param table_columns: The names of the columns to create with their data types. The keys are the column names and the values are the data types in PostgreSQL.
327
+ :type table_columns: Dict[str, str]
328
+ :param time_chunk_interval: The interval to chunk the time column by. Defaults to "1 year".
329
+ :type time_chunk_interval: str
330
+ :param other_chunkers: Other columns to chunk by. The keys are the column names and the values are the num of hashes. Defaults to None.
331
+ :type other_chunkers: Dict[str, int], optional
332
+ """
333
+ query_create_sensordata_table = f"""
334
+ CREATE TABLE {table_name} (
335
+ {time_column} TIMESTAMP NOT NULL,
336
+ {", ".join([f"{column_name} {column_type}{' NOT NULL' if column_name in other_chunkers else ''}" for column_name, column_type in table_columns.items()])}
337
+ );
338
+ """
339
+ query_create_sensordata_hypertable = f"SELECT create_hypertable('{table_name}', by_range('{time_column}', INTERVAL '{time_chunk_interval}'));"
340
+ if other_chunkers is not None:
341
+ for column_name, num_hash in other_chunkers.items():
342
+ query_create_sensordata_hypertable += f"\nSELECT add_dimension('{table_name}', by_hash('{column_name}', {num_hash}));"
343
+
344
+ uidx_name = f"uix_{table_name}_{'_'.join(other_chunkers.keys())}_{time_column}"
345
+ query_unique_index = f"CREATE UNIQUE INDEX {uidx_name} ON {table_name} ({', '.join(other_chunkers.keys())}, {time_column});"
346
+ with self.engine.connect() as conn:
347
+ conn.execute(text(query_create_sensordata_table))
348
+ conn.execute(text(query_create_sensordata_hypertable))
349
+ conn.execute(text(query_unique_index))
350
+ conn.commit()
@@ -133,7 +133,7 @@ class MetadataSql:
133
133
  for inst_type, common_df_by_type in common_df.groupby("inst_type"):
134
134
  inst_type = cast(INST_TYPE_LITERALS, inst_type)
135
135
  if all_fields_common:
136
- res[inst_type] = common_df_by_type
136
+ res[inst_type] = common_df_by_type.set_index(["ticker", "exchange"]) if len(common_df_by_type.columns) > 2 else common_df_by_type
137
137
  continue
138
138
  query_fields_type = (
139
139
  ["ticker", "exchange"] + [f for f in query_fields if f in TYPE_METADATA_COLUMNS[inst_type]]
@@ -147,7 +147,7 @@ class MetadataSql:
147
147
  f"instruments_{inst_type.lower()}", query_fields=query_fields_type, filter_fields=filter_fields_type
148
148
  )
149
149
  type_df = common_df_by_type.merge(type_df, on=["ticker", "exchange"], how="inner")
150
- res[inst_type] = type_df.set_index(["ticker", "exchange"])
150
+ res[inst_type] = type_df.set_index(["ticker", "exchange"]) if len(type_df.columns) > 2 else type_df
151
151
  return res
152
152
 
153
153
  def read_metadata_for_insttype(
@@ -187,9 +187,7 @@ class MetadataSql:
187
187
  query_fields_cross = "*"
188
188
  else:
189
189
  query_fields_common = ["ticker", "exchange"] + [f for f in query_fields if f in COMMON_METADATA_COLUMNS]
190
- query_fields_type = ["ticker", "exchange"] + [
191
- f for f in query_fields if f in TYPE_METADATA_COLUMNS[inst_type]
192
- ]
190
+ query_fields_type = [f for f in query_fields if f in TYPE_METADATA_COLUMNS[inst_type]]
193
191
  query_fields_cross = {
194
192
  "instruments": query_fields_common,
195
193
  f"instruments_{inst_type.lower()}": query_fields_type,
@@ -213,8 +211,8 @@ class MetadataSql:
213
211
  )
214
212
 
215
213
  if isinstance(df.columns, pd.Index):
216
- df = df.loc[:, ~df.columns.duplicated()].set_index(["ticker", "exchange"])
217
- else:
218
- df = df.set_index(["ticker", "exchange"])
214
+ df = df.loc[:, ~df.columns.duplicated()]
215
+ if len(df.columns) > 2:
216
+ df.set_index(["ticker", "exchange"], inplace=True)
219
217
  self._convert_datetime_columns(df)
220
218
  return df
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trade_database_manager
3
- Version: 0.0.1.dev4
3
+ Version: 0.0.3.dev1
4
4
  Summary: A wrapper of kdb and sql for convenient trade data management.
5
5
  Author-email: "Y.Q. Cui" <qianyun210603@hotmail.com>
6
6
  Classifier: Operating System :: POSIX :: Linux
@@ -1,7 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- setup.py
5
4
  trade_database_manager/__init__.py
6
5
  trade_database_manager/config.py
7
6
  trade_database_manager.egg-info/PKG-INFO
@@ -12,6 +11,7 @@ trade_database_manager.egg-info/top_level.txt
12
11
  trade_database_manager/core/__init__.py
13
12
  trade_database_manager/core/typedefs.py
14
13
  trade_database_manager/core/kdb/__init__.py
14
+ trade_database_manager/core/kdb/kdbmanager.py
15
15
  trade_database_manager/core/sql/__init__.py
16
16
  trade_database_manager/core/sql/sqlmanager.py
17
17
  trade_database_manager/core/sql/sqlreader.py
@@ -1,3 +0,0 @@
1
- from setuptools import setup
2
-
3
- setup()