1
0

改用marimo编辑notebook方便跨平台使用

This commit is contained in:
2026-01-29 09:55:45 +08:00
parent 579298d16e
commit 9759720fec
12 changed files with 3313 additions and 2360 deletions

1
.idea/vcs.xml generated
View File

@@ -2,6 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/backtestingpy" vcs="Git" />
</component>
</project>

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

File diff suppressed because one or more lines are too long

184
notebook/backtest.py Normal file
View File

@@ -0,0 +1,184 @@
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 one or more lines are too long

131
notebook/indicator.py Normal file
View File

@@ -0,0 +1,131 @@
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

@@ -1,72 +0,0 @@
{
"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
}

View File

@@ -8,6 +8,7 @@ dependencies = [
"duckdb>=1.4.3",
"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",
@@ -19,3 +20,12 @@ dependencies = [
"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",
]

2139
uv.lock generated

File diff suppressed because it is too large Load Diff