trade-database-manager 0.0.6.dev0__tar.gz → 0.0.8__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.
- {trade_database_manager-0.0.6.dev0/trade_database_manager.egg-info → trade_database_manager-0.0.8}/PKG-INFO +25 -3
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/README.md +20 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/pyproject.toml +3 -2
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/kdb/kdbmanager.py +2 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/sql/sqlmanager.py +145 -56
- trade_database_manager-0.0.8/trade_database_manager/core/sql/utils.py +35 -0
- trade_database_manager-0.0.8/trade_database_manager/manager/__init__.py +11 -0
- trade_database_manager-0.0.8/trade_database_manager/manager/fields_data_type.py +71 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/manager/metadata_sql.py +77 -18
- trade_database_manager-0.0.8/trade_database_manager/manager/metadata_sql_cb.py +109 -0
- trade_database_manager-0.0.8/trade_database_manager/manager/metadata_sql_fut.py +43 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/manager/typedefs.py +1 -1
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8/trade_database_manager.egg-info}/PKG-INFO +25 -3
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager.egg-info/SOURCES.txt +3 -0
- trade_database_manager-0.0.6.dev0/trade_database_manager/manager/__init__.py +0 -9
- trade_database_manager-0.0.6.dev0/trade_database_manager/manager/fields_data_type.py +0 -33
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/LICENSE +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/setup.cfg +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/__init__.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/config.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/__init__.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/kdb/__init__.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/sql/__init__.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/sql/sqlreader.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/sql/sqlwriter.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/core/typedefs.py +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager.egg-info/dependency_links.txt +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager.egg-info/requires.txt +0 -0
- {trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager.egg-info/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: trade_database_manager
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: A wrapper of kdb and sql for convenient trade data management.
|
|
5
|
-
Author-email: "Y.Q.
|
|
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
|
|
@@ -19,6 +20,7 @@ Requires-Dist: numpy
|
|
|
19
20
|
Requires-Dist: ruamel.yaml
|
|
20
21
|
Requires-Dist: sqlalchemy
|
|
21
22
|
Requires-Dist: psycopg2
|
|
23
|
+
Dynamic: license-file
|
|
22
24
|
|
|
23
25
|
<table border=1 cellpadding=10>
|
|
24
26
|
<tr>
|
|
@@ -75,3 +77,23 @@ then the system will send an email with the license file and a base64 key (Eithe
|
|
|
75
77
|
- `-T 1000`: This sets the timeout in seconds for client queries. In this case, it's set to 1000 seconds.
|
|
76
78
|
|
|
77
79
|
- `-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.
|
|
80
|
+
|
|
81
|
+
### MetaData Initialization
|
|
82
|
+
|
|
83
|
+
Allowed instruments types are given in "Instrument Types" section of [meta_enumerations.md](doc/meta_enumerations.md).
|
|
84
|
+
|
|
85
|
+
### Data Initialization for Instrument Type(s)
|
|
86
|
+
```python
|
|
87
|
+
from trade_database_manager.manager import MetadataSql
|
|
88
|
+
|
|
89
|
+
metadatalib = MetadataSql()
|
|
90
|
+
metadatalib.initialize(for_inst_types="CB")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
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`.
|
|
94
|
+
|
|
95
|
+
The table fields are listed in the [data_organization.md](doc/data_organization.md) file.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
@@ -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.
|
|
7
|
+
version = "0.0.8"
|
|
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.
|
|
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]
|
|
@@ -7,15 +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, Union
|
|
10
|
+
from typing import Any, Callable, Literal, Sequence, Union
|
|
11
11
|
|
|
12
12
|
import pandas as pd
|
|
13
|
-
from sqlalchemy import
|
|
14
|
-
|
|
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
|
+
)
|
|
15
27
|
from sqlalchemy.dialects.postgresql import insert
|
|
28
|
+
from sqlalchemy.orm import aliased, sessionmaker
|
|
29
|
+
from sqlalchemy.types import TypeEngine
|
|
16
30
|
|
|
17
31
|
from ...config import CONFIG
|
|
18
32
|
from ..typedefs import FILTERFIELD_TYPE, QUERYFIELD_TYPE
|
|
33
|
+
from .utils import infer_sql_type
|
|
19
34
|
|
|
20
35
|
|
|
21
36
|
def _insert_on_conflict_update(table, conn, keys, data_iter, indexes):
|
|
@@ -38,10 +53,13 @@ class SqlManager:
|
|
|
38
53
|
This class is used to manage SQL operations.
|
|
39
54
|
|
|
40
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.
|
|
41
58
|
"""
|
|
42
59
|
|
|
43
60
|
def __init__(self):
|
|
44
61
|
self.engine = create_engine(CONFIG["sqlconnstr"])
|
|
62
|
+
self._session = sessionmaker(bind=self.engine)() # TODO: is it better to be a class attribute?
|
|
45
63
|
self._inspector = None
|
|
46
64
|
|
|
47
65
|
@property
|
|
@@ -50,7 +68,7 @@ class SqlManager:
|
|
|
50
68
|
self._inspector = inspect(self.engine)
|
|
51
69
|
return self._inspector
|
|
52
70
|
|
|
53
|
-
def _execute(self, sql_executable: Union[str,
|
|
71
|
+
def _execute(self, sql_executable: Union[str, Executable]) -> Any:
|
|
54
72
|
if isinstance(sql_executable, str):
|
|
55
73
|
sql_executable = text(sql_executable)
|
|
56
74
|
with self.engine.begin() as conn:
|
|
@@ -134,9 +152,9 @@ class SqlManager:
|
|
|
134
152
|
def create_table(
|
|
135
153
|
self,
|
|
136
154
|
table_name: str,
|
|
137
|
-
table_columns: list[tuple[str, TypeEngine]],
|
|
155
|
+
table_columns: list[tuple[str, TypeEngine | type | tuple[type, tuple]]],
|
|
138
156
|
unique_index_columns: Sequence[str] = (),
|
|
139
|
-
primary_key:
|
|
157
|
+
primary_key: str | set[str] = set(),
|
|
140
158
|
):
|
|
141
159
|
"""
|
|
142
160
|
Creates a table.
|
|
@@ -148,12 +166,16 @@ class SqlManager:
|
|
|
148
166
|
:param unique_index_columns: Columns to enforce unique values on. Defaults to an empty sequence.
|
|
149
167
|
:type unique_index_columns: Sequence[str]
|
|
150
168
|
:param primary_key: The primary key(s) of the table. Defaults to an empty set.
|
|
151
|
-
:type primary_key: Union[str,set[str]]
|
|
169
|
+
:type primary_key: Union[str, set[str|tuple[str]]]
|
|
170
|
+
|
|
152
171
|
"""
|
|
153
172
|
table_meta = MetaData()
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
columns: list[Column|PrimaryKeyConstraint] = [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
|
+
|
|
157
179
|
table = Table(table_name, table_meta, *columns)
|
|
158
180
|
table.create(self.engine)
|
|
159
181
|
for column in unique_index_columns:
|
|
@@ -197,7 +219,7 @@ class SqlManager:
|
|
|
197
219
|
"""
|
|
198
220
|
# check if any index is referring to the column
|
|
199
221
|
sql_code = f"SELECT indexname, indexdef FROM pg_indexes WHERE indexdef LIKE '%%(%%{old_column_name}%%)%%' and tablename = '{table_name}'"
|
|
200
|
-
index_refering_column = dict(self.
|
|
222
|
+
index_refering_column = dict(self._execute(sql_code).fetchall())
|
|
201
223
|
|
|
202
224
|
# drop indexes referring to the column
|
|
203
225
|
for index_name in index_refering_column:
|
|
@@ -219,8 +241,26 @@ class SqlManager:
|
|
|
219
241
|
query_fields="*",
|
|
220
242
|
start_time: pd.Timestamp = None,
|
|
221
243
|
end_time: pd.Timestamp = None,
|
|
244
|
+
time_column: str = "timestamp",
|
|
222
245
|
filter_fields=None,
|
|
223
246
|
):
|
|
247
|
+
"""
|
|
248
|
+
Read data from a table within a time range.
|
|
249
|
+
|
|
250
|
+
:param table_name:
|
|
251
|
+
:type str
|
|
252
|
+
:param query_fields:
|
|
253
|
+
:type QUERYFIELD_TYPE
|
|
254
|
+
:param start_time:
|
|
255
|
+
:type pd.Timestamp
|
|
256
|
+
:param end_time:
|
|
257
|
+
:type pd.Timestamp
|
|
258
|
+
:param time_column:
|
|
259
|
+
:type str
|
|
260
|
+
:param filter_fields:
|
|
261
|
+
:type FILTERFIELD_TYPE
|
|
262
|
+
:return:
|
|
263
|
+
"""
|
|
224
264
|
meta = MetaData()
|
|
225
265
|
table = Table(table_name, meta, autoload_with=self.engine)
|
|
226
266
|
|
|
@@ -233,9 +273,9 @@ class SqlManager:
|
|
|
233
273
|
|
|
234
274
|
conditions = []
|
|
235
275
|
if start_time:
|
|
236
|
-
conditions.append(getattr(table.c,
|
|
276
|
+
conditions.append(getattr(table.c, time_column, table.c.end_time) >= start_time)
|
|
237
277
|
if end_time:
|
|
238
|
-
conditions.append(getattr(table.c,
|
|
278
|
+
conditions.append(getattr(table.c, time_column, table.c.start_time) <= end_time)
|
|
239
279
|
if filter_fields:
|
|
240
280
|
conditions.extend(
|
|
241
281
|
[
|
|
@@ -247,11 +287,11 @@ class SqlManager:
|
|
|
247
287
|
for field, filter_values in filter_fields.items()
|
|
248
288
|
]
|
|
249
289
|
)
|
|
250
|
-
stmt = stmt.where(
|
|
290
|
+
stmt = stmt.where(and_(*conditions))
|
|
251
291
|
res = self._execute(stmt)
|
|
252
292
|
return pd.DataFrame(res.fetchall(), columns=res.keys())
|
|
253
293
|
|
|
254
|
-
def read_data(self, table_name: str, query_fields: QUERYFIELD_TYPE = "*", filter_fields=None):
|
|
294
|
+
def read_data(self, table_name: str, query_fields: QUERYFIELD_TYPE = "*", filter_fields=None, unique=False):
|
|
255
295
|
"""
|
|
256
296
|
Reads data from a table.
|
|
257
297
|
|
|
@@ -275,6 +315,9 @@ class SqlManager:
|
|
|
275
315
|
query_fields = table
|
|
276
316
|
stmt = select(table)
|
|
277
317
|
|
|
318
|
+
if unique:
|
|
319
|
+
stmt = stmt.distinct()
|
|
320
|
+
|
|
278
321
|
if filter_fields:
|
|
279
322
|
conditions = [
|
|
280
323
|
(
|
|
@@ -284,7 +327,7 @@ class SqlManager:
|
|
|
284
327
|
)
|
|
285
328
|
for field, filter_values in filter_fields.items()
|
|
286
329
|
]
|
|
287
|
-
stmt = stmt.where(
|
|
330
|
+
stmt = stmt.where(and_(*conditions))
|
|
288
331
|
|
|
289
332
|
# cannot use pandas.read_sql here as it discards timezone info
|
|
290
333
|
res = self._execute(stmt)
|
|
@@ -296,6 +339,7 @@ class SqlManager:
|
|
|
296
339
|
joined_columns: Sequence[str],
|
|
297
340
|
query_fields: QUERYFIELD_TYPE = "*",
|
|
298
341
|
filter_fields: FILTERFIELD_TYPE = None,
|
|
342
|
+
unique = False,
|
|
299
343
|
):
|
|
300
344
|
"""
|
|
301
345
|
Reads data from multiple tables.
|
|
@@ -315,7 +359,7 @@ class SqlManager:
|
|
|
315
359
|
tables = {table_name: Table(table_name, meta, autoload_with=self.engine) for table_name in table_names}
|
|
316
360
|
|
|
317
361
|
joined_table = reduce(
|
|
318
|
-
lambda x, y: x.join(y,
|
|
362
|
+
lambda x, y: x.join(y, and_(*[x.columns[col] == y.columns[col] for col in joined_columns])),
|
|
319
363
|
tables.values(),
|
|
320
364
|
)
|
|
321
365
|
|
|
@@ -331,6 +375,9 @@ class SqlManager:
|
|
|
331
375
|
|
|
332
376
|
stmt = select(*query_fields_rel).select_from(joined_table)
|
|
333
377
|
|
|
378
|
+
if unique:
|
|
379
|
+
stmt = stmt.distinct()
|
|
380
|
+
|
|
334
381
|
if filter_fields:
|
|
335
382
|
conditions = []
|
|
336
383
|
for table_name, colnames in filter_fields.items():
|
|
@@ -342,55 +389,97 @@ class SqlManager:
|
|
|
342
389
|
else table.columns[field] == filter_values
|
|
343
390
|
)
|
|
344
391
|
|
|
345
|
-
stmt = stmt.where(
|
|
392
|
+
stmt = stmt.where(and_(*conditions))
|
|
346
393
|
|
|
347
394
|
# cannot use pandas.read_sql here as it discards timezone info
|
|
348
395
|
res = self._execute(stmt)
|
|
349
396
|
return pd.DataFrame(res.fetchall(), columns=res.keys())
|
|
350
397
|
|
|
351
|
-
def
|
|
398
|
+
def _read_extremum_in_group(
|
|
352
399
|
self,
|
|
353
400
|
table_name: str,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
401
|
+
target_column: str | list[str],
|
|
402
|
+
group_column: str | list[str],
|
|
403
|
+
extremum_column: str,
|
|
404
|
+
method: str | Callable,
|
|
405
|
+
filter_fields=None,
|
|
358
406
|
):
|
|
359
|
-
|
|
360
|
-
|
|
407
|
+
if isinstance(target_column, str):
|
|
408
|
+
target_column = [target_column]
|
|
409
|
+
if isinstance(group_column, str):
|
|
410
|
+
group_column = [group_column]
|
|
411
|
+
target_column_c = [Column(col) for col in target_column]
|
|
412
|
+
group_column_c = [Column(col) for col in group_column]
|
|
413
|
+
extremum_column_c = Column(extremum_column)
|
|
361
414
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
:param time_chunk_interval: The interval to chunk the time column by. Defaults to "1 year".
|
|
369
|
-
:type time_chunk_interval: str
|
|
370
|
-
: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.
|
|
371
|
-
:type other_chunkers: Dict[str, int], optional
|
|
372
|
-
"""
|
|
373
|
-
query_create_sensordata_table = f"""
|
|
374
|
-
CREATE TABLE {table_name} (
|
|
375
|
-
{time_column} TIMESTAMP NOT NULL,
|
|
376
|
-
{", ".join([f"{column_name} {column_type}{' NOT NULL' if column_name in other_chunkers else ''}" for column_name, column_type in table_columns.items()])}
|
|
377
|
-
);
|
|
378
|
-
"""
|
|
379
|
-
query_create_sensordata_hypertable = (
|
|
380
|
-
f"SELECT create_hypertable('{table_name}', by_range('{time_column}', INTERVAL '{time_chunk_interval}'));"
|
|
415
|
+
table_meta = MetaData()
|
|
416
|
+
|
|
417
|
+
full_table = (
|
|
418
|
+
Table(table_name, table_meta, *target_column_c, *group_column_c)
|
|
419
|
+
if extremum_column in target_column
|
|
420
|
+
else Table(table_name, table_meta, *target_column_c, *group_column_c, extremum_column_c)
|
|
381
421
|
)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
422
|
+
|
|
423
|
+
if isinstance(method, str):
|
|
424
|
+
method = getattr(func, method)
|
|
425
|
+
|
|
426
|
+
if filter_fields:
|
|
427
|
+
conditions = [
|
|
428
|
+
(
|
|
429
|
+
full_table.columns[field].in_(filter_values)
|
|
430
|
+
if isinstance(filter_values, Container) and not isinstance(filter_values, (str, bytes))
|
|
431
|
+
else full_table.columns[field] == filter_values
|
|
386
432
|
)
|
|
433
|
+
for field, filter_values in filter_fields.items()
|
|
434
|
+
]
|
|
435
|
+
subquery = (
|
|
436
|
+
self._session.query(*group_column_c, method(extremum_column_c).label("ColExtremum"))
|
|
437
|
+
.where(and_(*conditions))
|
|
438
|
+
.group_by(*group_column_c)
|
|
439
|
+
.subquery()
|
|
440
|
+
)
|
|
441
|
+
else:
|
|
442
|
+
subquery = (
|
|
443
|
+
self._session.query(*group_column_c, method(extremum_column_c).label("ColExtremum"))
|
|
444
|
+
.group_by(*group_column_c)
|
|
445
|
+
.subquery()
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
t_full = aliased(full_table)
|
|
449
|
+
t_sub = aliased(subquery)
|
|
450
|
+
|
|
451
|
+
cols_res = [getattr(t_full.c, col) for col in group_column + target_column]
|
|
452
|
+
|
|
453
|
+
query = self._session.query(*cols_res).join(
|
|
454
|
+
t_sub,
|
|
455
|
+
and_(
|
|
456
|
+
getattr(t_full.c, extremum_column) == t_sub.c.ColExtremum,
|
|
457
|
+
*[getattr(t_full.c, col) == getattr(t_sub.c, col) for col in group_column],
|
|
458
|
+
),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
return pd.DataFrame(query.all(), columns=group_column + target_column)
|
|
387
462
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
463
|
+
def read_max_in_group(
|
|
464
|
+
self,
|
|
465
|
+
table_name: str,
|
|
466
|
+
target_column: str | list[str],
|
|
467
|
+
group_column: str | list[str],
|
|
468
|
+
extremum_column: str,
|
|
469
|
+
filter_fields=None,
|
|
470
|
+
):
|
|
471
|
+
return self._read_extremum_in_group(
|
|
472
|
+
table_name, target_column, group_column, extremum_column, "max", filter_fields
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
def read_min_in_group(
|
|
476
|
+
self,
|
|
477
|
+
table_name: str,
|
|
478
|
+
target_column: str | list[str],
|
|
479
|
+
group_column: str | list[str],
|
|
480
|
+
extremum_column: str,
|
|
481
|
+
filter_fields=None,
|
|
482
|
+
):
|
|
483
|
+
return self._read_extremum_in_group(
|
|
484
|
+
table_name, target_column, group_column, extremum_column, "min", filter_fields
|
|
391
485
|
)
|
|
392
|
-
with self.engine.connect() as conn:
|
|
393
|
-
conn.execute(text(query_create_sensordata_table))
|
|
394
|
-
conn.execute(text(query_create_sensordata_hypertable))
|
|
395
|
-
conn.execute(text(query_unique_index))
|
|
396
|
-
conn.commit()
|
|
@@ -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}")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Time : 2024/4/21 12:41
|
|
3
|
+
# @Author : YQ Tsui
|
|
4
|
+
# @File : __init__.py
|
|
5
|
+
# @Purpose :
|
|
6
|
+
|
|
7
|
+
from .metadata_sql import MetadataSql
|
|
8
|
+
from .metadata_sql_cb import CBMetadataSql
|
|
9
|
+
from .metadata_sql_fut import FutMetadataSql
|
|
10
|
+
|
|
11
|
+
__all__ = ("MetadataSql", "CBMetadataSql", "FutMetadataSql")
|
|
@@ -0,0 +1,71 @@
|
|
|
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, DateTime, CHAR
|
|
8
|
+
|
|
9
|
+
FIELD_DATA_TYPE_SQL = {
|
|
10
|
+
"ticker": String(20),
|
|
11
|
+
"name": String(128),
|
|
12
|
+
"trading_code": String(20),
|
|
13
|
+
"inst_type": String(20),
|
|
14
|
+
"currency": String(6),
|
|
15
|
+
"exchange": String(10),
|
|
16
|
+
"timezone": String(30),
|
|
17
|
+
"tick_size": DOUBLE_PRECISION(),
|
|
18
|
+
"lot_size": DOUBLE_PRECISION(),
|
|
19
|
+
"min_lots": DOUBLE_PRECISION(),
|
|
20
|
+
"market_tplus": Integer(),
|
|
21
|
+
"listed_date": Date(),
|
|
22
|
+
"delisted_date": Date(),
|
|
23
|
+
"country": String(6),
|
|
24
|
+
"state": String(36),
|
|
25
|
+
# STK
|
|
26
|
+
"sector": String(30),
|
|
27
|
+
"industry": String(36),
|
|
28
|
+
"board_type": String(200),
|
|
29
|
+
# LOF & ETF
|
|
30
|
+
"issuer": String(60),
|
|
31
|
+
"current_mgr": String(60),
|
|
32
|
+
"custodian": String(60),
|
|
33
|
+
"issuer_country": String(6),
|
|
34
|
+
"fund_type": String(20),
|
|
35
|
+
"benchmark": String(60),
|
|
36
|
+
# Convertible Bond (CB)
|
|
37
|
+
"stock_ticker": String(20),
|
|
38
|
+
"stock_exchange": String(10),
|
|
39
|
+
"maturity_date": Date(),
|
|
40
|
+
"issue_price": DOUBLE_PRECISION(),
|
|
41
|
+
"total_issue_size": DOUBLE_PRECISION(),
|
|
42
|
+
"par_value": DOUBLE_PRECISION(),
|
|
43
|
+
"redeem_price": DOUBLE_PRECISION(),
|
|
44
|
+
"conversion_start_date": Date(),
|
|
45
|
+
"conversion_end_date": Date(),
|
|
46
|
+
"callback_terms": Text(),
|
|
47
|
+
"callback_type": String(20),
|
|
48
|
+
"adjust_terms": Text(),
|
|
49
|
+
"adjust_type": String(20),
|
|
50
|
+
"putback_terms": Text(),
|
|
51
|
+
"putback_type": String(20),
|
|
52
|
+
"callback_level": DOUBLE_PRECISION(),
|
|
53
|
+
# Futures (FUT)
|
|
54
|
+
"contract_unit": String(20),
|
|
55
|
+
"contract_multiplier": DOUBLE_PRECISION(),
|
|
56
|
+
"expiry_time": DateTime(),
|
|
57
|
+
"delivery_date": Date(),
|
|
58
|
+
"settlement_method": String(20),
|
|
59
|
+
"underlying_code": String(20),
|
|
60
|
+
"underlying_exchange": String(10),
|
|
61
|
+
"underlying_type": String(20),
|
|
62
|
+
"margin_method": String(20),
|
|
63
|
+
# Options (OPT), mainly similar to Futures
|
|
64
|
+
"option_type": String(20), # 'C' for Call, 'P' for Put
|
|
65
|
+
"strike": DOUBLE_PRECISION(),
|
|
66
|
+
"exercise_style": String(20), # 'A' for American, 'E' for European
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
DATE_TIME_COLS = {"listed_date", "delisted_date", "maturity_date", "conversion_start_date", "conversion_end_date", "expiry_date", "delivery_date"}
|
|
70
|
+
|
|
71
|
+
BASE_COLUMNS = [("ticker", String(36)), ("exchange", String(10))]
|
|
@@ -10,9 +10,8 @@ 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
|
-
from .fields_data_type import FIELD_DATA_TYPE_SQL, String
|
|
15
|
-
|
|
16
15
|
|
|
17
16
|
COMMON_METADATA_COLUMNS = [
|
|
18
17
|
"name",
|
|
@@ -31,6 +30,40 @@ TYPE_METADATA_COLUMNS = {
|
|
|
31
30
|
"STK": ["country", "state", "board_type", "issue_price"],
|
|
32
31
|
"ETF": ["issuer", "current_mgr", "custodian", "issuer_country", "fund_type", "benchmark"],
|
|
33
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
|
+
],
|
|
52
|
+
"FUT": ["contract_unit", "contract_multiplier", "expiry_time", "delivery_date", "settlement_method", "underlying_code", "underlying_exchange", "underlying_type", "margin_method", "sector"],
|
|
53
|
+
"OPT": [
|
|
54
|
+
"strike",
|
|
55
|
+
"option_type",
|
|
56
|
+
"exercise_style",
|
|
57
|
+
"contract_unit",
|
|
58
|
+
"contract_multiplier",
|
|
59
|
+
"expiry_time",
|
|
60
|
+
"delivery_date",
|
|
61
|
+
"settlement_method",
|
|
62
|
+
"underlying_code",
|
|
63
|
+
"underlying_exchange",
|
|
64
|
+
"underlying_type",
|
|
65
|
+
"margin_method",
|
|
66
|
+
],
|
|
34
67
|
}
|
|
35
68
|
|
|
36
69
|
|
|
@@ -55,18 +88,22 @@ class MetadataSql:
|
|
|
55
88
|
for_inst_types = list(TYPE_METADATA_COLUMNS.keys())
|
|
56
89
|
elif isinstance(for_inst_types, str):
|
|
57
90
|
for_inst_types = [for_inst_types]
|
|
58
|
-
columns_base = [("ticker", String(10)), ("exchange", String(10))]
|
|
59
91
|
if not self._manager.table_exists("instruments"):
|
|
60
|
-
columns =
|
|
92
|
+
columns = BASE_COLUMNS + [(col, FIELD_DATA_TYPE_SQL[col]) for col in COMMON_METADATA_COLUMNS]
|
|
61
93
|
self._manager.create_table("instruments", columns, {"primary_key": ["ticker", "exchange"]})
|
|
62
94
|
for inst_type in for_inst_types:
|
|
63
95
|
if not self._manager.table_exists(f"instruments_{inst_type.lower()}"):
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
96
|
+
if inst_type not in TYPE_METADATA_COLUMNS:
|
|
97
|
+
raise ValueError(f"Invalid instrument type: {inst_type}")
|
|
98
|
+
if inst_type in {"FUT", "OPT"}:
|
|
99
|
+
columns = BASE_COLUMNS + [(col, FIELD_DATA_TYPE_SQL[col]) for col in (COMMON_METADATA_COLUMNS + TYPE_METADATA_COLUMNS[inst_type])]
|
|
100
|
+
self._manager.create_table(
|
|
101
|
+
f"instruments_{inst_type.lower()}", columns, primary_key={"ticker", "exchange"})
|
|
102
|
+
else:
|
|
103
|
+
columns = BASE_COLUMNS + [(col, FIELD_DATA_TYPE_SQL[col]) for col in TYPE_METADATA_COLUMNS[inst_type]]
|
|
104
|
+
self._manager.create_table(
|
|
105
|
+
f"instruments_{inst_type.lower()}", columns, primary_key={"ticker", "exchange"}
|
|
106
|
+
)
|
|
70
107
|
|
|
71
108
|
def update_instrument_metadata(self, data: Union[pd.DataFrame, list[dict], dict]):
|
|
72
109
|
"""
|
|
@@ -75,11 +112,8 @@ class MetadataSql:
|
|
|
75
112
|
:param data: The data to be updated. It can be a DataFrame, a list of dictionaries, or a single dictionary.
|
|
76
113
|
:type data: Union[pd.DataFrame, list[dict], dict]
|
|
77
114
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
raise ValueError(
|
|
81
|
-
f"Non-common columns found ({','.join(type_specific_columns)}) but inst_type column not provided."
|
|
82
|
-
)
|
|
115
|
+
if "inst_type" not in data.columns:
|
|
116
|
+
raise ValueError("`inst_type` column not provided.")
|
|
83
117
|
if isinstance(data, dict):
|
|
84
118
|
data = [data]
|
|
85
119
|
if isinstance(data, list):
|
|
@@ -88,6 +122,19 @@ class MetadataSql:
|
|
|
88
122
|
data.set_index(["ticker", "exchange"], inplace=True)
|
|
89
123
|
else:
|
|
90
124
|
assert set(data.index.names) == {"ticker", "exchange"}, "Index names must be 'ticker' and 'exchange'."
|
|
125
|
+
|
|
126
|
+
if "FUT" in data["inst_type"].unique():
|
|
127
|
+
columns = data.columns.intersection(COMMON_METADATA_COLUMNS + TYPE_METADATA_COLUMNS["FUT"])
|
|
128
|
+
data_fut = data.loc[data["inst_type"] == "FUT", columns]
|
|
129
|
+
self._manager.insert("instruments_fut", data_fut, upsert=True)
|
|
130
|
+
data.drop(index = data[data["inst_type"] == "FUT"].index, inplace=True)
|
|
131
|
+
|
|
132
|
+
if "OPT" in data["inst_type"].unique():
|
|
133
|
+
columns = data.columns.intersection(COMMON_METADATA_COLUMNS + TYPE_METADATA_COLUMNS["OPT"])
|
|
134
|
+
data_opt = data.loc[data["inst_type"] == "OPT", columns]
|
|
135
|
+
self._manager.insert("instruments_opt", data_opt, upsert=True)
|
|
136
|
+
data.drop(index = data[data["inst_type"] == "OPT"].index, inplace=True)
|
|
137
|
+
|
|
91
138
|
data_common = data[data.columns.intersection(COMMON_METADATA_COLUMNS)]
|
|
92
139
|
|
|
93
140
|
self._manager.insert("instruments", data_common, upsert=True)
|
|
@@ -99,11 +146,13 @@ class MetadataSql:
|
|
|
99
146
|
if not columns.empty:
|
|
100
147
|
self._manager.insert(f"instruments_{inst_type.lower()}", data_type_df[columns], upsert=True)
|
|
101
148
|
|
|
102
|
-
|
|
103
|
-
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _convert_datetime_columns(data: pd.DataFrame):
|
|
151
|
+
for col in DATE_TIME_COLS:
|
|
104
152
|
if col in data.columns:
|
|
105
153
|
data[col] = data[col].apply(pd.to_datetime)
|
|
106
154
|
|
|
155
|
+
# TODO: Add support for Futures and Options as they don't use the common metadata columns
|
|
107
156
|
def read_metadata(
|
|
108
157
|
self,
|
|
109
158
|
ticker: Opt_T_SeqT[str] = None,
|
|
@@ -213,6 +262,8 @@ class MetadataSql:
|
|
|
213
262
|
|
|
214
263
|
if query_fields == "*":
|
|
215
264
|
query_fields_cross = "*"
|
|
265
|
+
query_fields_type = []
|
|
266
|
+
query_fields_common = query_fields
|
|
216
267
|
else:
|
|
217
268
|
query_fields_common = ["ticker", "exchange"] + [f for f in query_fields if f in COMMON_METADATA_COLUMNS]
|
|
218
269
|
query_fields_type = [f for f in query_fields if f in TYPE_METADATA_COLUMNS.get(inst_type, [])]
|
|
@@ -226,7 +277,15 @@ class MetadataSql:
|
|
|
226
277
|
}
|
|
227
278
|
filter_fields_type = {k: v for k, v in filter_fields.items() if k in TYPE_METADATA_COLUMNS.get(inst_type, [])}
|
|
228
279
|
|
|
229
|
-
if
|
|
280
|
+
if inst_type in {"FUT", "OPT"}:
|
|
281
|
+
df = self._manager.read_data(
|
|
282
|
+
f"instruments_{inst_type.lower()}", query_fields=query_fields, filter_fields=filter_fields
|
|
283
|
+
)
|
|
284
|
+
elif (
|
|
285
|
+
(query_fields != "*" or inst_type not in TYPE_METADATA_COLUMNS)
|
|
286
|
+
and not bool(query_fields_type)
|
|
287
|
+
and not bool(filter_fields_type)
|
|
288
|
+
):
|
|
230
289
|
df = self._manager.read_data(
|
|
231
290
|
"instruments", query_fields=query_fields_common, filter_fields=filter_fields_common
|
|
232
291
|
)
|
|
@@ -0,0 +1,109 @@
|
|
|
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 auxiliary_type: ["RangerBinary"]
|
|
106
|
+
:param data: The data to update.
|
|
107
|
+
:type data: pd.DataFrame
|
|
108
|
+
"""
|
|
109
|
+
self._manager.insert("cb_auxiliary_" + auxiliary_type, data, upsert=True)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# @Time : 2024/11/19 17:42
|
|
3
|
+
# @Author : YQ Tsui
|
|
4
|
+
# @File : metadata_sql_fut.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
|
+
class FutMetadataSql(MetadataSql):
|
|
13
|
+
|
|
14
|
+
def get_all_underlying_codes(self) -> pd.Series:
|
|
15
|
+
"""
|
|
16
|
+
Get all underlying codes
|
|
17
|
+
"""
|
|
18
|
+
return self._manager.read_data("instruments_fut", ["underlying_code"], unique=True)
|
|
19
|
+
|
|
20
|
+
def read_latest_daily_auxiliary_data(self, fields: T_SeqT[str] = "date",
|
|
21
|
+
tickers: Opt_T_SeqT[str] = None,
|
|
22
|
+
exchanges: Opt_T_SeqT[EXCHANGE_LITERALS] = None,
|
|
23
|
+
) -> pd.DataFrame:
|
|
24
|
+
"""
|
|
25
|
+
Read latest daily auxiliary data
|
|
26
|
+
"""
|
|
27
|
+
if isinstance(fields, str):
|
|
28
|
+
fields = [fields]
|
|
29
|
+
elif isinstance(fields, tuple):
|
|
30
|
+
fields = list(fields)
|
|
31
|
+
if tickers is None and exchanges is None:
|
|
32
|
+
filter_fields = None
|
|
33
|
+
else:
|
|
34
|
+
filter_fields = {}
|
|
35
|
+
if tickers is not None:
|
|
36
|
+
filter_fields["ticker"] = tickers
|
|
37
|
+
if exchanges is not None:
|
|
38
|
+
filter_fields["exchange"] = exchanges
|
|
39
|
+
|
|
40
|
+
df = self._manager.read_max_in_group(
|
|
41
|
+
"fut_daily_auxiliary_data", fields, ["underlying_code", "exchange"], "date", filter_fields=filter_fields
|
|
42
|
+
)
|
|
43
|
+
return df.set_index(["underlying_code", "exchange"])
|
|
@@ -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", "
|
|
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
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: trade_database_manager
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: A wrapper of kdb and sql for convenient trade data management.
|
|
5
|
-
Author-email: "Y.Q.
|
|
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
|
|
@@ -19,6 +20,7 @@ Requires-Dist: numpy
|
|
|
19
20
|
Requires-Dist: ruamel.yaml
|
|
20
21
|
Requires-Dist: sqlalchemy
|
|
21
22
|
Requires-Dist: psycopg2
|
|
23
|
+
Dynamic: license-file
|
|
22
24
|
|
|
23
25
|
<table border=1 cellpadding=10>
|
|
24
26
|
<tr>
|
|
@@ -75,3 +77,23 @@ then the system will send an email with the license file and a base64 key (Eithe
|
|
|
75
77
|
- `-T 1000`: This sets the timeout in seconds for client queries. In this case, it's set to 1000 seconds.
|
|
76
78
|
|
|
77
79
|
- `-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.
|
|
80
|
+
|
|
81
|
+
### MetaData Initialization
|
|
82
|
+
|
|
83
|
+
Allowed instruments types are given in "Instrument Types" section of [meta_enumerations.md](doc/meta_enumerations.md).
|
|
84
|
+
|
|
85
|
+
### Data Initialization for Instrument Type(s)
|
|
86
|
+
```python
|
|
87
|
+
from trade_database_manager.manager import MetadataSql
|
|
88
|
+
|
|
89
|
+
metadatalib = MetadataSql()
|
|
90
|
+
metadatalib.initialize(for_inst_types="CB")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
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`.
|
|
94
|
+
|
|
95
|
+
The table fields are listed in the [data_organization.md](doc/data_organization.md) file.
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
@@ -16,7 +16,10 @@ 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
|
|
24
|
+
trade_database_manager/manager/metadata_sql_fut.py
|
|
22
25
|
trade_database_manager/manager/typedefs.py
|
|
@@ -1,33 +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
|
-
from sqlalchemy import DOUBLE_PRECISION, Integer, String, Date
|
|
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
|
-
# STK
|
|
23
|
-
"sector": String(30),
|
|
24
|
-
"industry": String(36),
|
|
25
|
-
"board_type": String(200),
|
|
26
|
-
# LOF & ETF
|
|
27
|
-
"issuer": String(60),
|
|
28
|
-
"current_mgr": String(60),
|
|
29
|
-
"custodian": String(60),
|
|
30
|
-
"issuer_country": String(6),
|
|
31
|
-
"fund_type": String(20),
|
|
32
|
-
"benchmark": String(60),
|
|
33
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{trade_database_manager-0.0.6.dev0 → trade_database_manager-0.0.8}/trade_database_manager/config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|