本次的提交信息

This commit is contained in:
向键雄-全栈开发工程师 2024-10-08 14:06:24 +08:00
parent 8054b7b8d2
commit 0aa2901f4c
146 changed files with 5974 additions and 0 deletions

23
.env Normal file

@ -0,0 +1,23 @@
;使用本地docker数据库就将这段代码解开如何将IP换成本机IP或者localhost
; DATABASE_URL="mysql://root:123456@10.1.5.219:3306/ftrade"
; DATABASE_CREATE_URL ="mysql://root:123456@10.1.5.219:3306/ftrade"
DATABASE_URL="mysql://wangche:fN7sXX8saiQKXWbG@cqxqg.tech:3308/wangche"
DATABASE_CREATE_URL ="mysql://wangche:fN7sXX8saiQKXWbG@cqxqg.tech:3308/wangche"
REDIS_URL="redis://:redis_tQNjCH@cqxqg.tech:6380"
SITE_DOMAIN=127.0.0.1
SECURE_COOKIES=false
ENVIRONMENT=LOCAL
CORS_HEADERS=["*"]
CORS_ORIGINS=["http://localhost:3000"]
# postgres variables, must be the same as in DATABASE_URL
POSTGRES_USER=app
POSTGRES_PASSWORD=app
POSTGRES_HOST=app_db
POSTGRES_PORT=5432
POSTGRES_DB=app

8
.idea/.gitignore vendored Normal file

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

1
.idea/.name Normal file

@ -0,0 +1 @@
wance_data

12
.idea/dataSources.xml Normal file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="wangche@lawyer5.cn" uuid="7e849454-3537-491d-a390-aa5024bc7f42">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://lawyer5.cn:3308/wangche</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

@ -0,0 +1,146 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="133">
<item index="0" class="java.lang.String" itemvalue="httpx" />
<item index="1" class="java.lang.String" itemvalue="numba" />
<item index="2" class="java.lang.String" itemvalue="tzlocal" />
<item index="3" class="java.lang.String" itemvalue="greenlet" />
<item index="4" class="java.lang.String" itemvalue="tabulate" />
<item index="5" class="java.lang.String" itemvalue="multitasking" />
<item index="6" class="java.lang.String" itemvalue="PyYAML" />
<item index="7" class="java.lang.String" itemvalue="pycparser" />
<item index="8" class="java.lang.String" itemvalue="atlastk" />
<item index="9" class="java.lang.String" itemvalue="gitdb" />
<item index="10" class="java.lang.String" itemvalue="redis" />
<item index="11" class="java.lang.String" itemvalue="sentry-sdk" />
<item index="12" class="java.lang.String" itemvalue="aliyun-python-sdk-core" />
<item index="13" class="java.lang.String" itemvalue="starlette" />
<item index="14" class="java.lang.String" itemvalue="lxml" />
<item index="15" class="java.lang.String" itemvalue="soupsieve" />
<item index="16" class="java.lang.String" itemvalue="objectbox" />
<item index="17" class="java.lang.String" itemvalue="uvicorn" />
<item index="18" class="java.lang.String" itemvalue="xlrd" />
<item index="19" class="java.lang.String" itemvalue="GitPython" />
<item index="20" class="java.lang.String" itemvalue="pydantic" />
<item index="21" class="java.lang.String" itemvalue="click" />
<item index="22" class="java.lang.String" itemvalue="attrs" />
<item index="23" class="java.lang.String" itemvalue="alpaca-py" />
<item index="24" class="java.lang.String" itemvalue="flatbuffers" />
<item index="25" class="java.lang.String" itemvalue="pydantic_core" />
<item index="26" class="java.lang.String" itemvalue="platformdirs" />
<item index="27" class="java.lang.String" itemvalue="pytest-env" />
<item index="28" class="java.lang.String" itemvalue="matplotlib" />
<item index="29" class="java.lang.String" itemvalue="PyMySQL" />
<item index="30" class="java.lang.String" itemvalue="msgpack" />
<item index="31" class="java.lang.String" itemvalue="httpcore" />
<item index="32" class="java.lang.String" itemvalue="idna" />
<item index="33" class="java.lang.String" itemvalue="rsa" />
<item index="34" class="java.lang.String" itemvalue="decorator" />
<item index="35" class="java.lang.String" itemvalue="yahooquery" />
<item index="36" class="java.lang.String" itemvalue="smmap" />
<item index="37" class="java.lang.String" itemvalue="pluggy" />
<item index="38" class="java.lang.String" itemvalue="cffi" />
<item index="39" class="java.lang.String" itemvalue="aerich" />
<item index="40" class="java.lang.String" itemvalue="numpy" />
<item index="41" class="java.lang.String" itemvalue="pyasn1" />
<item index="42" class="java.lang.String" itemvalue="requests" />
<item index="43" class="java.lang.String" itemvalue="sniffio" />
<item index="44" class="java.lang.String" itemvalue="py-mini-racer" />
<item index="45" class="java.lang.String" itemvalue="exceptiongroup" />
<item index="46" class="java.lang.String" itemvalue="jsonpath" />
<item index="47" class="java.lang.String" itemvalue="websockets" />
<item index="48" class="java.lang.String" itemvalue="annotated-types" />
<item index="49" class="java.lang.String" itemvalue="scipy" />
<item index="50" class="java.lang.String" itemvalue="watchfiles" />
<item index="51" class="java.lang.String" itemvalue="opencv-python" />
<item index="52" class="java.lang.String" itemvalue="iso8601" />
<item index="53" class="java.lang.String" itemvalue="python-utils" />
<item index="54" class="java.lang.String" itemvalue="et-xmlfile" />
<item index="55" class="java.lang.String" itemvalue="pytest-asyncio" />
<item index="56" class="java.lang.String" itemvalue="python-multipart" />
<item index="57" class="java.lang.String" itemvalue="dictdiffer" />
<item index="58" class="java.lang.String" itemvalue="pandas" />
<item index="59" class="java.lang.String" itemvalue="mpmath" />
<item index="60" class="java.lang.String" itemvalue="ruff" />
<item index="61" class="java.lang.String" itemvalue="cmake" />
<item index="62" class="java.lang.String" itemvalue="email_validator" />
<item index="63" class="java.lang.String" itemvalue="typing_extensions" />
<item index="64" class="java.lang.String" itemvalue="cachetools" />
<item index="65" class="java.lang.String" itemvalue="multidict" />
<item index="66" class="java.lang.String" itemvalue="yarl" />
<item index="67" class="java.lang.String" itemvalue="pytz" />
<item index="68" class="java.lang.String" itemvalue="webencodings" />
<item index="69" class="java.lang.String" itemvalue="html5lib" />
<item index="70" class="java.lang.String" itemvalue="joblib" />
<item index="71" class="java.lang.String" itemvalue="lib-pybroker" />
<item index="72" class="java.lang.String" itemvalue="mypy" />
<item index="73" class="java.lang.String" itemvalue="python-dateutil" />
<item index="74" class="java.lang.String" itemvalue="python-dotenv" />
<item index="75" class="java.lang.String" itemvalue="h11" />
<item index="76" class="java.lang.String" itemvalue="cycler" />
<item index="77" class="java.lang.String" itemvalue="httptools" />
<item index="78" class="java.lang.String" itemvalue="frozenlist" />
<item index="79" class="java.lang.String" itemvalue="akracer" />
<item index="80" class="java.lang.String" itemvalue="certifi" />
<item index="81" class="java.lang.String" itemvalue="anyio" />
<item index="82" class="java.lang.String" itemvalue="peewee" />
<item index="83" class="java.lang.String" itemvalue="pyparsing" />
<item index="84" class="java.lang.String" itemvalue="sympy" />
<item index="85" class="java.lang.String" itemvalue="beautifulsoup4" />
<item index="86" class="java.lang.String" itemvalue="opencv-contrib-python" />
<item index="87" class="java.lang.String" itemvalue="dnspython" />
<item index="88" class="java.lang.String" itemvalue="iniconfig" />
<item index="89" class="java.lang.String" itemvalue="cryptography" />
<item index="90" class="java.lang.String" itemvalue="kiwisolver" />
<item index="91" class="java.lang.String" itemvalue="akshare" />
<item index="92" class="java.lang.String" itemvalue="APScheduler" />
<item index="93" class="java.lang.String" itemvalue="bcrypt" />
<item index="94" class="java.lang.String" itemvalue="requests-futures" />
<item index="95" class="java.lang.String" itemvalue="optionaldict" />
<item index="96" class="java.lang.String" itemvalue="charset-normalizer" />
<item index="97" class="java.lang.String" itemvalue="pysnowball" />
<item index="98" class="java.lang.String" itemvalue="diskcache" />
<item index="99" class="java.lang.String" itemvalue="async-timeout" />
<item index="100" class="java.lang.String" itemvalue="SQLAlchemy" />
<item index="101" class="java.lang.String" itemvalue="mypy-extensions" />
<item index="102" class="java.lang.String" itemvalue="llvmlite" />
<item index="103" class="java.lang.String" itemvalue="pypika-tortoise" />
<item index="104" class="java.lang.String" itemvalue="tortoise" />
<item index="105" class="java.lang.String" itemvalue="tomlkit" />
<item index="106" class="java.lang.String" itemvalue="yfinance" />
<item index="107" class="java.lang.String" itemvalue="aiosqlite" />
<item index="108" class="java.lang.String" itemvalue="aiomysql" />
<item index="109" class="java.lang.String" itemvalue="tomli" />
<item index="110" class="java.lang.String" itemvalue="xmltodict" />
<item index="111" class="java.lang.String" itemvalue="urllib3" />
<item index="112" class="java.lang.String" itemvalue="python-jose" />
<item index="113" class="java.lang.String" itemvalue="coverage" />
<item index="114" class="java.lang.String" itemvalue="pydantic-settings" />
<item index="115" class="java.lang.String" itemvalue="sseclient-py" />
<item index="116" class="java.lang.String" itemvalue="six" />
<item index="117" class="java.lang.String" itemvalue="pytest" />
<item index="118" class="java.lang.String" itemvalue="progressbar2" />
<item index="119" class="java.lang.String" itemvalue="tzdata" />
<item index="120" class="java.lang.String" itemvalue="ecdsa" />
<item index="121" class="java.lang.String" itemvalue="packaging" />
<item index="122" class="java.lang.String" itemvalue="async-asgi-testclient" />
<item index="123" class="java.lang.String" itemvalue="jmespath" />
<item index="124" class="java.lang.String" itemvalue="tqdm" />
<item index="125" class="java.lang.String" itemvalue="fastapi" />
<item index="126" class="java.lang.String" itemvalue="backtrader" />
<item index="127" class="java.lang.String" itemvalue="colorama" />
<item index="128" class="java.lang.String" itemvalue="wechatpy" />
<item index="129" class="java.lang.String" itemvalue="aiohttp" />
<item index="130" class="java.lang.String" itemvalue="frozendict" />
<item index="131" class="java.lang.String" itemvalue="aiosignal" />
<item index="132" class="java.lang.String" itemvalue="openpyxl" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml Normal file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.7" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="ftrade_back" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml Normal file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wance_data.iml" filepath="$PROJECT_DIR$/.idea/wance_data.iml" />
</modules>
</component>
</project>

6
.idea/other.xml Normal file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PySciProjectComponent">
<option name="PY_INTERACTIVE_PLOTS_SUGGESTED" value="true" />
</component>
</project>

6
.idea/sqldialects.xml Normal file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/src/utils/remove_duplicates_databases.py" dialect="GenericSQL" />
</component>
</project>

15
.idea/wance_data.iml Normal file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="ftrade_back" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PackageRequirementsSettings">
<option name="requirementsPath" value="" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="EPYTEXT" />
<option name="myDocStringFormat" value="Epytext" />
</component>
</module>

19
banner.txt Normal file

@ -0,0 +1,19 @@
# ## # # # ## ## # # # # # # # # # # # #
## ## # ## ## ## # ########## # ## # # #### ## ## ## ## # ############## ### ## #############
## ############# ## ## ## # ## # ############## # #### # ## ## ## # ############### ## ## ## ## ## ## ##
## ## # ## ## ## # ## ############### ## # ## ## ## ## ## ########## ## ## ## ## ## ## ## # ##
# ### ########### ############### # # ## # ## ## # ## #### ###### ## ## # ## ## ####### ## ## ######### ##
## ## # ## # ## ## ## ## ########### ## ## # ## ###### # ## ## ## # ########### ## # ## ## ## ### ##
## ## ## ############### ### ## ## # ## ## ## ## ########### # ## # ## ## ######## ## ## ## ### ## ## ## # ### # # ##
## ## ## # # #### ## ## ## ########## ## ## ## #### ## ## ## # # ## # ## ## ## # ####### ## ### ### ######## ##
## ## ### ########## ##### ###### ## ## ## ## ## ## ## ##### ## ## # ### ## ## ######## ## ## ### # # ## # ## ## ##
## ## ### ## ## # ## # ## ## # ########## ## ## # ### # # ##### ### # ## ## ## ## ## ## # # ## ## ## ## ##
## ## ## ######### # ## ## ## ## # ## # ## ## # ## # ## #### #### #### ## ## ## ## ## ## ## ## ## ## ## ##
# ## # ## ## ## ## ## # ## # ## ## # # # ## ### ## # ## ## ######## ##### ## ### ## ## ####### ##
## ######### ## ## ## ## ########### # ## ## ## ## ## ## #### ## ## ## # ## ### ## ### # # ##
## ## ## ## ## ## # ## # # ## # ## ## ## ## ### ## ## ## #### ### ########### ##
#### ## #### ## ###### # ## # # # ### # ## ## ### ## ### ## ### ## #### ### ##### ## ####
# # # # # # ############### # # # # # # # # # # ## # # # # #
==========================================================================================================================================================================================================================
*********************************************************************************************聪明帅气可爱睿智的老板保佑我永不报错************************************************************************************************
==========================================================================================================================================================================================================================

@ -0,0 +1,239 @@
from tortoise import BaseDBAsyncClient
async def upgrade(db: BaseDBAsyncClient) -> str:
return """
CREATE TABLE IF NOT EXISTS `users` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`nickname` VARCHAR(30) COMMENT '用户昵称',
`avatar_url` VARCHAR(255) COMMENT '头像',
`member_type` INT COMMENT '会员类型',
`beta_account_type` VARCHAR(30) COMMENT '内测账号类型',
`pre_cost_time` INT COMMENT '预支付时间(单位年)',
`qr_code` VARCHAR(255) COMMENT '专属客服二维码',
`dedicated_id` INT COMMENT '专属客服id',
`invited_user_id` INT COMMENT '邀请人id',
`created_user_id` INT COMMENT '创建人id',
`is_deleted` BOOL NOT NULL COMMENT '是否删除' DEFAULT 0,
`login_at` DATETIME(6) COMMENT '最后一次登录时间',
`created_at` DATETIME(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6),
`updated_at` DATETIME(6) NOT NULL COMMENT '修改时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
`deleted_at` DATETIME(6) COMMENT '删除时间',
KEY `idx_users_is_dele_9cdc79` (`is_deleted`)
) CHARACTER SET utf8mb4 COMMENT='用户';
CREATE TABLE IF NOT EXISTS `stock` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`stock_code` VARCHAR(30) NOT NULL COMMENT '股票代码',
`stock_name` VARCHAR(30) COMMENT '股票名称',
`type` VARCHAR(2) COMMENT '类型',
`stock_pinyin` VARCHAR(30) NOT NULL COMMENT '股票拼音'
) CHARACTER SET utf8mb4 COMMENT='股票相关信息';
CREATE TABLE IF NOT EXISTS `security_account` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`securities_name` VARCHAR(30) NOT NULL COMMENT '证券公司名字昵称',
`fund_account` BIGINT COMMENT '资金账户',
`account_alias` VARCHAR(30) NOT NULL COMMENT '账户别名',
`money` DOUBLE COMMENT '账户金额',
`available_money` DOUBLE COMMENT '可用金额',
`available_proportion` DOUBLE COMMENT '可用资金占比',
`freeze` DOUBLE COMMENT '冻结金额'
) CHARACTER SET utf8mb4 COMMENT='证券账户';
CREATE TABLE IF NOT EXISTS `backtest` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
`key` VARCHAR(30) NOT NULL COMMENT '回测key'
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `Entrust` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
`fund_account` INT COMMENT '资金账户',
`securities_alias` VARCHAR(30) COMMENT '账户别名',
`account_type` SMALLINT COMMENT '账户类型',
`stock_code` VARCHAR(30) NOT NULL COMMENT '证券代码',
`stock_name` VARCHAR(30) NOT NULL COMMENT '证券名称',
`limit_price` DOUBLE NOT NULL COMMENT '委托价',
`entrust_number` INT NOT NULL COMMENT '委托数量',
`deal_price` DOUBLE COMMENT '成交价格',
`deal_number` INT COMMENT '成家数量',
`order_type` VARCHAR(30) NOT NULL COMMENT '操作',
`entrust_date` DATE COMMENT '委托日期',
`entrust_money` DOUBLE NOT NULL COMMENT '委托金额',
`is_repair` BOOL NOT NULL COMMENT '是否补单' DEFAULT 0,
`entrust_time` TIME(6),
KEY `idx_Entrust_is_repa_5a195d` (`is_repair`)
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `orders` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '订单ID',
`stock_code` VARCHAR(30) NOT NULL COMMENT '股票代码',
`stock_name` VARCHAR(30) NOT NULL COMMENT '股票名称',
`limit_price` DOUBLE NOT NULL COMMENT '限价',
`order_quantity` INT NOT NULL COMMENT '委托数量',
`order_amount` DOUBLE NOT NULL COMMENT '委托金额',
`order_type` VARCHAR(20) NOT NULL COMMENT '订单类型',
`position` VARCHAR(30) NOT NULL COMMENT '仓位',
`user_id` INT NOT NULL COMMENT '用户Id',
`entrust_date` DATETIME(6) COMMENT '委托日期',
`entrust_time` TIME(6)
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `snowball` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`snowball_token` VARCHAR(10000) COMMENT '雪球用户的token'
) CHARACTER SET utf8mb4 COMMENT='雪球相关信息';
CREATE TABLE IF NOT EXISTS `Strategy` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`strategy_name` VARCHAR(255) NOT NULL COMMENT '策略名称',
`strategy_hash` VARCHAR(255) NOT NULL COMMENT '策略版本号',
`strategy_type` VARCHAR(255) COMMENT '策略类型',
`user_id` INT COMMENT '所属用户',
`backtest_count` INT COMMENT '回测次数',
`backtest_keys` JSON COMMENT '回测key列表',
`is_deleted` BOOL NOT NULL COMMENT '是否删除' DEFAULT 0,
`created_at` DATETIME(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6),
`updated_at` DATETIME(6) NOT NULL COMMENT '修改时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
`deleted_at` DATETIME(6) COMMENT '删除时间'
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `Backtest` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(20) NOT NULL UNIQUE COMMENT '回测key',
`user_id` INT COMMENT '回测用户',
`backtest_at` DATETIME(6) NOT NULL COMMENT '回测时间' DEFAULT CURRENT_TIMESTAMP(6),
`backtest_code` LONGTEXT NOT NULL COMMENT '回测代码',
`is_running` BOOL NOT NULL COMMENT '回测状态' DEFAULT 1,
`updated_at` DATETIME(6) NOT NULL COMMENT '修改时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
`deleted_at` DATETIME(6) COMMENT '删除时间',
`is_active` BOOL NOT NULL COMMENT '是否可用' DEFAULT 1,
`strategy_id` INT NOT NULL,
CONSTRAINT `fk_Backtest_Strategy_7eabb20d` FOREIGN KEY (`strategy_id`) REFERENCES `Strategy` (`id`) ON DELETE CASCADE,
KEY `idx_Backtest_key_eb92b1` (`key`),
KEY `idx_Backtest_strateg_83ac3d` (`strategy_id`)
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `stock_details` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`stock_code` VARCHAR(30) NOT NULL COMMENT '股票代码',
`stock_name` VARCHAR(30) COMMENT '股票名称',
`type` VARCHAR(2) COMMENT '类型',
`stock_pinyin` VARCHAR(30) NOT NULL COMMENT '股票拼音',
`latest_price` DOUBLE COMMENT '最新价',
`rise_fall` DOUBLE COMMENT '跌涨幅'
) CHARACTER SET utf8mb4 COMMENT='股票相关信息';
CREATE TABLE IF NOT EXISTS `Transaction` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`key` VARCHAR(20) NOT NULL COMMENT 'key,数据标识',
`cash` DOUBLE COMMENT '资金',
`transaction_name` VARCHAR(255) NOT NULL COMMENT '交易名称',
`transaction_type` VARCHAR(255) COMMENT '交易类型',
`user_id` INT COMMENT '用户id',
`is_running` BOOL NOT NULL COMMENT '运行状态' DEFAULT 1,
`is_deleted` BOOL NOT NULL COMMENT '是否删除' DEFAULT 0,
`process_id` INT COMMENT '进程号',
`bar` VARCHAR(10) COMMENT '频率K线',
`created_at` DATETIME(6) NOT NULL COMMENT '创建时间' DEFAULT CURRENT_TIMESTAMP(6),
`updated_at` DATETIME(6) NOT NULL COMMENT '修改时间' DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
`stopped_at` DATETIME(6) COMMENT '停止时间',
`deleted_at` DATETIME(6) COMMENT '删除时间',
`strategy_id` INT NOT NULL,
CONSTRAINT `fk_Transact_Strategy_849d0577` FOREIGN KEY (`strategy_id`) REFERENCES `Strategy` (`id`) ON DELETE CASCADE,
KEY `idx_Transaction_strateg_b8f0b2` (`strategy_id`)
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `position` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
`key` VARCHAR(30) NOT NULL COMMENT '',
`date` DATE NOT NULL COMMENT '日期',
`name` VARCHAR(30) NOT NULL COMMENT '名称',
`size` INT NOT NULL COMMENT '数量',
`price` DOUBLE NOT NULL COMMENT '价格',
`adjbase` DOUBLE NOT NULL COMMENT '复权基数',
`profit` DOUBLE NOT NULL COMMENT '利润'
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `trand_info` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'id',
`key` VARCHAR(255) COMMENT '唯一索引',
`tran_info_data` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `tran_observer_data` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(255) NOT NULL,
`tran_observer_data` LONGBLOB COMMENT '存储大量数据'
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `tranorders` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(255),
`order_return` VARCHAR(255)
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `tran_return` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(255),
`tran_return_data` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `tran_trade_info` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(25),
`tran_trade_info` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `back_observed_data` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'id表',
`key` VARCHAR(255) COMMENT 'key',
`observed_data` LONGBLOB COMMENT '格式化后的json数据'
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `back_observed_data_detail` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(255) NOT NULL,
`back_observed_data` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `back_position` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(25),
`back_position_data` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `back_result_indicator` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(25),
`indicator` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `back_trand_info` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(25),
`trade_info` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `tran_position` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`key` VARCHAR(25),
`tran_position_data` LONGBLOB
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `stock_bt_history` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`end_bt_time` VARCHAR(8) COMMENT '回测最终时间',
`bt_stock_code` VARCHAR(8) COMMENT '回测股票代码',
`bt_stock_name` VARCHAR(10) COMMENT '回测股票名称',
`bt_benchmark_code` VARCHAR(8) COMMENT '股票基准代码',
`bt_stock_period` VARCHAR(10) COMMENT '回测类型',
`bt_strategy_name` VARCHAR(10) COMMENT '回测策略名称',
`bt_stock_data` LONGBLOB COMMENT '回测股票数据'
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `stock_history` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`stock_code` INT NOT NULL COMMENT '股票代码',
`stock_name` VARCHAR(10),
`start_time_to_market` VARCHAR(10) COMMENT '股票上市时间',
`end_bt_time` VARCHAR(10) COMMENT '最终回测时间',
`symbol_data` LONGBLOB COMMENT '股票数据'
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `stock_data_processing` (
`bt_benchmark_code` VARCHAR(6) COMMENT '基准代码',
`bt_stock_period` VARCHAR(10) COMMENT '数据类型',
`bt_strategy_name` VARCHAR(10) COMMENT '回测策略名',
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`processing_data` LONGBLOB COMMENT '清洗后的数据',
`prosessing_date` VARCHAR(10) COMMENT '当前回测时间',
`stock_name` VARCHAR(10),
`stocke_code` VARCHAR(10)
) CHARACTER SET utf8mb4;
CREATE TABLE IF NOT EXISTS `aerich` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`version` VARCHAR(255) NOT NULL,
`app` VARCHAR(100) NOT NULL,
`content` JSON NOT NULL
) CHARACTER SET utf8mb4;"""
async def downgrade(db: BaseDBAsyncClient) -> str:
return """
"""

BIN
models.py Normal file

Binary file not shown.

4
pyproject.toml Normal file

@ -0,0 +1,4 @@
[tool.aerich]
tortoise_orm = "src.tortoises.TORTOISE_ORM"
location = "./migrations"
src_folder = "./."

4
src/__init__.py Normal file

@ -0,0 +1,4 @@
from dotenv import load_dotenv
# load_dotenv(dotenv_path=Path("../.env"))
load_dotenv()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

106
src/backtest/backtest.py Normal file

@ -0,0 +1,106 @@
import bt
import pandas as pd
import numpy as np
from xtquant import xtdata
import matplotlib.pyplot as plt
# 数据的列名
columns = ['open', 'high', 'low', 'close', 'volume', 'amount', 'settelmentPrice',
'openInterest', 'preClose', 'suspendFlag']
# 获取本地数据并进行处理
def get_local_data(field_list: list, stock_list: list, period: str, start_time: str, end_time: str,
count: int, dividend_type: str, fill_data: bool, data_dir: str):
result = xtdata.get_local_data(field_list=field_list, stock_list=stock_list, period=period, start_time=start_time,
end_time=end_time, count=count, dividend_type=dividend_type, fill_data=fill_data,
data_dir=data_dir)
return data_processing(result)
# 数据处理函数
def data_processing(result_local):
# 初始化一个空的列表,用于存储每个股票的数据框
df_list = []
# 遍历字典中的 DataFrame
for stock_code, df in result_local.items():
# 确保 df 是一个 DataFrame
if isinstance(df, pd.DataFrame):
# 将时间戳转换为日期时间格式,只保留年-月-日
df['time'] = pd.to_datetime(df['time'], unit='ms').dt.date
# 将 'time' 列设置为索引,保留为日期格式
df.set_index('time', inplace=True)
# 指定列名
df.columns = columns
# 添加一列 'stock_code' 用于标识不同的股票
df['stock_code'] = stock_code
# 将 DataFrame 添加到列表中
df_list.append(df[['close']]) # 只保留 'close' 列
else:
print(f"数据格式错误: {stock_code} 不包含 DataFrame")
# 使用 pd.concat() 将所有 DataFrame 合并为一个大的 DataFrame
combined_df = pd.concat(df_list, axis=1)
# 确保返回的 DataFrame 索引是日期格式
combined_df.index = pd.to_datetime(combined_df.index)
# 打印最终的 DataFrame
print(combined_df)
return combined_df
# 定义策略
def moving_average_strategy(data, short_window=20, long_window=50):
data['Short_MA'] = data['close'].rolling(window=short_window, min_periods=1).mean()
data['Long_MA'] = data['close'].rolling(window=long_window, min_periods=1).mean()
data['Signal'] = 0
data.loc[data.index[short_window:], 'Signal'] = np.where(
data['Short_MA'][short_window:] > data['Long_MA'][short_window:], 1, 0)
data['Position'] = data['Signal'].diff()
return data
# 定义策略函数
def bt_strategy(data):
# 计算策略信号
# data = moving_average_strategy(data)
# 定义策略
dual_ma_strategy = bt.Strategy('Dual MA Strategy', [bt.algos.RunOnce(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance()])
return dual_ma_strategy
# 运行回测
def run_backtest():
# 生成数据
data = get_local_data(field_list=[], stock_list=["300391.SZ"], period='1d', start_time='', end_time='', count=-1,
dividend_type='none', fill_data=True, data_dir="")
# 创建策略
strategy = bt_strategy(data)
# 创建回测
portfolio = bt.Backtest(strategy, data)
result = bt.run(portfolio)
return result
# 执行回测并显示结果
if __name__ == "__main__":
result = run_backtest()
result.plot()
b = xtdata.get_sector_list()
print(b)
xtdata.download_sector_data()
a = result.stats
print(a)
plt.show()

79
src/backtest/bollinger.py Normal file

@ -0,0 +1,79 @@
import bt
# 模拟从前端接收到的因子列表
factors = [
{'name': 'SMA', 'period': 50},
{'name': 'EMA', 'period': 200},
{'name': 'RSI', 'period': 14},
{'name': 'BollingerBands', 'period': 20}
]
def create_indicator(name, **kwargs):
"""
根据名称和参数动态创建bt的技术指标
"""
if name == 'SMA':
return bt.indicators.SMA(kwargs['period'])
elif name == 'EMA':
return bt.indicators.EMA(kwargs['period'])
elif name == 'RSI':
return bt.indicators.RSI(kwargs['period'])
elif name == 'BollingerBands':
return bt.indicators.BollingerBands(kwargs['period'])
else:
raise ValueError(f"未知的指标名称: {name}")
def create_dynamic_strategy(name, factors):
"""
根据传递的因子列表动态构建策略
"""
algos = [bt.algos.RunMonthly(), # 每月运行一次
bt.algos.SelectAll()] # 选择所有资产
# 动态生成指标选择逻辑
for factor in factors:
indicator = create_indicator(factor['name'], **factor)
if factor['name'] == 'RSI':
# RSI 特定的买入/卖出逻辑
buy_signal = bt.algos.SelectWhere(indicator < 30) # 超卖区域买入
sell_signal = bt.algos.SelectWhere(indicator > 70) # 超买区域卖出
algos.append(buy_signal)
algos.append(bt.algos.WeighTarget(1.0))
algos.append(bt.algos.Rebalance())
algos.append(sell_signal)
algos.append(bt.algos.WeighTarget(0.0))
algos.append(bt.algos.Rebalance())
elif factor['name'] == 'BollingerBands':
# 布林带特定的逻辑(买入接近下轨,卖出接近上轨)
buy_signal = bt.algos.SelectWhere(bt.data < indicator['lower'])
sell_signal = bt.algos.SelectWhere(bt.data > indicator['upper'])
algos.append(buy_signal)
algos.append(bt.algos.WeighTarget(1.0))
algos.append(bt.algos.Rebalance())
algos.append(sell_signal)
algos.append(bt.algos.WeighTarget(0.0))
algos.append(bt.algos.Rebalance())
# 可以为其他指标添加更多逻辑
return bt.Strategy(name, algos)
# 创建动态策略
dynamic_strategy = create_dynamic_strategy("Dynamic_Strategy", factors)
# 获取数据
data = bt.get('spy,agg', start='2020-01-01', end='2023-01-01')
# 创建回测
backtest = bt.Backtest(dynamic_strategy, data)
# 运行回测
result = bt.run(backtest)
# 展示结果
result.display()
result.plot()

@ -0,0 +1,274 @@
import asyncio
import json
import time
from datetime import datetime
import bt
import numpy as np
import pandas as pd
from xtquant import xtdata
import matplotlib.pyplot as plt
from src.backtest.until import get_local_data, convert_pandas_to_json_serializable
from src.models import wance_data_storage_backtest, wance_data_stock
from src.tortoises_orm_config import init_tortoise
# 布林带策略函数
async def create_bollinger_bands_strategy(data, stock_code: str, bollingerMA: int = 50, std_dev: int = 200):
# 生成布林带策略信号
signal = await bollinger_bands_strategy(data, bollingerMA, std_dev)
# 使用bt框架构建策略
strategy = bt.Strategy(f'{stock_code} 布林带策略',
[bt.algos.RunDaily(),
bt.algos.SelectAll(), # 选择所有股票
bt.algos.WeighTarget(signal), # 根据信号调整权重
bt.algos.Rebalance()]) # 调仓
return strategy, signal
async def bollinger_bands_strategy(df, window=20, num_std_dev=2):
"""
基于布林带策略生成买卖信号
参数:
df: pd.DataFrame, 股票的价格数据行索引为日期列为股票代码
window: int, 计算布林带中轨线的窗口期
num_std_dev: float, 标准差的倍数用于计算上下轨
返回:
signal: pd.DataFrame, 每只股票的买卖信号1 表示买入0 表示卖出
"""
# 计算中轨线(移动平均)
middle_band = df.rolling(window=window, min_periods=1).mean()
# 计算滚动标准差
rolling_std = df.rolling(window=window, min_periods=1).std()
# 计算上轨线和下轨线
upper_band = middle_band + (rolling_std * num_std_dev)
lower_band = middle_band - (rolling_std * num_std_dev)
# 初始化信号 DataFrame
signal = pd.DataFrame(index=df.index, columns=df.columns)
# 生成买入信号:当价格突破下轨时
for column in df.columns:
signal[column] = np.where(df[column] < lower_band[column], 1, np.nan) # 买入信号
# 生成卖出信号:当价格突破上轨时
for column in df.columns:
signal[column] = np.where(df[column] > upper_band[column], 0, signal[column]) # 卖出信号
# 前向填充信号,持仓不变
signal = signal.ffill()
# 将剩余的 NaN 替换为 0
signal = signal.fillna(0)
return signal
async def storage_backtest_data(source_column_name, result, signal, stock_code, stock_data_series, bollingerMA,
std_dev):
await init_tortoise()
# 要存储的字段列表
fields_to_store = [
'stock_code', 'strategy_name', 'stock_close_price', 'daily_price',
'price', 'returns', 'data_start_time', 'data_end_time',
'backtest_end_time', 'position', 'backtest_name', 'rf', 'total_return', 'cagr',
'max_drawdown', 'calmar', 'mtd', 'three_month',
'six_month', 'ytd', 'one_year', 'three_year',
'five_year', 'ten_year', 'incep', 'daily_sharpe',
'daily_sortino', 'daily_mean', 'daily_vol',
'daily_skew', 'daily_kurt', 'best_day', 'worst_day',
'monthly_sharpe', 'monthly_sortino', 'monthly_mean',
'monthly_vol', 'monthly_skew', 'monthly_kurt',
'best_month', 'worst_month', 'yearly_sharpe',
'yearly_sortino', 'yearly_mean', 'yearly_vol',
'yearly_skew', 'yearly_kurt', 'best_year', 'worst_year',
'avg_drawdown', 'avg_drawdown_days', 'avg_up_month',
'avg_down_month', 'win_year_perc', 'twelve_month_win_perc'
]
# 准备要存储的数据
data_to_store = {
'stock_code': stock_code,
'strategy_name': "布林带策略",
'stock_close_price': json.dumps(stock_data_series.fillna(0).rename_axis('time').reset_index().assign(
time=stock_data_series.index.strftime('%Y%m%d')).set_index('time').to_dict(orient='index')),
'daily_price': convert_pandas_to_json_serializable(result[source_column_name].daily_prices),
'price': convert_pandas_to_json_serializable(result[source_column_name].prices),
'returns': convert_pandas_to_json_serializable(result[source_column_name].returns.fillna(0)),
'data_start_time': pd.to_datetime(result.stats.loc["start"].iloc[0]).strftime('%Y%m%d'),
'data_end_time': pd.to_datetime(result.stats.loc["end"].iloc[0]).strftime('%Y%m%d'),
'backtest_end_time': int(datetime.now().strftime('%Y%m%d')),
'position': convert_pandas_to_json_serializable(signal),
'backtest_name': f'{stock_code} 布林带策略 MA{bollingerMA}-{std_dev}倍标准差',
'indicator_type': 'Bollinger',
'indicator_information': json.dumps({'bollingerMA': bollingerMA, 'std_dev': std_dev})
}
# 使用循环填充其他字段
for field in fields_to_store[12:]: # 从第10个字段开始
value = result.stats.loc[field].iloc[0]
data_to_store[field] = 0.0 if (isinstance(value, float) and np.isnan(value)) else value
# 检查是否存在该 backtest_name
existing_record = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=data_to_store['backtest_name']
).first()
if existing_record:
# 如果存在,更新记录
await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
id=existing_record.id
).update(**data_to_store)
else:
# 如果不存在,创建新的记录
await wance_data_storage_backtest.WanceDataStorageBacktest.create(**data_to_store)
return data_to_store
async def run_bollinger_backtest(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = 100,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
bollingerMA: int = 50,
std_dev: int = 200):
try:
# 初始化一个列表用于存储每只股票的回测结果字典
results_list = []
# 遍历每只股票的数据(每列代表一个股票的收盘价)
data = await get_local_data(field_list, stock_list, period, start_time, end_time, count, dividend_type,
fill_data,
data_dir)
for stock_code in stock_list:
data_column_name = f'close_{stock_code}'
source_column_name = f'{stock_code} 布林带策略'
backtest_name = f'{stock_code} 布林带策略 MA{bollingerMA}-{std_dev}倍标准差'
now_time = int(datetime.now().strftime('%Y%m%d'))
db_result = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
if db_result:
if db_result[0].backtest_end_time == now_time:
results_list.append({source_column_name: db_result[0]})
# elif data_column_name in data.columns:
if data_column_name in data.columns:
stock_data_series = data[[data_column_name]] # 提取该股票的收盘价 DataFrame
stock_data_series.columns = ['close'] # 重命名列为 'close'
# 创建布林带策略
strategy, signal = await create_bollinger_bands_strategy(stock_data_series, stock_code,
bollingerMA=bollingerMA,
std_dev=std_dev)
# 创建回测
backtest = bt.Backtest(strategy=strategy, data=stock_data_series, initial_capital=100000)
# 运行回测
result = bt.run(backtest)
# 存储回测结果
data_to_store = await storage_backtest_data(source_column_name, result, signal, stock_code,
stock_data_series,
bollingerMA, std_dev)
# # 绘制回测结果图表
# result.plot()
# # 绘制个别股票数据图表
# plt.figure(figsize=(12, 6))
# plt.plot(stock_data_series.index, stock_data_series['close'], label='Stock Price')
# plt.title(f'Stock Price for {stock_code}')
# plt.xlabel('Date')
# plt.ylabel('Price')
# plt.legend()
# plt.grid(True)
# plt.show()
# 将结果存储为字典并添加到列表中
results_list.append({source_column_name: data_to_store})
else:
print(f"数据中缺少列: {data_column_name}")
return results_list # 返回结果列表
except Exception as e:
print(f"Error occurred: {e}")
async def start_bollinger_backtest_service(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = -1,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
bollingerMA: int = 50,
std_dev: int = 200):
for stock_code in stock_list:
backtest_name = f'{stock_code} 布林带策略 MA{bollingerMA}-{std_dev}倍标准差'
db_result = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
now_time = int(datetime.now().strftime('%Y%m%d'))
if db_result and db_result[0].backtest_end_time == now_time:
return db_result
else:
# 执行回测
result = await run_bollinger_backtest(
field_list=field_list,
stock_list=stock_list,
period=period,
start_time=start_time,
end_time=end_time,
count=count,
dividend_type=dividend_type,
fill_data=fill_data,
data_dir=data_dir,
bollingerMA=bollingerMA,
std_dev=std_dev,
)
return result
async def init_backtest_db():
bollinger_list = [{"bollingerMA": 20, "std_dev": 2}, {"bollingerMA": 30, "std_dev": 2},
{"bollingerMA": 70, "std_dev": 2}, {"bollingerMA": 5, "std_dev": 1},
{"bollingerMA": 20, "std_dev": 3}, {"bollingerMA": 50, "std_dev": 2.5}]
await init_tortoise()
wance_db = await wance_data_stock.WanceDataStock.all()
bollinger_list_lenght = len(bollinger_list)
for stock_code in wance_db:
for i in range(bollinger_list_lenght):
bollingerMA = bollinger_list[i]['bollingerMA']
std_dev = bollinger_list[i]['std_dev']
source_column_name = f'{stock_code} 布林带策略 MA{bollingerMA}-{std_dev}倍标准差'
result = await run_bollinger_backtest(field_list=['close', 'time'],
stock_list=[stock_code.stock_code],
bollingerMA=bollingerMA,
std_dev=std_dev)
print(f"回测成功 {source_column_name}")
if __name__ == '__main__':
# 测试类的回测
asyncio.run(run_bollinger_backtest(field_list=['close', 'time'],
stock_list=['601222.SH', '601677.SH'],
bollingerMA=20,
std_dev=2))
# # 初始化数据库表
# asyncio.run(init_backtest_db())

@ -0,0 +1,267 @@
import asyncio
import json
import time
from datetime import datetime
import bt
import numpy as np
import pandas as pd
from xtquant import xtdata
import matplotlib.pyplot as plt
from src.backtest.until import get_local_data, convert_pandas_to_json_serializable
from src.models import wance_data_storage_backtest, wance_data_stock
from src.tortoises_orm_config import init_tortoise
# 双均线策略函数
async def create_dual_ma_strategy(data, stock_code: str, short_window: int = 50, long_window: int = 200):
# 生成双均线策略信号
signal = await dual_ma_strategy(data, short_window, long_window)
# 使用bt框架构建策略
strategy = bt.Strategy(f'{stock_code} 双均线策略',
[bt.algos.RunDaily(),
bt.algos.SelectAll(), # 选择所有股票
bt.algos.WeighTarget(signal), # 根据信号调整权重
bt.algos.Rebalance()]) # 调仓
return strategy, signal
async def dual_ma_strategy(df, short_window=20, long_window=50):
"""
基于双均线策略生成买卖信号
参数:
df: pd.DataFrame, 股票的价格数据行索引为日期列为股票代码
short_window: int, 短期均线窗口期
long_window: int, 长期均线窗口期
返回:
signal: pd.DataFrame, 每只股票的买卖信号1 表示买入0 表示卖出
"""
# 计算短期均线和长期均线
short_ma = df.rolling(window=short_window, min_periods=1).mean()
long_ma = df.rolling(window=long_window, min_periods=1).mean()
# 生成买入信号: 当短期均线从下方穿过长期均线
buy_signal = np.where(short_ma > long_ma, 1, np.nan)
# 生成卖出信号: 当短期均线从上方穿过长期均线
sell_signal = np.where(short_ma < long_ma, 0, np.nan)
# 合并买卖信号
signal = pd.DataFrame(buy_signal, index=df.index, columns=df.columns)
signal = np.where(short_ma < long_ma, 0, signal)
# 前向填充信号,持仓不变
signal = pd.DataFrame(signal, index=df.index, columns=df.columns).ffill()
# 将剩余的 NaN 替换为 0
signal = signal.fillna(0)
return signal
async def storage_backtest_data(source_column_name, result, signal, stock_code, stock_data_series, short_window,
long_window):
await init_tortoise()
# 要存储的字段列表
fields_to_store = [
'stock_code', 'strategy_name', 'stock_close_price', 'daily_price',
'price', 'returns', 'data_start_time', 'data_end_time',
'backtest_end_time', 'position', 'backtest_name', 'rf', 'total_return', 'cagr',
'max_drawdown', 'calmar', 'mtd', 'three_month',
'six_month', 'ytd', 'one_year', 'three_year',
'five_year', 'ten_year', 'incep', 'daily_sharpe',
'daily_sortino', 'daily_mean', 'daily_vol',
'daily_skew', 'daily_kurt', 'best_day', 'worst_day',
'monthly_sharpe', 'monthly_sortino', 'monthly_mean',
'monthly_vol', 'monthly_skew', 'monthly_kurt',
'best_month', 'worst_month', 'yearly_sharpe',
'yearly_sortino', 'yearly_mean', 'yearly_vol',
'yearly_skew', 'yearly_kurt', 'best_year', 'worst_year',
'avg_drawdown', 'avg_drawdown_days', 'avg_up_month',
'avg_down_month', 'win_year_perc', 'twelve_month_win_perc'
]
# 准备要存储的数据
data_to_store = {
'stock_code': stock_code,
'strategy_name': "双均线策略",
'stock_close_price': json.dumps(stock_data_series.fillna(0).rename_axis('time').reset_index().assign(
time=stock_data_series.index.strftime('%Y%m%d')).set_index('time').to_dict(orient='index')),
'daily_price': convert_pandas_to_json_serializable(result[source_column_name].daily_prices),
'price': convert_pandas_to_json_serializable(result[source_column_name].prices),
'returns': convert_pandas_to_json_serializable(result[source_column_name].returns.fillna(0)),
'data_start_time': pd.to_datetime(result.stats.loc["start"].iloc[0]).strftime('%Y%m%d'),
'data_end_time': pd.to_datetime(result.stats.loc["end"].iloc[0]).strftime('%Y%m%d'),
'backtest_end_time': int(datetime.now().strftime('%Y%m%d')),
'position': convert_pandas_to_json_serializable(signal),
'backtest_name': f'{stock_code} 双均线策略 MA{short_window}-{long_window}',
'indicator_type': 'SMA',
'indicator_information': json.dumps({'short_window': short_window, 'long_window': long_window})
}
# 使用循环填充其他字段
for field in fields_to_store[12:]: # 从第10个字段开始
value = result.stats.loc[field].iloc[0]
data_to_store[field] = 0.0 if (isinstance(value, float) and np.isnan(value)) else value
# 检查是否存在该 backtest_name
existing_record = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=data_to_store['backtest_name']
).first()
if existing_record:
# 如果存在,更新记录
await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
id=existing_record.id
).update(**data_to_store)
else:
# 如果不存在,创建新的记录
await wance_data_storage_backtest.WanceDataStorageBacktest.create(**data_to_store)
return data_to_store
async def run_sma_backtest(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = 100,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200):
try:
# 初始化一个列表用于存储每只股票的回测结果字典
results_list = []
# 遍历每只股票的数据(每列代表一个股票的收盘价)
data = await get_local_data(field_list, stock_list, period, start_time, end_time, count, dividend_type,
fill_data,
data_dir)
for stock_code in stock_list:
data_column_name = f'close_{stock_code}'
source_column_name = f'{stock_code} 双均线策略'
backtest_name = f'{stock_code} 双均线策略 MA{short_window}-{long_window}'
now_data = int(datetime.now().strftime('%Y%m%d'))
db_result_data = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
if db_result_data:
if db_result_data[0].backtest_end_time == now_data:
results_list.append({source_column_name: db_result_data[0]})
if data_column_name in data.columns:
stock_data_series = data[[data_column_name]] # 提取该股票的收盘价 DataFrame
stock_data_series.columns = ['close'] # 重命名列为 'close'
# 创建双均线策略
strategy, signal = await create_dual_ma_strategy(stock_data_series, stock_code,
short_window=short_window,
long_window=long_window)
# 创建回测
backtest = bt.Backtest(strategy=strategy, data=stock_data_series, initial_capital=100000)
# 运行回测
result = bt.run(backtest)
# 存储回测结果
data_to_store = await storage_backtest_data(source_column_name, result, signal, stock_code,
stock_data_series,
short_window, long_window)
# # 绘制回测结果图表
# result.plot()
# # 绘制个别股票数据图表
# plt.figure(figsize=(12, 6))
# plt.plot(stock_data_series.index, stock_data_series['close'], label='Stock Price')
# plt.title(f'Stock Price for {stock_code}')
# plt.xlabel('Date')
# plt.ylabel('Price')
# plt.legend()
# plt.grid(True)
# plt.show()
# 将结果存储为字典并添加到列表中
results_list.append({source_column_name: data_to_store})
else:
print(f"数据中缺少列: {data_column_name}")
return results_list # 返回结果列表
except Exception as e:
print(f"Error occurred: {e}")
async def start_sma_backtest_service(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = -1,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200):
for stock_code in stock_list:
backtest_name = f'{stock_code} 双均线策略 MA{short_window}-{long_window}'
db_result = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
now_time = int(datetime.now().strftime('%Y%m%d'))
if db_result and db_result[0].backtest_end_time == now_time:
return db_result
else:
# 执行回测
result = await run_sma_backtest(
field_list=field_list,
stock_list=stock_list,
period=period,
start_time=start_time,
end_time=end_time,
count=count,
dividend_type=dividend_type,
fill_data=fill_data,
data_dir=data_dir,
short_window=short_window,
long_window=long_window,
)
return result
async def init_backtest_db():
sma_list = [{"short_window": 5, "long_window": 10}, {"short_window": 10, "long_window": 30},
{"short_window": 30, "long_window": 60}, {"short_window": 30, "long_window": 90},
{"short_window": 70, "long_window": 140}, {"short_window": 120, "long_window": 250}]
await init_tortoise()
wance_db = await wance_data_stock.WanceDataStock.all()
sma_list_lenght = len(sma_list)
for stock_code in wance_db:
for i in range(sma_list_lenght):
short_window = sma_list[i]['short_window']
long_window = sma_list[i]['long_window']
source_column_name = f'{stock_code} 双均线策略 MA{short_window}-{long_window}'
result = await run_sma_backtest(field_list=['close', 'time'],
stock_list=[stock_code.stock_code],
short_window=short_window,
long_window=long_window)
print(f"回测成功 {source_column_name}")
if __name__ == '__main__':
# 测试类的回测
# asyncio.run(run_sma_backtest(field_list=['close', 'time'],
# stock_list=['601222.SH', '601677.SH'],
# short_window=10,
# long_window=30))
# 初始化数据库表
asyncio.run(init_backtest_db())

@ -0,0 +1,92 @@
import bt
import pandas as pd
import numpy as np
from xtquant import xtdata
import matplotlib.pyplot as plt
# 数据的列名
columns = ['open', 'high', 'low', 'close', 'volume', 'amount', 'settelmentPrice',
'openInterest', 'preClose', 'suspendFlag']
def get_local_data(field_list: list, stock_list: list, period: str, start_time: str, end_time: str,
count: int, dividend_type: str, fill_data: bool, data_dir: str):
result = xtdata.get_local_data(field_list=field_list, stock_list=stock_list, period=period, start_time=start_time,
end_time=end_time, count=count, dividend_type=dividend_type, fill_data=fill_data,
data_dir=data_dir)
return data_processing(result)
# 数据处理函数
def data_processing(result_local):
# 初始化一个空的列表,用于存储每个股票的数据框
df_list = []
# 遍历字典中的 DataFrame
for stock_code, df in result_local.items():
# 确保 df 是一个 DataFrame
if isinstance(df, pd.DataFrame):
# 将时间戳转换为日期时间格式,只保留年-月-日
df['time'] = pd.to_datetime(df['time'], unit='ms').dt.date
# 将 'time' 列设置为索引,保留为日期格式
df.set_index('time', inplace=True)
# 指定列名
df.columns = columns
# 添加一列 'stock_code' 用于标识不同的股票
df['stock_code'] = stock_code
# 将 DataFrame 添加到列表中
df_list.append(df[['close']]) # 只保留 'close' 列
else:
print(f"数据格式错误: {stock_code} 不包含 DataFrame")
# 使用 pd.concat() 将所有 DataFrame 合并为一个大的 DataFrame
combined_df = pd.concat(df_list, axis=1)
# 确保返回的 DataFrame 索引是日期格式
combined_df.index = pd.to_datetime(combined_df.index)
# 打印最终的 DataFrame
print(combined_df)
return combined_df
def bt_strategy(data):
# 计算策略信号
# data = moving_average_strategy(data)
# 定义策略
dual_ma_strategy = bt.Strategy('Dual MA Strategy', [bt.algos.RunOnce(),
bt.algos.SelectAll(),
bt.algos.WeighEqually(),
bt.algos.Rebalance()])
return dual_ma_strategy
# 运行回测
def run_backtest():
# 生成数据
data = get_local_data(field_list=[], stock_list=["300391.SZ"], period='1d', start_time='', end_time='', count=-1,
dividend_type='none', fill_data=True, data_dir="")
# 创建策略
strategy = bt_strategy(data)
# 创建回测
portfolio = bt.Backtest(strategy, data)
result = bt.run(portfolio)
return result
# 执行回测并显示结果
if __name__ == "__main__":
result = run_backtest()
result.plot()
b = xtdata.get_sector_list()
print(b)
xtdata.download_sector_data()
a = result.stats
print(a)
plt.show()

@ -0,0 +1,271 @@
import asyncio
import json
import time
from datetime import datetime
import bt
import numpy as np
import pandas as pd
from xtquant import xtdata
import matplotlib.pyplot as plt
from src.backtest.until import get_local_data, convert_pandas_to_json_serializable
from src.models import wance_data_storage_backtest, wance_data_stock
from src.tortoises_orm_config import init_tortoise
# MACD策略函数
async def create_dual_ma_strategy(data, stock_code: str, short_window: int = 50, long_window: int = 200):
# 生成MACD策略信号
signal = await macd_strategy(data, short_window, long_window)
# 使用bt框架构建策略
strategy = bt.Strategy(f'{stock_code} MACD策略',
[bt.algos.RunDaily(),
bt.algos.SelectAll(), # 选择所有股票
bt.algos.WeighTarget(signal), # 根据信号调整权重
bt.algos.Rebalance()]) # 调仓
return strategy, signal
# 定义 MACD 策略的函数
def macd_strategy(data, short_window=12, long_window=26, signal_window=9):
"""
MACD 策略 MACD 线穿过信号线时买入反之卖出
参数:
data: pd.DataFrame, 股票的价格数据行索引为日期列为股票代码
short_window: int, 短期 EMA 的窗口期
long_window: int, 长期 EMA 的窗口期
signal_window: int, 信号线 EMA 的窗口期
返回:
signal: pd.DataFrame, 每只股票的买卖信号1 表示买入-1 表示卖出
"""
# 计算短期和长期的 EMA
short_ema = data.ewm(span=short_window, adjust=False).mean()
long_ema = data.ewm(span=long_window, adjust=False).mean()
# 计算 MACD 线
macd_line = short_ema - long_ema
# 计算信号线
signal_line = macd_line.ewm(span=signal_window, adjust=False).mean()
# 生成买入和卖出信号
signal = pd.DataFrame(index=data.index, columns=data.columns)
for column in data.columns:
signal[column] = 0 # 初始化信号为 0
# 买入信号MACD 线从下方穿过信号线
signal[column] = (macd_line[column] > signal_line[column]) & (macd_line[column].shift(1) <= signal_line[column].shift(1)).astype(int)
# 卖出信号MACD 线从上方穿过信号线
signal[column] = (macd_line[column] < signal_line[column]) & (macd_line[column].shift(1) >= signal_line[column].shift(1)).astype(int) * -1 + signal[column]
# 前向填充信号,保持持仓不变
signal = signal.ffill().fillna(0)
return signal
async def storage_backtest_data(source_column_name, result, signal, stock_code, stock_data_series, short_window,
long_window):
await init_tortoise()
# 要存储的字段列表
fields_to_store = [
'stock_code', 'strategy_name', 'stock_close_price', 'daily_price',
'price', 'returns', 'data_start_time', 'data_end_time',
'backtest_end_time', 'position', 'backtest_name', 'rf', 'total_return', 'cagr',
'max_drawdown', 'calmar', 'mtd', 'three_month',
'six_month', 'ytd', 'one_year', 'three_year',
'five_year', 'ten_year', 'incep', 'daily_sharpe',
'daily_sortino', 'daily_mean', 'daily_vol',
'daily_skew', 'daily_kurt', 'best_day', 'worst_day',
'monthly_sharpe', 'monthly_sortino', 'monthly_mean',
'monthly_vol', 'monthly_skew', 'monthly_kurt',
'best_month', 'worst_month', 'yearly_sharpe',
'yearly_sortino', 'yearly_mean', 'yearly_vol',
'yearly_skew', 'yearly_kurt', 'best_year', 'worst_year',
'avg_drawdown', 'avg_drawdown_days', 'avg_up_month',
'avg_down_month', 'win_year_perc', 'twelve_month_win_perc'
]
# 准备要存储的数据
data_to_store = {
'stock_code': stock_code,
'strategy_name': "MACD策略",
'stock_close_price': json.dumps(stock_data_series.fillna(0).rename_axis('time').reset_index().assign(
time=stock_data_series.index.strftime('%Y%m%d')).set_index('time').to_dict(orient='index')),
'daily_price': convert_pandas_to_json_serializable(result[source_column_name].daily_prices),
'price': convert_pandas_to_json_serializable(result[source_column_name].prices),
'returns': convert_pandas_to_json_serializable(result[source_column_name].returns.fillna(0)),
'data_start_time': pd.to_datetime(result.stats.loc["start"].iloc[0]).strftime('%Y%m%d'),
'data_end_time': pd.to_datetime(result.stats.loc["end"].iloc[0]).strftime('%Y%m%d'),
'backtest_end_time': int(datetime.now().strftime('%Y%m%d')),
'position': convert_pandas_to_json_serializable(signal),
'backtest_name': f'{stock_code} MACD策略 MA{short_window}-{long_window}',
'indicator_type': 'MACD',
'indicator_information': json.dumps({'short_window': short_window, 'long_window': long_window})
}
# 使用循环填充其他字段
for field in fields_to_store[12:]: # 从第10个字段开始
value = result.stats.loc[field].iloc[0]
data_to_store[field] = 0.0 if (isinstance(value, float) and np.isnan(value)) else value
# 检查是否存在该 backtest_name
existing_record = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=data_to_store['backtest_name']
).first()
if existing_record:
# 如果存在,更新记录
await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
id=existing_record.id
).update(**data_to_store)
else:
# 如果不存在,创建新的记录
await wance_data_storage_backtest.WanceDataStorageBacktest.create(**data_to_store)
return data_to_store
async def run_macd_backtest(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = 100,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200):
try:
# 初始化一个列表用于存储每只股票的回测结果字典
results_list = []
# 遍历每只股票的数据(每列代表一个股票的收盘价)
data = await get_local_data(field_list, stock_list, period, start_time, end_time, count, dividend_type,
fill_data,
data_dir)
for stock_code in stock_list:
data_column_name = f'close_{stock_code}'
source_column_name = f'{stock_code} MACD策略'
backtest_name = f'{stock_code} MACD策略 MA{short_window}-{long_window}'
now_data = int(datetime.now().strftime('%Y%m%d'))
db_result_data = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
if db_result_data:
if db_result_data[0].backtest_end_time == now_data:
results_list.append({source_column_name: db_result_data[0]})
elif data_column_name in data.columns:
stock_data_series = data[[data_column_name]] # 提取该股票的收盘价 DataFrame
stock_data_series.columns = ['close'] # 重命名列为 'close'
# 创建MACD策略
strategy, signal = await create_dual_ma_strategy(stock_data_series, stock_code,
short_window=short_window,
long_window=long_window)
# 创建回测
backtest = bt.Backtest(strategy=strategy, data=stock_data_series, initial_capital=100000)
# 运行回测
result = bt.run(backtest)
# 存储回测结果
data_to_store = await storage_backtest_data(source_column_name, result, signal, stock_code,
stock_data_series,
short_window, long_window)
# # 绘制回测结果图表
# result.plot()
# # 绘制个别股票数据图表
# plt.figure(figsize=(12, 6))
# plt.plot(stock_data_series.index, stock_data_series['close'], label='Stock Price')
# plt.title(f'Stock Price for {stock_code}')
# plt.xlabel('Date')
# plt.ylabel('Price')
# plt.legend()
# plt.grid(True)
# plt.show()
# 将结果存储为字典并添加到列表中
results_list.append({source_column_name: data_to_store})
else:
print(f"数据中缺少列: {data_column_name}")
return results_list # 返回结果列表
except Exception as e:
print(f"Error occurred: {e}")
async def start_macd_backtest_service(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = -1,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200):
for stock_code in stock_list:
backtest_name = f'{stock_code} MACD策略 MA{short_window}-{long_window}'
db_result = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
now_time = int(datetime.now().strftime('%Y%m%d'))
if db_result and db_result[0].backtest_end_time == now_time:
return db_result
else:
# 执行回测
result = await run_macd_backtest(
field_list=field_list,
stock_list=stock_list,
period=period,
start_time=start_time,
end_time=end_time,
count=count,
dividend_type=dividend_type,
fill_data=fill_data,
data_dir=data_dir,
short_window=short_window,
long_window=long_window,
)
return result
async def init_backtest_db():
MACD_list = [{"short_window": 5, "long_window": 10}, {"short_window": 10, "long_window": 30},
{"short_window": 30, "long_window": 60}, {"short_window": 30, "long_window": 90},
{"short_window": 70, "long_window": 140}, {"short_window": 120, "long_window": 250}]
await init_tortoise()
wance_db = await wance_data_stock.WanceDataStock.all()
MACD_list_lenght = len(MACD_list)
for stock_code in wance_db:
for i in range(MACD_list_lenght):
short_window = MACD_list[i]['short_window']
long_window = MACD_list[i]['long_window']
source_column_name = f'{stock_code} MACD策略 MA{short_window}-{long_window}'
result = await run_macd_backtest(field_list=['close', 'time'],
stock_list=[stock_code.stock_code],
short_window=short_window,
long_window=long_window)
print(f"回测成功 {source_column_name}")
if __name__ == '__main__':
# 测试类的回测
# asyncio.run(run_macd_backtest(field_list=['close', 'time'],
# stock_list=['601222.SH', '601677.SH'],
# short_window=10,
# long_window=30))
# 初始化数据库表
asyncio.run(init_backtest_db())

@ -0,0 +1,264 @@
import asyncio
import json
import time
from datetime import datetime
import bt
import numpy as np
import pandas as pd
from xtquant import xtdata
import matplotlib.pyplot as plt
from src.backtest.until import get_local_data, convert_pandas_to_json_serializable
from src.models import wance_data_storage_backtest, wance_data_stock
from src.tortoises_orm_config import init_tortoise
# 反双均线策略函数
async def create_dual_ma_strategy(data, stock_code: str, short_window: int = 50, long_window: int = 200):
# 生成反双均线策略信号
signal = await reverse_dual_ma_strategy(data, short_window, long_window)
# 使用bt框架构建策略
strategy = bt.Strategy(f'{stock_code} 反双均线策略',
[bt.algos.RunDaily(),
bt.algos.SelectAll(), # 选择所有股票
bt.algos.WeighTarget(signal), # 根据信号调整权重
bt.algos.Rebalance()]) # 调仓
return strategy, signal
# 定义反反双均线策略的函数
def reverse_dual_ma_strategy(data, short_window=50, long_window=200):
"""
反反双均线策略当短期均线跌破长期均线时买入穿过长期均线时卖出
参数:
data: pd.DataFrame, 股票的价格数据行索引为日期列为股票代码
short_window: int, 短期均线的窗口期
long_window: int, 长期均线的窗口期
返回:
signal: pd.DataFrame, 每只股票的买卖信号1 表示买入0 表示卖出
"""
# 计算短期均线和长期均线
short_ma = data.rolling(window=short_window).mean()
long_ma = data.rolling(window=long_window).mean()
# 初始化信号 DataFrame
signal = pd.DataFrame(index=data.index, columns=data.columns)
# 生成买入信号:短期均线从上往下穿过长期均线
for column in data.columns:
signal[column] = (short_ma[column] < long_ma[column]).astype(int) # 跌破时买入信号为1
signal[column] = (short_ma[column] > long_ma[column]).astype(int) * -1 + signal[column] # 穿过时卖出信号为0
# 前向填充信号,保持持仓不变
signal = signal.ffill()
return signal
async def storage_backtest_data(source_column_name, result, signal, stock_code, stock_data_series, short_window,
long_window):
await init_tortoise()
# 要存储的字段列表
fields_to_store = [
'stock_code', 'strategy_name', 'stock_close_price', 'daily_price',
'price', 'returns', 'data_start_time', 'data_end_time',
'backtest_end_time', 'position', 'backtest_name', 'rf', 'total_return', 'cagr',
'max_drawdown', 'calmar', 'mtd', 'three_month',
'six_month', 'ytd', 'one_year', 'three_year',
'five_year', 'ten_year', 'incep', 'daily_sharpe',
'daily_sortino', 'daily_mean', 'daily_vol',
'daily_skew', 'daily_kurt', 'best_day', 'worst_day',
'monthly_sharpe', 'monthly_sortino', 'monthly_mean',
'monthly_vol', 'monthly_skew', 'monthly_kurt',
'best_month', 'worst_month', 'yearly_sharpe',
'yearly_sortino', 'yearly_mean', 'yearly_vol',
'yearly_skew', 'yearly_kurt', 'best_year', 'worst_year',
'avg_drawdown', 'avg_drawdown_days', 'avg_up_month',
'avg_down_month', 'win_year_perc', 'twelve_month_win_perc'
]
# 准备要存储的数据
data_to_store = {
'stock_code': stock_code,
'strategy_name': "反双均线策略",
'stock_close_price': json.dumps(stock_data_series.fillna(0).rename_axis('time').reset_index().assign(
time=stock_data_series.index.strftime('%Y%m%d')).set_index('time').to_dict(orient='index')),
'daily_price': convert_pandas_to_json_serializable(result[source_column_name].daily_prices),
'price': convert_pandas_to_json_serializable(result[source_column_name].prices),
'returns': convert_pandas_to_json_serializable(result[source_column_name].returns.fillna(0)),
'data_start_time': pd.to_datetime(result.stats.loc["start"].iloc[0]).strftime('%Y%m%d'),
'data_end_time': pd.to_datetime(result.stats.loc["end"].iloc[0]).strftime('%Y%m%d'),
'backtest_end_time': int(datetime.now().strftime('%Y%m%d')),
'position': convert_pandas_to_json_serializable(signal),
'backtest_name': f'{stock_code} 反双均线策略 MA{short_window}-{long_window}',
'indicator_type': 'reverse_SMA',
'indicator_information': json.dumps({'short_window': short_window, 'long_window': long_window})
}
# 使用循环填充其他字段
for field in fields_to_store[12:]: # 从第10个字段开始
value = result.stats.loc[field].iloc[0]
data_to_store[field] = 0.0 if (isinstance(value, float) and np.isnan(value)) else value
# 检查是否存在该 backtest_name
existing_record = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=data_to_store['backtest_name']
).first()
if existing_record:
# 如果存在,更新记录
await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
id=existing_record.id
).update(**data_to_store)
else:
# 如果不存在,创建新的记录
await wance_data_storage_backtest.WanceDataStorageBacktest.create(**data_to_store)
return data_to_store
async def run_reverse_reverse_SMA_backtest(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = 100,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200):
try:
# 初始化一个列表用于存储每只股票的回测结果字典
results_list = []
# 遍历每只股票的数据(每列代表一个股票的收盘价)
data = await get_local_data(field_list, stock_list, period, start_time, end_time, count, dividend_type,
fill_data,
data_dir)
for stock_code in stock_list:
data_column_name = f'close_{stock_code}'
source_column_name = f'{stock_code} 反双均线策略'
backtest_name = f'{stock_code} 反双均线策略 MA{short_window}-{long_window}'
now_data = int(datetime.now().strftime('%Y%m%d'))
db_result_data = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
if db_result_data:
if db_result_data[0].backtest_end_time == now_data:
results_list.append({source_column_name: db_result_data[0]})
elif data_column_name in data.columns:
stock_data_series = data[[data_column_name]] # 提取该股票的收盘价 DataFrame
stock_data_series.columns = ['close'] # 重命名列为 'close'
# 创建反双均线策略
strategy, signal = await create_dual_ma_strategy(stock_data_series, stock_code,
short_window=short_window,
long_window=long_window)
# 创建回测
backtest = bt.Backtest(strategy=strategy, data=stock_data_series, initial_capital=100000)
# 运行回测
result = bt.run(backtest)
# 存储回测结果
data_to_store = await storage_backtest_data(source_column_name, result, signal, stock_code,
stock_data_series,
short_window, long_window)
# # 绘制回测结果图表
# result.plot()
# # 绘制个别股票数据图表
# plt.figure(figsize=(12, 6))
# plt.plot(stock_data_series.index, stock_data_series['close'], label='Stock Price')
# plt.title(f'Stock Price for {stock_code}')
# plt.xlabel('Date')
# plt.ylabel('Price')
# plt.legend()
# plt.grid(True)
# plt.show()
# 将结果存储为字典并添加到列表中
results_list.append({source_column_name: data_to_store})
else:
print(f"数据中缺少列: {data_column_name}")
return results_list # 返回结果列表
except Exception as e:
print(f"Error occurred: {e}")
async def start_reverse_SMA_backtest_service(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = -1,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200):
for stock_code in stock_list:
backtest_name = f'{stock_code} 反双均线策略 MA{short_window}-{long_window}'
db_result = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
now_time = int(datetime.now().strftime('%Y%m%d'))
if db_result and db_result[0].backtest_end_time == now_time:
return db_result
else:
# 执行回测
result = await run_reverse_reverse_SMA_backtest(
field_list=field_list,
stock_list=stock_list,
period=period,
start_time=start_time,
end_time=end_time,
count=count,
dividend_type=dividend_type,
fill_data=fill_data,
data_dir=data_dir,
short_window=short_window,
long_window=long_window,
)
return result
async def init_backtest_db():
reverse_SMA_list = [{"short_window": 5, "long_window": 10}, {"short_window": 10, "long_window": 30},
{"short_window": 30, "long_window": 60}, {"short_window": 30, "long_window": 90},
{"short_window": 70, "long_window": 140}, {"short_window": 120, "long_window": 250}]
await init_tortoise()
wance_db = await wance_data_stock.WanceDataStock.all()
reverse_SMA_list_lenght = len(reverse_SMA_list)
for stock_code in wance_db:
for i in range(reverse_SMA_list_lenght):
short_window = reverse_SMA_list[i]['short_window']
long_window = reverse_SMA_list[i]['long_window']
source_column_name = f'{stock_code} 反双均线策略 MA{short_window}-{long_window}'
result = await run_reverse_reverse_SMA_backtest(field_list=['close', 'time'],
stock_list=[stock_code.stock_code],
short_window=short_window,
long_window=long_window)
print(f"回测成功 {source_column_name}")
if __name__ == '__main__':
# 测试类的回测
# asyncio.run(run_reverse_SMA_backtest(field_list=['close', 'time'],
# stock_list=['601222.SH', '601677.SH'],
# short_window=10,
# long_window=30))
# 初始化数据库表
asyncio.run(init_backtest_db())

23
src/backtest/router.py Normal file

@ -0,0 +1,23 @@
from fastapi import APIRouter, HTTPException # 从 FastAPI 中导入 APIRouter用于创建 API 路由器
from src.backtest.service import start_backtest_service
from src.pydantic.backtest_request import BackRequest
router = APIRouter() # 创建一个 FastAPI 路由器实例
@router.get("/start_backtest")
async def start_backtest(request: BackRequest):
result = await start_backtest_service(field_list=['close', 'time'],
stock_list=request.stock_list,
period=request.period,
start_time=request.start_time,
end_time=request.end_time,
count=request.count,
dividend_type=request.dividend_type,
fill_data=request.fill_data,
ma_type=request.ma_type,
short_window=request.short_window,
long_window=request.long_window
)
return result

@ -0,0 +1,296 @@
import asyncio
import json
import time
from datetime import datetime
import bt
import numpy as np
import pandas as pd
from xtquant import xtdata
import matplotlib.pyplot as plt
from src.backtest.until import get_local_data, convert_pandas_to_json_serializable
from src.models import wance_data_storage_backtest, wance_data_stock
from src.tortoises_orm_config import init_tortoise
# RSI策略函数
async def create_dual_ma_strategy(data, stock_code: str, short_window: int = 50, long_window: int = 200,
overbought: int = 70, oversold: int = 30):
# 生成RSI策略信号
signal = await rsi_strategy(data, short_window, long_window, overbought, oversold)
# 使用bt框架构建策略
strategy = bt.Strategy(f'{stock_code} RSI策略',
[bt.algos.RunDaily(),
bt.algos.SelectAll(), # 选择所有股票
bt.algos.WeighTarget(signal), # 根据信号调整权重
bt.algos.Rebalance()]) # 调仓
return strategy, signal
async def rsi_strategy(df, short_window=14, long_window=28, overbought=70, oversold=30):
"""
基于RSI的策略生成买卖信号
参数:
df: pd.DataFrame, 股票的价格数据行索引为日期列为股票代码
short_window: int, 短期RSI的窗口期
long_window: int, 长期RSI的窗口期
overbought: int, 超买水平
oversold: int, 超卖水平
返回:
signal: pd.DataFrame, 每只股票的买卖信号1 表示买入0 表示卖出
"""
delta = df.diff().fillna(0)
gain = (delta.where(delta > 0, 0).rolling(window=short_window).mean()).fillna(0)
loss = (-delta.where(delta < 0, 0).rolling(window=short_window).mean()).fillna(0)
short_rsi = (100 - (100 / (1 + (gain / loss)))).fillna(0)
long_gain = (delta.where(delta > 0, 0).rolling(window=long_window).mean()).fillna(0)
long_loss = (-delta.where(delta < 0, 0).rolling(window=long_window).mean()).fillna(0)
long_rsi = (100 - (100 / (1 + (long_gain / long_loss)))).fillna(0)
signal = pd.DataFrame(index=df.index, columns=df.columns)
for column in df.columns:
signal[column] = np.where((short_rsi[column] < 30) & (long_rsi[column] < 30) & (short_rsi[column] != 0) & (long_rsi[column] != 0), 1, 0)
signal[column] = np.where((short_rsi[column] > 70) & (long_rsi[column] > 70) & (short_rsi[column] != 0) & (long_rsi[column] != 0), 0, signal[column])
return signal.ffill().fillna(0)
async def storage_backtest_data(source_column_name, result, signal, stock_code, stock_data_series, short_window: int,
long_window: int, overbought: int = 70,
oversold: int = 30):
await init_tortoise()
# 要存储的字段列表
fields_to_store = [
'stock_code', 'strategy_name', 'stock_close_price', 'daily_price',
'price', 'returns', 'data_start_time', 'data_end_time',
'backtest_end_time', 'position', 'backtest_name', 'rf', 'total_return', 'cagr',
'max_drawdown', 'calmar', 'mtd', 'three_month',
'six_month', 'ytd', 'one_year', 'three_year',
'five_year', 'ten_year', 'incep', 'daily_sharpe',
'daily_sortino', 'daily_mean', 'daily_vol',
'daily_skew', 'daily_kurt', 'best_day', 'worst_day',
'monthly_sharpe', 'monthly_sortino', 'monthly_mean',
'monthly_vol', 'monthly_skew', 'monthly_kurt',
'best_month', 'worst_month', 'yearly_sharpe',
'yearly_sortino', 'yearly_mean', 'yearly_vol',
'yearly_skew', 'yearly_kurt', 'best_year', 'worst_year',
'avg_drawdown', 'avg_drawdown_days', 'avg_up_month',
'avg_down_month', 'win_year_perc', 'twelve_month_win_perc'
]
# 准备要存储的数据
data_to_store = {
'stock_code': stock_code,
'strategy_name': "RSI策略",
'stock_close_price': json.dumps(stock_data_series.fillna(0).rename_axis('time').reset_index().assign(
time=stock_data_series.index.strftime('%Y%m%d')).set_index('time').to_dict(orient='index')),
'daily_price': convert_pandas_to_json_serializable(result[source_column_name].daily_prices),
'price': convert_pandas_to_json_serializable(result[source_column_name].prices),
'returns': convert_pandas_to_json_serializable(result[source_column_name].returns.fillna(0)),
'data_start_time': pd.to_datetime(result.stats.loc["start"].iloc[0]).strftime('%Y%m%d'),
'data_end_time': pd.to_datetime(result.stats.loc["end"].iloc[0]).strftime('%Y%m%d'),
'backtest_end_time': int(datetime.now().strftime('%Y%m%d')),
'position': convert_pandas_to_json_serializable(signal),
'backtest_name': f'{stock_code} RSI策略 RSI{short_window}-RSI{long_window}-overbought{overbought}-oversold{oversold}',
'indicator_type': 'RSI',
'indicator_information': json.dumps(
{'short_window': short_window, 'long_window': long_window, 'overbought': overbought, 'oversold': oversold})
}
# 使用循环填充其他字段
for field in fields_to_store[12:]: # 从第12个字段开始
value = result.stats.loc[field].iloc[0]
if isinstance(value, float):
if np.isnan(value):
data_to_store[field] = 0.0 # NaN 处理为 0
elif np.isinf(value): # 判断是否为无穷大或无穷小
if value > 0:
data_to_store[field] = 99999.9999 # 正无穷处理
else:
data_to_store[field] = -99999.9999 # 负无穷处理
else:
data_to_store[field] = value # 正常的浮点值
else:
data_to_store[field] = value # 非浮点类型保持不变
# 检查是否存在该 backtest_name
existing_record = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=data_to_store['backtest_name']
).first()
if existing_record:
# 如果存在,更新记录
await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
id=existing_record.id
).update(**data_to_store)
else:
# 如果不存在,创建新的记录
await wance_data_storage_backtest.WanceDataStorageBacktest.create(**data_to_store)
return data_to_store
async def run_rsi_backtest(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = 100,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200,
overbought: int = 70,
oversold: int = 30
):
try:
# 初始化一个列表用于存储每只股票的回测结果字典
results_list = []
# 遍历每只股票的数据(每列代表一个股票的收盘价)
data = await get_local_data(field_list, stock_list, period, start_time, end_time, count, dividend_type,
fill_data,
data_dir)
for stock_code in stock_list:
data_column_name = f'close_{stock_code}'
source_column_name = f'{stock_code} RSI策略'
backtest_name = f'{stock_code} RSI策略 RSI{short_window}-RSI{long_window}'
now_data = int(datetime.now().strftime('%Y%m%d'))
db_result_data = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
if db_result_data:
if db_result_data[0].backtest_end_time == now_data:
results_list.append({source_column_name: db_result_data[0]})
# elif data_column_name in data.columns:
if data_column_name in data.columns:
stock_data_series = data[[data_column_name]] # 提取该股票的收盘价 DataFrame
stock_data_series.columns = ['close'] # 重命名列为 'close'
# 创建RSI策略
strategy, signal = await create_dual_ma_strategy(stock_data_series, stock_code,
short_window=short_window, long_window=long_window)
# 创建回测
backtest = bt.Backtest(strategy=strategy, data=stock_data_series, initial_capital=100000)
# 运行回测
result = bt.run(backtest)
# 存储回测结果
data_to_store = await storage_backtest_data(source_column_name, result, signal, stock_code,
stock_data_series, short_window, long_window, overbought,
oversold)
# # 绘制回测结果图表
# result.plot()
# # 绘制个别股票数据图表
# plt.figure(figsize=(12, 6))
# plt.plot(stock_data_series.index, stock_data_series['close'], label='Stock Price')
# plt.title(f'Stock Price for {stock_code}')
# plt.xlabel('Date')
# plt.ylabel('Price')
# plt.legend()
# plt.grid(True)
# plt.show()
# 将结果存储为字典并添加到列表中
results_list.append({source_column_name: data_to_store})
else:
print(f"数据中缺少列: {data_column_name}")
return results_list # 返回结果列表
except Exception as e:
print(f"Error occurred: {e}")
async def start_rsi_backtest_service(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = -1,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
short_window: int = 50,
long_window: int = 200,
overbought: int = 70,
oversold: int = 30
):
for stock_code in stock_list:
backtest_name = f'{stock_code} RSI策略 RSI{short_window}-RSI{long_window}'
db_result = await wance_data_storage_backtest.WanceDataStorageBacktest.filter(
backtest_name=backtest_name)
now_time = int(datetime.now().strftime('%Y%m%d'))
if db_result and db_result[0].backtest_end_time == now_time:
return db_result
else:
# 执行回测
result = await run_rsi_backtest(
field_list=field_list,
stock_list=stock_list,
period=period,
start_time=start_time,
end_time=end_time,
count=count,
dividend_type=dividend_type,
fill_data=fill_data,
data_dir=data_dir,
short_window=short_window,
long_window=long_window,
overbought=overbought,
oversold=oversold
)
return result
async def init_backtest_db():
sma_list = [{"short_window": 3, "long_window": 6}, {"short_window": 6, "long_window": 12},
{"short_window": 12, "long_window": 24}, {"short_window": 14, "long_window": 18},
{"short_window": 15, "long_window": 10}]
await init_tortoise()
wance_db = await wance_data_stock.WanceDataStock.all()
sma_list_lenght = len(sma_list)
for stock_code in wance_db:
for i in range(sma_list_lenght):
short_window = sma_list[i]['short_window']
long_window = sma_list[i]['long_window']
source_column_name = f'{stock_code} RSI策略 RSI{short_window}-RSI{long_window}'
result = await start_rsi_backtest_service(field_list=['close', 'time'],
stock_list=[stock_code.stock_code],
short_window=short_window,
long_window=long_window,
overbought=70,
oversold=30)
print(f"回测成功 {source_column_name}")
if __name__ == '__main__':
# 测试类的回测
asyncio.run(run_rsi_backtest(field_list=['close', 'time'],
stock_list=['601222.SH', '601677.SH'],
count=-1,
short_window=10,
long_window=30,
overbought=70,
oversold=30
))
# # 初始化数据库表
# asyncio.run(init_backtest_db())

74
src/backtest/service.py Normal file

@ -0,0 +1,74 @@
from src.backtest.bollinger_bands import run_bollinger_backtest, start_bollinger_backtest_service
from src.backtest.dual_moving_average import run_sma_backtest, start_sma_backtest_service
from src.backtest.reverse_dual_ma_strategy import start_reverse_SMA_backtest_service
from src.backtest.rsi_strategy import start_rsi_backtest_service
from src.backtest.until import data_check
async def start_backtest_service(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = -1,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '',
ma_type: str = 'SMA',
short_window: int = 50,
long_window: int = 200,
bollingerMA: int = 200,
std_dev: int = 200,
overbought: int = 70,
oversold: int = 30,
signal_window: int = 9):
# 数据检查
await data_check(field_list=field_list,
stock_list=stock_list,
period=period,
start_time=start_time,
end_time=end_time,
count=count,
dividend_type=dividend_type,
fill_data=fill_data,
data_dir=data_dir)
# 策略映射
strategies = {
'SMA': start_sma_backtest_service,
'Bollinger': start_bollinger_backtest_service,
'RSI': start_rsi_backtest_service,
'RESMA': start_reverse_SMA_backtest_service,
'MACD': start_rsi_backtest_service
}
# 通用参数
base_params = {
'field_list': field_list,
'stock_list': stock_list,
'period': period,
'start_time': start_time,
'end_time': end_time,
'count': count,
'dividend_type': dividend_type,
'fill_data': fill_data,
'data_dir': data_dir,
}
# 特定策略参数
strategy_params = {
'SMA': {'short_window': short_window, 'long_window': long_window},
'Bollinger': {'bollingerMA': bollingerMA, 'std_dev': std_dev},
'RSI': {'short_window': short_window, 'long_window': long_window, 'overbought': overbought,
'oversold': oversold},
'RESMA': {'short_window': short_window, 'long_window': long_window},
'MACD': {'short_window': short_window, 'long_window': signal_window}
}
# 选择策略并执行
strategy_func = strategies.get(ma_type)
if strategy_func:
result = await strategy_func(**base_params, **strategy_params[ma_type])
return result
else:
return None

99
src/backtest/until.py Normal file

@ -0,0 +1,99 @@
import json
from datetime import datetime
import numpy as np
from xtquant import xtdata
import pandas as pd
# 数据的列名
columns = ['open', 'high', 'low', 'close', 'volume', 'amount', 'settelmentPrice',
'openInterest', 'preClose', 'suspendFlag']
# 获取本地数据并进行处理
async def get_local_data(field_list: list, stock_list: list, period: str, start_time: str, end_time: str,
count: int, dividend_type: str, fill_data: bool, data_dir: str):
result = xtdata.get_local_data(field_list=field_list, stock_list=stock_list, period=period, start_time=start_time,
end_time=end_time, count=count, dividend_type=dividend_type, fill_data=fill_data,
data_dir=data_dir)
return await data_processing(result)
async def data_processing(result_local):
# 初始化一个空的列表,用于存储每个股票的数据框
df_list = []
# 遍历字典中的 DataFrame
for stock_code, df in result_local.items():
# 确保 df 是一个 DataFrame
if isinstance(df, pd.DataFrame):
# 将时间戳转换为日期时间格式,只保留年-月-日
df['time'] = pd.to_datetime(df['time'], unit='ms').dt.date
# 将 'time' 列设置为索引,保留为日期格式
df.set_index('time', inplace=True)
# 将 'close' 列重命名为 'close_股票代码'
df.rename(columns={'close': f'close_{stock_code}'}, inplace=True)
# 将 DataFrame 添加到列表中
df_list.append(df[[f'close_{stock_code}']]) # 只保留 'close_股票代码' 列
else:
print(f"数据格式错误: {stock_code} 不包含 DataFrame")
# 使用 pd.concat() 将所有 DataFrame 合并为一个大的 DataFrame
combined_df = pd.concat(df_list, axis=1)
# 确保返回的 DataFrame 索引是日期格式
combined_df.index = pd.to_datetime(combined_df.index)
return combined_df
def convert_pandas_to_json_serializable(data: pd.Series) -> str:
"""
Pandas Series DataFrame 中的 Timestamp 索引转换为字符串并返回 JSON 可序列化的结果
参数:
data: pd.Series pd.DataFrame, 带有时间戳索引的 pandas 数据
返回:
JSON 字符串键为日期字符串值为原数据的值
"""
# 判断数据类型
if isinstance(data, (pd.Series, pd.DataFrame)):
# 如果索引是时间戳类型,则转换为 YYYYMMDD 格式
if isinstance(data.index, pd.DatetimeIndex):
data.index = data.index.strftime('%Y%m%d')
# 处理 NaN 和 None 的情况,替换为 0 或其他合适的默认值
data = data.replace([np.nan, None], 0)
# 将索引重置为普通列,然后转换为字典
json_serializable_data = data.rename_axis('date').reset_index().to_dict(orient='records')
# 将字典转换为 JSON 格式字符串
json_string = json.dumps(json_serializable_data)
return json_string
else:
raise ValueError("输入必须为 Pandas Series 或 DataFrame")
async def data_check(field_list: list,
stock_list: list,
period: str = '1d',
start_time: str = '',
end_time: str = '',
count: int = -1,
dividend_type: str = 'none',
fill_data: bool = True,
data_dir: str = '', ):
result_data = xtdata.get_local_data(field_list=[], stock_list=stock_list, period=period, start_time=start_time,
end_time=end_time, count=count, dividend_type=dividend_type, fill_data=fill_data,
data_dir=data_dir)
time_now = int(datetime.now().strftime('%Y%m%d'))
for i in stock_list:
close = int(result_data.get(i).index[-1])
if close != 0 and close < time_now:
xtdata.download_history_data(stock_code=i,
period='1d',
start_time='',
end_time='',
incrementally=True)

43
src/constants.py Normal file

@ -0,0 +1,43 @@
from enum import Enum
THISPOLICYNAME = ''
def setThisPolicyName(name):
THISPOLICYNAME = name
def getThisPolicyName():
return THISPOLICYNAME
DB_NAMING_CONVENTION = {
"ix": "%(column_0_label)s_idx",
"uq": "%(table_name)s_%(column_0_name)s_key",
"ck": "%(table_name)s_%(constraint_name)s_check",
"fk": "%(table_name)s_%(column_0_name)s_fkey",
"pk": "%(table_name)s_pkey",
}
class RedisKeyConstants:
SMS_CODE_KEY = "sms:code:"
TOKEN_KEY = "Youcailogin:"
ACCESS_TOKEN_KEY = "applet_access_token_youcai"
SESSION_KEY = "session_id_youcai:"
OFFICIAL_ACCESS_TOKEN_KEY = "official_access_token_youcai"
class Environment(str, Enum):
LOCAL = "LOCAL"
STAGING = "STAGING"
TESTING = "TESTING"
PRODUCTION = "PRODUCTION"
@property
def is_debug(self):
return self in (self.LOCAL, self.STAGING, self.TESTING)
@property
def is_testing(self):
return self == self.TESTING
@property
def is_deployed(self) -> bool:
return self in (self.STAGING, self.PRODUCTION)

@ -0,0 +1,412 @@
import asyncio
from datetime import datetime
import akshare as ak
import pandas as pd
from xtquant import xtdata
from src.models import wance_data_stock
from src.tortoises_orm_config import init_tortoise
from src.utils.history_data_processing_utils import translation_dict, get_best_match, safe_get_value, on_progress
from src.utils.split_stock_utils import split_stock_code, join_stock_code, percent_to_float
from src.xtdata.service import download_history_data_service, get_full_tick_keys_service, download_history_data2_service
# period - 周期,用于表示要获取的周期和具体数据类型
period_list = ["1d", "1h", "30m", "15m", "5m", "1m", "tick", "1w", "1mon", "1q", "1hy", "1y"]
# 数据的列名
columns = ['open', 'high', 'low', 'close', 'volume', 'amount', 'settelmentPrice',
'openInterest', 'preClose', 'suspendFlag']
def processing_data(field_list: list, stock_list: list, period: str, start_time: str, end_time: str, count: int,
dividend_type: str, fill_data: bool):
"""
:param field_list: []
:param stock_list: ["186511.SH","173312.SH","231720.SH","173709.SH","019523.SH"]
:param period: "1d"
:param start_time: "20240506"
:param end_time: ""
:param count: -1
:param dividend_type: "none"
:param fill_data: False
:return:
"""
try:
# 获取本地数据
result_local = xtdata.get_local_data(field_list=field_list,
stock_list=stock_list,
period=period,
start_time=start_time,
end_time=end_time,
count=count,
dividend_type=dividend_type,
fill_data=fill_data,
data_dir=""
)
# 初始化一个空的列表,用于存储每个股票的数据框
df_list = []
# 遍历字典中的 DataFrame
for stock_code, df in result_local.items():
# 确保 df 是一个 DataFrame
if isinstance(df, pd.DataFrame):
# 将时间戳转换为日期时间格式,并格式化为字符串 'YYYYMMDD'
df['time'] = pd.to_datetime(df['time'], unit='ms').dt.strftime('%Y%m%d')
# 将 'time' 列设置为索引
df.set_index('time', inplace=True)
# 指定列名
df.columns = columns
# 添加一列 'stock_code' 用于标识不同的股票
df['stock_code'] = stock_code
# 将 DataFrame 添加到列表中
df_list.append(df)
else:
print(f"数据格式错误: {stock_code} 不包含 DataFrame")
# 使用 pd.concat() 将所有 DataFrame 合并为一个大的 DataFrame
combined_df = pd.concat(df_list)
# 打印合并后的 DataFrame
print(combined_df)
print(f"开始获取股票数据{result_local}")
except Exception as e:
print(f"处理数据发生错误: {str(e)}")
def history_data_processing():
"""
本地路径 D:\\e海方舟-量化交易版\\userdata_mini\\datadir
:return:
"""
try:
result = xtdata.get_full_tick(code_list=['SH', 'SZ'])
result = list(result.keys())
# for key in result:
# xtdata.download_history_data(stock_code=key, period="1d", start_time="", end_time="", incrementally="")
datas = xtdata.get_local_data(field_list=[], stock_list=result, period='1d', start_time='', end_time='',
count=-1,
dividend_type='none', fill_data=True)
# datas = xtdata.download_history_data2(stock_list=result, period="1d", start_time="", end_time="",
# callback=on_progress)
print(datas, "这里是返回的数据")
except Exception as e:
print(f"处理数据发生错误: {str(e)}")
async def init_indicator():
await init_tortoise()
# 从数据库中获取股票列表
stock_list = await wance_data_stock.WanceDataStock.filter(stock_type__contains=["stock"]).all()
stock_zh_a_spot_em_df = ak.stock_zh_a_spot_em()
# 遍历股票列表拿到股票实体
for stock in stock_list:
try:
# 使用 akshare 获取股票指标数据
stock_code = stock.stock_code # 假设 stock_code 是股票代码的字段
stock_code_suffix = split_stock_code(stock_code) # 提取股票代码部分
stock_code_xq = join_stock_code(stock_code_suffix)
stock_code_front = stock_code_suffix[0]
# 筛选匹配的行
match = stock_zh_a_spot_em_df.loc[stock_zh_a_spot_em_df['代码'] == stock_code_front, '涨跌幅']
# 从akshare中获取数据
stock_a_indicator_lg_df = ak.stock_a_indicator_lg(symbol=stock_code_front) # 乐咕乐股-A 股个股指标: 市盈率, 市净率, 股息率
stock_financial_abstract_ths_df = ak.stock_financial_abstract_ths(symbol=stock_code_front,
indicator="按报告期") # 同花顺-财务指标-主要指标
stock_financial_abstract_df = ak.stock_financial_abstract(symbol=stock_code_front) # 新浪财经-财务报表-关键指标
stock_individual_spot_xq_df = ak.stock_individual_spot_xq(symbol=stock_code_xq) # 雪球-行情中心-个股
stock_zh_valuation_baidu_df = ak.stock_zh_valuation_baidu(symbol=stock_code_front, indicator="市现率",
period="近一年") # 百度股市通-A 股-财务报表-估值数据
stock_fhps_detail_em_df = ak.stock_fhps_detail_em(symbol=stock_code_front) # 东方财富网-数据中心-分红送配-分红送配详情
# 查询数据库中是否已有该股票的数据
existing_record = await wance_data_stock.WanceDataStock.filter(stock_code=stock_code).first()
if existing_record is None:
print(f"未找到股票记录: {stock_code}")
continue
# 处理并插入数据到数据库
last_indicator_row = stock_a_indicator_lg_df.iloc[-1]
last_abstract_row = stock_financial_abstract_ths_df.iloc[0]
last_financial_row = stock_financial_abstract_df.iloc[:, 2]
last_spot_xq_row = stock_individual_spot_xq_df.iloc[:, 1]
last_baidu_df_row = stock_zh_valuation_baidu_df.iloc[-1]
last_detail_em_row = stock_fhps_detail_em_df.iloc[-1]
# 更新字段的对应数据
# 每股指标模块
existing_record.financial_dividend = safe_get_value(last_detail_em_row.get('现金分红-现金分红比例'))
existing_record.financial_ex_gratia = safe_get_value(last_spot_xq_row[8])
existing_record.financial_cash_flow = safe_get_value(last_financial_row[10])
existing_record.financial_asset_value = safe_get_value(last_financial_row[9])
existing_record.financial_reserve_per = safe_get_value(last_abstract_row.get('每股资本公积金'))
existing_record.financial_undistributed_profit = safe_get_value(last_abstract_row.get('每股未分配利润'))
# 盈利能力模块
existing_record.profit_asset_value = safe_get_value(last_financial_row.iloc[33])
existing_record.profit_sale_ratio = safe_get_value(last_financial_row.iloc[43])
existing_record.profit_gross_rate = safe_get_value(last_financial_row.iloc[44])
existing_record.profit_business_increase = safe_get_value(last_financial_row.iloc[53])
existing_record.profit_dividend_rate = safe_get_value(last_spot_xq_row.iloc[26])
# 成长能力
existing_record.growth_Income_rate = percent_to_float(
safe_get_value(last_abstract_row.get('营业总收入同比增长率', 0.0)))
existing_record.growth_growth_rate = safe_get_value(last_financial_row.iloc[46])
existing_record.growth_nonnet_profit = percent_to_float(
safe_get_value(last_abstract_row.get('扣非净利润同比增长率', 0.0)))
existing_record.growth_attributable_rate = safe_get_value(last_financial_row.iloc[54])
# 估值指标
existing_record.valuation_PEGTTM_ratio = safe_get_value(last_indicator_row.get('pe_ttm'))
existing_record.valuation_PEG_percentile = safe_get_value(last_indicator_row.get('pe'))
existing_record.valuation_PB_TTM = safe_get_value(last_indicator_row.get('ps'))
existing_record.valuation_PB_percentile = safe_get_value(last_indicator_row.get('pb'))
existing_record.valuation_PTS_TTM = safe_get_value(last_indicator_row.get('dv_ratio'))
existing_record.valuation_PTS_percentile = safe_get_value(last_indicator_row.get('ps_ttm'))
existing_record.valuation_market_TTM = safe_get_value(last_baidu_df_row[-1])
existing_record.valuation_market_percentile = safe_get_value(1 / last_baidu_df_row[-1] * 100) if \
last_baidu_df_row[-1] != 0 else 0
# 行情指标
existing_record.market_indicator = safe_get_value(match.values[0]) if not match.empty else 0
# 保存更改
await existing_record.save()
print(f"更新股票指标成功!: {stock_code}")
except Exception as e:
print(f"处理股票 {stock_code} 时发生错误: {e}")
continue # 继续处理下一个股票
async def init_stock_pool(incremental: bool = False):
"""
初始化股票池参数包括股票名股票代码股票上市时间股票板块等信息
@param incremental: 是否执行增量下载
@type incremental: bool
"""
await init_tortoise()
# 获取所有现有股票代码
existing_stocks = set()
if incremental:
existing_stocks = {stock.stock_code for stock in await wance_data_stock.WanceDataStock.all()}
# 初始化股票池
tick_result = xtdata.get_full_tick(['SH', 'SZ'])
stocks_to_create = [] # 使用一个列表批量创建
for key in tick_result.keys():
if incremental and key in existing_stocks:
print(f"股票代码 {key} 已经存在,跳过...\n")
continue
detail_result = xtdata.get_instrument_detail(key, False)
InstrumentName_result = detail_result.get("InstrumentName")
start_time = detail_result.get("OpenDate") or detail_result.get("CreateDate")
end_time = datetime.now().strftime('%Y%m%d')
time_expire = detail_result.get("ExpireDate")
type_dict = xtdata.get_instrument_type(key)
type_list = []
if type_dict:
for i in type_dict.keys():
type_list.append(i)
# 只在 type_list 包含 "stock" 时继续执行
if "stock" in type_list:
# 检查股票是否已存在
existing_stock = await wance_data_stock.WanceDataStock.filter(stock_code=key).first()
if not existing_stock:
stocks_to_create.append(
wance_data_stock.WanceDataStock(
stock_code=key,
stock_name=InstrumentName_result,
stock_sector=[], # 初始化为空列表,后续会加入板块
stock_type=type_list,
time_start=start_time,
time_end=end_time,
time_expire=time_expire,
market_sector=key.rsplit('.', 1)[-1]
)
)
print(f"加载成功 股票名称 {InstrumentName_result} 股票代码 {key} 股票类型 {type_list} \n")
else:
print(f"股票代码 {key} 已经存在,跳过...\n")
else:
print(f"跳过非股票类型:{key} 类型:{type_list} \n")
# 如果有新的股票,批量创建所有新股票记录
if stocks_to_create:
bulk_db_result = await wance_data_stock.WanceDataStock.bulk_create(stocks_to_create)
print(bulk_db_result, "股票池创建完成 \n")
else:
print("没有新的股票需要创建 \n")
# 获取并更新sector模块
sector_list = xtdata.get_sector_list()
for sector in sector_list:
# 使用模糊匹配找到最佳的中文板块匹配
best_match = get_best_match(sector, translation_dict)
if best_match:
translated_sector = translation_dict[best_match] # 获取对应的英文名称
else:
print(f"没有找到合适的板块匹配:{sector} \n")
continue # 如果没有找到匹配,跳过该板块
# 获取板块对应的股票列表
sector_stock = xtdata.get_stock_list_in_sector(sector)
# 获取所有相关股票
stocks_to_update = await wance_data_stock.WanceDataStock.filter(stock_code__in=sector_stock)
# 遍历并更新每个股票的sector避免重复添加相同的英文板块
for stock in stocks_to_update:
if translated_sector not in stock.stock_sector: # 检查是否已经存在该英文板块
stock.stock_sector.append(translated_sector)
await stock.save() # 保存更新后的数据
else:
print(f"{stock.stock_code} 已经包含板块 {translated_sector}, 跳过重复添加 \n")
print(f"更新板块完成 {sector}: {translated_sector} \n")
print(f"所有股票已经加载完成 \n")
"""
# 初始化股票池参数 股票名、股票代码、股票上市时间、股票板块、股票最后回测时间、股票退市时间、股票所属市场
@param incremental:是否执行增量下载
@type incremental:bool
@return:
@rtype:
"""
"""
async def init_stock_pool(incremental: bool = False):
# 这行代码存在一个问题就是,存入的板块信息,是中文的,在查询的时候应为是中文的数据所以没有办法被选中
await init_tortoise()
# 获取所有现有股票代码
existing_stocks = set()
if incremental:
existing_stocks = {stock.stock_code for stock in await wance_data_stock.WanceDataStock.all()}
# 初始化股票池
tick_result = xtdata.get_full_tick(['SH', 'SZ'])
stocks_to_create = [] # 使用一个列表批量创建
for key in tick_result.keys():
# 如果是增量更新,且该股票已经存在,则跳过
if incremental and key in existing_stocks:
continue
detail_result = xtdata.get_instrument_detail(key, False)
InstrumentName_result = detail_result.get("InstrumentName")
start_time = detail_result.get("OpenDate") or detail_result.get("CreateDate")
end_time = datetime.now().strftime('%Y%m%d')
time_expire = detail_result.get("ExporeDate")
type_dict = xtdata.get_instrument_type(key)
type_list = []
if type_dict:
for i in type_dict.keys():
type_list.append(i)
stocks_to_create.append(
wance_data_stock.WanceDataStock(
stock_code=key,
stock_name=InstrumentName_result,
stock_sector=[],
stock_type=type_list,
time_start=start_time,
time_end=end_time,
time_expire=time_expire,
market_sector=key.rsplit('.', 1)[-1]
)
)
print(f"加载成功 股票名称 {InstrumentName_result} 股票代码 {key} 股票类型 {type_list} \n")
# 如果有新的股票,批量创建所有新股票记录
if stocks_to_create:
bulk_db_result = await wance_data_stock.WanceDataStock.bulk_create(stocks_to_create)
print(bulk_db_result, "股票池创建完成 \n")
else:
print("没有新的股票需要创建 \n")
# 获取并更新sector模块
sector_list = xtdata.get_sector_list()
if incremental:
# 获取已经更新过的sector
updated_sectors = set()
# 获取所有已经存在的股票及其相关板块
existing_stock_sectors = await wance_data_stock.WanceDataStock.all().values('stock_code', 'stock_sector')
for stock_info in existing_stock_sectors:
for sector in stock_info['stock_sector']:
updated_sectors.add(sector)
# 过滤出没有更新过的sector
sector_list = [sector for sector in sector_list if sector not in updated_sectors]
for sector in sector_list:
sector_stock = xtdata.get_stock_list_in_sector(sector)
# 获取所有相关股票
stocks_to_update = await wance_data_stock.WanceDataStock.filter(stock_code__in=sector_stock)
# 遍历并更新每个股票的sector避免重复添加
for stock in stocks_to_update:
if sector not in stock.stock_sector:
stock.stock_sector.append(sector)
await stock.save() # 保存更新后的数据
print(f"更新板块完成 {stock.stock_code}: {stock.stock_sector} \n")
print(f"所有股票已经加载完成 \n")
"""
"""
# 每次都会操作数据库
async def init_stock_pool():
await init_tortoise()
# 初始化股票池
tick_result = xtdata.get_full_tick(['SH', 'SZ'])
tick_keys = list(tick_result.keys())
for key in tick_keys:
detail_result = xtdata.get_instrument_detail(key, False)
InstrumentName_result = detail_result.get("InstrumentName")
type_result = xtdata.get_instrument_type(key)
# 创建新的股票记录
await wance_data_stock.WanceDataStock.create(
stock_code=key,
stock_name=InstrumentName_result,
stock_sector="",
stock_type=type_result
)
# 获取并更新sector模块
sector_list = xtdata.get_sector_list()
for sector in sector_list:
sector_stock = xtdata.get_stock_list_in_sector(sector)
for stock in sector_stock:
# 更新已有股票的sector
await wance_data_stock.WanceDataStock.filter(stock_code=stock).update(stock_sector=sector)
"""
if __name__ == '__main__':
# processing_data(field_list=[],
# stock_list=["000062.SZ","600611.SH"],
# period="1d",
# start_time="20240506",
# end_time="",
# count=-1,
# dividend_type="none",
# fill_data=False,
# )
# history_data_processing()
asyncio.run(init_stock_pool(False))
asyncio.run(init_indicator())
# xtdata.run()

@ -0,0 +1,15 @@

92
src/exceptions.py Normal file

@ -0,0 +1,92 @@
from typing import Any
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException
from src.utils.helpers import first
def register_exception_handler(app: FastAPI):
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""请求参数验证错误处理"""
error = first(exc.errors())
field = error.get('loc')[1] or ''
return JSONResponse(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
content=jsonable_encoder({
"status_code": status.HTTP_422_UNPROCESSABLE_ENTITY,
"message": "{} param {}".format(field, error.get('msg')),
"detail": exc.errors()}),
)
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):
"""Http 异常处理"""
return JSONResponse(
status_code=exc.status_code,
content=jsonable_encoder({
"status_code": exc.status_code,
"message": exc.detail}),
)
@app.exception_handler(Exception)
async def exception_callback(request: Request, exc: Exception):
"""其他异常处理,遇到其他问题再自定义异常"""
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=jsonable_encoder({
"status_code": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": "Internal Server Error",
# "detail": ''.join(exc.args)
}),
)
class CommonHttpException(HTTPException):
def __init__(self, detail, status_code, **kwargs: dict[str, Any]) -> None:
super().__init__(status_code=status_code, detail=detail, **kwargs)
class DetailedHTTPException(HTTPException):
STATUS_CODE = status.HTTP_500_INTERNAL_SERVER_ERROR
DETAIL = "Server error"
def __init__(self, **kwargs: dict[str, Any]) -> None:
super().__init__(status_code=self.STATUS_CODE, detail=self.DETAIL, **kwargs)
class PermissionDenied(DetailedHTTPException):
STATUS_CODE = status.HTTP_403_FORBIDDEN
DETAIL = "Permission denied"
class NotFound(DetailedHTTPException):
STATUS_CODE = status.HTTP_404_NOT_FOUND
class BadRequest(DetailedHTTPException):
STATUS_CODE = status.HTTP_400_BAD_REQUEST
DETAIL = "Bad Request"
class UnprocessableEntity(DetailedHTTPException):
STATUS_CODE = status.HTTP_422_UNPROCESSABLE_ENTITY
DETAIL = "Unprocessable entity"
class NotAuthenticated(DetailedHTTPException):
STATUS_CODE = status.HTTP_401_UNAUTHORIZED
DETAIL = "User not authenticated"
class WxResponseError(DetailedHTTPException):
STATUS_CODE = status.HTTP_400_BAD_REQUEST
DETAIL = "请求微信异常"
def __init__(self) -> None:
super().__init__(headers={"WWW-Authenticate": "Bearer"})

52
src/main.py Normal file

@ -0,0 +1,52 @@
import sentry_sdk
import uvicorn
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from src.exceptions import register_exception_handler
from src.tortoises import register_tortoise_orm
from src.xtdata.router import router as xtdata_router
from src.backtest.router import router as backtest_router
from xtquant import xtdata
from src.settings.config import app_configs, settings
app = FastAPI(**app_configs)
register_tortoise_orm(app)
register_exception_handler(app)
app.include_router(xtdata_router, prefix="/getwancedata", tags=["盘口数据"])
app.include_router(backtest_router, prefix="/backtest", tags=["盘口数据"])
if settings.ENVIRONMENT.is_deployed:
sentry_sdk.init(
dsn=settings.SENTRY_DSN,
environment=settings.ENVIRONMENT,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_origin_regex=settings.CORS_ORIGINS_REGEX,
allow_credentials=True,
allow_methods=("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"),
allow_headers=settings.CORS_HEADERS,
)
@app.get("/")
async def root():
return {"message": "Hello, FastAPI!"}
if __name__ == "__main__":
uvicorn.run('src.main:app', host="0.0.0.0", port=8011, reload=True)

292
src/models/__init__.py Normal file

@ -0,0 +1,292 @@
from datetime import datetime
from decimal import Decimal
from enum import Enum, IntEnum
from typing import Optional
from pydantic import BaseModel
from src.settings.config import settings
def with_table_name(table_name: str):
return f"{settings.DATABASE_PREFIX}{table_name}"
class Decimals:
MAX_DIGITS = 15
DECIMAL_PLACES = 3
DECIMAL_PLACES_MORE = 6
class SecurityPriceCode(Enum):
TRANSFER_FEE = "fee_1" # 过户费
STAMP_TAX = "fee_2" # 印花税
TOTAL_COMMISSION = "csf_fee" # 总证券佣金
class UserSecurityExtraReminded(BaseModel):
"""
reminded_target_buy_price: 最近提醒目标买入价
reminded_target_buy_time: 目标买入价最近提醒时间
reminded_buy_price: 最近提醒买入价
reminded_buy_fluctuation: 最近提醒买入波动
reminded_buy_time: 买入最近提醒时间
reminded_sell_price: 最近提醒目标卖出价
reminded_sell_fluctuation: 最近提醒卖出波动
reminded_sell_time: 卖出最近提醒时间
buy_up_first_interval: 买入上涨初次提醒波动间隔
buy_up_continue_interval: 买入继续上涨提醒波动间隔
buy_down_first_interval: 买入下跌初次提醒波动间隔
buy_down_continue_interval: 买入继续下跌提醒波动间隔
sell_down_first_interval: 卖出下跌初次提醒波动间隔
sell_down_continue_interval: 卖出继续下跌提醒波动间隔
browse_msg_alarm_flag: 浏览器消息提醒通知设置
wx_msg_alarm_flag: 微信公众号消息提醒通知设置
"""
reminded_target_buy_price: int = 0
reminded_target_buy_time: Optional[datetime] = None
reminded_buy_price: Decimal = 0.000
reminded_buy_fluctuation: int = 0
reminded_buy_time: Optional[datetime] = None
reminded_sell_price: Decimal = 0.000
reminded_sell_fluctuation: int = 0
reminded_sell_time: Optional[datetime] = None
buy_up_first_interval: int = 1
buy_up_continue_interval: int = 1
buy_down_first_interval: int = -1
buy_down_continue_interval: int = -1
sell_down_first_interval: int = -1
sell_down_continue_interval: int = -1
browse_msg_alarm_flag: bool = True
wx_msg_alarm_flag: bool = True
class Config:
json_encoders = {Decimal: str}
class Status(IntEnum):
"""
状态
0-正常
1-删除
2-禁用
3-退市
"""
# 正常
NORMAL = 0
STOP = 1
DISABLED = 2
DELISTED = 3
class StockType(str, Enum):
"""
股票类型
"""
SH = "sh"
SZ = "sz"
class UserSecurityAccountType(IntEnum):
"""
用户中国证券账户账户类型
"""
NORMAL = 0 # 普通账户
MARGIN = 1 # 融资账户
class ExchangeType(str, Enum):
"""
交易类型
"""
SH = "SH"
SZ = "SZ"
NQ = "NQ"
BJ = "BJ"
class SellReferType(IntEnum):
"""
参考卖出类型
highest-最高卖出价
latest-最近卖出价
"""
HIGHEST = 0 # 最高卖出价
LATEST = 1 # 最近卖出价
class InvestmentSource(IntEnum):
"""
投资资金来源
"""
CASH = 0 # 现金
FINANCING = 1 # 场内融资
LOANED = 2 # 场外借贷
class SecuritySource(IntEnum):
"""
证券获得方式
"""
BUY = 0 # 正常买入
BONUS_SHARES = 1 # 送股
CONVERT_SHARES = 2 # 转增股
class StatusSell(IntEnum):
"""
售出状态
"""
UNSOLD = 0 # 未售出
PARTIAL_SOLD = 1 # 部分售出
ALL_SOLD = 2 # 全部售出
class BuyAndSellType(IntEnum):
"""
卖出卖出记录类型
"""
COMMISSION = 0 # 委托单
CONDITION = 1 # 条件单
CONTRACT = 2 # 成交单
class SecurityType(IntEnum):
"""
证券类型
"""
security_stock_a = 0 # A 股股票
security_stock_b = 1 # B 股股票
security_etf = 2 # 场内基金-ETF
security_lof = 3 # 场内基金-LOF
class PushConfigModel(BaseModel):
"""
驼峰转下线
"""
device_id: int = None
device_name: str = None
eable_flag: str = None
update_at: Optional[datetime] = None
class CashFlowType(IntEnum):
INTO = 0 # 转入
OUT = 1 # 转出
class AdminStatus(str, Enum):
"""
管理员账户状态
"""
DELETE = "delete" # 删除
NORMAL = "normal" # 正常
FORBIDDEN = "forbidden" # 禁用
UNAUTHORIZED = "unauthorized" # 未验证
class CustomerSecurityAccountSecurityAdminStatus(str, Enum):
"""
客户的证券账户中的券商状态
"""
JOIN_GROUP = "join_group" # 新入群
ACCOUNT_READY_NOT_DEPOSIT = "account_ready_not_deposit" # 已开户但未入金
ACCOUNT_SUBSTANDARD = "account_substandard" # 非有效户(20个交易日里平均市值1万)
ACCOUNT_UNSETTLED = "account_unsettled" # 待结算
ACCOUNT_SETTLED = "account_settled" # 已结算
class CustomerSecurityAccountVipStatus(str, Enum):
"""
客户的证券账户中的vip状态
"""
REWARDED = "rewarded" # 已奖励
UNREWARD = "unreward" # 未奖励
class UserBenefitStatus(IntEnum):
"""
用户福利领取状态
"""
UNRECEIVED = 0 # 未领取
RECEIVED = 1 # 已领取
class SecurityAdminTransferStatus(IntEnum):
"""
券商转账的状态
"""
PAYED = 1 # 已支付
CONFIRMED = 2 # 已确认
DISAGREED = 3 # 有异议
class YuanMaTalkQuestionPublicStatus(IntEnum):
"""
问题是否公开
"""
PUBLIC = 1 # 公开
PRIVATE = 0 # 私密
class YuanMaTalkQuestionStatus(str, Enum):
"""
问题状态
"""
NEW = "new" # 新问题
REPLIED = "replied" # 已回复
WAITING = "waiting" # 待回复
CLOSED = "closed" # 关闭
DELETED = "deleted" # 删除
FORBIDDEN = "forbidden" # 禁止
class LastReplySourceType(str, Enum):
"""
问题状态
"""
ADMIN = "admin" # 管理后台
USER = "user" # 用户端
class AccountType(IntEnum):
"""
账户类型
"""
LIANGRONG = 1 # 两融账户
ORDINARY = 0 # 普通用户
class supervisionFee(IntEnum):
"""
深市是否申请监管费
"""
NOT_CHARGE = 1 # 不收
CHARGE = 0 # 收
class transferFee(IntEnum):
"""
账户类型
"""
NOT_CHARGE = 1 # 不收
CHARGE = 0 # 收
class IsType(IntEnum):
"""
是否默认
"""
YES = 1 # 是
NO = 0 # 否

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -0,0 +1,13 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class BackObservedData(Model):
id = fields.IntField(pk=True, description='id表', )
key = fields.CharField(max_length=255, null=True, description='key', )
observed_data = fields.BinaryField(null=True, description='格式化后的json数据', )
class Meta:
table = with_table_name("back_observed_data")

@ -0,0 +1,12 @@
from tortoise import Model, fields
from src.models import with_table_name
class BackObservedDataDetail(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=255, )
back_observed_data = fields.BinaryField(null=True, )
class Meta:
table = with_table_name("back_observed_data_detail")

@ -0,0 +1,14 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class BackPosition(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=25, null=True, )
back_position_data = fields.BinaryField(null=True, )
class Meta:
table = with_table_name("back_position")

@ -0,0 +1,14 @@
from tortoise import Model, fields
from src.models import with_table_name
class BackResultIndicator(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=25, null=True, )
indicator = fields.BinaryField(null=True, )
class Meta:
table = with_table_name("back_result_indicator")

@ -0,0 +1,13 @@
from tortoise import Model, fields
from src.models import with_table_name
class BackTrandInfo(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=25, null=True, )
trade_info = fields.BinaryField(null=True, )
class Meta:
table = with_table_name("back_trand_info")

25
src/models/backtest.py Normal file

@ -0,0 +1,25 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class Backtest(Model):
id = fields.IntField(pk=True)
strategy: fields.ForeignKeyRelation[Strategy] = fields.ForeignKeyField(
model_name="models.Strategy",
related_name="integration_strategy",
# db_constraint=False,
on_delete=fields.CASCADE,
index=True,
)
key = fields.CharField(max_length=20, unique=True, index=True, description="回测key")
user_id = fields.IntField(null=True, description="回测用户")
backtest_at = fields.DatetimeField(auto_now_add=True, description="回测时间")
backtest_code = fields.TextField(description="回测代码")
is_running = fields.BooleanField(default=True, description="回测状态")
updated_at = fields.DatetimeField(auto_now=True, description="修改时间")
deleted_at = fields.DatetimeField(null=True, description="删除时间")
is_active = fields.BooleanField(default=True, description="是否可用")
class Meta:
table = with_table_name("Backtest")

@ -0,0 +1,23 @@
from pydantic import BaseModel, Field
from typing import Dict
class BenchmarkCodes(BaseModel):
"""
模型类用于表示基准名称和对应代码的映射
"""
codes: Dict[str, str] = Field(
default_factory=lambda: {
"沪深300": "000300", # 使用字符串来表示代码
"中证500": "000905"
},
title="Benchmark Codes",
description="A mapping of benchmark names to their respective codes."
)
# 添加一个属性或方法来根据基准名称返回代码
async def get_code(self, benchmark_name: str) -> str:
if benchmark_name in self.codes:
return self.codes[benchmark_name]
else:
raise ValueError(f"Unknown benchmark name: {benchmark_name}")

123
src/models/observed_data.py Normal file

@ -0,0 +1,123 @@
from objectbox.model import Entity, Id, Property, PropertyType
@Entity(id=1, uid=1)
class ObservedData:
id = Id(id=1, uid=1001)
key = Property(str, id=2, uid=1002)
date = Property(str, id=3, uid=1003)
time_return = Property(float, type=PropertyType.float, id=4, uid=1004)
cash = Property(float, type=PropertyType.float, id=5, uid=1005)
benchmark_return = Property(float, type=PropertyType.float, id=6, uid=1006)
is_running = Property(bool, id=7, uid=1007)
benchmark_ratio = Property(float, type=PropertyType.float, id=8, uid=1008)
max_draw_down = Property(float, type=PropertyType.float, id=9, uid=1009)
strategy_ratio = Property(float, type=PropertyType.float, id=10, uid=1010)
@Entity(id=2, uid=2)
class BackResultIndicator:
id = Id(id=5, uid=2001)
key = Property(str, id=6, uid=2002)
indicator = Property(str, id=7, uid=2003)
@Entity(id=3, uid=3)
class Position:
id = Id(id=1, uid=3001)
key = Property(str, id=2, uid=3002)
date = Property(str, id=3, uid=3003)
name = Property(str, id=4, uid=3004)
size = Property(int, id=5, uid=3005)
price = Property(float, id=6, uid=3006)
adjbase = Property(float, id=7, uid=3007)
profit_loss = Property(float, id=8, uid=3008)
@Entity(id=4, uid=4)
class TradeInfo:
id = Id(id=1, uid=4001)
key = Property(str, id=2, uid=4002)
stock_code = Property(str, id=3, uid=4003)
ordtype = Property(str, id=4, uid=4004)
executed_size = Property(int, id=5, uid=4005)
executed_price = Property(float, id=6, uid=4006)
value = Property(float, id=7, uid=4007)
size = Property(int, id=8, uid=4008)
price = Property(float, id=9, uid=4009)
status = Property(str, id=10, uid=4010)
pnl = Property(float, id=11, uid=4011)
date = Property(str, id=13, uid=4013)
commission = Property(float, id=14, uid=4014)
@Entity(id=5, uid=5)
class TranObserverData:
id = Id(id=1, uid=5001)
key = Property(str, id=2, uid=5002)
date = Property(str, id=3, uid=5003)
time_return = Property(float, type=PropertyType.float, id=4, uid=5004)
cumulative_return = Property(float, type=PropertyType.float, id=5, uid=5005)
trades = Property(float, type=PropertyType.float, id=6, uid=5006)
total_revenue = Property(float, type=PropertyType.float, id=7, uid=5007)
current_price = Property(float, type=PropertyType.float, id=8, uid=5008)
open = Property(float, type=PropertyType.float, id=9, uid=5009)
close = Property(float, type=PropertyType.float, id=10, uid=50010)
high = Property(float, type=PropertyType.float, id=11, uid=50011)
low = Property(float, type=PropertyType.float, id=12, uid=50012)
volume = Property(float, type=PropertyType.float, id=13, uid=50013)
cash = Property(float, type=PropertyType.float, id=14, uid=50014)
annualized_return = Property(float, type=PropertyType.float, id=15, uid=50015)
@Entity(id=6, uid=6)
class TranReturn:
id = Id(id=1, uid=6001)
key = Property(str, id=2, uid=6002)
date = Property(str, id=3, uid=6003)
time_return = Property(float, type=PropertyType.float, id=4, uid=6004)
benchmark_return = Property(float, type=PropertyType.float, id=5, uid=6005)
cumulative_return = Property(float, type=PropertyType.float, id=6, uid=6006)
max_draw_down = Property(float, type=PropertyType.float, id=7, uid=6007)
@Entity(id=7, uid=7)
class TranOrders:
id = Id(id=1, uid=7001)
key = Property(str, id=2, uid=7002)
order_return = Property(str, id=3, uid=7003)
@Entity(id=8, uid=8)
class TranTradeInfo:
id = Id(id=1, uid=8001)
key = Property(str, id=2, uid=8002)
stock_code = Property(str, id=3, uid=8003)
ordtype = Property(str, id=4, uid=8004)
executed_size = Property(int, id=5, uid=8005)
executed_price = Property(float, id=6, uid=8006)
value = Property(float, id=7, uid=8007)
size = Property(int, id=8, uid=8008)
price = Property(float, id=9, uid=8009)
status = Property(str, id=10, uid=8010)
pnl = Property(float, id=11, uid=8011)
date = Property(str, id=13, uid=8013)
commission = Property(float, id=14, uid=8014)
exec_type = Property(str, id=15, uid=8015)
trail_stop = Property(float, id=16, uid=8016)
trail_price_limit = Property(float, id=17, uid=8017)
@Entity(id=9, uid=9)
class TranData:
id = Id(id=1, uid=9001)
key = Property(str, id=2, uid=9002)
alpha = Property(float,type=PropertyType.float, id=3,uid=9003)
beta = Property(float, type=PropertyType.float, id=4,uid=9004)
sharpe_ratio = Property(float, id=5,uid=9005)
start_date = Property(str,id=6, uid=9006)
close_date = Property(str, id=7, uid=9007)
use_date = Property(str, id=8, uid=9008)
filename = Property(str, id=11, uid=1011)
day_alpha_array = Property(str,id=12,uid=1012)

68
src/models/order.py Normal file

@ -0,0 +1,68 @@
from tortoise.models import Model
from tortoise import fields, models
from tortoise.contrib.pydantic import pydantic_model_creator
from src.models import with_table_name, AccountType
class Order(Model):
id = fields.IntField(pk=True, description="订单ID")
stock_code = fields.CharField(max_length=30, description="股票代码")
stock_name = fields.CharField(max_length=30, description="股票名称")
limit_price = fields.FloatField(description="限价")
order_quantity = fields.IntField(description="委托数量")
order_amount = fields.FloatField(description="委托金额")
order_type = fields.CharField(max_length=20, description="订单类型") # 例如:买入、卖出
position = fields.CharField(max_length=30, description="仓位")
user_id = fields.IntField(null=False, description="用户Id")
entrust_date = fields.DatetimeField(null=True, description="委托日期")
entrust_time = fields.TimeField(null=True)
class Meta:
table = with_table_name("orders")
OrderResponse = pydantic_model_creator(
Order
)
class Entrust(Model):
id = fields.IntField(pk=True, description="主键id")
fund_account = fields.IntField(null=True, description="资金账户")
securities_alias = fields.CharField(max_length=30, null=True, description="账户别名")
account_type = fields.IntEnumField(AccountType, null=True, description="账户类型")
stock_code = fields.CharField(max_length=30, description="证券代码")
stock_name = fields.CharField(max_length=30, null=False, description="证券名称")
limit_price = fields.FloatField(description="委托价")
entrust_number = fields.IntField(description="委托数量")
deal_price = fields.FloatField(null=True, max_digits=10, description="成交价格")
deal_number = fields.IntField(null=True, description="成家数量")
order_type = fields.CharField(max_length=30, description="操作") # 买入卖出
entrust_date = fields.DateField(null=True, description="委托日期")
entrust_money = fields.FloatField(description="委托金额")
# Python
is_repair = fields.BooleanField(default=False, index=True, description="是否补单")
entrust_time = fields.TimeField(null=True) # 委托时间
class Meta:
table = with_table_name("Entrust")
EntrustResponse = pydantic_model_creator(
Entrust
)
class Backtesting(Model):
id = fields.IntField(pk=True, description="主键id")
key = fields.CharField(max_length=30, description="回测key")
class Meta:
table = with_table_name("backtest")
BacktestingResponse = pydantic_model_creator(
Backtesting
)

65
src/models/page_Info.py Normal file

@ -0,0 +1,65 @@
from datetime import datetime
from pydantic import BaseModel, Field
from typing import Optional, List
from src.pydantics.transaction import TransactionsPydantic, TransactionPydantic
class PageInfo(BaseModel):
page: Optional[int]
pageSize: Optional[int]
searchname: Optional[str]
# class PageResponse(BaseModel):
# current_page: int
# page_size: int
# total_pages: int
# total_items: int
# items: List[dict] # 使用 List[dict] 类型
#
# # 单个事务的 Pydantic 模型
# class TransactionPagePydantic(BaseModel):
# id: int
# key: str
# cash: float
# transaction_name: str
# transaction_type: Optional[str] # 可以为 None
# user_id: Optional[int] = None
# is_running: bool
# is_deleted: bool
# process_id: Optional[int] = None
# bar: Optional[str] = None
# created_at: datetime
# updated_at: datetime
# stopped_at: Optional[str] = None
# deleted_at: Optional[str] = None
# strategy_id: int
#
# class Config:
# orm_mode = True
# from_attributes = True
#
# # 分页的 Pydantic 模型
# class PageData(BaseModel):
# results: List[TransactionPydantic]
# total: int
# page: int
# pages: int
# size: int
# # next: Optional[str] = None
# # previous: Optional[str] = None
# # total_pages: int
#
# # 包装响应的 Pydantic 模型
# class EntityResponse(BaseModel):
# status_code: int
# message: str
# data: PageData

36
src/models/position.py Normal file

@ -0,0 +1,36 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class Position(Model):
id = fields.IntField(pk=True, description="主键")
key = fields.CharField(max_length=30, description="")
date = fields.DateField(description="日期")
name = fields.CharField(max_length=30, description="名称")
size = fields.IntField(description="数量")
price = fields.FloatField(description="价格")
adjbase = fields.FloatField(description="复权基数")
profit = fields.FloatField(description="利润")
class Meta:
table = with_table_name("position")
def create(id,pk,key, date, name, size, price, adjbase, profit):
try:
position = Position(id=id,pk=pk,key=key, date=date, name=name, size=size, price=price, adjbase=adjbase, profit=profit)
position.save()
return position
except Exception as e:
print(f"Position create error: {str(e)}")
return None
def bulk_create(param):
try:
Position.bulk_create(param)
return "Position bulk create success"
except Exception as e:
print(f"Position bulk create error: {str(e)}")
return None

@ -0,0 +1,31 @@
from tortoise import Model, fields
from src.models import with_table_name, AccountType, supervisionFee, transferFee, IsType
from tortoise.contrib.pydantic import pydantic_model_creator
class SecurityAccount(Model):
"""
证券账户
"""
id = fields.IntField(pk=True, description="主键")
securities_name = fields.CharField(max_length=30, null=False, description="证券公司名字昵称")
fund_account = fields.BigIntField(null=True, description="资金账户")
account_alias = fields.CharField(max_length=30, null=False, description="账户别名")
money = fields.FloatField(null=True, description="账户金额")
available_money = fields.FloatField(null=True, description="可用金额")
available_proportion = fields.FloatField(null=True, description="可用资金占比")
freeze = fields.FloatField(null=True, description="冻结金额")
class Meta:
table = with_table_name("security_account")
SecurityAccountResponse = pydantic_model_creator(
SecurityAccount
)

21
src/models/snowball.py Normal file

@ -0,0 +1,21 @@
from tortoise import Model, fields
from src.models import with_table_name
from tortoise.contrib.pydantic import pydantic_model_creator
class Snowball(Model):
"""
雪球相关信息
"""
id = fields.IntField(pk=True, description="主键")
snowball_token = fields.CharField(max_length=10000,null=True, description="雪球用户的token")
class Meta:
table = with_table_name("snowball")
SnowballResponse = pydantic_model_creator(
Snowball
)

23
src/models/stock.py Normal file

@ -0,0 +1,23 @@
from tortoise import Model, fields
from tortoise.contrib.pydantic import pydantic_model_creator
from src.models import with_table_name, StockType
class Stock(Model):
"""
股票相关信息
"""
id = fields.IntField(pk=True, description="主键")
stock_code = fields.CharField(max_length=30, description="股票代码")
stock_name = fields.CharField(max_length=30, null=True, description="股票名称")
type = fields.CharEnumField(StockType, null=True, description="类型")
stock_pinyin = fields.CharField(max_length=30, description="股票拼音")
class Meta:
table = with_table_name("stock")
StockResponse = pydantic_model_creator(
Stock
)

@ -0,0 +1,17 @@
from tortoise import Model, fields
from src.models import with_table_name
class StockBtHistory(Model):
id = fields.IntField(pk=True, )
end_bt_time = fields.CharField(max_length=8, null=True,description='回测最终时间', )
bt_stock_code = fields.CharField(max_length=8, null=True,description='回测股票代码', )
bt_stock_name = fields.CharField(max_length=10, null=True, description='回测股票名称', )
bt_benchmark_code = fields.CharField(max_length=8, null=True,description='股票基准代码', )
bt_stock_period = fields.CharField(max_length=10, null=True, description='回测类型', )
bt_strategy_name = fields.CharField(max_length=10, null=True, description='回测策略名称', )
bt_stock_data = fields.BinaryField(null=True, description='回测股票数据', )
class Meta:
table = with_table_name("stock_bt_history")

@ -0,0 +1,19 @@
from tortoise import Model, fields
from tortoise.contrib.pydantic import pydantic_model_creator
from src.models import with_table_name, StockType
class StockDataProcessing(Model):
bt_benchmark_code = fields.CharField(max_length=6, null=True, description='基准代码', )
bt_stock_period = fields.CharField(max_length=10, null=True, description='数据类型', )
bt_strategy_name = fields.CharField(max_length=10, null=True, description='回测策略名', )
id = fields.IntField(pk=True, )
processing_data = fields.BinaryField(null=True, description='清洗后的数据', )
prosessing_date = fields.CharField(max_length=10, null=True, description='当前回测时间', )
stock_name = fields.CharField(max_length=10, null=True, )
stocke_code = fields.CharField(max_length=10, null=True, )
class Meta:
table = with_table_name("stock_data_processing")

@ -0,0 +1,25 @@
from tortoise import Model, fields
from tortoise.contrib.pydantic import pydantic_model_creator
from src.models import with_table_name, StockType
class StockDetails(Model):
"""
股票相关信息
"""
id = fields.IntField(pk=True, description="主键")
stock_code = fields.CharField(max_length=30, description="股票代码")
stock_name = fields.CharField(max_length=30, null=True, description="股票名称")
type = fields.CharEnumField(StockType, null=True, description="类型")
stock_pinyin = fields.CharField(max_length=30, description="股票拼音")
latest_price = fields.FloatField(null=True, description="最新价")
rise_fall = fields.FloatField(null=True, description="跌涨幅")
class Meta:
table = with_table_name("stock_details")
StockDetailsResponse = pydantic_model_creator(
StockDetails
)

@ -0,0 +1,13 @@
from tortoise import Model, fields
from src.models import with_table_name
class StockHistory(Model):
id = fields.IntField(pk=True, )
stock_code = fields.IntField(description='股票代码', )
stock_name = fields.CharField(max_length=10, null=True, )
start_time_to_market = fields.CharField(max_length=10, null=True, description='股票上市时间', )
end_bt_time = fields.CharField(max_length=10, null=True, description='最终回测时间', )
symbol_data = fields.BinaryField(null=True, description='股票数据', )
class Meta:
table = with_table_name("stock_history")

19
src/models/strategy.py Normal file

@ -0,0 +1,19 @@
from tortoise import Model, fields
from src.models import with_table_name
class Strategy(Model):
id = fields.IntField(pk=True, description='主键')
strategy_name = fields.CharField(max_length=255, description='策略名称')
strategy_hash = fields.CharField(max_length=255, description='策略版本号')
strategy_type = fields.CharField(max_length=255, null=True, description='策略类型')
user_id = fields.IntField(null=True, description='所属用户')
backtest_count = fields.IntField(null=True, description='回测次数')
backtest_keys = fields.JSONField(null=True, description='回测key列表')
is_deleted = fields.BooleanField(max_length=255, default=False, description='是否删除')
created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
updated_at = fields.DatetimeField(auto_now=True, description="修改时间")
deleted_at = fields.DatetimeField(null=True, description="删除时间")
class Meta:
table = with_table_name("Strategy")

35
src/models/test_table.py Normal file

@ -0,0 +1,35 @@
from tortoise import Model, fields
from tortoise.contrib.pydantic import pydantic_model_creator
from src.models import with_table_name
class TestTable(Model):
"""
用户
"""
id = fields.IntField(pk=True, description="主键")
nickname = fields.CharField(max_length=30, null=True, description="用户昵称")
avatar_url = fields.CharField(max_length=255, null=True, description="头像")
member_type = fields.IntField(null=True, description="会员类型")
beta_account_type = fields.CharField(null=True, max_length=30, description="内测账号类型")
pre_cost_time = fields.IntField(null=True, description="预支付时间(单位年)")
qr_code = fields.CharField(max_length=255, null=True, description="专属客服二维码")
dedicated_id = fields.IntField(null=True, description="专属客服id")
invited_user_id = fields.IntField(null=True, description="邀请人id")
created_user_id = fields.IntField(null=True, description="创建人id")
is_deleted = fields.BooleanField(default=False, index=True, description="是否删除")
login_at = fields.DatetimeField(null=True, description="最后一次登录时间")
created_at = fields.DatetimeField(auto_now_add=True, description="创建时间")
updated_at = fields.DatetimeField(auto_now=True, description="修改时间")
deleted_at = fields.DatetimeField(null=True, description="删除时间")
class Meta:
table = with_table_name("users")
UserResponse = pydantic_model_creator(
TestTable
)

@ -0,0 +1,13 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class TranObserverData(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=255, )
tran_observer_data = fields.BinaryField(null=True, description='存储大量数据', )
class Meta:
table = with_table_name("tran_observer_data")

14
src/models/tran_orders.py Normal file

@ -0,0 +1,14 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class TranOrders(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=255, null=True, )
order_return = fields.CharField(max_length=255, null=True, )
class meta:
table = with_table_name('tradorders')

@ -0,0 +1,13 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class TranPosition(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=25, null=True, )
tran_position_data = fields.BinaryField(null=True, )
class Meta:
table = with_table_name("tran_position")

13
src/models/tran_return.py Normal file

@ -0,0 +1,13 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class TranReturn(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=255, null=True, )
tran_return_data = fields.BinaryField(null=True, )
class Meta:
table = with_table_name('tran_return')

@ -0,0 +1,12 @@
from tortoise import Model, fields
from src.models import with_table_name
from src.models.strategy import Strategy
class TranTradeInfo(Model):
id = fields.IntField(pk=True, )
key = fields.CharField(max_length=25, null=True, )
tran_trade_info = fields.BinaryField(null=True, )
class Meta:
table = with_table_name("tran_trade_info")

Some files were not shown because too many files have changed in this diff Show More