trade-database-manager 0.0.3.dev1__tar.gz → 0.0.6.dev0__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.3.dev1/trade_database_manager.egg-info → trade_database_manager-0.0.6.dev0}/PKG-INFO +1 -1
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/pyproject.toml +2 -6
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/kdb/__init__.py +1 -1
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/kdb/kdbmanager.py +41 -17
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/sql/__init__.py +2 -1
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/sql/sqlmanager.py +67 -21
- trade_database_manager-0.0.6.dev0/trade_database_manager/manager/fields_data_type.py +33 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/manager/metadata_sql.py +53 -21
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0/trade_database_manager.egg-info}/PKG-INFO +1 -1
- trade_database_manager-0.0.3.dev1/trade_database_manager/manager/fields_data_type.py +0 -23
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/LICENSE +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/README.md +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/setup.cfg +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/__init__.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/config.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/__init__.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/sql/sqlreader.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/sql/sqlwriter.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/core/typedefs.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/manager/__init__.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager/manager/typedefs.py +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager.egg-info/SOURCES.txt +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager.egg-info/dependency_links.txt +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/trade_database_manager.egg-info/requires.txt +0 -0
- {trade_database_manager-0.0.3.dev1 → trade_database_manager-0.0.6.dev0}/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.
|
|
3
|
+
Version: 0.0.6.dev0
|
|
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.
|
|
7
|
+
version = "0.0.6.dev0"
|
|
8
8
|
description = "A wrapper of kdb and sql for convenient trade data management."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -30,7 +30,7 @@ include = ["trade_database_manager*"]
|
|
|
30
30
|
const-rgx='[a-z_][a-z0-9_]{2,30}$'
|
|
31
31
|
|
|
32
32
|
[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"
|
|
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,R0917,R1705,R1710,R1725,R1735,W0102,W0212,W0221,W0223,W0231,W0237,W0612,W0621,W0622,W0703,W1309,E1102,E1136"
|
|
34
34
|
ignore-paths="\\.ipynb_checkpoints/*"
|
|
35
35
|
|
|
36
36
|
[tool.pylint.format]
|
|
@@ -43,7 +43,3 @@ target-version = ['py310']
|
|
|
43
43
|
[tool.isort]
|
|
44
44
|
profile = "black"
|
|
45
45
|
line_length = 120
|
|
46
|
-
|
|
47
|
-
[tool.flake8]
|
|
48
|
-
max-line-length = 120
|
|
49
|
-
ignore = "E203,E501,W503"
|
|
@@ -17,7 +17,6 @@ class KdbManager:
|
|
|
17
17
|
def instance(cls):
|
|
18
18
|
if cls._instance is None:
|
|
19
19
|
cls._instance = cls()
|
|
20
|
-
cls._instance.__init__()
|
|
21
20
|
return cls._instance
|
|
22
21
|
|
|
23
22
|
def __init__(self):
|
|
@@ -47,7 +46,7 @@ class KdbManager:
|
|
|
47
46
|
:type path: str
|
|
48
47
|
"""
|
|
49
48
|
with pykx.QConnection(self.host, self.port, username=self.username, password=self.password) as conn:
|
|
50
|
-
conn(f
|
|
49
|
+
conn(f'.path.mkdir "{path}"')
|
|
51
50
|
|
|
52
51
|
def write(self, table_name: str, data: pd.DataFrame, path: str = "", splayed: bool = False):
|
|
53
52
|
"""
|
|
@@ -69,7 +68,9 @@ class KdbManager:
|
|
|
69
68
|
with pykx.QConnection(self.host, self.port, username=self.username, password=self.password) as conn:
|
|
70
69
|
conn(f"{{`:{real_path}/ set x}}", data)
|
|
71
70
|
|
|
72
|
-
def write_partitioned(
|
|
71
|
+
def write_partitioned(
|
|
72
|
+
self, table_name: str, data: pd.DataFrame, path: str = "", partition_func=None, key_column=None
|
|
73
|
+
):
|
|
73
74
|
"""
|
|
74
75
|
Writes data to the kdb database with partitioning.
|
|
75
76
|
|
|
@@ -89,16 +90,31 @@ class KdbManager:
|
|
|
89
90
|
assert "datetime" in data.columns, "datetime column not found"
|
|
90
91
|
data.sort_values(by="datetime", inplace=True)
|
|
91
92
|
if key_column is not None and key_column in data.columns:
|
|
92
|
-
with pykx.QConnection(
|
|
93
|
+
with pykx.QConnection(
|
|
94
|
+
host=self.host, port=self.port, username=self.username, password=self.password
|
|
95
|
+
) as conn:
|
|
93
96
|
for bucket, df in data.groupby(partition_func(data["datetime"])):
|
|
94
|
-
conn(
|
|
97
|
+
conn(
|
|
98
|
+
f"{{`{table_name} set x; .partable.createOrAppend[`:{path};{bucket};`{key_column};`{table_name}]}}",
|
|
99
|
+
df.reset_index(drop=True),
|
|
100
|
+
)
|
|
95
101
|
else:
|
|
96
|
-
with pykx.QConnection(
|
|
102
|
+
with pykx.QConnection(
|
|
103
|
+
host=self.host, port=self.port, username=self.username, password=self.password
|
|
104
|
+
) as conn:
|
|
97
105
|
for bucket, df in data.groupby(by=partition_func(data["datetime"])):
|
|
98
|
-
conn(f
|
|
99
|
-
|
|
100
|
-
def read_partitioned(
|
|
101
|
-
|
|
106
|
+
conn(f"{{`{table_name} set x;.Q.dpt[`:{path};{bucket};`{table_name}]}}", df.reset_index(drop=True))
|
|
107
|
+
|
|
108
|
+
def read_partitioned(
|
|
109
|
+
self,
|
|
110
|
+
table_name: str,
|
|
111
|
+
path: str = "",
|
|
112
|
+
fields=None,
|
|
113
|
+
start_time=None,
|
|
114
|
+
end_time=None,
|
|
115
|
+
partition_func=None,
|
|
116
|
+
other_conditions=None,
|
|
117
|
+
):
|
|
102
118
|
"""
|
|
103
119
|
Reads data from the kdb database with partitioning.
|
|
104
120
|
|
|
@@ -120,31 +136,39 @@ class KdbManager:
|
|
|
120
136
|
end_time_str = end_time.strftime(time_format) if end_time is not None else None
|
|
121
137
|
if isinstance(fields, (bytes, str)):
|
|
122
138
|
fields = [fields]
|
|
123
|
-
select_clause =
|
|
139
|
+
select_clause = (
|
|
140
|
+
f"select from {table_name}" if fields is None else f"select {','.join(fields)} from {table_name}"
|
|
141
|
+
)
|
|
124
142
|
where_cond = ""
|
|
125
143
|
if start_time_str is not None and end_time_str is not None:
|
|
126
144
|
where_cond += f"datetime within ({start_time_str};{end_time_str})"
|
|
127
145
|
if partition_func is not None:
|
|
128
|
-
where_cond =
|
|
146
|
+
where_cond = (
|
|
147
|
+
f"int in {' '.join(str(x) for x in range(partition_func(start_time), partition_func(end_time) + 1))}"
|
|
148
|
+
+ ","
|
|
149
|
+
+ where_cond
|
|
150
|
+
)
|
|
129
151
|
elif start_time_str is not None:
|
|
130
152
|
where_cond += f"datetime>={start_time_str}"
|
|
131
153
|
if partition_func is not None:
|
|
132
|
-
where_cond = f"int>={partition_func(start_time)}" +
|
|
154
|
+
where_cond = f"int>={partition_func(start_time)}" + "," + where_cond
|
|
133
155
|
elif end_time_str is not None:
|
|
134
156
|
where_cond += f"datetime<={end_time_str}"
|
|
135
157
|
if partition_func is not None:
|
|
136
|
-
where_cond = f"int<= {partition_func(end_time)}" +
|
|
158
|
+
where_cond = f"int<= {partition_func(end_time)}" + "," + where_cond
|
|
137
159
|
if other_conditions is not None:
|
|
138
|
-
where_cond = other_conditions if where_cond == "" else where_cond +
|
|
160
|
+
where_cond = other_conditions if where_cond == "" else where_cond + "," + other_conditions
|
|
139
161
|
if where_cond:
|
|
140
162
|
where_cond = " where " + where_cond
|
|
141
163
|
final_query = select_clause + where_cond
|
|
142
164
|
|
|
143
165
|
with pykx.QConnection(host=self.host, port=self.port, username=self.username, password=self.password) as conn:
|
|
144
|
-
conn(
|
|
166
|
+
conn(
|
|
167
|
+
"`currpath__ set .path.pwd[]"
|
|
168
|
+
) # save current path to currpath__ as following command will change the path
|
|
145
169
|
try:
|
|
146
170
|
conn(f"\\l {path}") # load the path
|
|
147
171
|
q_table = conn(final_query)
|
|
148
172
|
return q_table.pd().set_index("datetime")
|
|
149
173
|
finally:
|
|
150
|
-
conn('system "cd ", currpath__')
|
|
174
|
+
conn('system "cd ", currpath__')
|
|
@@ -7,10 +7,11 @@
|
|
|
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,
|
|
10
|
+
from typing import Any, Literal, Sequence, Union
|
|
11
11
|
|
|
12
12
|
import pandas as pd
|
|
13
|
-
from sqlalchemy import
|
|
13
|
+
from sqlalchemy import Index, MetaData, Table, Column, create_engine, inspect, select, sql, text
|
|
14
|
+
from sqlalchemy.types import TypeEngine
|
|
14
15
|
from sqlalchemy.dialects.postgresql import insert
|
|
15
16
|
|
|
16
17
|
from ...config import CONFIG
|
|
@@ -41,6 +42,13 @@ class SqlManager:
|
|
|
41
42
|
|
|
42
43
|
def __init__(self):
|
|
43
44
|
self.engine = create_engine(CONFIG["sqlconnstr"])
|
|
45
|
+
self._inspector = None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def inspector(self):
|
|
49
|
+
if self._inspector is None:
|
|
50
|
+
self._inspector = inspect(self.engine)
|
|
51
|
+
return self._inspector
|
|
44
52
|
|
|
45
53
|
def _execute(self, sql_executable: Union[str, sql.base.Executable]) -> Any:
|
|
46
54
|
if isinstance(sql_executable, str):
|
|
@@ -94,8 +102,7 @@ class SqlManager:
|
|
|
94
102
|
:rtype: int
|
|
95
103
|
"""
|
|
96
104
|
if_exists: Literal["replace", "append"] = "append"
|
|
97
|
-
|
|
98
|
-
new_table = not inspector.has_table(table_name)
|
|
105
|
+
new_table = not self.inspector.has_table(table_name)
|
|
99
106
|
method = partial(_insert_on_conflict_update, indexes=df.index.names) if upsert and not new_table else None
|
|
100
107
|
num_rows = df.to_sql(
|
|
101
108
|
table_name,
|
|
@@ -113,17 +120,44 @@ class SqlManager:
|
|
|
113
120
|
self.add_index(table_name, column, unique=False)
|
|
114
121
|
return num_rows
|
|
115
122
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
def table_exists(self, table_name: str) -> bool:
|
|
124
|
+
"""
|
|
125
|
+
Checks if a table exists.
|
|
126
|
+
|
|
127
|
+
:param table_name: The name of the table to check for.
|
|
128
|
+
:type table_name: str
|
|
129
|
+
:return: True if the table exists, False otherwise.
|
|
130
|
+
:rtype: bool
|
|
131
|
+
"""
|
|
132
|
+
return self.inspector.has_table(table_name)
|
|
133
|
+
|
|
134
|
+
def create_table(
|
|
135
|
+
self,
|
|
136
|
+
table_name: str,
|
|
137
|
+
table_columns: list[tuple[str, TypeEngine]],
|
|
138
|
+
unique_index_columns: Sequence[str] = (),
|
|
139
|
+
primary_key: Union[str, set[str]] = set(),
|
|
140
|
+
):
|
|
141
|
+
"""
|
|
142
|
+
Creates a table.
|
|
143
|
+
|
|
144
|
+
:param table_name: The name of the table to create.
|
|
145
|
+
:type table_name: str
|
|
146
|
+
: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.
|
|
147
|
+
:type table_columns: dict[str, Type]
|
|
148
|
+
:param unique_index_columns: Columns to enforce unique values on. Defaults to an empty sequence.
|
|
149
|
+
:type unique_index_columns: Sequence[str]
|
|
150
|
+
:param primary_key: The primary key(s) of the table. Defaults to an empty set.
|
|
151
|
+
:type primary_key: Union[str,set[str]]
|
|
152
|
+
"""
|
|
153
|
+
table_meta = MetaData()
|
|
154
|
+
if isinstance(primary_key, str):
|
|
155
|
+
primary_key = {primary_key}
|
|
156
|
+
columns = [Column(name, col_type, primary_key=name in primary_key) for name, col_type in table_columns]
|
|
157
|
+
table = Table(table_name, table_meta, *columns)
|
|
158
|
+
table.create(self.engine)
|
|
159
|
+
for column in unique_index_columns:
|
|
160
|
+
self.add_index(table_name, column, unique=True)
|
|
127
161
|
|
|
128
162
|
def insert_column(self, table_name: str, column_name: str, column_type: str):
|
|
129
163
|
"""
|
|
@@ -314,8 +348,14 @@ class SqlManager:
|
|
|
314
348
|
res = self._execute(stmt)
|
|
315
349
|
return pd.DataFrame(res.fetchall(), columns=res.keys())
|
|
316
350
|
|
|
317
|
-
def create_ts_table(
|
|
318
|
-
|
|
351
|
+
def create_ts_table(
|
|
352
|
+
self,
|
|
353
|
+
table_name: str,
|
|
354
|
+
time_column: str,
|
|
355
|
+
table_columns: dict[str, str],
|
|
356
|
+
time_chunk_interval: str = "1 year",
|
|
357
|
+
other_chunkers: dict[str, int] = None,
|
|
358
|
+
):
|
|
319
359
|
"""
|
|
320
360
|
Creates a table with a timestamp column.
|
|
321
361
|
|
|
@@ -330,19 +370,25 @@ class SqlManager:
|
|
|
330
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.
|
|
331
371
|
:type other_chunkers: Dict[str, int], optional
|
|
332
372
|
"""
|
|
333
|
-
query_create_sensordata_table
|
|
373
|
+
query_create_sensordata_table = f"""
|
|
334
374
|
CREATE TABLE {table_name} (
|
|
335
375
|
{time_column} TIMESTAMP NOT NULL,
|
|
336
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()])}
|
|
337
377
|
);
|
|
338
378
|
"""
|
|
339
|
-
query_create_sensordata_hypertable =
|
|
379
|
+
query_create_sensordata_hypertable = (
|
|
380
|
+
f"SELECT create_hypertable('{table_name}', by_range('{time_column}', INTERVAL '{time_chunk_interval}'));"
|
|
381
|
+
)
|
|
340
382
|
if other_chunkers is not None:
|
|
341
383
|
for column_name, num_hash in other_chunkers.items():
|
|
342
|
-
query_create_sensordata_hypertable +=
|
|
384
|
+
query_create_sensordata_hypertable += (
|
|
385
|
+
f"\nSELECT add_dimension('{table_name}', by_hash('{column_name}', {num_hash}));"
|
|
386
|
+
)
|
|
343
387
|
|
|
344
388
|
uidx_name = f"uix_{table_name}_{'_'.join(other_chunkers.keys())}_{time_column}"
|
|
345
|
-
query_unique_index =
|
|
389
|
+
query_unique_index = (
|
|
390
|
+
f"CREATE UNIQUE INDEX {uidx_name} ON {table_name} ({', '.join(other_chunkers.keys())}, {time_column});"
|
|
391
|
+
)
|
|
346
392
|
with self.engine.connect() as conn:
|
|
347
393
|
conn.execute(text(query_create_sensordata_table))
|
|
348
394
|
conn.execute(text(query_create_sensordata_hypertable))
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
}
|
|
@@ -11,6 +11,8 @@ import pandas as pd
|
|
|
11
11
|
|
|
12
12
|
from ..core.sql.sqlmanager import SqlManager
|
|
13
13
|
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
|
+
|
|
14
16
|
|
|
15
17
|
COMMON_METADATA_COLUMNS = [
|
|
16
18
|
"name",
|
|
@@ -26,7 +28,9 @@ COMMON_METADATA_COLUMNS = [
|
|
|
26
28
|
"delisted_date",
|
|
27
29
|
]
|
|
28
30
|
TYPE_METADATA_COLUMNS = {
|
|
29
|
-
"STK": ["
|
|
31
|
+
"STK": ["country", "state", "board_type", "issue_price"],
|
|
32
|
+
"ETF": ["issuer", "current_mgr", "custodian", "issuer_country", "fund_type", "benchmark"],
|
|
33
|
+
"LOF": ["issuer", "current_mgr", "custodian", "issuer_country", "fund_type", "benchmark"],
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
|
|
@@ -46,6 +50,24 @@ class MetadataSql:
|
|
|
46
50
|
cls._manager = SqlManager()
|
|
47
51
|
return cls._instance
|
|
48
52
|
|
|
53
|
+
def initialize(self, for_inst_types="all"):
|
|
54
|
+
if for_inst_types == "all":
|
|
55
|
+
for_inst_types = list(TYPE_METADATA_COLUMNS.keys())
|
|
56
|
+
elif isinstance(for_inst_types, str):
|
|
57
|
+
for_inst_types = [for_inst_types]
|
|
58
|
+
columns_base = [("ticker", String(10)), ("exchange", String(10))]
|
|
59
|
+
if not self._manager.table_exists("instruments"):
|
|
60
|
+
columns = columns_base + [(col, FIELD_DATA_TYPE_SQL.get(col, String())) for col in COMMON_METADATA_COLUMNS]
|
|
61
|
+
self._manager.create_table("instruments", columns, {"primary_key": ["ticker", "exchange"]})
|
|
62
|
+
for inst_type in for_inst_types:
|
|
63
|
+
if not self._manager.table_exists(f"instruments_{inst_type.lower()}"):
|
|
64
|
+
columns = columns_base + [
|
|
65
|
+
(col, FIELD_DATA_TYPE_SQL.get(col, String())) for col in TYPE_METADATA_COLUMNS[inst_type]
|
|
66
|
+
]
|
|
67
|
+
self._manager.create_table(
|
|
68
|
+
f"instruments_{inst_type.lower()}", columns, primary_key={"ticker", "exchange"}
|
|
69
|
+
)
|
|
70
|
+
|
|
49
71
|
def update_instrument_metadata(self, data: Union[pd.DataFrame, list[dict], dict]):
|
|
50
72
|
"""
|
|
51
73
|
Updates the instrument metadata in the database.
|
|
@@ -70,10 +92,12 @@ class MetadataSql:
|
|
|
70
92
|
|
|
71
93
|
self._manager.insert("instruments", data_common, upsert=True)
|
|
72
94
|
if "inst_type" in data.columns:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if not data_type_df.empty:
|
|
76
|
-
|
|
95
|
+
g = data.groupby("inst_type", group_keys=False)
|
|
96
|
+
for inst_type, data_type_df in g:
|
|
97
|
+
if not data_type_df.empty and inst_type in TYPE_METADATA_COLUMNS:
|
|
98
|
+
columns = data_type_df.columns.intersection(TYPE_METADATA_COLUMNS[inst_type])
|
|
99
|
+
if not columns.empty:
|
|
100
|
+
self._manager.insert(f"instruments_{inst_type.lower()}", data_type_df[columns], upsert=True)
|
|
77
101
|
|
|
78
102
|
def _convert_datetime_columns(self, data: pd.DataFrame):
|
|
79
103
|
for col in ["listed_date", "delisted_date"]:
|
|
@@ -132,8 +156,12 @@ class MetadataSql:
|
|
|
132
156
|
res = {}
|
|
133
157
|
for inst_type, common_df_by_type in common_df.groupby("inst_type"):
|
|
134
158
|
inst_type = cast(INST_TYPE_LITERALS, inst_type)
|
|
135
|
-
if all_fields_common:
|
|
136
|
-
res[inst_type] =
|
|
159
|
+
if all_fields_common or inst_type not in TYPE_METADATA_COLUMNS:
|
|
160
|
+
res[inst_type] = (
|
|
161
|
+
common_df_by_type.set_index(["ticker", "exchange"])
|
|
162
|
+
if len(common_df_by_type.columns) > 2
|
|
163
|
+
else common_df_by_type
|
|
164
|
+
)
|
|
137
165
|
continue
|
|
138
166
|
query_fields_type = (
|
|
139
167
|
["ticker", "exchange"] + [f for f in query_fields if f in TYPE_METADATA_COLUMNS[inst_type]]
|
|
@@ -187,7 +215,7 @@ class MetadataSql:
|
|
|
187
215
|
query_fields_cross = "*"
|
|
188
216
|
else:
|
|
189
217
|
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[
|
|
218
|
+
query_fields_type = [f for f in query_fields if f in TYPE_METADATA_COLUMNS.get(inst_type, [])]
|
|
191
219
|
query_fields_cross = {
|
|
192
220
|
"instruments": query_fields_common,
|
|
193
221
|
f"instruments_{inst_type.lower()}": query_fields_type,
|
|
@@ -196,19 +224,23 @@ class MetadataSql:
|
|
|
196
224
|
filter_fields_common = {
|
|
197
225
|
k: v for k, v in filter_fields.items() if k in ["ticker", "exchange"] + COMMON_METADATA_COLUMNS
|
|
198
226
|
}
|
|
199
|
-
filter_fields_type = {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
227
|
+
filter_fields_type = {k: v for k, v in filter_fields.items() if k in TYPE_METADATA_COLUMNS.get(inst_type, [])}
|
|
228
|
+
|
|
229
|
+
if (query_fields != "*" or inst_type not in TYPE_METADATA_COLUMNS) and not bool(query_fields_type) and not bool(filter_fields_type):
|
|
230
|
+
df = self._manager.read_data(
|
|
231
|
+
"instruments", query_fields=query_fields_common, filter_fields=filter_fields_common
|
|
232
|
+
)
|
|
233
|
+
else:
|
|
234
|
+
filter_fields_cross = {
|
|
235
|
+
"instruments": filter_fields_common,
|
|
236
|
+
f"instruments_{inst_type.lower()}": filter_fields_type,
|
|
237
|
+
}
|
|
238
|
+
df = self._manager.read_data_across_tables(
|
|
239
|
+
["instruments", f"instruments_{inst_type.lower()}"],
|
|
240
|
+
joined_columns=["ticker", "exchange"],
|
|
241
|
+
query_fields=query_fields_cross,
|
|
242
|
+
filter_fields=filter_fields_cross,
|
|
243
|
+
)
|
|
212
244
|
|
|
213
245
|
if isinstance(df.columns, pd.Index):
|
|
214
246
|
df = df.loc[:, ~df.columns.duplicated()]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: trade_database_manager
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6.dev0
|
|
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,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
|
-
}
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|