1
0

Compare commits

..

5 Commits

Author SHA1 Message Date
48c6154fab 更新环境 2026-02-02 09:08:37 +08:00
3174f306bb 优化表结构,增加表数据 2026-01-30 17:57:12 +08:00
b90d030899 尝试加载数据 2026-01-29 18:10:40 +08:00
9f06ebf87d 完成macd图的绘制 2026-01-29 10:41:53 +08:00
d4db4f3021 移除不使用的依赖 2026-01-29 10:32:42 +08:00
20 changed files with 4587 additions and 3221 deletions

7
.idea/dataSources.xml generated
View File

@@ -8,5 +8,12 @@
<jdbc-url>jdbc:postgresql://81.71.3.24:6785/leopard_dev</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="leopard.sqlite" uuid="c9e16f8e-81be-45cf-847c-47a6750eeee2">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$USER_HOME$/Documents/leopard_data/leopard.sqlite</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

7
.idea/data_source_mapping.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/bd7b5f2a-eb99-4aad-81ec-1fec76b3d7fc/console.sql" value="bd7b5f2a-eb99-4aad-81ec-1fec76b3d7fc" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/c9e16f8e-81be-45cf-847c-47a6750eeee2/console.sql" value="c9e16f8e-81be-45cf-847c-47a6750eeee2" />
</component>
</project>

6
.idea/db-forest-config.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="db-tree-configuration">
<option name="data" value="" />
</component>
</project>

1
.idea/sqldialects.xml generated
View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/sql/initial.sql" dialect="SQLite" />
<file url="PROJECT" dialect="PostgreSQL" />
</component>
</project>

136
data.py Normal file
View File

@@ -0,0 +1,136 @@
from datetime import date, datetime, timedelta
from time import sleep
from sqlalchemy import Column, Double, Integer, String, create_engine
from sqlalchemy.orm import DeclarativeBase, Session
from tushare import pro_api
TUSHARE_API_KEY = '64ebff4fa679167600b905ee45dd88e76f3963c0ff39157f3f085f0e'
class Base(DeclarativeBase):
pass
class Stock(Base):
__tablename__ = 'stock'
code = Column(String, primary_key=True, comment="代码")
name = Column(String, comment="名称")
fullname = Column(String, comment="全名")
market = Column(String, comment="市场")
exchange = Column(String, comment="交易所")
industry = Column(String, comment="行业")
list_date = Column(String, comment="上市日期")
class Daily(Base):
__tablename__ = 'daily'
code = Column(String, primary_key=True)
trade_date = Column(String, primary_key=True)
open = Column(Double)
close = Column(Double)
high = Column(Double)
low = Column(Double)
previous_close = Column(Double)
turnover = Column(Double)
volume = Column(Integer)
price_change_amount = Column(Double)
factor = Column(Double)
def main():
print("开始更新数据")
engine = create_engine(f"sqlite:////Users/lanyuanxiaoyao/Documents/leopard_data/leopard.sqlite")
try:
Stock.metadata.create_all(engine, checkfirst=True)
Daily.metadata.create_all(engine, checkfirst=True)
pro = pro_api(TUSHARE_API_KEY)
# with engine.connect() as connection:
# stocks = pro.stock_basic(list_status="L", market="主板", fields="ts_code,name,fullname,market,exchange,industry,list_date")
# for row in stocks.itertuples():
# stmt = insert(Stock).values(
# code=row.ts_code,
# name=row.name,
# fullname=row.fullname,
# market=row.market,
# exchange=row.exchange,
# industry=row.industry,
# list_date=row.list_date,
# )
# stmt = stmt.on_conflict_do_update(
# index_elements=["code"],
# set_={
# "name": stmt.excluded.name,
# "fullname": stmt.excluded.fullname,
# "market": stmt.excluded.market,
# "exchange": stmt.excluded.exchange,
# "industry": stmt.excluded.industry,
# "list_date": stmt.excluded.list_date,
# },
# )
# print(stmt)
# connection.execute(stmt)
# connection.commit()
#
# print("清理行情数据")
# connection.execute(text("delete from daily where code not in (select distinct code from stock)"))
# connection.commit()
#
# print("清理财务数据")
# connection.execute(text("delete from finance_indicator where code not in (select distinct code from stock)"))
# connection.commit()
with Session(engine) as session:
stock_codes = [row[0] for row in session.query(Stock.code).all()]
latest_date = session.query(Daily.trade_date).order_by(Daily.trade_date.desc()).first()
if latest_date is None:
latest_date = '1990-12-19'
else:
latest_date = latest_date.trade_date
latest_date = datetime.strptime(latest_date, '%Y-%m-%d').date()
current_date = date.today() - timedelta(days=1)
delta = (current_date - latest_date).days
print(f"最新数据日期:{latest_date},当前日期:{current_date},待更新天数:{delta}")
if delta > 0:
update_dates = []
for i in range(delta):
latest_date = latest_date + timedelta(days=1)
update_dates.append(latest_date.strftime('%Y%m%d'))
for target_date in update_dates:
print(f"正在采集:{target_date}")
dailies = pro.daily(trade_date=target_date)
dailies.set_index("ts_code", inplace=True)
factors = pro.adj_factor(trade_date=target_date)
factors.set_index("ts_code", inplace=True)
results = dailies.join(factors, lsuffix="_daily", rsuffix="_factor", how="left")
rows = []
for row in results.itertuples():
if row.Index in stock_codes:
rows.append(
Daily(
code=row.Index,
trade_date=datetime.strptime(target_date, '%Y%m%d').strftime("%Y-%m-%d"),
open=row.open,
close=row.close,
high=row.high,
low=row.low,
previous_close=row.pre_close,
turnover=row.amount,
volume=row.vol,
price_change_amount=row.pct_chg,
factor=row.adj_factor,
)
)
session.add_all(rows)
session.commit()
sleep(1)
finally:
engine.dispose()
if __name__ == '__main__':
main()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1088
notebook/backtest.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,184 +0,0 @@
import marimo
__generated_with = "0.19.6"
app = marimo.App(width="full", auto_download=["ipynb"], sql_output="pandas")
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell
def _():
import urllib
import sqlalchemy
host = "81.71.3.24"
port = 6785
username = "leopard"
password = urllib.parse.quote_plus("9NEzFzovnddf@PyEP?e*AYAWnCyd7UhYwQK$pJf>7?ccFiN^x4$eKEZ5~E<7<+~X")
database = "leopard_dev"
engine = sqlalchemy.create_engine(f"postgresql://{username}:{password}@{host}:{port}/{database}")
return (engine,)
@app.cell
def _(engine, mo):
dailies_df = mo.sql(
f"""
select trade_date,
open * factor as Open,
close * factor as Close,
high * factor as High,
low * factor as Low,
volume as Volume,
coalesce(factor, 1.0) as factor
from leopard_daily daily
left join leopard_stock stock on stock.id = daily.stock_id
where stock.code = '000001.SZ'
and daily.trade_date between '2024-01-01 00:00:00' and '2025-12-31 23:59:59'
order by daily.trade_date
""",
engine=engine
)
return (dailies_df,)
@app.cell
def _(dailies_df):
import pandas as pd
dailies_df.rename(
columns={'open': 'Open', 'close': 'Close', 'high': 'High', 'low': 'Low', 'volume': 'Volume'}, inplace=True
)
dailies_df['trade_date'] = pd.to_datetime(dailies_df['trade_date'], format='%Y-%m-%d')
dailies_df.set_index('trade_date', inplace=True)
dailies_df
return
@app.cell
def _(dailies_df):
import talib
dailies_df['sma10'] = talib.SMA(dailies_df['Close'], timeperiod=10)
dailies_df['sma30'] = talib.SMA(dailies_df['Close'], timeperiod=30)
dailies_df['sma60'] = talib.SMA(dailies_df['Close'], timeperiod=60)
dailies_df['sma120'] = talib.SMA(dailies_df['Close'], timeperiod=120)
dailies_df
return
@app.cell
def _(dailies_df):
# 指标计算完成后截取后面指标的完整的部份使用
target_dailies_df = dailies_df.loc['2025-01-01':'2025-12-31']
target_dailies_df
return (target_dailies_df,)
@app.cell
def _():
from backtesting import Strategy
from backtesting.lib import crossover
class SmaCross(Strategy):
def init(self):
self.sma10 = self.I(lambda x: x, self.data.sma10)
self.sma30 = self.I(lambda x: x, self.data.sma30)
self.sma60 = self.I(lambda x: x, self.data.sma60)
# self.sma120 = self.I(lambda x: x, self.data.sma120)
def next(self):
if self.sma60 > 0 and crossover(self.data.sma10, self.data.sma30):
self.buy()
elif self.position.size > 0 and crossover(self.data.sma30, self.data.sma10):
self.position.close()
return (SmaCross,)
@app.function
def stats_print(stats):
indicator_name_mapping = {
# 'Start': '回测开始时间',
# 'End': '回测结束时间',
# 'Duration': '回测持续时长',
# 'Exposure Time [%]': '持仓时间占比(%',
'Equity Final [$]': '最终收益',
'Equity Peak [$]': '峰值收益',
'Return [%]': '总收益率(%',
'Buy & Hold Return [%]': '买入并持有收益率(%',
'Return (Ann.) [%]': '年化收益率(%',
'Volatility (Ann.) [%]': '年化波动率(%',
# 'CAGR [%]': '复合年均增长率(%',
# 'Sharpe Ratio': '夏普比率',
'Sortino Ratio': '索提诺比率',
'Calmar Ratio': '卡尔玛比率',
# 'Alpha [%]': '阿尔法系数(%',
# 'Beta': '贝塔系数',
'Max. Drawdown [%]': '最大回撤(%',
'Avg. Drawdown [%]': '平均回撤(%',
'Max. Drawdown Duration': '最大回撤持续时长',
'Avg. Drawdown Duration': '平均回撤持续时长',
'# Trades': '总交易次数',
'Win Rate [%]': '胜率(%',
# 'Best Trade [%]': '最佳单笔交易收益率(%',
# 'Worst Trade [%]': '最差单笔交易收益率(%',
# 'Avg. Trade [%]': '平均单笔交易收益率(%',
# 'Max. Trade Duration': '单笔交易最长持有时长',
# 'Avg. Trade Duration': '单笔交易平均持有时长',
# 'Profit Factor': '盈利因子',
# 'Expectancy [%]': '期望收益(%',
'SQN': '系统质量数',
# 'Kelly Criterion': '凯利准则',
}
for k, v in stats.items():
if k in indicator_name_mapping:
cn_name = indicator_name_mapping.get(k, k)
if isinstance(v, (int, float)):
if "%" in cn_name or k in ['Sharpe Ratio', 'Sortino Ratio', 'Calmar Ratio', 'Profit Factor']:
formatted_value = f"{v:.2f}"
elif "$" in cn_name:
formatted_value = f"{v:.2f}"
elif "次数" in cn_name:
formatted_value = f"{v:.0f}"
else:
formatted_value = f"{v:.4f}"
else:
formatted_value = str(v)
print(f'{cn_name}: {formatted_value}')
@app.cell
def _(SmaCross, target_dailies_df):
from backtesting import Backtest
import backtesting._plotting as plotting
from bokeh.colors.named import tomato, lime
plotting.BULL_COLOR = tomato
plotting.BEAR_COLOR = lime
bt = Backtest(target_dailies_df, SmaCross, cash=100000, commission=.002, finalize_trades=True)
stats = bt.run()
stats_print(stats)
return bt, stats
@app.cell
def _(stats):
stats._trades
# stats._equity_curve
return
@app.cell
def _(bt):
bt.plot()
return
if __name__ == "__main__":
app.run()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,455 @@
{
"cells": [
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-30T05:41:51.291397Z",
"start_time": "2026-01-30T04:34:22.917761Z"
}
},
"cell_type": "code",
"source": [
"import urllib.parse\n",
"\n",
"import pandas as pd\n",
"import sqlalchemy\n",
"from sqlalchemy import text\n",
"from sqlalchemy.orm import DeclarativeBase, Session\n",
"\n",
"postgresql_engin = sqlalchemy.create_engine(\n",
" f\"postgresql://leopard:{urllib.parse.quote_plus(\"9NEzFzovnddf@PyEP?e*AYAWnCyd7UhYwQK$pJf>7?ccFiN^x4$eKEZ5~E<7<+~X\")}@81.71.3.24:6785/leopard\"\n",
")\n",
"sqlite_engine = sqlalchemy.create_engine(f\"sqlite:////Users/lanyuanxiaoyao/Documents/leopard_data/leopard.sqlite\")\n",
"\n",
"\n",
"class Base(DeclarativeBase):\n",
" pass\n",
"\n",
"\n",
"class Daily(Base):\n",
" __tablename__ = 'daily'\n",
"\n",
" code = sqlalchemy.Column(sqlalchemy.String, primary_key=True)\n",
" trade_date = sqlalchemy.Column(sqlalchemy.Date, primary_key=True)\n",
" open = sqlalchemy.Column(sqlalchemy.Double)\n",
" close = sqlalchemy.Column(sqlalchemy.Double)\n",
" high = sqlalchemy.Column(sqlalchemy.Double)\n",
" low = sqlalchemy.Column(sqlalchemy.Double)\n",
" previous_close = sqlalchemy.Column(sqlalchemy.Double)\n",
" turnover = sqlalchemy.Column(sqlalchemy.Double)\n",
" volume = sqlalchemy.Column(sqlalchemy.Integer)\n",
" price_change_amount = sqlalchemy.Column(sqlalchemy.Double)\n",
" factor = sqlalchemy.Column(sqlalchemy.Double)\n",
"\n",
"\n",
"try:\n",
" with Session(postgresql_engin) as pg_session:\n",
" results = pg_session.execute(text(\"select distinct trade_date from leopard_daily\")).fetchall()\n",
" results = list(map(lambda x: x[0].strftime(\"%Y-%m-%d\"), results))\n",
" dates = [results[i: i + 30] for i in range(0, len(results), 30)]\n",
"\n",
" for index, date in enumerate(dates):\n",
" print(date)\n",
" daily_df = pd.read_sql(\n",
" f\"\"\"\n",
" select code,\n",
" trade_date,\n",
" open,\n",
" close,\n",
" high,\n",
" low,\n",
" previous_close,\n",
" turnover,\n",
" volume,\n",
" price_change_amount,\n",
" factor\n",
" from leopard_daily d\n",
" left join leopard_stock s on d.stock_id = s.id\n",
" where d.trade_date in ('{\"','\".join(date)}')\n",
" \"\"\",\n",
" postgresql_engin\n",
" )\n",
" with Session(sqlite_engine) as session:\n",
" rows = []\n",
" for _, row in daily_df.iterrows():\n",
" rows.append(\n",
" Daily(\n",
" code=row[\"code\"],\n",
" trade_date=row[\"trade_date\"],\n",
" open=row[\"open\"],\n",
" close=row[\"close\"],\n",
" high=row[\"high\"],\n",
" low=row[\"low\"],\n",
" previous_close=row[\"previous_close\"],\n",
" turnover=row[\"turnover\"],\n",
" volume=row[\"volume\"],\n",
" price_change_amount=row[\"price_change_amount\"],\n",
" factor=row[\"factor\"]\n",
" )\n",
" )\n",
" session.add_all(rows)\n",
" session.commit()\n",
"finally:\n",
" postgresql_engin.dispose()\n",
" sqlite_engine.dispose()"
],
"id": "48821306efc640a1",
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"['2025-12-25', '2025-12-26', '2025-12-29', '2025-12-30', '2025-12-31', '2026-01-05', '2026-01-06', '2026-01-07', '2026-01-08', '2026-01-09']\n"
]
}
],
"execution_count": 22
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-30T09:24:09.859231Z",
"start_time": "2026-01-30T09:24:09.746912Z"
}
},
"cell_type": "code",
"source": [
"import tushare as ts\n",
"\n",
"pro = ts.pro_api(\"64ebff4fa679167600b905ee45dd88e76f3963c0ff39157f3f085f0e\")\n",
"# stocks = pro.stock_basic(ts_code=\"600200.SH\", list_status=\"D\", fields=\"ts_code,name,fullname,market,exchange,industry,list_date,delist_date\")\n",
"# stocks"
],
"id": "ed58a1faaf2cdb8e",
"outputs": [],
"execution_count": 34
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-30T07:14:29.897120Z",
"start_time": "2026-01-30T07:14:29.664124Z"
}
},
"cell_type": "code",
"source": "# stocks.to_csv(\"dlist.csv\")",
"id": "3c8c0a38d6b2992e",
"outputs": [],
"execution_count": 24
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-30T09:46:34.808300Z",
"start_time": "2026-01-30T09:46:34.129412Z"
}
},
"cell_type": "code",
"source": [
"daily_df = pro.daily(trade_date=\"20251231\")\n",
"daily_df.set_index(\"ts_code\", inplace=True)\n",
"factor_df = pro.adj_factor(trade_date=\"20251231\")\n",
"factor_df.set_index(\"ts_code\", inplace=True)"
],
"id": "c052a945869aa329",
"outputs": [],
"execution_count": 50
},
{
"metadata": {
"ExecuteTime": {
"end_time": "2026-01-30T09:46:36.697015Z",
"start_time": "2026-01-30T09:46:36.642975Z"
}
},
"cell_type": "code",
"source": [
"result_df = daily_df.join(factor_df, lsuffix=\"_daily\", rsuffix=\"_factor\", how=\"left\")\n",
"result_df\n",
"# factor_df"
],
"id": "d61ee80d2cd9f06b",
"outputs": [
{
"data": {
"text/plain": [
" trade_date_daily open high low close pre_close change \\\n",
"ts_code \n",
"000001.SZ 20251231 11.48 11.49 11.40 11.41 11.48 -0.07 \n",
"000002.SZ 20251231 4.66 4.68 4.62 4.65 4.62 0.03 \n",
"000004.SZ 20251231 11.30 11.35 11.07 11.08 11.27 -0.19 \n",
"000006.SZ 20251231 9.95 10.03 9.69 9.95 9.86 0.09 \n",
"000007.SZ 20251231 11.72 11.75 11.28 11.44 11.62 -0.18 \n",
"... ... ... ... ... ... ... ... \n",
"920978.BJ 20251231 37.64 38.39 36.88 36.90 37.78 -0.88 \n",
"920981.BJ 20251231 32.20 32.29 31.75 31.96 32.07 -0.11 \n",
"920982.BJ 20251231 233.00 238.49 232.10 233.70 234.80 -1.10 \n",
"920985.BJ 20251231 7.32 7.35 7.17 7.19 7.30 -0.11 \n",
"920992.BJ 20251231 17.33 17.60 17.29 17.39 17.38 0.01 \n",
"\n",
" pct_chg vol amount trade_date_factor adj_factor \n",
"ts_code \n",
"000001.SZ -0.6098 590620.37 675457.357 20251231 134.5794 \n",
"000002.SZ 0.6494 1075561.25 499883.113 20251231 181.7040 \n",
"000004.SZ -1.6859 18056.00 20248.567 20251231 4.0640 \n",
"000006.SZ 0.9128 270369.08 267758.676 20251231 39.7400 \n",
"000007.SZ -1.5491 80556.00 92109.366 20251231 8.2840 \n",
"... ... ... ... ... ... \n",
"920978.BJ -2.3293 33945.04 126954.937 20251231 1.2885 \n",
"920981.BJ -0.3430 8237.16 26301.206 20251231 1.4343 \n",
"920982.BJ -0.4685 5210.09 122452.646 20251231 4.2831 \n",
"920985.BJ -1.5068 35174.30 25350.257 20251231 1.6280 \n",
"920992.BJ 0.0575 6991.87 12193.445 20251231 1.4932 \n",
"\n",
"[5458 rows x 12 columns]"
],
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>trade_date_daily</th>\n",
" <th>open</th>\n",
" <th>high</th>\n",
" <th>low</th>\n",
" <th>close</th>\n",
" <th>pre_close</th>\n",
" <th>change</th>\n",
" <th>pct_chg</th>\n",
" <th>vol</th>\n",
" <th>amount</th>\n",
" <th>trade_date_factor</th>\n",
" <th>adj_factor</th>\n",
" </tr>\n",
" <tr>\n",
" <th>ts_code</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>000001.SZ</th>\n",
" <td>20251231</td>\n",
" <td>11.48</td>\n",
" <td>11.49</td>\n",
" <td>11.40</td>\n",
" <td>11.41</td>\n",
" <td>11.48</td>\n",
" <td>-0.07</td>\n",
" <td>-0.6098</td>\n",
" <td>590620.37</td>\n",
" <td>675457.357</td>\n",
" <td>20251231</td>\n",
" <td>134.5794</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000002.SZ</th>\n",
" <td>20251231</td>\n",
" <td>4.66</td>\n",
" <td>4.68</td>\n",
" <td>4.62</td>\n",
" <td>4.65</td>\n",
" <td>4.62</td>\n",
" <td>0.03</td>\n",
" <td>0.6494</td>\n",
" <td>1075561.25</td>\n",
" <td>499883.113</td>\n",
" <td>20251231</td>\n",
" <td>181.7040</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000004.SZ</th>\n",
" <td>20251231</td>\n",
" <td>11.30</td>\n",
" <td>11.35</td>\n",
" <td>11.07</td>\n",
" <td>11.08</td>\n",
" <td>11.27</td>\n",
" <td>-0.19</td>\n",
" <td>-1.6859</td>\n",
" <td>18056.00</td>\n",
" <td>20248.567</td>\n",
" <td>20251231</td>\n",
" <td>4.0640</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000006.SZ</th>\n",
" <td>20251231</td>\n",
" <td>9.95</td>\n",
" <td>10.03</td>\n",
" <td>9.69</td>\n",
" <td>9.95</td>\n",
" <td>9.86</td>\n",
" <td>0.09</td>\n",
" <td>0.9128</td>\n",
" <td>270369.08</td>\n",
" <td>267758.676</td>\n",
" <td>20251231</td>\n",
" <td>39.7400</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000007.SZ</th>\n",
" <td>20251231</td>\n",
" <td>11.72</td>\n",
" <td>11.75</td>\n",
" <td>11.28</td>\n",
" <td>11.44</td>\n",
" <td>11.62</td>\n",
" <td>-0.18</td>\n",
" <td>-1.5491</td>\n",
" <td>80556.00</td>\n",
" <td>92109.366</td>\n",
" <td>20251231</td>\n",
" <td>8.2840</td>\n",
" </tr>\n",
" <tr>\n",
" <th>...</th>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" <td>...</td>\n",
" </tr>\n",
" <tr>\n",
" <th>920978.BJ</th>\n",
" <td>20251231</td>\n",
" <td>37.64</td>\n",
" <td>38.39</td>\n",
" <td>36.88</td>\n",
" <td>36.90</td>\n",
" <td>37.78</td>\n",
" <td>-0.88</td>\n",
" <td>-2.3293</td>\n",
" <td>33945.04</td>\n",
" <td>126954.937</td>\n",
" <td>20251231</td>\n",
" <td>1.2885</td>\n",
" </tr>\n",
" <tr>\n",
" <th>920981.BJ</th>\n",
" <td>20251231</td>\n",
" <td>32.20</td>\n",
" <td>32.29</td>\n",
" <td>31.75</td>\n",
" <td>31.96</td>\n",
" <td>32.07</td>\n",
" <td>-0.11</td>\n",
" <td>-0.3430</td>\n",
" <td>8237.16</td>\n",
" <td>26301.206</td>\n",
" <td>20251231</td>\n",
" <td>1.4343</td>\n",
" </tr>\n",
" <tr>\n",
" <th>920982.BJ</th>\n",
" <td>20251231</td>\n",
" <td>233.00</td>\n",
" <td>238.49</td>\n",
" <td>232.10</td>\n",
" <td>233.70</td>\n",
" <td>234.80</td>\n",
" <td>-1.10</td>\n",
" <td>-0.4685</td>\n",
" <td>5210.09</td>\n",
" <td>122452.646</td>\n",
" <td>20251231</td>\n",
" <td>4.2831</td>\n",
" </tr>\n",
" <tr>\n",
" <th>920985.BJ</th>\n",
" <td>20251231</td>\n",
" <td>7.32</td>\n",
" <td>7.35</td>\n",
" <td>7.17</td>\n",
" <td>7.19</td>\n",
" <td>7.30</td>\n",
" <td>-0.11</td>\n",
" <td>-1.5068</td>\n",
" <td>35174.30</td>\n",
" <td>25350.257</td>\n",
" <td>20251231</td>\n",
" <td>1.6280</td>\n",
" </tr>\n",
" <tr>\n",
" <th>920992.BJ</th>\n",
" <td>20251231</td>\n",
" <td>17.33</td>\n",
" <td>17.60</td>\n",
" <td>17.29</td>\n",
" <td>17.39</td>\n",
" <td>17.38</td>\n",
" <td>0.01</td>\n",
" <td>0.0575</td>\n",
" <td>6991.87</td>\n",
" <td>12193.445</td>\n",
" <td>20251231</td>\n",
" <td>1.4932</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"<p>5458 rows × 12 columns</p>\n",
"</div>"
]
},
"execution_count": 51,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": 51
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

1157
notebook/indicator.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,131 +0,0 @@
import marimo
__generated_with = "0.19.6"
app = marimo.App(width="full", auto_download=["ipynb"], sql_output="pandas")
@app.cell
def _():
import marimo as mo
return (mo,)
@app.cell
def _():
import urllib
import sqlalchemy
host = "81.71.3.24"
port = 6785
username = "leopard"
password = urllib.parse.quote_plus("9NEzFzovnddf@PyEP?e*AYAWnCyd7UhYwQK$pJf>7?ccFiN^x4$eKEZ5~E<7<+~X")
database = "leopard_dev"
engine = sqlalchemy.create_engine(f"postgresql://{username}:{password}@{host}:{port}/{database}")
return (engine,)
@app.cell
def _(engine, mo):
dailies_df = mo.sql(
f"""
select trade_date, open * factor as "Open", close * factor as "Close", high * factor as "High", low * factor as "Low", volume as "Volume", coalesce(factor, 1.0) as factor
from leopard_daily daily
left join leopard_stock stock on stock.id = daily.stock_id
where stock.code = '000001.SZ'
and daily.trade_date between '2024-01-01 00:00:00' and '2025-12-31 23:59:59'
order by daily.trade_date
""",
engine=engine
)
return (dailies_df,)
@app.cell
def _(dailies_df):
import pandas as pd
dailies_df["trade_date"] = pd.to_datetime(dailies_df["trade_date"], format='%Y-%m-%d')
dailies_df.set_index("trade_date", inplace=True)
dailies_df
return
@app.cell
def _(dailies_df):
import talib
dailies_df['sma30'] = talib.SMA(dailies_df['Close'], timeperiod=30)
dailies_df['sma60'] = talib.SMA(dailies_df['Close'], timeperiod=60)
dailies_df['sma120'] = talib.SMA(dailies_df['Close'], timeperiod=120)
macd, signal, hist = talib.MACD(dailies_df["Close"], fastperiod=10, slowperiod=20, signalperiod=9)
dailies_df['macd'] = macd
dailies_df['signal'] = signal
dailies_df['hist'] = hist
target_dailies_df = dailies_df.loc['2025-01-01':'2025-12-31']
target_dailies_df
return (target_dailies_df,)
@app.cell
def _(target_dailies_df):
target_dailies_df.reset_index(inplace=True)
target_dailies_df_inc = target_dailies_df[target_dailies_df["Close"] > target_dailies_df["Open"]]
target_dailies_df_dec = target_dailies_df[target_dailies_df["Close"] < target_dailies_df["Open"]]
return target_dailies_df_dec, target_dailies_df_inc
@app.cell
def daily_chart(
target_dailies_df,
target_dailies_df_dec,
target_dailies_df_inc,
):
from bokeh.io import show, output_notebook
from bokeh.layouts import column
from bokeh.plotting import figure
output_notebook()
width = 1200
up_color = "black"
down_color = "grey"
price_figure = figure(width=width, height=400, tools='pan,wheel_zoom,box_zoom,reset')
price_figure.min_border = 0
x_padding = 1
y_padding = 10
price_figure.x_range.bounds = (min(target_dailies_df.index) - x_padding, max(target_dailies_df.index) + x_padding)
price_figure.y_range.bounds = (min(target_dailies_df['Low']) - y_padding, max(target_dailies_df['High']) + y_padding)
price_figure.xaxis.major_label_overrides = {i: date.strftime('%Y-%m-%d') for i, date in zip(target_dailies_df.index, target_dailies_df['trade_date'])}
price_figure.segment(target_dailies_df.index, target_dailies_df['High'], target_dailies_df.index, target_dailies_df['Low'], color='black')
price_figure.vbar(target_dailies_df_inc.index, 0.6, target_dailies_df_inc['Open'], target_dailies_df_inc['Close'], color=up_color)
price_figure.vbar(target_dailies_df_dec.index, 0.6, target_dailies_df_dec['Open'], target_dailies_df_dec['Close'], color=down_color)
price_figure.line(target_dailies_df.index, target_dailies_df['sma30'], color='orange')
price_figure.line(target_dailies_df.index, target_dailies_df['sma60'], color='red')
# 控制图表只能放大不能缩小
macd_figure = figure(width=width, height=200, tools='pan,wheel_zoom,box_zoom,reset')
macd_figure.x_range.bounds = (min(target_dailies_df.index) - x_padding, max(target_dailies_df.index) + x_padding)
macd_figure.y_range.bounds = (min(target_dailies_df['macd']) - y_padding, max(target_dailies_df['macd']) + y_padding)
macd_figure.line(target_dailies_df.index, target_dailies_df['macd'], color='orange')
macd_figure.line(target_dailies_df.index, target_dailies_df['signal'], color='red')
# Add MACD histogram bars for positive and negative values
macd_positive = target_dailies_df[target_dailies_df['macd'] > 0]
macd_negative = target_dailies_df[target_dailies_df['macd'] < 0]
macd_figure.vbar(macd_positive.index, 0.6, 0, macd_positive['macd'], color=up_color)
macd_figure.vbar(macd_negative.index, 0.6, 0, macd_negative['macd'], color=down_color)
# Add zero line
macd_figure.line(target_dailies_df.index, [0] * len(target_dailies_df), color='black', line_dash='dashed')
# show(price_figure)
# show(macd_figure)
show(column(price_figure, macd_figure))
return
if __name__ == "__main__":
app.run()

View File

@@ -0,0 +1,72 @@
{
"cells": [
{
"metadata": {},
"cell_type": "code",
"outputs": [],
"execution_count": null,
"source": [
"import pandas as pd\n",
"\n",
"host = '81.71.3.24'\n",
"port = 6785\n",
"database = 'leopard_dev'\n",
"username = 'leopard'\n",
"password = '9NEzFzovnddf@PyEP?e*AYAWnCyd7UhYwQK$pJf>7?ccFiN^x4$eKEZ5~E<7<+~X'"
],
"id": "1e8d815ee9b8c936"
},
{
"cell_type": "code",
"execution_count": null,
"id": "initial_id",
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"import psycopg2\n",
"\n",
"dailies_df = pd.DataFrame()\n",
"\n",
"with psycopg2.connect(host=host, port=port, database=database, user=username, password=password) as connection:\n",
" with connection.cursor() as cursor:\n",
" # language=PostgreSQL\n",
" cursor.execute(\n",
" \"\"\"select trade_date, open, close, high, low, factor\n",
"from leopard_daily daily\n",
" left join leopard_stock stock on stock.id = daily.stock_id\n",
"where stock.code = '000001.SZ'\n",
" and daily.trade_date between '2025-01-01 00:00:00' and '2025-12-31 23:59:59'\n",
"order by daily.trade_date\"\"\"\n",
" )\n",
" rows = cursor.fetchall()\n",
"\n",
" dailies_df = pd.DataFrame.from_records(rows, columns=['trade_date', 'open', 'close', 'high', 'low', 'factor'])\n",
"\n",
"dailies_df"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

82
notebook/sqlalchemy.ipynb Normal file

File diff suppressed because one or more lines are too long

View File

@@ -4,10 +4,13 @@ version = "0.1.0"
description = "Stock analysis"
requires-python = ">=3.14"
dependencies = [
"adata>=2.9.5",
"akshare>=1.18.20",
"backtesting~=0.6.5",
"baostock>=0.8.9",
"duckdb>=1.4.4",
"jupyter~=1.1.1",
"jupyter-bokeh>=4.0.5",
"marimo>=0.19.6",
"matplotlib~=3.10.8",
"mplfinance>=0.12.10b0",
"pandas~=2.3.3",
@@ -18,13 +21,5 @@ dependencies = [
"ta-lib>=0.6.8",
"tabulate>=0.9.0",
"tqdm>=4.67.1",
]
[dependency-groups]
dev = [
"mcp>=1",
"openai>=2.16.0",
"pydantic>=2",
"pydantic-ai>=1.48.0",
"ruff>=0.14.14",
"tushare>=1.4.24",
]

94
sql/initial.sql Normal file
View File

@@ -0,0 +1,94 @@
CREATE TABLE stock
(
code varchar not null,
name varchar not null,
fullname varchar,
industry varchar,
listed_date date,
market varchar,
exchange varchar,
primary key (code)
);
CREATE TABLE daily
(
code varchar not null,
trade_date date not null,
open double,
close double,
high double,
low double,
previous_close double,
turnover double,
volume integer,
price_change_amount double,
factor double,
primary key (code, trade_date)
);
CREATE TABLE finance_indicator
(
code varchar not null,
year integer not null,
accounts_payable double,
accounts_payable_to_total_assets_ratio double,
accounts_receivable double,
accounts_receivable_to_total_assets_ratio double,
accounts_receivable_turnover double,
capital_surplus double,
cash_and_cash_equivalents double,
cash_and_cash_equivalents_to_total_assets_ratio double,
cash_flow_adequacy_ratio double,
cash_flow_from_financing_activities double,
cash_flow_from_investing_activities double,
cash_flow_from_operating_activities double,
cash_flow_ratio double,
cash_reinvestment_ratio double,
current_assets double,
current_assets_to_total_assets_ratio double,
current_liabilities double,
current_liabilities_to_total_assets_ratio double,
current_liabilities_to_total_liabilities_ratio double,
current_ratio double,
days_accounts_receivable_turnover double,
days_fixed_assets_turnover double,
days_inventory_turnover double,
days_total_assets_turnover double,
earnings_per_share double,
fixed_assets double,
fixed_assets_to_total_assets_ratio double,
fixed_assets_turnover double,
goodwill double,
goodwill_to_total_assets_ratio double,
inventory double,
inventory_to_total_assets_ratio double,
inventory_turnover double,
liabilities_to_total_assets_ratio double,
long_term_funds_to_fixed_assets_ratio double,
long_term_liabilities double,
long_term_liabilities_to_total_assets_ratio double,
long_term_liabilities_to_total_liabilities_ratio double,
net_cash_flow_from_operating_activities double,
net_profit double,
net_profit_margin double,
operating_cost double,
operating_expenses double,
operating_gross_profit_margin double,
operating_profit double,
operating_profit_margin double,
operating_revenue double,
operating_safety_margin_ratio double,
quick_ratio double,
return_on_assets double,
return_on_equity double,
shareholders_equity double,
shareholders_equity_to_total_assets_ratio double,
surplus_reserve double,
total_assets double,
total_assets_turnover double,
total_liabilities double,
total_share_capital double,
undistributed_profit double,
primary key (code, year)
)

2221
uv.lock generated

File diff suppressed because it is too large Load Diff