trade-database-manager 0.0.3.dev1__tar.gz → 0.0.6__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.3.dev1/trade_database_manager.egg-info → trade_database_manager-0.0.6}/PKG-INFO +23 -2
  2. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/README.md +20 -0
  3. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/pyproject.toml +4 -7
  4. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/kdb/__init__.py +1 -1
  5. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/kdb/kdbmanager.py +43 -17
  6. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/sql/__init__.py +2 -1
  7. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/sql/sqlmanager.py +164 -54
  8. trade_database_manager-0.0.6/trade_database_manager/core/sql/utils.py +35 -0
  9. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/manager/__init__.py +2 -1
  10. trade_database_manager-0.0.6/trade_database_manager/manager/fields_data_type.py +55 -0
  11. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/manager/metadata_sql.py +77 -23
  12. trade_database_manager-0.0.6/trade_database_manager/manager/metadata_sql_cb.py +108 -0
  13. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/manager/typedefs.py +1 -1
  14. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6/trade_database_manager.egg-info}/PKG-INFO +23 -2
  15. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager.egg-info/SOURCES.txt +2 -0
  16. trade_database_manager-0.0.3.dev1/trade_database_manager/manager/fields_data_type.py +0 -23
  17. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/LICENSE +0 -0
  18. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/setup.cfg +0 -0
  19. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/__init__.py +0 -0
  20. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/config.py +0 -0
  21. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/__init__.py +0 -0
  22. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/sql/sqlreader.py +0 -0
  23. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/sql/sqlwriter.py +0 -0
  24. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager/core/typedefs.py +0 -0
  25. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager.egg-info/dependency_links.txt +0 -0
  26. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager.egg-info/requires.txt +0 -0
  27. {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6}/trade_database_manager.egg-info/top_level.txt +0 -0
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trade_database_manager
3
- Version: 0.0.3.dev1
3
+ Version: 0.0.6
4
4
  Summary: A wrapper of kdb and sql for convenient trade data management.
5
- Author-email: "Y.Q. Cui" <qianyun210603@hotmail.com>
5
+ Author-email: "Y.Q. Tsui" <qianyun210603@hotmail.com>
6
6
  Classifier: Operating System :: POSIX :: Linux
7
7
  Classifier: Operating System :: Microsoft :: Windows
8
8
  Classifier: Development Status :: 3 - Alpha
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
15
16
  Description-Content-Type: text/markdown
16
17
  License-File: LICENSE
17
18
  Requires-Dist: pandas>=2.0.0
@@ -75,3 +76,23 @@ then the system will send an email with the license file and a base64 key (Eithe
75
76
  - `-T 1000`: This sets the timeout in seconds for client queries. In this case, it's set to 1000 seconds.
76
77
 
77
78
  - `-U /opt/l64/trade.q`: This sets the access control list file. In this case, the file is located at `/opt/l64/trade.q`. This file contains a list of usernames and passwords for clients that are allowed to connect to the kdb+ process.
79
+
80
+ ### MetaData Initialization
81
+
82
+ Allowed instruments types are given in "Instrument Types" section of [meta_enumerations.md](doc/meta_enumerations.md).
83
+
84
+ ### Data Initialization for Instrument Type(s)
85
+ ```python
86
+ from trade_database_manager.manager import MetadataSql
87
+
88
+ metadatalib = MetadataSql()
89
+ metadatalib.initialize(for_inst_types="CB")
90
+ ```
91
+
92
+ This will try to create two tables, `instruments` and `instruments_cb` in the database if not yet exists. The `instruments` table will store the common information of all instruments, and the `instruments_cb` table will store the type-specific information of the instruments of type `CB`.
93
+
94
+ The table fields are listed in the [data_organization.md](doc/data_organization.md) file.
95
+
96
+
97
+
98
+
@@ -53,3 +53,23 @@ then the system will send an email with the license file and a base64 key (Eithe
53
53
  - `-T 1000`: This sets the timeout in seconds for client queries. In this case, it's set to 1000 seconds.
54
54
 
55
55
  - `-U /opt/l64/trade.q`: This sets the access control list file. In this case, the file is located at `/opt/l64/trade.q`. This file contains a list of usernames and passwords for clients that are allowed to connect to the kdb+ process.
56
+
57
+ ### MetaData Initialization
58
+
59
+ Allowed instruments types are given in "Instrument Types" section of [meta_enumerations.md](doc/meta_enumerations.md).
60
+
61
+ ### Data Initialization for Instrument Type(s)
62
+ ```python
63
+ from trade_database_manager.manager import MetadataSql
64
+
65
+ metadatalib = MetadataSql()
66
+ metadatalib.initialize(for_inst_types="CB")
67
+ ```
68
+
69
+ This will try to create two tables, `instruments` and `instruments_cb` in the database if not yet exists. The `instruments` table will store the common information of all instruments, and the `instruments_cb` table will store the type-specific information of the instruments of type `CB`.
70
+
71
+ The table fields are listed in the [data_organization.md](doc/data_organization.md) file.
72
+
73
+
74
+
75
+
@@ -4,11 +4,11 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "trade_database_manager"
7
- version = "0.0.3.dev1"
7
+ version = "0.0.6"
8
8
  description = "A wrapper of kdb and sql for convenient trade data management."
9
9
  readme = "README.md"
10
10
  authors = [
11
- {name = "Y.Q. Cui", email = "qianyun210603@hotmail.com"},
11
+ {name = "Y.Q. Tsui", email = "qianyun210603@hotmail.com"},
12
12
  ]
13
13
  dependencies = ["pandas>=2.0.0", "numpy", "ruamel.yaml", "sqlalchemy", "psycopg2"]
14
14
  classifiers = [
@@ -21,6 +21,7 @@ classifiers = [
21
21
  "Programming Language :: Python :: 3.9",
22
22
  "Programming Language :: Python :: 3.10",
23
23
  "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
24
25
  ]
25
26
 
26
27
  [tool.setuptools.packages.find]
@@ -30,7 +31,7 @@ include = ["trade_database_manager*"]
30
31
  const-rgx='[a-z_][a-z0-9_]{2,30}$'
31
32
 
32
33
  [tool.pylint.main]
33
- disable="C0104,C0114,C0115,C0116,C0301,C0302,C0411,C0413,C1802,R0401,R0801,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1720,W0105,W0123,W0201,W0511,W0613,W1113,W1514,E0401,E1121,C0103,C0209,R0402,R1705,R1710,R1725,R1735,W0102,W0212,W0221,W0223,W0231,W0237,W0612,W0621,W0622,W0703,W1309,E1102,E1136"
34
+ disable="C0104,C0114,C0115,C0116,C0301,C0302,C0411,C0413,C1802,R0401,R0801,R0902,R0903,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1720,W0105,W0123,W0201,W0511,W0613,W1113,W1514,E0401,E1121,C0103,C0209,R0402,R0917,R1705,R1710,R1725,R1735,W0102,W0212,W0221,W0223,W0231,W0237,W0612,W0621,W0622,W0703,W1309,E1102,E1136"
34
35
  ignore-paths="\\.ipynb_checkpoints/*"
35
36
 
36
37
  [tool.pylint.format]
@@ -43,7 +44,3 @@ target-version = ['py310']
43
44
  [tool.isort]
44
45
  profile = "black"
45
46
  line_length = 120
46
-
47
- [tool.flake8]
48
- max-line-length = 120
49
- ignore = "E203,E501,W503"
@@ -2,4 +2,4 @@
2
2
  # @Time : 2024/4/15 20:28
3
3
  # @Author : YQ Tsui
4
4
  # @File : __init__.py
5
- # @Purpose :
5
+ # @Purpose :
@@ -4,8 +4,10 @@
4
4
  # @File : kdbmanager.py
5
5
  # @Purpose :
6
6
  import os.path
7
+
7
8
  import pandas as pd
8
9
  import pykx
10
+
9
11
  from ...config import CONFIG
10
12
 
11
13
 
@@ -17,7 +19,6 @@ class KdbManager:
17
19
  def instance(cls):
18
20
  if cls._instance is None:
19
21
  cls._instance = cls()
20
- cls._instance.__init__()
21
22
  return cls._instance
22
23
 
23
24
  def __init__(self):
@@ -47,7 +48,7 @@ class KdbManager:
47
48
  :type path: str
48
49
  """
49
50
  with pykx.QConnection(self.host, self.port, username=self.username, password=self.password) as conn:
50
- conn(f".path.mkdir \"{path}\"")
51
+ conn(f'.path.mkdir "{path}"')
51
52
 
52
53
  def write(self, table_name: str, data: pd.DataFrame, path: str = "", splayed: bool = False):
53
54
  """
@@ -69,7 +70,9 @@ class KdbManager:
69
70
  with pykx.QConnection(self.host, self.port, username=self.username, password=self.password) as conn:
70
71
  conn(f"{{`:{real_path}/ set x}}", data)
71
72
 
72
- def write_partitioned(self, table_name: str, data: pd.DataFrame, path: str = "", partition_func=None, key_column=None):
73
+ def write_partitioned(
74
+ self, table_name: str, data: pd.DataFrame, path: str = "", partition_func=None, key_column=None
75
+ ):
73
76
  """
74
77
  Writes data to the kdb database with partitioning.
75
78
 
@@ -89,16 +92,31 @@ class KdbManager:
89
92
  assert "datetime" in data.columns, "datetime column not found"
90
93
  data.sort_values(by="datetime", inplace=True)
91
94
  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:
95
+ with pykx.QConnection(
96
+ host=self.host, port=self.port, username=self.username, password=self.password
97
+ ) as conn:
93
98
  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))
99
+ conn(
100
+ f"{{`{table_name} set x; .partable.createOrAppend[`:{path};{bucket};`{key_column};`{table_name}]}}",
101
+ df.reset_index(drop=True),
102
+ )
95
103
  else:
96
- with pykx.QConnection(host=self.host, port=self.port, username=self.username, password=self.password) as conn:
104
+ with pykx.QConnection(
105
+ host=self.host, port=self.port, username=self.username, password=self.password
106
+ ) as conn:
97
107
  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):
108
+ conn(f"{{`{table_name} set x;.Q.dpt[`:{path};{bucket};`{table_name}]}}", df.reset_index(drop=True))
109
+
110
+ def read_partitioned(
111
+ self,
112
+ table_name: str,
113
+ path: str = "",
114
+ fields=None,
115
+ start_time=None,
116
+ end_time=None,
117
+ partition_func=None,
118
+ other_conditions=None,
119
+ ):
102
120
  """
103
121
  Reads data from the kdb database with partitioning.
104
122
 
@@ -120,31 +138,39 @@ class KdbManager:
120
138
  end_time_str = end_time.strftime(time_format) if end_time is not None else None
121
139
  if isinstance(fields, (bytes, str)):
122
140
  fields = [fields]
123
- select_clause = f"select from {table_name}" if fields is None else f"select {','.join(fields)} from {table_name}"
141
+ select_clause = (
142
+ f"select from {table_name}" if fields is None else f"select {','.join(fields)} from {table_name}"
143
+ )
124
144
  where_cond = ""
125
145
  if start_time_str is not None and end_time_str is not None:
126
146
  where_cond += f"datetime within ({start_time_str};{end_time_str})"
127
147
  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
148
+ where_cond = (
149
+ f"int in {' '.join(str(x) for x in range(partition_func(start_time), partition_func(end_time) + 1))}"
150
+ + ","
151
+ + where_cond
152
+ )
129
153
  elif start_time_str is not None:
130
154
  where_cond += f"datetime>={start_time_str}"
131
155
  if partition_func is not None:
132
- where_cond = f"int>={partition_func(start_time)}" + ',' + where_cond
156
+ where_cond = f"int>={partition_func(start_time)}" + "," + where_cond
133
157
  elif end_time_str is not None:
134
158
  where_cond += f"datetime<={end_time_str}"
135
159
  if partition_func is not None:
136
- where_cond = f"int<= {partition_func(end_time)}" + ',' + where_cond
160
+ where_cond = f"int<= {partition_func(end_time)}" + "," + where_cond
137
161
  if other_conditions is not None:
138
- where_cond = other_conditions if where_cond == "" else where_cond + ',' + other_conditions
162
+ where_cond = other_conditions if where_cond == "" else where_cond + "," + other_conditions
139
163
  if where_cond:
140
164
  where_cond = " where " + where_cond
141
165
  final_query = select_clause + where_cond
142
166
 
143
167
  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
168
+ conn(
169
+ "`currpath__ set .path.pwd[]"
170
+ ) # save current path to currpath__ as following command will change the path
145
171
  try:
146
172
  conn(f"\\l {path}") # load the path
147
173
  q_table = conn(final_query)
148
174
  return q_table.pd().set_index("datetime")
149
175
  finally:
150
- conn('system "cd ", currpath__')
176
+ conn('system "cd ", currpath__')
@@ -2,4 +2,5 @@
2
2
  # @Time : 2024/4/15 20:28
3
3
  # @Author : YQ Tsui
4
4
  # @File : __init__.py
5
- # @Purpose :
5
+ # @Purpose :
6
+ from .sqlmanager import SqlManager
@@ -7,14 +7,30 @@
7
7
  import re
8
8
  from collections.abc import Container
9
9
  from functools import partial, reduce
10
- from typing import Any, Literal, Sequence, Type, Union
10
+ from typing import Any, Callable, Literal, Sequence, Union
11
11
 
12
12
  import pandas as pd
13
- from sqlalchemy import DOUBLE_PRECISION, Index, Integer, MetaData, String, Table, create_engine, inspect, select, sql, text
13
+ from sqlalchemy import (
14
+ Column,
15
+ Executable,
16
+ Index,
17
+ MetaData,
18
+ PrimaryKeyConstraint,
19
+ Table,
20
+ and_,
21
+ create_engine,
22
+ func,
23
+ inspect,
24
+ select,
25
+ text,
26
+ )
14
27
  from sqlalchemy.dialects.postgresql import insert
28
+ from sqlalchemy.orm import aliased, sessionmaker
29
+ from sqlalchemy.types import TypeEngine
15
30
 
16
31
  from ...config import CONFIG
17
32
  from ..typedefs import FILTERFIELD_TYPE, QUERYFIELD_TYPE
33
+ from .utils import infer_sql_type
18
34
 
19
35
 
20
36
  def _insert_on_conflict_update(table, conn, keys, data_iter, indexes):
@@ -37,12 +53,22 @@ class SqlManager:
37
53
  This class is used to manage SQL operations.
38
54
 
39
55
  :ivar sqlalchemy.engine.Engine engine: An instance of the SQLAlchemy Engine class for executing SQL operations.
56
+ :ivar sqlalchemy.orm.Session _session: An instance of the SQLAlchemy Session class for executing SQL operations.
57
+ :ivar sqlalchemy.engine.reflection.Inspector _inspector: An instance of the SQLAlchemy Inspector class for inspecting the database.
40
58
  """
41
59
 
42
60
  def __init__(self):
43
61
  self.engine = create_engine(CONFIG["sqlconnstr"])
62
+ self._session = sessionmaker(bind=self.engine)() # TODO: is it better to be a class attribute?
63
+ self._inspector = None
44
64
 
45
- def _execute(self, sql_executable: Union[str, sql.base.Executable]) -> Any:
65
+ @property
66
+ def inspector(self):
67
+ if self._inspector is None:
68
+ self._inspector = inspect(self.engine)
69
+ return self._inspector
70
+
71
+ def _execute(self, sql_executable: Union[str, Executable]) -> Any:
46
72
  if isinstance(sql_executable, str):
47
73
  sql_executable = text(sql_executable)
48
74
  with self.engine.begin() as conn:
@@ -94,8 +120,7 @@ class SqlManager:
94
120
  :rtype: int
95
121
  """
96
122
  if_exists: Literal["replace", "append"] = "append"
97
- inspector = inspect(self.engine)
98
- new_table = not inspector.has_table(table_name)
123
+ new_table = not self.inspector.has_table(table_name)
99
124
  method = partial(_insert_on_conflict_update, indexes=df.index.names) if upsert and not new_table else None
100
125
  num_rows = df.to_sql(
101
126
  table_name,
@@ -113,17 +138,48 @@ class SqlManager:
113
138
  self.add_index(table_name, column, unique=False)
114
139
  return num_rows
115
140
 
116
- @staticmethod
117
- def _convert_to_sqlalchemy_type(column_type: Type, **kwargs):
118
- if isinstance(column_type, type):
119
- column_type = column_type.__name__
120
- if column_type == "str":
121
- return String(**kwargs)
122
- if column_type == "int":
123
- return Integer()
124
- if column_type == "float":
125
- return DOUBLE_PRECISION()
126
- raise ValueError(f"Unsupported column type {column_type}")
141
+ def table_exists(self, table_name: str) -> bool:
142
+ """
143
+ Checks if a table exists.
144
+
145
+ :param table_name: The name of the table to check for.
146
+ :type table_name: str
147
+ :return: True if the table exists, False otherwise.
148
+ :rtype: bool
149
+ """
150
+ return self.inspector.has_table(table_name)
151
+
152
+ def create_table(
153
+ self,
154
+ table_name: str,
155
+ table_columns: list[tuple[str, TypeEngine | type | tuple[type, tuple]]],
156
+ unique_index_columns: Sequence[str] = (),
157
+ primary_key: str | set[str] = set(),
158
+ ):
159
+ """
160
+ Creates a table.
161
+
162
+ :param table_name: The name of the table to create.
163
+ :type table_name: str
164
+ :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.
165
+ :type table_columns: dict[str, Type]
166
+ :param unique_index_columns: Columns to enforce unique values on. Defaults to an empty sequence.
167
+ :type unique_index_columns: Sequence[str]
168
+ :param primary_key: The primary key(s) of the table. Defaults to an empty set.
169
+ :type primary_key: Union[str, set[str|tuple[str]]]
170
+
171
+ """
172
+ table_meta = MetaData()
173
+ columns = [Column(name, infer_sql_type(col_type)) for name, col_type in table_columns]
174
+ if isinstance(primary_key, str) and primary_key != "":
175
+ columns.append(PrimaryKeyConstraint(primary_key))
176
+ elif primary_key:
177
+ columns.append(PrimaryKeyConstraint(*primary_key, name=f"pk_{table_name}"))
178
+
179
+ table = Table(table_name, table_meta, *columns)
180
+ table.create(self.engine)
181
+ for column in unique_index_columns:
182
+ self.add_index(table_name, column, unique=True)
127
183
 
128
184
  def insert_column(self, table_name: str, column_name: str, column_type: str):
129
185
  """
@@ -163,7 +219,7 @@ class SqlManager:
163
219
  """
164
220
  # check if any index is referring to the column
165
221
  sql_code = f"SELECT indexname, indexdef FROM pg_indexes WHERE indexdef LIKE '%%(%%{old_column_name}%%)%%' and tablename = '{table_name}'"
166
- index_refering_column = dict(self.engine.execute(sql_code).fetchall())
222
+ index_refering_column = dict(self._execute(sql_code).fetchall())
167
223
 
168
224
  # drop indexes referring to the column
169
225
  for index_name in index_refering_column:
@@ -213,7 +269,7 @@ class SqlManager:
213
269
  for field, filter_values in filter_fields.items()
214
270
  ]
215
271
  )
216
- stmt = stmt.where(sql.and_(*conditions))
272
+ stmt = stmt.where(and_(*conditions))
217
273
  res = self._execute(stmt)
218
274
  return pd.DataFrame(res.fetchall(), columns=res.keys())
219
275
 
@@ -250,7 +306,7 @@ class SqlManager:
250
306
  )
251
307
  for field, filter_values in filter_fields.items()
252
308
  ]
253
- stmt = stmt.where(sql.and_(*conditions))
309
+ stmt = stmt.where(and_(*conditions))
254
310
 
255
311
  # cannot use pandas.read_sql here as it discards timezone info
256
312
  res = self._execute(stmt)
@@ -281,7 +337,7 @@ class SqlManager:
281
337
  tables = {table_name: Table(table_name, meta, autoload_with=self.engine) for table_name in table_names}
282
338
 
283
339
  joined_table = reduce(
284
- lambda x, y: x.join(y, sql.and_(*[x.columns[col] == y.columns[col] for col in joined_columns])),
340
+ lambda x, y: x.join(y, and_(*[x.columns[col] == y.columns[col] for col in joined_columns])),
285
341
  tables.values(),
286
342
  )
287
343
 
@@ -308,43 +364,97 @@ class SqlManager:
308
364
  else table.columns[field] == filter_values
309
365
  )
310
366
 
311
- stmt = stmt.where(sql.and_(*conditions))
367
+ stmt = stmt.where(and_(*conditions))
312
368
 
313
369
  # cannot use pandas.read_sql here as it discards timezone info
314
370
  res = self._execute(stmt)
315
371
  return pd.DataFrame(res.fetchall(), columns=res.keys())
316
372
 
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.
373
+ def _read_extremum_in_group(
374
+ self,
375
+ table_name: str,
376
+ target_column: str | list[str],
377
+ group_column: str | list[str],
378
+ extremum_column: str,
379
+ method: str | Callable,
380
+ filter_fields=None,
381
+ ):
382
+ if isinstance(target_column, str):
383
+ target_column = [target_column]
384
+ if isinstance(group_column, str):
385
+ group_column = [group_column]
386
+ target_column_c = [Column(col) for col in target_column]
387
+ group_column_c = [Column(col) for col in group_column]
388
+ extremum_column_c = Column(extremum_column)
321
389
 
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()
390
+ table_meta = MetaData()
391
+
392
+ full_table = (
393
+ Table(table_name, table_meta, *target_column_c, *group_column_c)
394
+ if extremum_column in target_column
395
+ else Table(table_name, table_meta, *target_column_c, *group_column_c, extremum_column_c)
396
+ )
397
+
398
+ if isinstance(method, str):
399
+ method = getattr(func, method)
400
+
401
+ if filter_fields:
402
+ conditions = [
403
+ (
404
+ full_table.columns[field].in_(filter_values)
405
+ if isinstance(filter_values, Container) and not isinstance(filter_values, (str, bytes))
406
+ else full_table.columns[field] == filter_values
407
+ )
408
+ for field, filter_values in filter_fields.items()
409
+ ]
410
+ subquery = (
411
+ self._session.query(*group_column_c, method(extremum_column_c).label("ColExtremum"))
412
+ .where(and_(*conditions))
413
+ .group_by(*group_column_c)
414
+ .subquery()
415
+ )
416
+ else:
417
+ subquery = (
418
+ self._session.query(*group_column_c, method(extremum_column_c).label("ColExtremum"))
419
+ .group_by(*group_column_c)
420
+ .subquery()
421
+ )
422
+
423
+ t_full = aliased(full_table)
424
+ t_sub = aliased(subquery)
425
+
426
+ cols_res = [getattr(t_full.c, col) for col in group_column + target_column]
427
+
428
+ query = self._session.query(*cols_res).join(
429
+ t_sub,
430
+ and_(
431
+ getattr(t_full.c, extremum_column) == t_sub.c.ColExtremum,
432
+ *[getattr(t_full.c, col) == getattr(t_sub.c, col) for col in group_column],
433
+ ),
434
+ )
435
+
436
+ return pd.DataFrame(query.all(), columns=group_column + target_column)
437
+
438
+ def read_max_in_group(
439
+ self,
440
+ table_name: str,
441
+ target_column: str | list[str],
442
+ group_column: str | list[str],
443
+ extremum_column: str,
444
+ filter_fields=None,
445
+ ):
446
+ return self._read_extremum_in_group(
447
+ table_name, target_column, group_column, extremum_column, "max", filter_fields
448
+ )
449
+
450
+ def read_min_in_group(
451
+ self,
452
+ table_name: str,
453
+ target_column: str | list[str],
454
+ group_column: str | list[str],
455
+ extremum_column: str,
456
+ filter_fields=None,
457
+ ):
458
+ return self._read_extremum_in_group(
459
+ table_name, target_column, group_column, extremum_column, "min", filter_fields
460
+ )
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/10/31 21:07
3
+ # @Author : YQ Tsui
4
+ # @File : utils.py
5
+ # @Purpose : utility functions for sqlmanager
6
+
7
+ from datetime import date, datetime
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ from sqlalchemy.types import DOUBLE_PRECISION, Date, DateTime, Integer, String, Text, TypeEngine
12
+
13
+
14
+ def infer_sql_type(input_type):
15
+ if isinstance(input_type, TypeEngine):
16
+ return input_type
17
+ if isinstance(input_type, (tuple, list)):
18
+ input_type, *args = input_type
19
+ else:
20
+ args = ()
21
+ if issubclass(input_type, str):
22
+ if args:
23
+ return String(*args)
24
+ return Text()
25
+ if issubclass(input_type, (int, np.signedinteger)):
26
+ return Integer()
27
+ if issubclass(input_type, (np.floating, float)):
28
+ return DOUBLE_PRECISION()
29
+ if issubclass(input_type, (bool, np.bool_)):
30
+ return Integer()
31
+ if issubclass(input_type, (np.datetime64, pd.Timestamp, datetime)):
32
+ return Date() if args and args[0] else DateTime()
33
+ if issubclass(input_type, date):
34
+ return Date()
35
+ raise ValueError(f"Unsupported type {input_type}")
@@ -5,5 +5,6 @@
5
5
  # @Purpose :
6
6
 
7
7
  from .metadata_sql import MetadataSql
8
+ from .metadata_sql_cb import CBMetadataSql
8
9
 
9
- __all__ = ("MetadataSql",)
10
+ __all__ = ("MetadataSql", "CBMetadataSql")
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/4/19 16:44
3
+ # @Author : YQ Tsui
4
+ # @File : fields_data_type.py
5
+ # @Purpose :
6
+
7
+ from sqlalchemy import DOUBLE_PRECISION, Date, Integer, String, Text
8
+
9
+ FIELD_DATA_TYPE_SQL = {
10
+ "ticker": String(20),
11
+ "name": String(20),
12
+ "currency": String(6),
13
+ "exchange": String(10),
14
+ "timezone": String(30),
15
+ "tick_size": DOUBLE_PRECISION(),
16
+ "lot_size": DOUBLE_PRECISION(),
17
+ "min_lots": DOUBLE_PRECISION(),
18
+ "market_tplus": Integer(),
19
+ "listed_date": Date(),
20
+ "delisted_date": Date(),
21
+ "country": String(6),
22
+ "state": String(36),
23
+ # STK
24
+ "sector": String(30),
25
+ "industry": String(36),
26
+ "board_type": String(200),
27
+ # LOF & ETF
28
+ "issuer": String(60),
29
+ "current_mgr": String(60),
30
+ "custodian": String(60),
31
+ "issuer_country": String(6),
32
+ "fund_type": String(20),
33
+ "benchmark": String(60),
34
+ # Convertible Bond (CB)
35
+ "stock_ticker": String(20),
36
+ "stock_exchange": String(10),
37
+ "maturity_date": Date(),
38
+ "issue_price": DOUBLE_PRECISION(),
39
+ "total_issue_size": DOUBLE_PRECISION(),
40
+ "par_value": DOUBLE_PRECISION(),
41
+ "redeem_price": DOUBLE_PRECISION(),
42
+ "conversion_start_date": Date(),
43
+ "conversion_end_date": Date(),
44
+ "callback_terms": Text(),
45
+ "callback_type": String(20),
46
+ "adjust_terms": Text(),
47
+ "adjust_type": String(20),
48
+ "putback_terms": Text(),
49
+ "putback_type": String(20),
50
+ "callback_level": DOUBLE_PRECISION(),
51
+ }
52
+
53
+ DATE_TIME_COLS = {"listed_date", "delisted_date", "maturity_date", "conversion_start_date", "conversion_end_date"}
54
+
55
+ BASE_COLUMNS = [("ticker", String(10)), ("exchange", String(10))]
@@ -10,6 +10,7 @@ from typing import Union, cast
10
10
  import pandas as pd
11
11
 
12
12
  from ..core.sql.sqlmanager import SqlManager
13
+ from .fields_data_type import BASE_COLUMNS, DATE_TIME_COLS, FIELD_DATA_TYPE_SQL
13
14
  from .typedefs import EXCHANGE_LITERALS, INST_TYPE_LITERALS, Opt_T_SeqT, T_DictT
14
15
 
15
16
  COMMON_METADATA_COLUMNS = [
@@ -26,7 +27,28 @@ COMMON_METADATA_COLUMNS = [
26
27
  "delisted_date",
27
28
  ]
28
29
  TYPE_METADATA_COLUMNS = {
29
- "STK": ["sector", "industry", "country", "state", "board_type", "issue_price"],
30
+ "STK": ["country", "state", "board_type", "issue_price"],
31
+ "ETF": ["issuer", "current_mgr", "custodian", "issuer_country", "fund_type", "benchmark"],
32
+ "LOF": ["issuer", "current_mgr", "custodian", "issuer_country", "fund_type", "benchmark"],
33
+ "CB": [
34
+ "country",
35
+ "state",
36
+ "stock_ticker",
37
+ "stock_exchange",
38
+ "maturity_date",
39
+ "issue_price",
40
+ "total_issue_size",
41
+ "par_value",
42
+ "redemption_price",
43
+ "conversion_start_date",
44
+ "conversion_end_date",
45
+ "callback_terms",
46
+ "callback_type",
47
+ "adjust_terms",
48
+ "adjust_type",
49
+ "putback_terms",
50
+ "putback_type",
51
+ ],
30
52
  }
31
53
 
32
54
 
@@ -46,6 +68,21 @@ class MetadataSql:
46
68
  cls._manager = SqlManager()
47
69
  return cls._instance
48
70
 
71
+ def initialize(self, for_inst_types="all"):
72
+ if for_inst_types == "all":
73
+ for_inst_types = list(TYPE_METADATA_COLUMNS.keys())
74
+ elif isinstance(for_inst_types, str):
75
+ for_inst_types = [for_inst_types]
76
+ if not self._manager.table_exists("instruments"):
77
+ columns = BASE_COLUMNS + [(col, FIELD_DATA_TYPE_SQL[col]) for col in COMMON_METADATA_COLUMNS]
78
+ self._manager.create_table("instruments", columns, {"primary_key": ["ticker", "exchange"]})
79
+ for inst_type in for_inst_types:
80
+ if not self._manager.table_exists(f"instruments_{inst_type.lower()}"):
81
+ columns = BASE_COLUMNS + [(col, FIELD_DATA_TYPE_SQL[col]) for col in TYPE_METADATA_COLUMNS[inst_type]]
82
+ self._manager.create_table(
83
+ f"instruments_{inst_type.lower()}", columns, primary_key={"ticker", "exchange"}
84
+ )
85
+
49
86
  def update_instrument_metadata(self, data: Union[pd.DataFrame, list[dict], dict]):
50
87
  """
51
88
  Updates the instrument metadata in the database.
@@ -70,13 +107,16 @@ class MetadataSql:
70
107
 
71
108
  self._manager.insert("instruments", data_common, upsert=True)
72
109
  if "inst_type" in data.columns:
73
- for inst_type, columns in TYPE_METADATA_COLUMNS.items():
74
- data_type_df = data.loc[data.inst_type == inst_type, data.columns.intersection(columns)]
75
- if not data_type_df.empty:
76
- self._manager.insert(f"instruments_{inst_type.lower()}", data_type_df, upsert=True)
110
+ g = data.groupby("inst_type", group_keys=False)
111
+ for inst_type, data_type_df in g:
112
+ if not data_type_df.empty and inst_type in TYPE_METADATA_COLUMNS:
113
+ columns = data_type_df.columns.intersection(TYPE_METADATA_COLUMNS[inst_type])
114
+ if not columns.empty:
115
+ self._manager.insert(f"instruments_{inst_type.lower()}", data_type_df[columns], upsert=True)
77
116
 
78
- def _convert_datetime_columns(self, data: pd.DataFrame):
79
- for col in ["listed_date", "delisted_date"]:
117
+ @staticmethod
118
+ def _convert_datetime_columns(data: pd.DataFrame):
119
+ for col in DATE_TIME_COLS:
80
120
  if col in data.columns:
81
121
  data[col] = data[col].apply(pd.to_datetime)
82
122
 
@@ -132,8 +172,12 @@ class MetadataSql:
132
172
  res = {}
133
173
  for inst_type, common_df_by_type in common_df.groupby("inst_type"):
134
174
  inst_type = cast(INST_TYPE_LITERALS, inst_type)
135
- if all_fields_common:
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
175
+ if all_fields_common or inst_type not in TYPE_METADATA_COLUMNS:
176
+ res[inst_type] = (
177
+ common_df_by_type.set_index(["ticker", "exchange"])
178
+ if len(common_df_by_type.columns) > 2
179
+ else common_df_by_type
180
+ )
137
181
  continue
138
182
  query_fields_type = (
139
183
  ["ticker", "exchange"] + [f for f in query_fields if f in TYPE_METADATA_COLUMNS[inst_type]]
@@ -185,9 +229,11 @@ class MetadataSql:
185
229
 
186
230
  if query_fields == "*":
187
231
  query_fields_cross = "*"
232
+ query_fields_type = []
233
+ query_fields_common = query_fields
188
234
  else:
189
235
  query_fields_common = ["ticker", "exchange"] + [f for f in query_fields if f in COMMON_METADATA_COLUMNS]
190
- query_fields_type = [f for f in query_fields if f in TYPE_METADATA_COLUMNS[inst_type]]
236
+ query_fields_type = [f for f in query_fields if f in TYPE_METADATA_COLUMNS.get(inst_type, [])]
191
237
  query_fields_cross = {
192
238
  "instruments": query_fields_common,
193
239
  f"instruments_{inst_type.lower()}": query_fields_type,
@@ -196,19 +242,27 @@ class MetadataSql:
196
242
  filter_fields_common = {
197
243
  k: v for k, v in filter_fields.items() if k in ["ticker", "exchange"] + COMMON_METADATA_COLUMNS
198
244
  }
199
- filter_fields_type = {
200
- k: v for k, v in filter_fields.items() if k in ["ticker", "exchange"] + TYPE_METADATA_COLUMNS[inst_type]
201
- }
202
- filter_fields_cross = {
203
- "instruments": filter_fields_common,
204
- f"instruments_{inst_type.lower()}": filter_fields_type,
205
- }
206
- df = self._manager.read_data_across_tables(
207
- ["instruments", f"instruments_{inst_type.lower()}"],
208
- joined_columns=["ticker", "exchange"],
209
- query_fields=query_fields_cross,
210
- filter_fields=filter_fields_cross,
211
- )
245
+ filter_fields_type = {k: v for k, v in filter_fields.items() if k in TYPE_METADATA_COLUMNS.get(inst_type, [])}
246
+
247
+ if (
248
+ (query_fields != "*" or inst_type not in TYPE_METADATA_COLUMNS)
249
+ and not bool(query_fields_type)
250
+ and not bool(filter_fields_type)
251
+ ):
252
+ df = self._manager.read_data(
253
+ "instruments", query_fields=query_fields_common, filter_fields=filter_fields_common
254
+ )
255
+ else:
256
+ filter_fields_cross = {
257
+ "instruments": filter_fields_common,
258
+ f"instruments_{inst_type.lower()}": filter_fields_type,
259
+ }
260
+ df = self._manager.read_data_across_tables(
261
+ ["instruments", f"instruments_{inst_type.lower()}"],
262
+ joined_columns=["ticker", "exchange"],
263
+ query_fields=query_fields_cross,
264
+ filter_fields=filter_fields_cross,
265
+ )
212
266
 
213
267
  if isinstance(df.columns, pd.Index):
214
268
  df = df.loc[:, ~df.columns.duplicated()]
@@ -0,0 +1,108 @@
1
+ # -*- coding: utf-8 -*-
2
+ # @Time : 2024/11/12 13:49
3
+ # @Author : YQ Tsui
4
+ # @File : metadata_sql_cb.py
5
+ # @Purpose :
6
+
7
+ import pandas as pd
8
+
9
+ from .metadata_sql import MetadataSql
10
+ from .typedefs import EXCHANGE_LITERALS, Opt_T_SeqT, T_SeqT
11
+
12
+
13
+ class CBMetadataSql(MetadataSql):
14
+
15
+ def read_latest_conversion_price(
16
+ self,
17
+ fields: T_SeqT[str] = ("conversion_price", "effective_date"),
18
+ tickers: Opt_T_SeqT[str] = None,
19
+ exchanges: Opt_T_SeqT[EXCHANGE_LITERALS] = None,
20
+ latest_by: str = "announcement_date",
21
+ ) -> pd.DataFrame:
22
+ """
23
+ Reads the convert price metadata from the database.
24
+
25
+ :param fields: The fields to query.
26
+ :type fields: T_SeqT[str]
27
+ :param tickers: The tickers to query, None for all.
28
+ :type tickers: Opt_T_SeqT[str]
29
+ :param exchanges: The exchanges to query, None for all.
30
+ :type exchanges: Opt_T_SeqT[EXCHANGE_LITERALS]
31
+ :param latest_by: The field to use for latest by.
32
+ :type latest_by: str
33
+ """
34
+ if isinstance(fields, str):
35
+ fields = [fields]
36
+ elif isinstance(fields, tuple):
37
+ fields = list(fields)
38
+ if tickers is None and exchanges is None:
39
+ filter_fields = None
40
+ else:
41
+ filter_fields = {}
42
+ if tickers is not None:
43
+ filter_fields["ticker"] = tickers
44
+ if exchanges is not None:
45
+ filter_fields["exchange"] = exchanges
46
+
47
+ df = self._manager.read_max_in_group(
48
+ "cb_convert_price_history", fields, ["ticker", "exchange"], latest_by, filter_fields=filter_fields
49
+ )
50
+ return df.set_index(["ticker", "exchange"])
51
+
52
+ def update_conversion_price(self, data: pd.DataFrame):
53
+ """
54
+ Updates the convert price metadata in the database.
55
+
56
+ :param data: The data to update.
57
+ :type data: pd.DataFrame
58
+ """
59
+ self._manager.insert("cb_convert_price_history", data, upsert=True)
60
+
61
+ def read_bond_coupon(
62
+ self, fields: T_SeqT[str], tickers: Opt_T_SeqT[str] = None, exchanges: Opt_T_SeqT[EXCHANGE_LITERALS] = None
63
+ ) -> pd.DataFrame:
64
+ """
65
+ Reads the convert coupon metadata from the database.
66
+
67
+ :param fields: The fields to query. available fields: ["pay_date", "coupon", "coupon_type", "period_start", "period_end", "remaining_principle", "principle_repayment"]
68
+ :type fields: T_SeqT[str]
69
+ :param tickers: The tickers to query, None for all.
70
+ :type tickers: Opt_T_SeqT[str]
71
+ :param exchanges: The exchanges to query, None for all.
72
+ :type exchanges: Opt_T_SeqT[EXCHANGE_LITERALS]
73
+ """
74
+ filter_fields = {}
75
+ if tickers is not None:
76
+ filter_fields["ticker"] = tickers
77
+ if exchanges is not None:
78
+ filter_fields["exchange"] = exchanges
79
+
80
+ df = self._manager.read_data("cb_coupon_schedule", query_fields=fields, filter_fields=filter_fields)
81
+ return df.set_index(["ticker", "exchange"])
82
+
83
+ def update_bond_coupon(self, data):
84
+ """
85
+ Updates the convert coupon metadata in the database.
86
+
87
+ :param data: The data to update.
88
+ :type data: pd.DataFrame
89
+ """
90
+ self._manager.insert("cb_coupon_schedule", data, upsert=True)
91
+
92
+ def update_convert_bond_cashflow(self, data):
93
+ """
94
+ Updates the convert bond cashflow metadata in the database.
95
+
96
+ :param data: The data to update.
97
+ :type data: pd.DataFrame
98
+ """
99
+ self._manager.insert("cb_realized_cash_flow", data, upsert=True)
100
+
101
+ def update_auxiliary(self, auxiliary_type: str, data: pd.DataFrame):
102
+ """
103
+ Updates the auxiliary data in the database.
104
+
105
+ :param data: The data to update.
106
+ :type data: pd.DataFrame
107
+ """
108
+ self._manager.insert("cb_auxiliary_" + auxiliary_type, data, upsert=True)
@@ -6,7 +6,7 @@
6
6
 
7
7
  from typing import Dict, Literal, Optional, Sequence, TypeVar, Union
8
8
 
9
- INST_TYPE_LITERALS = Literal["STK", "FUT", "OPT", "IDX", "ETF", "FND", "BND", "CASH", "CRYPTO"]
9
+ INST_TYPE_LITERALS = Literal["STK", "FUT", "OPT", "IDX", "ETF", "LOF", "FUND", "BOND", "CASH", "CRYPTO", "CB"]
10
10
  EXCHANGE_LITERALS = Literal[
11
11
  "SSE",
12
12
  "SZSE",
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: trade_database_manager
3
- Version: 0.0.3.dev1
3
+ Version: 0.0.6
4
4
  Summary: A wrapper of kdb and sql for convenient trade data management.
5
- Author-email: "Y.Q. Cui" <qianyun210603@hotmail.com>
5
+ Author-email: "Y.Q. Tsui" <qianyun210603@hotmail.com>
6
6
  Classifier: Operating System :: POSIX :: Linux
7
7
  Classifier: Operating System :: Microsoft :: Windows
8
8
  Classifier: Development Status :: 3 - Alpha
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.9
13
13
  Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
15
16
  Description-Content-Type: text/markdown
16
17
  License-File: LICENSE
17
18
  Requires-Dist: pandas>=2.0.0
@@ -75,3 +76,23 @@ then the system will send an email with the license file and a base64 key (Eithe
75
76
  - `-T 1000`: This sets the timeout in seconds for client queries. In this case, it's set to 1000 seconds.
76
77
 
77
78
  - `-U /opt/l64/trade.q`: This sets the access control list file. In this case, the file is located at `/opt/l64/trade.q`. This file contains a list of usernames and passwords for clients that are allowed to connect to the kdb+ process.
79
+
80
+ ### MetaData Initialization
81
+
82
+ Allowed instruments types are given in "Instrument Types" section of [meta_enumerations.md](doc/meta_enumerations.md).
83
+
84
+ ### Data Initialization for Instrument Type(s)
85
+ ```python
86
+ from trade_database_manager.manager import MetadataSql
87
+
88
+ metadatalib = MetadataSql()
89
+ metadatalib.initialize(for_inst_types="CB")
90
+ ```
91
+
92
+ This will try to create two tables, `instruments` and `instruments_cb` in the database if not yet exists. The `instruments` table will store the common information of all instruments, and the `instruments_cb` table will store the type-specific information of the instruments of type `CB`.
93
+
94
+ The table fields are listed in the [data_organization.md](doc/data_organization.md) file.
95
+
96
+
97
+
98
+
@@ -16,7 +16,9 @@ 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
18
18
  trade_database_manager/core/sql/sqlwriter.py
19
+ trade_database_manager/core/sql/utils.py
19
20
  trade_database_manager/manager/__init__.py
20
21
  trade_database_manager/manager/fields_data_type.py
21
22
  trade_database_manager/manager/metadata_sql.py
23
+ trade_database_manager/manager/metadata_sql_cb.py
22
24
  trade_database_manager/manager/typedefs.py
@@ -1,23 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- # @Time : 2024/4/19 16:44
3
- # @Author : YQ Tsui
4
- # @File : fields_data_type.py
5
- # @Purpose :
6
-
7
- FIELD_DATA_TYPE_SQL = {
8
- "ticker": "VARCHAR(20)",
9
- "name": "VARCHAR(20)",
10
- "currency": "VARCHAR(6)",
11
- "exchange": "VARCHAR(10)",
12
- "timezone": "VARCHAR(30)",
13
- "tick_size": "REAL",
14
- "lot_size": "REAL",
15
- "min_lots": "REAL",
16
- "market_tplus": "INTEGER",
17
- "listed_date": "DATE",
18
- "delisted_date": "DATE",
19
- "sector": "VARCHAR(30)",
20
- "industry": "VARCHAR(36)",
21
- "country": "VARCHAR(36)",
22
- "board_type": "VARCHAR(20)",
23
- }