├── .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 | --------------------------------------------------------------------------------