diff --git a/backtest.ipynb b/backtest.ipynb index f8fea5e..fa5dc0a 100644 --- a/backtest.ipynb +++ b/backtest.ipynb @@ -2,404 +2,702 @@ "cells": [ { "metadata": { + "SqlCellData": { + "data_source_name": "leopard_dev@81.71.3.24", + "variableName$1": "dailies_df" + }, "ExecuteTime": { - "end_time": "2026-01-19T10:00:35.820002Z", - "start_time": "2026-01-19T10:00:34.354216Z" + "end_time": "2026-01-20T02:26:30.134545Z", + "start_time": "2026-01-20T02:26:22.978651Z" + } + }, + "cell_type": "code", + "source": [ + "%%sql\n", + "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\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" + ], + "id": "93e66142c88e4d2f", + "outputs": [ + { + "data": { + "text/plain": [ + " trade_date open close high low \\\n", + "0 2025-01-02 1498.907493 1460.572263 1504.018857 1455.460899 \n", + "1 2025-01-03 1461.850104 1454.183058 1474.628514 1451.627376 \n", + "2 2025-01-06 1454.183058 1461.850104 1466.961468 1433.737602 \n", + "3 2025-01-07 1459.294422 1470.794991 1473.350673 1452.905217 \n", + "4 2025-01-08 1469.517150 1469.517150 1486.129083 1456.738740 \n", + ".. ... ... ... ... ... \n", + "183 2025-10-09 1493.155774 1502.380920 1503.698798 1485.248506 \n", + "184 2025-10-10 1498.427286 1506.334554 1514.241822 1497.109408 \n", + "185 2025-10-13 1491.837896 1502.380920 1510.288188 1486.566384 \n", + "186 2025-10-14 1501.063042 1524.784846 1528.738480 1497.109408 \n", + "187 2025-10-15 1526.130396 1534.205160 1536.896748 1515.364044 \n", + "\n", + " volume factor \n", + "0 1819596.99 127.7841 \n", + "1 1154680.44 127.7841 \n", + "2 1085536.30 127.7841 \n", + "3 747862.88 127.7841 \n", + "4 1062386.01 127.7841 \n", + ".. ... ... \n", + "183 1047469.06 131.7878 \n", + "184 1087947.75 131.7878 \n", + "185 1168801.73 131.7878 \n", + "186 1843428.36 131.7878 \n", + "187 1271061.03 134.5794 \n", + "\n", + "[188 rows x 7 columns]" + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
trade_dateopenclosehighlowvolumefactor
02025-01-021498.9074931460.5722631504.0188571455.4608991819596.99127.7841
12025-01-031461.8501041454.1830581474.6285141451.6273761154680.44127.7841
22025-01-061454.1830581461.8501041466.9614681433.7376021085536.30127.7841
32025-01-071459.2944221470.7949911473.3506731452.905217747862.88127.7841
42025-01-081469.5171501469.5171501486.1290831456.7387401062386.01127.7841
........................
1832025-10-091493.1557741502.3809201503.6987981485.2485061047469.06131.7878
1842025-10-101498.4272861506.3345541514.2418221497.1094081087947.75131.7878
1852025-10-131491.8378961502.3809201510.2881881486.5663841168801.73131.7878
1862025-10-141501.0630421524.7848461528.7384801497.1094081843428.36131.7878
1872025-10-151526.1303961534.2051601536.8967481515.3640441271061.03134.5794
\n", + "

188 rows × 7 columns

\n", + "
" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 87 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-01-20T02:26:30.368092Z", + "start_time": "2026-01-20T02:26:30.241161Z" } }, "cell_type": "code", "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'" + "dailies_df.rename(\n", + " columns={'open': 'Open', 'close': 'Close', 'high': 'High', 'low': 'Low', 'volume': 'Volume'}, inplace=True\n", + ")\n", + "dailies_df['trade_date'] = pd.to_datetime(dailies_df['trade_date'], format='%Y-%m-%d')\n", + "dailies_df.set_index('trade_date', inplace=True)\n", + "dailies_df" ], - "id": "d815a3591c4f9463", + "id": "c0ed078ffd5b57fd", + "outputs": [ + { + "data": { + "text/plain": [ + " Open Close High Low Volume \\\n", + "trade_date \n", + "2025-01-02 1498.907493 1460.572263 1504.018857 1455.460899 1819596.99 \n", + "2025-01-03 1461.850104 1454.183058 1474.628514 1451.627376 1154680.44 \n", + "2025-01-06 1454.183058 1461.850104 1466.961468 1433.737602 1085536.30 \n", + "2025-01-07 1459.294422 1470.794991 1473.350673 1452.905217 747862.88 \n", + "2025-01-08 1469.517150 1469.517150 1486.129083 1456.738740 1062386.01 \n", + "... ... ... ... ... ... \n", + "2025-10-09 1493.155774 1502.380920 1503.698798 1485.248506 1047469.06 \n", + "2025-10-10 1498.427286 1506.334554 1514.241822 1497.109408 1087947.75 \n", + "2025-10-13 1491.837896 1502.380920 1510.288188 1486.566384 1168801.73 \n", + "2025-10-14 1501.063042 1524.784846 1528.738480 1497.109408 1843428.36 \n", + "2025-10-15 1526.130396 1534.205160 1536.896748 1515.364044 1271061.03 \n", + "\n", + " factor \n", + "trade_date \n", + "2025-01-02 127.7841 \n", + "2025-01-03 127.7841 \n", + "2025-01-06 127.7841 \n", + "2025-01-07 127.7841 \n", + "2025-01-08 127.7841 \n", + "... ... \n", + "2025-10-09 131.7878 \n", + "2025-10-10 131.7878 \n", + "2025-10-13 131.7878 \n", + "2025-10-14 131.7878 \n", + "2025-10-15 134.5794 \n", + "\n", + "[188 rows x 6 columns]" + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
OpenCloseHighLowVolumefactor
trade_date
2025-01-021498.9074931460.5722631504.0188571455.4608991819596.99127.7841
2025-01-031461.8501041454.1830581474.6285141451.6273761154680.44127.7841
2025-01-061454.1830581461.8501041466.9614681433.7376021085536.30127.7841
2025-01-071459.2944221470.7949911473.3506731452.905217747862.88127.7841
2025-01-081469.5171501469.5171501486.1290831456.7387401062386.01127.7841
.....................
2025-10-091493.1557741502.3809201503.6987981485.2485061047469.06131.7878
2025-10-101498.4272861506.3345541514.2418221497.1094081087947.75131.7878
2025-10-131491.8378961502.3809201510.2881881486.5663841168801.73131.7878
2025-10-141501.0630421524.7848461528.7384801497.1094081843428.36131.7878
2025-10-151526.1303961534.2051601536.8967481515.3640441271061.03134.5794
\n", + "

188 rows × 6 columns

\n", + "
" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 88 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-01-20T02:28:38.147008Z", + "start_time": "2026-01-20T02:28:38.094513Z" + } + }, + "cell_type": "code", + "source": [ + "dailies_df['sma30'] = dailies_df['Close'].rolling(10).mean()\n", + "dailies_df['sma60'] = dailies_df['Close'].rolling(30).mean()\n", + "dailies_df.tail()" + ], + "id": "c558d68773d228c1", + "outputs": [ + { + "data": { + "text/plain": [ + " Open Close High Low Volume \\\n", + "trade_date \n", + "2025-10-09 1493.155774 1502.380920 1503.698798 1485.248506 1047469.06 \n", + "2025-10-10 1498.427286 1506.334554 1514.241822 1497.109408 1087947.75 \n", + "2025-10-13 1491.837896 1502.380920 1510.288188 1486.566384 1168801.73 \n", + "2025-10-14 1501.063042 1524.784846 1528.738480 1497.109408 1843428.36 \n", + "2025-10-15 1526.130396 1534.205160 1536.896748 1515.364044 1271061.03 \n", + "\n", + " factor sma30 sma60 \n", + "trade_date \n", + "2025-10-09 131.7878 1504.094161 1546.925196 \n", + "2025-10-10 131.7878 1504.357737 1543.762289 \n", + "2025-10-13 131.7878 1503.698798 1540.862958 \n", + "2025-10-14 131.7878 1506.202766 1536.997182 \n", + "2025-10-15 134.5794 1507.803737 1533.840781 " + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
OpenCloseHighLowVolumefactorsma30sma60
trade_date
2025-10-091493.1557741502.3809201503.6987981485.2485061047469.06131.78781504.0941611546.925196
2025-10-101498.4272861506.3345541514.2418221497.1094081087947.75131.78781504.3577371543.762289
2025-10-131491.8378961502.3809201510.2881881486.5663841168801.73131.78781503.6987981540.862958
2025-10-141501.0630421524.7848461528.7384801497.1094081843428.36131.78781506.2027661536.997182
2025-10-151526.1303961534.2051601536.8967481515.3640441271061.03134.57941507.8037371533.840781
\n", + "
" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "execution_count": 97 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-01-20T02:28:09.232747Z", + "start_time": "2026-01-20T02:28:09.215190Z" + } + }, + "cell_type": "code", + "source": [ + "from backtesting import Strategy\n", + "from backtesting.lib import crossover\n", + "\n", + "\n", + "class SmaCross(Strategy):\n", + " def init(self):\n", + " pass\n", + "\n", + " def next(self):\n", + " if crossover(self.data.sma30, self.data.sma60):\n", + " self.position.close()\n", + " self.buy()\n", + " elif crossover(self.data.sma60, self.data.sma30):\n", + " self.position.close()\n", + " self.sell()" + ], + "id": "b56aaaf4cad7bc7d", "outputs": [], - "execution_count": 2 - }, - { - "cell_type": "code", - "id": "initial_id", - "metadata": { - "collapsed": true, - "ExecuteTime": { - "end_time": "2026-01-19T10:00:42.739695Z", - "start_time": "2026-01-19T10:00:35.820726Z" - } - }, - "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" - ], - "outputs": [ - { - "data": { - "text/plain": [ - " trade_date open close high low factor\n", - "0 2025-01-02 11.73 11.43 11.77 11.39 127.7841\n", - "1 2025-01-03 11.44 11.38 11.54 11.36 127.7841\n", - "2 2025-01-06 11.38 11.44 11.48 11.22 127.7841\n", - "3 2025-01-07 11.42 11.51 11.53 11.37 127.7841\n", - "4 2025-01-08 11.50 11.50 11.63 11.40 127.7841\n", - ".. ... ... ... ... ... ...\n", - "183 2025-10-09 11.33 11.40 11.41 11.27 131.7878\n", - "184 2025-10-10 11.37 11.43 11.49 11.36 131.7878\n", - "185 2025-10-13 11.32 11.40 11.46 11.28 131.7878\n", - "186 2025-10-14 11.39 11.57 11.60 11.36 131.7878\n", - "187 2025-10-15 11.34 11.40 11.42 11.26 134.5794\n", - "\n", - "[188 rows x 6 columns]" - ], - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
trade_dateopenclosehighlowfactor
02025-01-0211.7311.4311.7711.39127.7841
12025-01-0311.4411.3811.5411.36127.7841
22025-01-0611.3811.4411.4811.22127.7841
32025-01-0711.4211.5111.5311.37127.7841
42025-01-0811.5011.5011.6311.40127.7841
.....................
1832025-10-0911.3311.4011.4111.27131.7878
1842025-10-1011.3711.4311.4911.36131.7878
1852025-10-1311.3211.4011.4611.28131.7878
1862025-10-1411.3911.5711.6011.36131.7878
1872025-10-1511.3411.4011.4211.26134.5794
\n", - "

188 rows × 6 columns

\n", - "
" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "execution_count": 3 + "execution_count": 94 }, { "metadata": { "ExecuteTime": { - "end_time": "2026-01-19T10:01:46.133661Z", - "start_time": "2026-01-19T10:01:46.044719Z" + "end_time": "2026-01-20T02:28:12.350117Z", + "start_time": "2026-01-20T02:28:12.330024Z" } }, "cell_type": "code", "source": [ - "dailies_df['factor'] = dailies_df['factor'].fillna(1.0)\n", - "dailies_df['open'] = dailies_df['open'] * dailies_df['factor']\n", - "dailies_df['close'] = dailies_df['close'] * dailies_df['factor']\n", - "dailies_df['high'] = dailies_df['high'] * dailies_df['factor']\n", - "dailies_df['low'] = dailies_df['low'] * dailies_df['factor']\n", - "dailies_df" + "def stats_print(stats):\n", + " indicator_name_mapping = {\n", + " 'Start': '回测开始时间',\n", + " 'End': '回测结束时间',\n", + " 'Duration': '回测持续时长',\n", + " 'Exposure Time [%]': '持仓时间占比(%)',\n", + " 'Equity Final [$]': '最终权益',\n", + " 'Equity Peak [$]': '权益峰值',\n", + " 'Return [%]': '总收益率(%)',\n", + " 'Buy & Hold Return [%]': '买入并持有收益率(%)',\n", + " 'Return (Ann.) [%]': '年化收益率(%)',\n", + " 'Volatility (Ann.) [%]': '年化波动率(%)',\n", + " 'CAGR [%]': '复合年均增长率(%)',\n", + " 'Sharpe Ratio': '夏普比率',\n", + " 'Sortino Ratio': '索提诺比率',\n", + " 'Calmar Ratio': '卡尔玛比率',\n", + " 'Alpha [%]': '阿尔法系数(%)',\n", + " 'Beta': '贝塔系数',\n", + " 'Max. Drawdown [%]': '最大回撤(%)',\n", + " 'Avg. Drawdown [%]': '平均回撤(%)',\n", + " 'Max. Drawdown Duration': '最大回撤持续时长',\n", + " 'Avg. Drawdown Duration': '平均回撤持续时长',\n", + " '# Trades': '交易总次数',\n", + " 'Win Rate [%]': '胜率(%)',\n", + " 'Best Trade [%]': '最佳单笔交易收益率(%)',\n", + " 'Worst Trade [%]': '最差单笔交易收益率(%)',\n", + " 'Avg. Trade [%]': '平均单笔交易收益率(%)',\n", + " 'Max. Trade Duration': '单笔交易最长持有时长',\n", + " 'Avg. Trade Duration': '单笔交易平均持有时长',\n", + " 'Profit Factor': '盈利因子',\n", + " 'Expectancy [%]': '期望收益(%)',\n", + " 'SQN': '系统质量数',\n", + " 'Kelly Criterion': '凯利准则',\n", + " }\n", + " for k, v in stats.items():\n", + " if k in indicator_name_mapping:\n", + " cn_name = indicator_name_mapping.get(k, k)\n", + " if isinstance(v, (int, float)):\n", + " if \"%\" in cn_name or k in ['Sharpe Ratio', 'Sortino Ratio', 'Calmar Ratio', 'Profit Factor']:\n", + " formatted_value = f\"{v:.2f}\"\n", + " elif \"$\" in cn_name:\n", + " formatted_value = f\"{v:.2f}\"\n", + " elif \"次数\" in cn_name:\n", + " formatted_value = f\"{v:.0f}\"\n", + " else:\n", + " formatted_value = f\"{v:.4f}\"\n", + " else:\n", + " formatted_value = str(v)\n", + " print(f'{cn_name}: {formatted_value}')" ], - "id": "29226cf1c6fe9f94", + "id": "a23e811212958477", + "outputs": [], + "execution_count": 95 + }, + { + "metadata": { + "ExecuteTime": { + "end_time": "2026-01-20T02:28:43.128279Z", + "start_time": "2026-01-20T02:28:43.044156Z" + } + }, + "cell_type": "code", + "source": [ + "from backtesting import Backtest\n", + "\n", + "bt = Backtest(dailies_df, SmaCross, cash=100000, commission=.002, finalize_trades=True)\n", + "stats = bt.run()\n", + "stats_print(stats)" + ], + "id": "afc750727129ff2e", "outputs": [ { - "data": { - "text/plain": [ - " trade_date open close high low \\\n", - "0 2025-01-02 191536.544976 186637.912112 192189.696025 185984.761064 \n", - "1 2025-01-03 186801.199875 185821.473302 188434.077496 185494.897778 \n", - "2 2025-01-06 185821.473302 186801.199875 187454.350923 183208.869108 \n", - "3 2025-01-07 186474.624350 187944.214209 188270.789734 185658.185540 \n", - "4 2025-01-08 187780.926447 187780.926447 189903.667355 186148.048826 \n", - ".. ... ... ... ... ... \n", - "183 2025-10-09 196779.714513 197995.476209 198169.156451 195737.633059 \n", - "184 2025-10-10 197474.435482 198516.516936 199558.598389 197300.755240 \n", - "185 2025-10-13 196606.034270 197995.476209 199037.557663 195911.313301 \n", - "186 2025-10-14 197821.795966 200948.040328 201469.081055 197300.755240 \n", - "187 2025-10-15 205385.713015 206472.409910 206834.642208 203936.783823 \n", - "\n", - " factor \n", - "0 127.7841 \n", - "1 127.7841 \n", - "2 127.7841 \n", - "3 127.7841 \n", - "4 127.7841 \n", - ".. ... \n", - "183 131.7878 \n", - "184 131.7878 \n", - "185 131.7878 \n", - "186 131.7878 \n", - "187 134.5794 \n", - "\n", - "[188 rows x 6 columns]" - ], - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
trade_dateopenclosehighlowfactor
02025-01-02191536.544976186637.912112192189.696025185984.761064127.7841
12025-01-03186801.199875185821.473302188434.077496185494.897778127.7841
22025-01-06185821.473302186801.199875187454.350923183208.869108127.7841
32025-01-07186474.624350187944.214209188270.789734185658.185540127.7841
42025-01-08187780.926447187780.926447189903.667355186148.048826127.7841
.....................
1832025-10-09196779.714513197995.476209198169.156451195737.633059131.7878
1842025-10-10197474.435482198516.516936199558.598389197300.755240131.7878
1852025-10-13196606.034270197995.476209199037.557663195911.313301131.7878
1862025-10-14197821.795966200948.040328201469.081055197300.755240131.7878
1872025-10-15205385.713015206472.409910206834.642208203936.783823134.5794
\n", - "

188 rows × 6 columns

\n", - "
" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "回测开始时间: 2025-01-02 00:00:00\n", + "回测结束时间: 2025-10-15 00:00:00\n", + "回测持续时长: 286 days 00:00:00\n", + "持仓时间占比(%): 71.81\n", + "最终权益: 114727.4805\n", + "权益峰值: 117838.4813\n", + "总收益率(%): 14.73\n", + "买入并持有收益率(%): 5.04\n", + "年化收益率(%): 20.22\n", + "年化波动率(%): 17.17\n", + "复合年均增长率(%): 12.87\n", + "夏普比率: 1.18\n", + "索提诺比率: 2.45\n", + "卡尔玛比率: 2.21\n", + "阿尔法系数(%): 14.96\n", + "贝塔系数: -0.0456\n", + "最大回撤(%): -9.13\n", + "平均回撤(%): -3.40\n", + "最大回撤持续时长: 97 days 00:00:00\n", + "平均回撤持续时长: 33 days 00:00:00\n", + "交易总次数: 3\n", + "胜率(%): 66.67\n", + "最佳单笔交易收益率(%): 10.02\n", + "最差单笔交易收益率(%): -0.49\n", + "平均单笔交易收益率(%): 4.76\n", + "单笔交易最长持有时长: 78 days 00:00:00\n", + "单笔交易平均持有时长: 68 days 00:00:00\n", + "盈利因子: 30.80\n", + "期望收益(%): 4.84\n", + "系统质量数: 1.6469\n", + "凯利准则: 0.6455\n" + ] } ], - "execution_count": 5 + "execution_count": 98 } ], "metadata": { diff --git a/note.md b/note.md new file mode 100644 index 0000000..16494b6 --- /dev/null +++ b/note.md @@ -0,0 +1,126 @@ +# 基础时间与权益指标 + +## Exposure Time [%] - 持仓时间占比(%) + +衡量策略在统计周期内实际持有头寸的时间占总时间的百分比。反映策略的“活跃度”(如占比100%=全程持仓,占比低=多数时间空仓),辅助判断交易频率和资金使用效率。 + +## Equity Final [$] - 最终权益 + +策略运行结束时的账户总资金(本金+盈亏),是最基础的收益结果指标,直接体现最终资金规模。 + +## Equity Peak [$] - 权益峰值 + +统计周期内账户权益的最大值。对比最终权益可判断策略是否从峰值回落,结合回撤指标能评估收益稳定性。 + +# 收益率类指标(核心收益衡量) + +## Return [%] - 总收益率(%) + +周期内总收益占初始本金的百分比(`(最终权益-初始本金)/初始本金×100%`),直观反映整体盈利/亏损程度,但未考虑时间因素。 + +## Buy & Hold Return [%] - 买入并持有收益率(%) + +作为“基准收益率”,模拟“买入标的后长期持有不操作”的收益。用于对比策略收益,判断策略是否跑赢简单持有,是评估策略有效性的核心参照。 + +## Return (Ann.) [%] - 年化收益率(%) + +将周期总收益折算为按年计算的收益(`(1+总收益率)^(365/持仓天数) - 1`),消除时间周期差异,是跨周期对比不同策略收益的关键指标。 + +## CAGR [%] - 复合年均增长率(%) + +即“年均复合收益率”(`(最终权益/初始本金)^(1/年数) - 1`),衡量资金长期复利增长速度,更贴合多年期策略的收益评估,避免短期高收益误导。 + +# 风险类指标(衡量亏损与波动风险) + +## Volatility (Ann.) [%] - 年化波动率(%) + +收益波动程度的年化标准差,反映收益稳定性。波动率越高,收益起伏越大,策略“震荡风险”越高;波动率低则收益更平稳。 + +## Max. Drawdown [%] - 最大回撤(%) + +权益从峰值回落至谷值的最大幅度(`(峰值-谷值)/峰值×100%`),核心风险指标。比如最大回撤20%,说明策略曾从最高点亏掉20%,直接体现抗风险能力。 + +## Avg. Drawdown [%] - 平均回撤(%) + +所有回撤的平均幅度。若最大回撤远高于平均回撤,说明策略曾出现极端亏损;两者接近则回撤风险更均匀。 + +## Max. Drawdown Duration - 最大回撤持续时长 + +最大回撤从峰值回落至恢复峰值的时间(天/月),反映策略从最大亏损中回血的速度,时长越长则恢复能力越弱。 + +## Avg. Drawdown Duration - 平均回撤持续时长 + +所有回撤周期的平均时间,辅助判断策略亏损后的恢复效率。 + +# 风险收益比指标(性价比核心) + +## Sharpe Ratio - 夏普比率 + +衡量“每承担1单位风险的超额收益”(`(年化收益率-无风险利率)/年化波动率` +),无风险利率通常取国债利率(3%左右)。比率越高性价比越高(>1较好,>2优秀)。 + +## Sortino Ratio - 索提诺比率 + +改进版夏普比率,仅用“下行波动率”(亏损方向波动)计算风险,更贴合投资者对“亏损风险”的关注,适合评估稳健型策略。 + +## Calmar Ratio - 卡尔玛比率 + +`年化收益率/最大回撤(绝对值)`,直接反映“收益能否覆盖最大亏损”,比率越高,策略用小回撤换高收益的能力越强。 + +# 资产定价系数(市场相关性) + +## Alpha [%] - 阿尔法系数(%) + +衡量策略“超额收益”(超出市场基准的收益),Alpha为正说明策略能跑赢市场,反映主动管理能力;为负则跑输市场。 + +## Beta - 贝塔系数 + +衡量策略与市场基准的波动同步性。Beta=1=和市场波动一致;Beta>1=策略更激进(波动更大);Beta<1=策略更稳健;Beta<0=与市场反向波动。 + +# 交易行为与质量指标 + +## Trades - 交易总次数 + +统计周期内总交易笔数,反映策略交易频率(高频/低频),结合胜率可判断策略有效性。 + +## Win Rate [%] - 胜率(%) + +盈利交易次数/总次数×100%,直观反映“赚钱交易的比例”,但需结合盈亏比(平均盈利/平均亏损)使用(如胜率50%+盈亏比3:1仍盈利)。 + +## Best Trade [%] - 最佳单笔交易收益率(%) + +单次盈利幅度最大值,反映策略盈利上限,但需警惕是否由偶然高收益拉高整体表现。 + +## Worst Trade [%] - 最差单笔交易收益率(%) + +单次亏损幅度最大值(通常为负),反映单笔交易的最大亏损风险。 + +## Avg. Trade [%] - 平均单笔交易收益率(%) + +所有交易收益率的平均值,反映单笔交易的平均盈利能力,为正则单笔交易平均赚钱。 + +## Max. Trade Duration - 单笔交易最长持有时长 + +单次持仓的最长时间,定义策略风格(日内/短线/中长线)。 + +## Avg. Trade Duration - 单笔交易平均持有时长 + +所有交易持仓时长的平均值,辅助判断资金周转效率。 + +# 综合绩效指标(策略可持续性) + +## Profit Factor - 盈利因子 + +`总盈利金额/总亏损金额(绝对值)`,核心指标。>1说明总盈利>总亏损,数值越高盈利能力越强(如盈利因子2=每亏1元赚2元)。 + +## Expectancy [%] - 期望收益(%) + +每笔交易的预期收益(`胜率×平均盈利 - (1-胜率)×平均亏损`),为正则策略长期可盈利,数值越高预期收益越好。 + +## SQN - 系统质量数 + +衡量策略收益的“统计显著性”(`√交易次数 × 期望收益 / 交易收益标准差`)。SQN>1.6合格,>2.5优秀,>3.0卓越,判断收益是否为偶然结果。 + +## Kelly Criterion - 凯利准则 + +计算“最优仓位比例”(`(胜率×(盈亏比+1)-1)/盈亏比`),确定每笔交易的最佳资金投入比例,最大化长期复利收益,同时控制破产风险(实际应用中通常减半使用)。 diff --git a/pyproject.toml b/pyproject.toml index f4ccb4b..37f6a56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,10 @@ description = "Stock analysis" requires-python = ">=3.14" dependencies = [ "backtesting~=0.6.5", + "duckdb>=1.4.3", "jupyter~=1.1.1", "matplotlib~=3.10.8", + "mplfinance>=0.12.10b0", "pandas~=2.3.3", "pandas-stubs~=2.3.3", "peewee~=3.19.0", diff --git a/sql2dataframe.ipynb b/sql2dataframe.ipynb new file mode 100644 index 0000000..87ad9a0 --- /dev/null +++ b/sql2dataframe.ipynb @@ -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 +} diff --git a/uv.lock b/uv.lock index 350e2e3..49a0887 100644 --- a/uv.lock +++ b/uv.lock @@ -338,6 +338,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] +[[package]] +name = "duckdb" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/da/17c3eb5458af69d54dedc8d18e4a32ceaa8ce4d4c699d45d6d8287e790c3/duckdb-1.4.3.tar.gz", hash = "sha256:fea43e03604c713e25a25211ada87d30cd2a044d8f27afab5deba26ac49e5268", size = 18478418, upload-time = "2025-12-09T10:59:22.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/f4/a38651e478fa41eeb8e43a0a9c0d4cd8633adea856e3ac5ac95124b0fdbf/duckdb-1.4.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:316711a9e852bcfe1ed6241a5f654983f67e909e290495f3562cccdf43be8180", size = 29042272, upload-time = "2025-12-09T10:58:51.826Z" }, + { url = "https://files.pythonhosted.org/packages/16/de/2cf171a66098ce5aeeb7371511bd2b3d7b73a2090603b0b9df39f8aaf814/duckdb-1.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9e625b2b4d52bafa1fd0ebdb0990c3961dac8bb00e30d327185de95b68202131", size = 15419343, upload-time = "2025-12-09T10:58:54.439Z" }, + { url = "https://files.pythonhosted.org/packages/35/28/6b0a7830828d4e9a37420d87e80fe6171d2869a9d3d960bf5d7c3b8c7ee4/duckdb-1.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:130c6760f6c573f9c9fe9aba56adba0fab48811a4871b7b8fd667318b4a3e8da", size = 13748905, upload-time = "2025-12-09T10:58:56.656Z" }, + { url = "https://files.pythonhosted.org/packages/15/4d/778628e194d63967870873b9581c8a6b4626974aa4fbe09f32708a2d3d3a/duckdb-1.4.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c88effaa557a11267706b01419c542fe42f893dee66e5a6daa5974ea2d4a46", size = 18487261, upload-time = "2025-12-09T10:58:58.866Z" }, + { url = "https://files.pythonhosted.org/packages/c6/5f/87e43af2e4a0135f9675449563e7c2f9b6f1fe6a2d1691c96b091f3904dd/duckdb-1.4.3-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b35491db98ccd11d151165497c084a9d29d3dc42fc80abea2715a6c861ca43d", size = 20497138, upload-time = "2025-12-09T10:59:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/94/41/abec537cc7c519121a2a83b9a6f180af8915fabb433777dc147744513e74/duckdb-1.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:23b12854032c1a58d0452e2b212afa908d4ce64171862f3792ba9a596ba7c765", size = 12836056, upload-time = "2025-12-09T10:59:03.388Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5a/8af5b96ce5622b6168854f479ce846cf7fb589813dcc7d8724233c37ded3/duckdb-1.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:90f241f25cffe7241bf9f376754a5845c74775e00e1c5731119dc88cd71e0cb2", size = 13527759, upload-time = "2025-12-09T10:59:05.496Z" }, +] + [[package]] name = "executing" version = "2.2.1" @@ -850,8 +865,10 @@ version = "0.1.0" source = { virtual = "." } dependencies = [ { name = "backtesting" }, + { name = "duckdb" }, { name = "jupyter" }, { name = "matplotlib" }, + { name = "mplfinance" }, { name = "pandas" }, { name = "pandas-stubs" }, { name = "peewee" }, @@ -861,8 +878,10 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "backtesting", specifier = "~=0.6.5" }, + { name = "duckdb", specifier = ">=1.4.3" }, { name = "jupyter", specifier = "~=1.1.1" }, { name = "matplotlib", specifier = "~=3.10.8" }, + { name = "mplfinance", specifier = ">=0.12.10b0" }, { name = "pandas", specifier = "~=2.3.3" }, { name = "pandas-stubs", specifier = "~=2.3.3" }, { name = "peewee", specifier = "~=3.19.0" }, @@ -953,6 +972,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, ] +[[package]] +name = "mplfinance" +version = "0.12.10b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/a9/34e7998d02fb58fae04f750444ce4e95e75f3a08dad17fb2d32098a97834/mplfinance-0.12.10b0.tar.gz", hash = "sha256:7da150b5851aa5119ad6e06b55e48338b619bb6773f1b85df5de67a5ffd917bf", size = 70117, upload-time = "2023-08-02T15:13:53.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/d9/31c436ea7673c21a5bf3fc747bc7f63377582dfe845c3004d3e46f9deee0/mplfinance-0.12.10b0-py3-none-any.whl", hash = "sha256:76d3b095f05ff35de730751649de063bea4064d0c49b21b6182c82997a7f52bb", size = 75016, upload-time = "2023-08-02T15:13:52.022Z" }, +] + [[package]] name = "narwhals" version = "2.15.0"