本次的提交信息
This commit is contained in:
parent
8054b7b8d2
commit
0aa2901f4c
23
.env
Normal file
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
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
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
||||
wance_data
|
12
.idea/dataSources.xml
Normal file
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>
|
146
.idea/inspectionProfiles/Project_Default.xml
Normal file
146
.idea/inspectionProfiles/Project_Default.xml
Normal file
@ -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>
|
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
Normal file
@ -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
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
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
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
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
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
19
banner.txt
Normal file
@ -0,0 +1,19 @@
|
||||
# ## # # # ## ## # # # # # # # # # # # #
|
||||
## ## # ## ## ## # ########## # ## # # #### ## ## ## ## # ############## ### ## #############
|
||||
## ############# ## ## ## # ## # ############## # #### # ## ## ## # ############### ## ## ## ## ## ## ##
|
||||
## ## # ## ## ## # ## ############### ## # ## ## ## ## ## ########## ## ## ## ## ## ## ## # ##
|
||||
# ### ########### ############### # # ## # ## ## # ## #### ###### ## ## # ## ## ####### ## ## ######### ##
|
||||
## ## # ## # ## ## ## ## ########### ## ## # ## ###### # ## ## ## # ########### ## # ## ## ## ### ##
|
||||
## ## ## ############### ### ## ## # ## ## ## ## ########### # ## # ## ## ######## ## ## ## ### ## ## ## # ### # # ##
|
||||
## ## ## # # #### ## ## ## ########## ## ## ## #### ## ## ## # # ## # ## ## ## # ####### ## ### ### ######## ##
|
||||
## ## ### ########## ##### ###### ## ## ## ## ## ## ## ##### ## ## # ### ## ## ######## ## ## ### # # ## # ## ## ##
|
||||
## ## ### ## ## # ## # ## ## # ########## ## ## # ### # # ##### ### # ## ## ## ## ## ## # # ## ## ## ## ##
|
||||
## ## ## ######### # ## ## ## ## # ## # ## ## # ## # ## #### #### #### ## ## ## ## ## ## ## ## ## ## ## ##
|
||||
# ## # ## ## ## ## ## # ## # ## ## # # # ## ### ## # ## ## ######## ##### ## ### ## ## ####### ##
|
||||
## ######### ## ## ## ## ########### # ## ## ## ## ## ## #### ## ## ## # ## ### ## ### # # ##
|
||||
## ## ## ## ## ## # ## # # ## # ## ## ## ## ### ## ## ## #### ### ########### ##
|
||||
#### ## #### ## ###### # ## # # # ### # ## ## ### ## ### ## ### ## #### ### ##### ## ####
|
||||
# # # # # # ############### # # # # # # # # # # ## # # # # #
|
||||
==========================================================================================================================================================================================================================
|
||||
*********************************************************************************************聪明帅气可爱睿智的老板保佑我永不报错************************************************************************************************
|
||||
==========================================================================================================================================================================================================================
|
239
migrations/models/3_20240909094115_None.py
Normal file
239
migrations/models/3_20240909094115_None.py
Normal file
@ -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
BIN
models.py
Normal file
Binary file not shown.
4
pyproject.toml
Normal file
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
4
src/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# load_dotenv(dotenv_path=Path("../.env"))
|
||||
load_dotenv()
|
BIN
src/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
src/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/constants.cpython-311.pyc
Normal file
BIN
src/__pycache__/constants.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/exceptions.cpython-311.pyc
Normal file
BIN
src/__pycache__/exceptions.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/main.cpython-311.pyc
Normal file
BIN
src/__pycache__/main.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/responses.cpython-311.pyc
Normal file
BIN
src/__pycache__/responses.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/tortoises.cpython-311.pyc
Normal file
BIN
src/__pycache__/tortoises.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/tortoises_orm_config.cpython-311.pyc
Normal file
BIN
src/__pycache__/tortoises_orm_config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/backtest/__pycache__/bollinger_bands.cpython-311.pyc
Normal file
BIN
src/backtest/__pycache__/bollinger_bands.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/backtest/__pycache__/dual_moving_average.cpython-311.pyc
Normal file
BIN
src/backtest/__pycache__/dual_moving_average.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
src/backtest/__pycache__/router.cpython-311.pyc
Normal file
BIN
src/backtest/__pycache__/router.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/backtest/__pycache__/rsi_strategy.cpython-311.pyc
Normal file
BIN
src/backtest/__pycache__/rsi_strategy.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/backtest/__pycache__/service.cpython-311.pyc
Normal file
BIN
src/backtest/__pycache__/service.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/backtest/__pycache__/until.cpython-311.pyc
Normal file
BIN
src/backtest/__pycache__/until.cpython-311.pyc
Normal file
Binary file not shown.
106
src/backtest/backtest.py
Normal file
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
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()
|
||||
|
274
src/backtest/bollinger_bands.py
Normal file
274
src/backtest/bollinger_bands.py
Normal file
@ -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())
|
267
src/backtest/dual_moving_average.py
Normal file
267
src/backtest/dual_moving_average.py
Normal file
@ -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())
|
92
src/backtest/living_backtesting.py
Normal file
92
src/backtest/living_backtesting.py
Normal file
@ -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()
|
271
src/backtest/macd_strategy.py
Normal file
271
src/backtest/macd_strategy.py
Normal file
@ -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())
|
264
src/backtest/reverse_dual_ma_strategy.py
Normal file
264
src/backtest/reverse_dual_ma_strategy.py
Normal file
@ -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
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
|
296
src/backtest/rsi_strategy.py
Normal file
296
src/backtest/rsi_strategy.py
Normal file
@ -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
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
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
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)
|
412
src/data_processing/history_data_processing.py
Normal file
412
src/data_processing/history_data_processing.py
Normal file
@ -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()
|
15
src/data_processing/response_factor.py
Normal file
15
src/data_processing/response_factor.py
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
92
src/exceptions.py
Normal file
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
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
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 # 否
|
BIN
src/models/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/__init__.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/back_observed_data.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/back_observed_data.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/back_observed_data_detail.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/back_observed_data_detail.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/back_position.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/back_position.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/back_result_indicator.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/back_result_indicator.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/back_trand_info.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/back_trand_info.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/backtest.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/backtest.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/order.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/order.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/position.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/position.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/security_account.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/security_account.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/snowball.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/snowball.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/stock.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/stock.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/stock_bt_history.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/stock_bt_history.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/stock_data_processing.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/stock_data_processing.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/stock_details.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/stock_details.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/stock_history.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/stock_history.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/strategy.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/strategy.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/test_table.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/test_table.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/tran_observer_data.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/tran_observer_data.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/tran_orders.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/tran_orders.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/tran_position.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/tran_position.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/tran_return.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/tran_return.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/tran_trade_info.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/tran_trade_info.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/trand_info.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/trand_info.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/transaction.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/transaction.cpython-311.pyc
Normal file
Binary file not shown.
BIN
src/models/__pycache__/wance_data_stock.cpython-311.pyc
Normal file
BIN
src/models/__pycache__/wance_data_stock.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
13
src/models/back_observed_data.py
Normal file
13
src/models/back_observed_data.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 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")
|
12
src/models/back_observed_data_detail.py
Normal file
12
src/models/back_observed_data_detail.py
Normal file
@ -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")
|
14
src/models/back_position.py
Normal file
14
src/models/back_position.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 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")
|
14
src/models/back_result_indicator.py
Normal file
14
src/models/back_result_indicator.py
Normal file
@ -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")
|
||||
|
13
src/models/back_trand_info.py
Normal file
13
src/models/back_trand_info.py
Normal file
@ -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
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")
|
23
src/models/backtest_parameters.py
Normal file
23
src/models/backtest_parameters.py
Normal file
@ -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
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
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
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
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
|
31
src/models/security_account.py
Normal file
31
src/models/security_account.py
Normal file
@ -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
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
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
|
||||
)
|
17
src/models/stock_bt_history.py
Normal file
17
src/models/stock_bt_history.py
Normal file
@ -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")
|
19
src/models/stock_data_processing.py
Normal file
19
src/models/stock_data_processing.py
Normal file
@ -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")
|
25
src/models/stock_details.py
Normal file
25
src/models/stock_details.py
Normal file
@ -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
|
||||
)
|
13
src/models/stock_history.py
Normal file
13
src/models/stock_history.py
Normal file
@ -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
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
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
|
||||
)
|
13
src/models/tran_observer_data.py
Normal file
13
src/models/tran_observer_data.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 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
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')
|
13
src/models/tran_position.py
Normal file
13
src/models/tran_position.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 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
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')
|
||||
|
12
src/models/tran_trade_info.py
Normal file
12
src/models/tran_trade_info.py
Normal file
@ -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
Loading…
Reference in New Issue
Block a user