From af7b43337045d7e4fa29bdbdebaae90a75ff66f5 Mon Sep 17 00:00:00 2001 From: "vn.py" Date: Wed, 20 Mar 2019 18:12:19 +0800 Subject: [PATCH] [Mod]improve code style of csv_loader --- check.py | 54 ---------- docs/.gitignore | 1 - docs/index.md | 1 - docs/locale/en/LC_MESSAGES/introduction.po | 23 ---- docs/locale/introduction.pot | 22 ---- docs/locale/zh-CN/LC_MESSAGES/introduction.po | 23 ---- docs/translation-update.sh | 4 - tests/trader/run.py | 2 + vnpy/app/csv_loader/__init__.py | 6 +- vnpy/app/csv_loader/csv_loader.py | 81 -------------- vnpy/app/csv_loader/engine.py | 99 ++++++++++++++++++ vnpy/app/csv_loader/ui/__init__.py | 2 +- vnpy/app/csv_loader/ui/csv.ico | Bin 0 -> 67646 bytes vnpy/app/csv_loader/ui/csv_loader_widget.py | 69 ------------ vnpy/app/csv_loader/ui/widget.py | 90 ++++++++++++++++ 15 files changed, 195 insertions(+), 282 deletions(-) delete mode 100644 check.py delete mode 100644 docs/.gitignore delete mode 100644 docs/locale/en/LC_MESSAGES/introduction.po delete mode 100644 docs/locale/introduction.pot delete mode 100644 docs/locale/zh-CN/LC_MESSAGES/introduction.po delete mode 100644 docs/translation-update.sh delete mode 100644 vnpy/app/csv_loader/csv_loader.py create mode 100644 vnpy/app/csv_loader/engine.py create mode 100644 vnpy/app/csv_loader/ui/csv.ico delete mode 100644 vnpy/app/csv_loader/ui/csv_loader_widget.py create mode 100644 vnpy/app/csv_loader/ui/widget.py diff --git a/check.py b/check.py deleted file mode 100644 index e8439add..00000000 --- a/check.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Check code quality for vn.py project. -""" - -import logging -import os -import subprocess -from typing import Callable - -logger = logging.Logger(__file__) - - -def check_and_warning(*args: list, fast_fail: bool = False): - """ - Run check and show related warning - """ - passed = True - for i in args: - if isinstance(i, Callable): - print(f"check using {i}") - cwd = os.getcwd() - res = i() - os.chdir(cwd) - if not res: - passed = False - logger.warning("check of %s failed!", i) - if not passed and fast_fail: - return False - return passed - - -def check_flake8(): - """ - Check code with flake8. - """ - passed = True - try: - subprocess.check_call(["python", "-m", "flake8", "./"]) - except subprocess.SubprocessError: - passed = False - return passed - - -def check_all(): - """ - Run check with all tools (only flake8 for now). - """ - return check_and_warning(check_flake8) - - -if __name__ == "__main__": - if not check_all(): - exit(1) - exit(0) diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 7dd8f770..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.doctrees \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 3dbff961..b5eea238 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,5 @@ # vn.py文档 - * [vn.py简介](introduction.md) * [项目安装](install.md) * [基本使用](quickstart.md) diff --git a/docs/locale/en/LC_MESSAGES/introduction.po b/docs/locale/en/LC_MESSAGES/introduction.po deleted file mode 100644 index be28769d..00000000 --- a/docs/locale/en/LC_MESSAGES/introduction.po +++ /dev/null @@ -1,23 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2019, vn.py Team -# This file is distributed under the same license as the vnpy package. -# FIRST AUTHOR , 2019. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: vnpy 2.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-02-13 01:17-0400\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" - -#: ../introduction.md:1 -msgid "vn.py文档" -msgstr "" - diff --git a/docs/locale/introduction.pot b/docs/locale/introduction.pot deleted file mode 100644 index d51b56a1..00000000 --- a/docs/locale/introduction.pot +++ /dev/null @@ -1,22 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2019, vn.py Team -# This file is distributed under the same license as the vnpy package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: vnpy 2.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-02-13 01:17-0400\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: ../introduction.md:1 -msgid "vn.py文档" -msgstr "" - diff --git a/docs/locale/zh-CN/LC_MESSAGES/introduction.po b/docs/locale/zh-CN/LC_MESSAGES/introduction.po deleted file mode 100644 index be28769d..00000000 --- a/docs/locale/zh-CN/LC_MESSAGES/introduction.po +++ /dev/null @@ -1,23 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2019, vn.py Team -# This file is distributed under the same license as the vnpy package. -# FIRST AUTHOR , 2019. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: vnpy 2.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-02-13 01:17-0400\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.6.0\n" - -#: ../introduction.md:1 -msgid "vn.py文档" -msgstr "" - diff --git a/docs/translation-update.sh b/docs/translation-update.sh deleted file mode 100644 index f7ef5ce5..00000000 --- a/docs/translation-update.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -sphinx-build -b gettext . locale -sphinx-intl update -p locale -l zh-CN -l en \ No newline at end of file diff --git a/tests/trader/run.py b/tests/trader/run.py index b319b213..f7bd4df1 100644 --- a/tests/trader/run.py +++ b/tests/trader/run.py @@ -11,6 +11,7 @@ from vnpy.gateway.ctp import CtpGateway from vnpy.gateway.tiger import TigerGateway from vnpy.app.cta_strategy import CtaStrategyApp +from vnpy.app.csv_loader import CsvLoaderApp def main(): @@ -27,6 +28,7 @@ def main(): main_engine.add_gateway(TigerGateway) main_engine.add_app(CtaStrategyApp) + main_engine.add_app(CsvLoaderApp) main_window = MainWindow(main_engine, event_engine) main_window.showMaximized() diff --git a/vnpy/app/csv_loader/__init__.py b/vnpy/app/csv_loader/__init__.py index 390f61c2..c346646f 100644 --- a/vnpy/app/csv_loader/__init__.py +++ b/vnpy/app/csv_loader/__init__.py @@ -1,7 +1,7 @@ from pathlib import Path from vnpy.trader.app import BaseApp -from .csv_loader import APP_NAME, CsvLoader +from .engine import APP_NAME, CsvLoaderEngine class CsvLoaderApp(BaseApp): @@ -10,7 +10,7 @@ class CsvLoaderApp(BaseApp): app_name = APP_NAME app_module = __module__ app_path = Path(__file__).parent - display_name = "CSV行情载入器" - engine_class = CsvLoader + display_name = "CSV加载器" + engine_class = CsvLoaderEngine widget_name = "CsvLoaderWidget" icon_name = "csv.ico" diff --git a/vnpy/app/csv_loader/csv_loader.py b/vnpy/app/csv_loader/csv_loader.py deleted file mode 100644 index 3d8c651a..00000000 --- a/vnpy/app/csv_loader/csv_loader.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Author: nanoric - -Load data from a csv file. - -Differences to 1.9.2: - * combine Date column and Time column into one DateTime column - -Sample csv file: - -```csv -"DateTime","Open","High","Low","Close","Volume" -2010-04-16T09:16:00.000000,3450.0,3488.0,3450.0,3468.0,489 -2010-04-16T09:17:00.000000,3468.0,3473.8,3467.0,3467.0,302 -2010-04-16T09:18:00.000000,3467.0,3471.0,3466.0,3467.0,203 -2010-04-16T09:19:00.000000,3467.0,3468.2,3448.0,3448.0,280 -2010-04-16T09:20:00.000000,3448.0,3459.0,3448.0,3454.0,250 -2010-04-16T09:21:00.000000,3454.0,3456.8,3454.0,3456.8,109 -``` - -""" - -import csv -from datetime import datetime - -from vnpy.event import EventEngine -from vnpy.trader.constant import Exchange, Interval -from vnpy.trader.database import DbBarData -from vnpy.trader.engine import BaseEngine, MainEngine -from vnpy.trader.object import BarData - -APP_NAME = "CsvLoader" - - -class CsvLoader(BaseEngine): - - def __init__(self, main_engine: MainEngine, event_engine: EventEngine): - """""" - super().__init__(main_engine, event_engine, APP_NAME) - - self.file_path: str = '' - - self.symbol: str = "" - self.exchange: Exchange = Exchange.SSE - self.interval: Interval = Interval.MINUTE - self.datetime_head: str = '' - self.open_head: str = '' - self.close_head: str = '' - self.low_head: str = '' - self.high_head: str = '' - self.volume_head: str = '' - - def load(self): - """""" - symbol = self.symbol - exchange = self.exchange - interval = self.interval - - datetime_head = self.datetime_head - open_head = self.open_head - close_head = self.close_head - low_head = self.low_head - high_head = self.high_head - volume_head = self.volume_head - - with open(self.file_path, 'rt') as f: - reader = csv.DictReader(f) - for item in reader: - bar = BarData( - '', - symbol, - exchange, - datetime.fromisoformat(item[datetime_head]), - interval, - item[volume_head], - item[open_head], - item[high_head], - item[low_head], - item[close_head], - ) - DbBarData.from_bar(bar).save() diff --git a/vnpy/app/csv_loader/engine.py b/vnpy/app/csv_loader/engine.py new file mode 100644 index 00000000..0f4152d2 --- /dev/null +++ b/vnpy/app/csv_loader/engine.py @@ -0,0 +1,99 @@ +""" +Author: nanoric + +Load data from a csv file. + +Differences to 1.9.2: + * combine Date column and Time column into one Datetime column + +Sample csv file: + +```csv +"Datetime","Open","High","Low","Close","Volume" +2010-04-16T09:16:00.000000,3450.0,3488.0,3450.0,3468.0,489 +2010-04-16T09:17:00.000000,3468.0,3473.8,3467.0,3467.0,302 +2010-04-16T09:18:00.000000,3467.0,3471.0,3466.0,3467.0,203 +2010-04-16T09:19:00.000000,3467.0,3468.2,3448.0,3448.0,280 +2010-04-16T09:20:00.000000,3448.0,3459.0,3448.0,3454.0,250 +2010-04-16T09:21:00.000000,3454.0,3456.8,3454.0,3456.8,109 +``` + +""" + +import csv +from datetime import datetime + +from vnpy.event import EventEngine +from vnpy.trader.constant import Exchange, Interval +from vnpy.trader.database import DbBarData +from vnpy.trader.engine import BaseEngine, MainEngine + + +APP_NAME = "CsvLoader" + + +class CsvLoaderEngine(BaseEngine): + """""" + + def __init__(self, main_engine: MainEngine, event_engine: EventEngine): + """""" + super().__init__(main_engine, event_engine, APP_NAME) + + self.file_path: str = '' + + self.symbol: str = "" + self.exchange: Exchange = Exchange.SSE + self.interval: Interval = Interval.MINUTE + self.datetime_head: str = '' + self.open_head: str = '' + self.close_head: str = '' + self.low_head: str = '' + self.high_head: str = '' + self.volume_head: str = '' + + def load(self, + file_path: str, + symbol: str, + exchange: Exchange, + interval: Interval, + datetime_head: str, + open_head: str, + close_head: str, + low_head: str, + high_head: str, + volume_head: str): + """""" + vt_symbol = f"{symbol}.{exchange.value}" + + start = None + end = None + count = 0 + + with open(file_path, 'rt') as f: + reader = csv.DictReader(f) + + for item in reader: + db_bar = DbBarData() + + db_bar.symbol = symbol + db_bar.exchange = exchange.value + db_bar.datetime = datetime.fromisoformat(item[datetime_head]) + db_bar.interval = interval.value + db_bar.volume = item[volume_head] + db_bar.open_price = item[open_head] + db_bar.high_price = item[high_head] + db_bar.low_price = item[low_head] + db_bar.close_price = item[close_head] + db_bar.vt_symbol = vt_symbol + db_bar.gateway_name = "DB" + + db_bar.save() + + # do some statistics + count += 1 + if not start: + start = db_bar.datetime + + end = db_bar.datetime + + return start, end, count diff --git a/vnpy/app/csv_loader/ui/__init__.py b/vnpy/app/csv_loader/ui/__init__.py index 3a01a8c6..8e613ee3 100644 --- a/vnpy/app/csv_loader/ui/__init__.py +++ b/vnpy/app/csv_loader/ui/__init__.py @@ -1 +1 @@ -from .csv_loader_widget import CsvLoaderWidget +from .widget import CsvLoaderWidget diff --git a/vnpy/app/csv_loader/ui/csv.ico b/vnpy/app/csv_loader/ui/csv.ico new file mode 100644 index 0000000000000000000000000000000000000000..4c9b95345e5085abf527e886898e55e72eda8862 GIT binary patch literal 67646 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