185 lines
5.7 KiB
Python
185 lines
5.7 KiB
Python
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()
|