├── .flake8
├── .github
├── CODE_OF_CONDUCT.md
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
├── SUPPORT.md
└── workflows
│ └── pythonapp.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── pyproject.toml
└── vnpy_sqlite
├── __init__.py
└── sqlite_database.py
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | exclude = venv,build,__pycache__,__init__.py,ib,talib,uic
3 | ignore =
4 | E501 line too long, fixed by black
5 | W503 line break before binary operator
6 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # 行为准则
2 |
3 | 这是一份VeighNa项目社区的行为准则,也是项目作者自己在刚入行量化金融行业时对于理想中的社区的期望:
4 |
5 | * 为交易员而生:作为一款从金融机构量化业务中诞生的交易系统开发框架,设计上都优先满足机构专业交易员的使用习惯,而不是其他用户(散户、爱好者、技术人员等)
6 |
7 | * 对新用户友好,保持耐心:大部分人在接触新东西的时候都是磕磕碰碰、有很多的问题,请记住此时别人对你伸出的援助之手,并把它传递给未来需要的人
8 |
9 | * 尊重他人,慎重言行:礼貌文明的交流方式除了能得到别人同样的回应,更能减少不必要的摩擦,保证高效的交流
10 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## 环境
2 |
3 | * 操作系统: 如Windows 11或者Ubuntu 22.04
4 | * Python版本: 如VeighNa Studio-4.0.0
5 | * VeighNa版本: 如v4.0.0发行版或者dev branch 20250320(下载日期)
6 |
7 | ## Issue类型
8 | 三选一:Bug/Enhancement/Question
9 |
10 | ## 预期程序行为
11 |
12 |
13 | ## 实际程序行为
14 |
15 |
16 | ## 重现步骤
17 |
18 | 针对Bug类型Issue,请提供具体重现步骤以及报错截图
19 |
20 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | 建议每次发起的PR内容尽可能精简,复杂的修改请拆分为多次PR,便于管理合并。
2 |
3 | ## 改进内容
4 |
5 | 1.
6 | 2.
7 | 3.
8 |
9 | ## 相关的Issue号(如有)
10 |
11 | Close #
--------------------------------------------------------------------------------
/.github/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # 获取帮助
2 |
3 | 在开发和使用VeighNa项目的过程中遇到问题时,获取帮助的渠道包括:
4 |
5 | * Github Issues:[Issues页面](https://github.com/vnpy/vnpy/issues)
6 | * 官方QQ群: 262656087
7 | * 项目论坛:[VeighNa量化社区](http://www.vnpy.com/forum)
8 | * 项目邮箱: vn.py@foxmail.com
9 |
--------------------------------------------------------------------------------
/.github/workflows/pythonapp.yml:
--------------------------------------------------------------------------------
1 | name: Python application
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: windows-latest
9 |
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Set up Python 3.13
13 | uses: actions/setup-python@v1
14 | with:
15 | python-version: '3.13'
16 | - name: Install dependencies
17 | run: |
18 | python -m pip install --upgrade pip
19 | pip install ta-lib==0.6.3 --index=https://pypi.vnpy.com
20 | pip install vnpy ruff mypy uv
21 | - name: Lint with ruff
22 | run: |
23 | # Run ruff linter based on pyproject.toml configuration
24 | ruff check .
25 | - name: Type check with mypy
26 | run: |
27 | # Run mypy type checking based on pyproject.toml configuration
28 | mypy vnpy_sqlite
29 | - name: Build packages with uv
30 | run: |
31 | # Build source distribution and wheel distribution
32 | uv build
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 1.1.0版本
2 |
3 | 1. vnpy框架4.0版本升级适配
4 |
5 | # 1.0.1版本
6 |
7 | 1. 增加Tick数据汇总信息支持
8 | 2. 增加写入数据时的流式参数stream支持
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present, Xiaoyou Chen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VeighNa框架的SQLite数据库接口
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## 说明
14 |
15 | 基于peewee开发的SQLite数据库接口,无需另外安装配置数据库软件,易于使用适合初学者用户。
16 |
17 | ## 使用
18 |
19 | 在VeighNa中使用SQLite时,需要在全局配置中填写以下字段信息:
20 |
21 | |名称|含义|必填|举例|
22 | |---------|----|---|---|
23 | |database.name|名称|是|sqlite|
24 | |database.database|实例|是|database.db|
25 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "vnpy_sqlite"
3 | dynamic = ["version"]
4 | description = "SQLite database adapter for VeighNa quant trading framework."
5 | readme = "README.md"
6 | license = {text = "MIT"}
7 | authors = [{name = "Xiaoyou Chen", email = "xiaoyou.chen@mail.vnpy.com"}]
8 | classifiers = [
9 | "Development Status :: 5 - Production/Stable",
10 | "License :: OSI Approved :: MIT License",
11 | "Operating System :: OS Independent",
12 | "Programming Language :: Python :: 3",
13 | "Programming Language :: Python :: 3.10",
14 | "Programming Language :: Python :: 3.11",
15 | "Programming Language :: Python :: 3.12",
16 | "Programming Language :: Python :: 3.13",
17 | "Topic :: Office/Business :: Financial :: Investment",
18 | "Programming Language :: Python :: Implementation :: CPython",
19 | "Natural Language :: Chinese (Simplified)",
20 | "Typing :: Typed"
21 | ]
22 | requires-python = ">=3.10"
23 | dependencies = [
24 | "peewee>=3.17.9",
25 | ]
26 | keywords = ["quant", "quantitative", "investment", "trading", "algotrading"]
27 |
28 | [project.urls]
29 | "Homepage" = "https://www.vnpy.com"
30 | "Documentation" = "https://www.vnpy.com/docs"
31 | "Changes" = "https://github.com/vnpy/vnpy_sqlite/blob/master/CHANGELOG.md"
32 | "Source" = "https://github.com/vnpy/vnpy_sqlite/"
33 | "Forum" = "https://www.vnpy.com/forum"
34 |
35 | [build-system]
36 | requires = ["hatchling>=1.27.0"]
37 | build-backend = "hatchling.build"
38 |
39 | [tool.hatch.version]
40 | path = "vnpy_sqlite/__init__.py"
41 | pattern = "__version__ = ['\"](?P[^'\"]+)['\"]"
42 |
43 | [tool.hatch.build.targets.wheel]
44 | packages = ["vnpy_sqlite"]
45 | include-package-data = true
46 |
47 | [tool.hatch.build.targets.sdist]
48 | include = ["vnpy_sqlite*"]
49 |
50 | [tool.ruff]
51 | target-version = "py310"
52 | output-format = "full"
53 |
54 | [tool.ruff.lint]
55 | select = [
56 | "B", # flake8-bugbear
57 | "E", # pycodestyle error
58 | "F", # pyflakes
59 | "UP", # pyupgrade
60 | "W", # pycodestyle warning
61 | ]
62 | ignore = ["E501"]
63 |
64 | [tool.mypy]
65 | python_version = "3.10"
66 | warn_return_any = true
67 | warn_unused_configs = true
68 | disallow_untyped_defs = true
69 | disallow_incomplete_defs = true
70 | check_untyped_defs = true
71 | disallow_untyped_decorators = true
72 | no_implicit_optional = true
73 | strict_optional = true
74 | warn_redundant_casts = true
75 | warn_unused_ignores = true
76 | warn_no_return = true
77 | ignore_missing_imports = true
--------------------------------------------------------------------------------
/vnpy_sqlite/__init__.py:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 | #
3 | # Copyright (c) 2015-present, Xiaoyou Chen
4 | #
5 | # Permission is hereby granted, free of charge, to any person obtaining a copy
6 | # of this software and associated documentation files (the "Software"), to deal
7 | # in the Software without restriction, including without limitation the rights
8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | # copies of the Software, and to permit persons to whom the Software is
10 | # furnished to do so, subject to the following conditions:
11 | #
12 | # The above copyright notice and this permission notice shall be included in all
13 | # copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | # SOFTWARE.
22 |
23 |
24 | from .sqlite_database import SqliteDatabase as Database
25 |
26 |
27 | __all__ = ["Database"]
28 |
29 |
30 | __version__ = "1.1.0"
31 |
--------------------------------------------------------------------------------
/vnpy_sqlite/sqlite_database.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 |
3 | from peewee import (
4 | AutoField,
5 | CharField,
6 | DateTimeField,
7 | FloatField, IntegerField,
8 | Model,
9 | SqliteDatabase as PeeweeSqliteDatabase,
10 | ModelSelect,
11 | ModelDelete,
12 | chunked,
13 | fn
14 | )
15 |
16 | from vnpy.trader.constant import Exchange, Interval
17 | from vnpy.trader.object import BarData, TickData
18 | from vnpy.trader.utility import get_file_path
19 | from vnpy.trader.database import (
20 | BaseDatabase,
21 | BarOverview,
22 | DB_TZ,
23 | TickOverview,
24 | convert_tz
25 | )
26 |
27 |
28 | path: CharField = str(get_file_path("database.db"))
29 | db: PeeweeSqliteDatabase = PeeweeSqliteDatabase(path)
30 |
31 |
32 | class DbBarData(Model):
33 | """K线数据表映射对象"""
34 |
35 | id: AutoField = AutoField()
36 |
37 | symbol: CharField = CharField()
38 | exchange: CharField = CharField()
39 | datetime: DateTimeField = DateTimeField()
40 | interval: CharField = CharField()
41 |
42 | volume: FloatField = FloatField()
43 | turnover: FloatField = FloatField()
44 | open_interest: FloatField = FloatField()
45 | open_price: FloatField = FloatField()
46 | high_price: FloatField = FloatField()
47 | low_price: FloatField = FloatField()
48 | close_price: FloatField = FloatField()
49 |
50 | class Meta:
51 | database: PeeweeSqliteDatabase = db
52 | indexes: tuple = ((("symbol", "exchange", "interval", "datetime"), True),)
53 |
54 |
55 | class DbTickData(Model):
56 | """TICK数据表映射对象"""
57 |
58 | id: AutoField = AutoField()
59 |
60 | symbol: CharField = CharField()
61 | exchange: CharField = CharField()
62 | datetime: DateTimeField = DateTimeField()
63 |
64 | name: CharField = CharField()
65 | volume: FloatField = FloatField()
66 | turnover: FloatField = FloatField()
67 | open_interest: FloatField = FloatField()
68 | last_price: FloatField = FloatField()
69 | last_volume: FloatField = FloatField()
70 | limit_up: FloatField = FloatField()
71 | limit_down: FloatField = FloatField()
72 |
73 | open_price: FloatField = FloatField()
74 | high_price: FloatField = FloatField()
75 | low_price: FloatField = FloatField()
76 | pre_close: FloatField = FloatField()
77 |
78 | bid_price_1: FloatField = FloatField()
79 | bid_price_2: FloatField = FloatField(null=True)
80 | bid_price_3: FloatField = FloatField(null=True)
81 | bid_price_4: FloatField = FloatField(null=True)
82 | bid_price_5: FloatField = FloatField(null=True)
83 |
84 | ask_price_1: FloatField = FloatField()
85 | ask_price_2: FloatField = FloatField(null=True)
86 | ask_price_3: FloatField = FloatField(null=True)
87 | ask_price_4: FloatField = FloatField(null=True)
88 | ask_price_5: FloatField = FloatField(null=True)
89 |
90 | bid_volume_1: FloatField = FloatField()
91 | bid_volume_2: FloatField = FloatField(null=True)
92 | bid_volume_3: FloatField = FloatField(null=True)
93 | bid_volume_4: FloatField = FloatField(null=True)
94 | bid_volume_5: FloatField = FloatField(null=True)
95 |
96 | ask_volume_1: FloatField = FloatField()
97 | ask_volume_2: FloatField = FloatField(null=True)
98 | ask_volume_3: FloatField = FloatField(null=True)
99 | ask_volume_4: FloatField = FloatField(null=True)
100 | ask_volume_5: FloatField = FloatField(null=True)
101 |
102 | localtime: DateTimeField = DateTimeField(null=True)
103 |
104 | class Meta:
105 | database: PeeweeSqliteDatabase = db
106 | indexes: tuple = ((("symbol", "exchange", "datetime"), True),)
107 |
108 |
109 | class DbBarOverview(Model):
110 | """K线汇总数据表映射对象"""
111 |
112 | id: AutoField = AutoField()
113 |
114 | symbol: CharField = CharField()
115 | exchange: CharField = CharField()
116 | interval: CharField = CharField()
117 | count: int = IntegerField()
118 | start: DateTimeField = DateTimeField()
119 | end: DateTimeField = DateTimeField()
120 |
121 | class Meta:
122 | database: PeeweeSqliteDatabase = db
123 | indexes: tuple = ((("symbol", "exchange", "interval"), True),)
124 |
125 |
126 | class DbTickOverview(Model):
127 | """Tick汇总数据表映射对象"""
128 |
129 | id: AutoField = AutoField()
130 |
131 | symbol: CharField = CharField()
132 | exchange: CharField = CharField()
133 | count: int = IntegerField()
134 | start: DateTimeField = DateTimeField()
135 | end: DateTimeField = DateTimeField()
136 |
137 | class Meta:
138 | database: PeeweeSqliteDatabase = db
139 | indexes: tuple = ((("symbol", "exchange"), True),)
140 |
141 |
142 | class SqliteDatabase(BaseDatabase):
143 | """SQLite数据库接口"""
144 |
145 | def __init__(self) -> None:
146 | """"""
147 | self.db: PeeweeSqliteDatabase = db
148 | self.db.connect()
149 | self.db.create_tables([DbBarData, DbTickData, DbBarOverview, DbTickOverview])
150 |
151 | def save_bar_data(self, bars: list[BarData], stream: bool = False) -> bool:
152 | """保存K线数据"""
153 | # 读取主键参数
154 | bar: BarData = bars[0]
155 | symbol: CharField = bar.symbol
156 | exchange: Exchange = bar.exchange
157 | interval: Interval = bar.interval
158 |
159 | # 将BarData数据转换为字典,并调整时区
160 | data: list = []
161 |
162 | for bar in bars:
163 | bar.datetime = convert_tz(bar.datetime)
164 |
165 | d: dict = bar.__dict__
166 | d["exchange"] = d["exchange"].value
167 | d["interval"] = d["interval"].value
168 | d.pop("gateway_name")
169 | d.pop("vt_symbol")
170 | d.pop("extra")
171 | data.append(d)
172 |
173 | # 使用upsert操作将数据更新到数据库中
174 | with self.db.atomic():
175 | for c in chunked(data, 50):
176 | DbBarData.insert_many(c).on_conflict_replace().execute()
177 |
178 | # 更新K线汇总数据
179 | overview: DbBarOverview = DbBarOverview.get_or_none(
180 | DbBarOverview.symbol == symbol,
181 | DbBarOverview.exchange == exchange.value,
182 | DbBarOverview.interval == interval.value,
183 | )
184 |
185 | if not overview:
186 | overview = DbBarOverview()
187 | overview.symbol = symbol
188 | overview.exchange = exchange.value
189 | overview.interval = interval.value
190 | overview.start = bars[0].datetime
191 | overview.end = bars[-1].datetime
192 | overview.count = len(bars)
193 | elif stream:
194 | overview.end = bars[-1].datetime
195 | overview.count += len(bars)
196 | else:
197 | overview.start = min(bars[0].datetime, overview.start)
198 | overview.end = max(bars[-1].datetime, overview.end)
199 |
200 | s: ModelSelect = DbBarData.select().where(
201 | (DbBarData.symbol == symbol)
202 | & (DbBarData.exchange == exchange.value)
203 | & (DbBarData.interval == interval.value)
204 | )
205 | overview.count = s.count()
206 |
207 | overview.save()
208 |
209 | return True
210 |
211 | def save_tick_data(self, ticks: list[TickData], stream: bool = False) -> bool:
212 | """保存TICK数据"""
213 | # 读取主键参数
214 | tick: TickData = ticks[0]
215 | symbol: CharField = tick.symbol
216 | exchange: Exchange = tick.exchange
217 |
218 | # 将TickData数据转换为字典,并调整时区
219 | data: list = []
220 |
221 | for tick in ticks:
222 | tick.datetime = convert_tz(tick.datetime)
223 |
224 | d: dict = tick.__dict__
225 | d["exchange"] = d["exchange"].value
226 | d.pop("gateway_name")
227 | d.pop("vt_symbol")
228 | d.pop("extra")
229 | data.append(d)
230 |
231 | # 使用upsert操作将数据更新到数据库中
232 | with self.db.atomic():
233 | for c in chunked(data, 10):
234 | DbTickData.insert_many(c).on_conflict_replace().execute()
235 |
236 | # 更新Tick汇总数据
237 | overview: DbTickOverview = DbTickOverview.get_or_none(
238 | DbTickOverview.symbol == symbol,
239 | DbTickOverview.exchange == exchange.value,
240 | )
241 |
242 | if not overview:
243 | overview = DbTickOverview()
244 | overview.symbol = symbol
245 | overview.exchange = exchange.value
246 | overview.start = ticks[0].datetime
247 | overview.end = ticks[-1].datetime
248 | overview.count = len(ticks)
249 | elif stream:
250 | overview.end = ticks[-1].datetime
251 | overview.count += len(ticks)
252 | else:
253 | overview.start = min(ticks[0].datetime, overview.start)
254 | overview.end = max(ticks[-1].datetime, overview.end)
255 |
256 | s: ModelSelect = DbTickData.select().where(
257 | (DbTickData.symbol == symbol)
258 | & (DbTickData.exchange == exchange.value)
259 | )
260 | overview.count = s.count()
261 |
262 | overview.save()
263 |
264 | return True
265 |
266 | def load_bar_data(
267 | self,
268 | symbol: str,
269 | exchange: Exchange,
270 | interval: Interval,
271 | start: datetime,
272 | end: datetime
273 | ) -> list[BarData]:
274 | """读取K线数据"""
275 | s: ModelSelect = (
276 | DbBarData.select().where(
277 | (DbBarData.symbol == symbol)
278 | & (DbBarData.exchange == exchange.value)
279 | & (DbBarData.interval == interval.value)
280 | & (DbBarData.datetime >= start)
281 | & (DbBarData.datetime <= end)
282 | ).order_by(DbBarData.datetime)
283 | )
284 |
285 | bars: list[BarData] = []
286 | for db_bar in s:
287 | bar: BarData = BarData(
288 | symbol=db_bar.symbol,
289 | exchange=Exchange(db_bar.exchange),
290 | datetime=datetime.fromtimestamp(db_bar.datetime.timestamp(), DB_TZ),
291 | interval=Interval(db_bar.interval),
292 | volume=db_bar.volume,
293 | turnover=db_bar.turnover,
294 | open_interest=db_bar.open_interest,
295 | open_price=db_bar.open_price,
296 | high_price=db_bar.high_price,
297 | low_price=db_bar.low_price,
298 | close_price=db_bar.close_price,
299 | gateway_name="DB"
300 | )
301 | bars.append(bar)
302 |
303 | return bars
304 |
305 | def load_tick_data(
306 | self,
307 | symbol: str,
308 | exchange: Exchange,
309 | start: datetime,
310 | end: datetime
311 | ) -> list[TickData]:
312 | """读取TICK数据"""
313 | s: ModelSelect = (
314 | DbTickData.select().where(
315 | (DbTickData.symbol == symbol)
316 | & (DbTickData.exchange == exchange.value)
317 | & (DbTickData.datetime >= start)
318 | & (DbTickData.datetime <= end)
319 | ).order_by(DbTickData.datetime)
320 | )
321 |
322 | ticks: list[TickData] = []
323 | for db_tick in s:
324 | tick: TickData = TickData(
325 | symbol=db_tick.symbol,
326 | exchange=Exchange(db_tick.exchange),
327 | datetime=datetime.fromtimestamp(db_tick.datetime.timestamp(), DB_TZ),
328 | name=db_tick.name,
329 | volume=db_tick.volume,
330 | turnover=db_tick.turnover,
331 | open_interest=db_tick.open_interest,
332 | last_price=db_tick.last_price,
333 | last_volume=db_tick.last_volume,
334 | limit_up=db_tick.limit_up,
335 | limit_down=db_tick.limit_down,
336 | open_price=db_tick.open_price,
337 | high_price=db_tick.high_price,
338 | low_price=db_tick.low_price,
339 | pre_close=db_tick.pre_close,
340 | bid_price_1=db_tick.bid_price_1,
341 | bid_price_2=db_tick.bid_price_2,
342 | bid_price_3=db_tick.bid_price_3,
343 | bid_price_4=db_tick.bid_price_4,
344 | bid_price_5=db_tick.bid_price_5,
345 | ask_price_1=db_tick.ask_price_1,
346 | ask_price_2=db_tick.ask_price_2,
347 | ask_price_3=db_tick.ask_price_3,
348 | ask_price_4=db_tick.ask_price_4,
349 | ask_price_5=db_tick.ask_price_5,
350 | bid_volume_1=db_tick.bid_volume_1,
351 | bid_volume_2=db_tick.bid_volume_2,
352 | bid_volume_3=db_tick.bid_volume_3,
353 | bid_volume_4=db_tick.bid_volume_4,
354 | bid_volume_5=db_tick.bid_volume_5,
355 | ask_volume_1=db_tick.ask_volume_1,
356 | ask_volume_2=db_tick.ask_volume_2,
357 | ask_volume_3=db_tick.ask_volume_3,
358 | ask_volume_4=db_tick.ask_volume_4,
359 | ask_volume_5=db_tick.ask_volume_5,
360 | localtime=db_tick.localtime,
361 | gateway_name="DB"
362 | )
363 | ticks.append(tick)
364 |
365 | return ticks
366 |
367 | def delete_bar_data(
368 | self,
369 | symbol: str,
370 | exchange: Exchange,
371 | interval: Interval
372 | ) -> int:
373 | """删除K线数据"""
374 | d: ModelDelete = DbBarData.delete().where(
375 | (DbBarData.symbol == symbol)
376 | & (DbBarData.exchange == exchange.value)
377 | & (DbBarData.interval == interval.value)
378 | )
379 | count: int = d.execute()
380 |
381 | # 删除K线汇总数据
382 | d2: ModelDelete = DbBarOverview.delete().where(
383 | (DbBarOverview.symbol == symbol)
384 | & (DbBarOverview.exchange == exchange.value)
385 | & (DbBarOverview.interval == interval.value)
386 | )
387 | d2.execute()
388 |
389 | return count
390 |
391 | def delete_tick_data(
392 | self,
393 | symbol: str,
394 | exchange: Exchange
395 | ) -> int:
396 | """删除TICK数据"""
397 | d: ModelDelete = DbTickData.delete().where(
398 | (DbTickData.symbol == symbol)
399 | & (DbTickData.exchange == exchange.value)
400 | )
401 | count: int = d.execute()
402 |
403 | # 删除Tick汇总数据
404 | d2: ModelDelete = DbTickOverview.delete().where(
405 | (DbTickOverview.symbol == symbol)
406 | & (DbTickOverview.exchange == exchange.value)
407 | )
408 | d2.execute()
409 |
410 | return count
411 |
412 | def get_bar_overview(self) -> list[BarOverview]:
413 | """查询数据库中的K线汇总信息"""
414 | # 如果已有K线,但缺失汇总信息,则执行初始化
415 | data_count: int = DbBarData.select().count()
416 | overview_count: int = DbBarOverview.select().count()
417 | if data_count and not overview_count:
418 | self.init_bar_overview()
419 |
420 | s: ModelSelect = DbBarOverview.select()
421 | overviews: list[BarOverview] = []
422 | for overview in s:
423 | overview.exchange = Exchange(overview.exchange)
424 | overview.interval = Interval(overview.interval)
425 | overviews.append(overview)
426 | return overviews
427 |
428 | def get_tick_overview(self) -> list[TickOverview]:
429 | """查询数据库中的Tick汇总信息"""
430 | s: ModelSelect = DbTickOverview.select()
431 | overviews: list = []
432 | for overview in s:
433 | overview.exchange = Exchange(overview.exchange)
434 | overviews.append(overview)
435 | return overviews
436 |
437 | def init_bar_overview(self) -> None:
438 | """初始化数据库中的K线汇总信息"""
439 | s: ModelSelect = (
440 | DbBarData.select(
441 | DbBarData.symbol,
442 | DbBarData.exchange,
443 | DbBarData.interval,
444 | fn.COUNT(DbBarData.id).alias("count")
445 | ).group_by(
446 | DbBarData.symbol,
447 | DbBarData.exchange,
448 | DbBarData.interval
449 | )
450 | )
451 |
452 | for data in s:
453 | overview: DbBarOverview = DbBarOverview()
454 | overview.symbol = data.symbol
455 | overview.exchange = data.exchange
456 | overview.interval = data.interval
457 | overview.count = data.count
458 |
459 | start_bar: DbBarData = (
460 | DbBarData.select()
461 | .where(
462 | (DbBarData.symbol == data.symbol)
463 | & (DbBarData.exchange == data.exchange)
464 | & (DbBarData.interval == data.interval)
465 | )
466 | .order_by(DbBarData.datetime.asc())
467 | .first()
468 | )
469 | overview.start = start_bar.datetime
470 |
471 | end_bar: DbBarData = (
472 | DbBarData.select()
473 | .where(
474 | (DbBarData.symbol == data.symbol)
475 | & (DbBarData.exchange == data.exchange)
476 | & (DbBarData.interval == data.interval)
477 | )
478 | .order_by(DbBarData.datetime.desc())
479 | .first()
480 | )
481 | overview.end = end_bar.datetime
482 |
483 | overview.save()
484 |
--------------------------------------------------------------------------------