diff --git a/backend_run.log b/backend_run.log index 354eafe..a6179a3 100644 --- a/backend_run.log +++ b/backend_run.log @@ -155,3 +155,225 @@ INFO: Waiting for application startup. INFO: Application startup complete. INFO: 127.0.0.1:55384 - "GET /api/health HTTP/1.1" 200 OK INFO: 127.0.0.1:55388 - "GET /api/stock/000001.SZ/chips?adjust=qfq HTTP/1.1" 401 Unauthorized +INFO: 127.0.0.1:55749 - "GET /api/stock/000001.SZ/chips?adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:55760 - "GET /api/stock/000001.SZ/chips?adjust=bfq&date=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56017 - "GET /api/auth/me HTTP/1.1" 200 OK +INFO: 127.0.0.1:56029 - "GET /api/preferences HTTP/1.1" 200 OK +INFO: 127.0.0.1:56030 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56031 - "GET /api/stocks/facets HTTP/1.1" 200 OK +INFO: 127.0.0.1:56040 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:56044 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:56041 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:56051 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56054 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56062 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56065 - "GET /api/stock/002648.SZ/chips?date=2026-04-29&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56071 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56075 - "GET /api/stock/002648.SZ/chips?date=2026-03-11&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56081 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56084 - "GET /api/stock/002648.SZ/chips?date=2026-05-08&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56089 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56096 - "GET /api/stock/002648.SZ/chips?date=2026-03-10&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56100 - "GET /api/stock/002648.SZ/chips?date=2025-12-25&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56111 - "GET /api/stock/002648.SZ/chips?date=2025-10-24&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56116 - "GET /api/stock/002648.SZ/chips?date=2025-09-05&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56121 - "GET /api/stock/002648.SZ/chips?date=2025-06-26&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56106 - "GET /api/stock/002648.SZ/chips?date=2025-12-24&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56129 - "GET /api/stock/002648.SZ/chips?date=2025-09-16&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56133 - "GET /api/stock/002648.SZ/chips?date=2025-06-12&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56139 - "GET /api/stock/002648.SZ/chips?date=2025-03-28&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56145 - "GET /api/stock/002648.SZ/chips?date=2024-12-30&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56149 - "GET /api/stock/002648.SZ/chips?date=2024-11-13&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56156 - "GET /api/stock/002648.SZ/chips?date=2024-09-04&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56160 - "GET /api/stock/002648.SZ/chips?date=2024-10-22&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56167 - "GET /api/stock/002648.SZ/chips?date=2025-04-29&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56171 - "GET /api/stock/002648.SZ/chips?date=2025-07-09&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56177 - "GET /api/stock/002648.SZ/chips?date=2025-09-24&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56196 - "GET /api/stock/002648.SZ/chips?date=2025-12-23&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56200 - "GET /api/stock/002648.SZ/chips?date=2026-04-01&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56207 - "GET /api/stock/002648.SZ/chips?date=2026-06-22&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56210 - "GET /api/stock/002648.SZ/chips?date=2026-08-12&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56218 - "GET /api/stock/002648.SZ/chips?date=2026-06-17&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56265 - "GET /api/stock/002648.SZ/chips?date=2026-03-17&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56384 - "GET /api/stock/002648.SZ/chips?date=2026-05-22&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56458 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56501 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56520 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56545 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56564 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:56568 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:56571 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:56577 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56581 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:56584 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:56587 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:56589 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56690 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56693 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56699 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56715 - "GET /api/auth/me HTTP/1.1" 200 OK +INFO: 127.0.0.1:56727 - "GET /api/preferences HTTP/1.1" 200 OK +INFO: 127.0.0.1:56731 - "GET /api/stocks/facets HTTP/1.1" 200 OK +INFO: 127.0.0.1:56730 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56733 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:56734 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:56736 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:56739 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56747 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56749 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56754 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56756 - "GET /api/stock/002648.SZ/chips?date=2026-06-11&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56772 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56781 - "GET /api/auth/me HTTP/1.1" 200 OK +INFO: 127.0.0.1:56793 - "GET /api/preferences HTTP/1.1" 200 OK +INFO: 127.0.0.1:56795 - "GET /api/stocks/facets HTTP/1.1" 200 OK +INFO: 127.0.0.1:56794 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56805 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:56806 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:56809 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:56811 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56818 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56828 - "GET /api/stock/002648.SZ/chips?date=2026-08-13&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56835 - "GET /api/stock/002648.SZ/chips?date=2026-03-30&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56863 - "GET /api/stock/002648.SZ/chips?date=2026-07-23&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56882 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56887 - "GET /api/stocks/facets HTTP/1.1" 200 OK +INFO: 127.0.0.1:56874 - "GET /api/market/overview HTTP/1.1" 200 OK +INFO: 127.0.0.1:56900 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:56898 - "GET /api/trades?ts_code=000006.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:56899 - "GET /api/screener/preview/000006.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:56904 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56907 - "GET /api/stock/000006.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:56916 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56923 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2021-04-07 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56929 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56931 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56940 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56943 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56947 - "GET /api/trades?ts_code=000001.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:56951 - "GET /api/screener/preview/000001.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:56958 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56970 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56979 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56975 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56985 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2021-04-07 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56990 - "GET /api/screener/preview/000001.SZ?limit=800&adjust=qfq&timeframe=1d&end=2017-12-19 HTTP/1.1" 200 OK +INFO: 127.0.0.1:56998 - "GET /api/trades?ts_code=000006.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:57000 - "GET /api/screener/preview/000006.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:57004 - "GET /api/screener/preview/000006.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57012 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57017 - "GET /api/stocks?search=%E5%8C%96%E5%AD%A6%E7%BB%99&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57019 - "GET /api/stocks?search=%E5%8C%96%E5%AD%A6&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57024 - "GET /api/stocks?sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57026 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57037 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:57034 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:57035 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:57039 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57048 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57061 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:57058 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK +INFO: 127.0.0.1:57059 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:57066 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57099 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57103 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2021-04-07 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57131 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57134 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57139 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57143 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57136 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57146 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57151 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57153 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57917 - "GET /api/auth/me HTTP/1.1" 200 OK +INFO: 127.0.0.1:57927 - "GET /api/preferences HTTP/1.1" 200 OK +INFO: 127.0.0.1:57931 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:57932 - "GET /api/stocks/facets HTTP/1.1" 200 OK +INFO: 127.0.0.1:57937 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK +INFO: 127.0.0.1:57939 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:57938 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:57944 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58070 - "GET /api/auth/me HTTP/1.1" 200 OK +INFO: 127.0.0.1:58075 - "GET /api/preferences HTTP/1.1" 200 OK +INFO: 127.0.0.1:58086 - "GET /api/stocks/facets HTTP/1.1" 200 OK +INFO: 127.0.0.1:58085 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58088 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK +INFO: 127.0.0.1:58091 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:58089 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:58096 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58098 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58110 - "GET /api/stock/600118.SH/chips?date=2026-03-25&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58114 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58122 - "GET /api/stock/600118.SH/chips?date=2026-04-28&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58134 - "GET /api/stock/600118.SH/chips?date=2026-06-17&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58127 - "GET /api/stock/600118.SH/chips?date=2026-05-07&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58139 - "GET /api/stock/600118.SH/chips?date=2026-06-30&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58145 - "GET /api/stock/600118.SH/chips?date=2026-08-03&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58150 - "GET /api/stock/600118.SH/chips?date=2026-07-16&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58155 - "GET /api/stock/600118.SH/chips?date=2026-08-13&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58166 - "GET /api/stock/600118.SH/chips?date=2026-07-28&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58161 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58173 - "GET /api/stock/600118.SH/chips?date=2026-06-12&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58178 - "GET /api/stock/600118.SH/chips?date=2026-05-29&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58186 - "GET /api/stock/600118.SH/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58195 - "GET /api/stock/600118.SH/chips?date=2026-04-28&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58202 - "GET /api/stock/600118.SH/chips?date=2026-06-02&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58207 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58210 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58213 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58226 - "GET /api/stock/600118.SH/chips?date=2026-06-17&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58241 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:58247 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58222 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58249 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58237 - "GET /api/stock/600118.SH/chips?date=2025-08-07&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58240 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:58258 - "GET /api/stock/002648.SZ/chips?date=2026-04-27&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58219 - "GET /api/stock/600118.SH/chips?date=2026-06-15&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58270 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58273 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58277 - "GET /api/stock/002648.SZ/chips?date=2026-06-16&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58282 - "GET /api/stock/002648.SZ/chips?date=2026-03-02&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58288 - "GET /api/stock/002648.SZ/chips?date=2026-04-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58293 - "GET /api/stock/002648.SZ/chips?date=2026-06-01&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58298 - "GET /api/stock/002648.SZ/chips?date=2026-06-16&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58305 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58309 - "GET /api/stock/002648.SZ/chips?date=2026-06-09&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58315 - "GET /api/stock/002648.SZ/chips?date=2026-06-05&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58323 - "GET /api/stock/002648.SZ/chips?date=2026-04-03&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58326 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58329 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58333 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58341 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58346 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58350 - "GET /api/stock/002648.SZ/chips?date=2026-08-14&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58356 - "GET /api/stock/002648.SZ/chips?date=2026-06-24&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58364 - "GET /api/stock/002648.SZ/chips?date=2026-04-30&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58368 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58371 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58374 - "GET /api/stock/002648.SZ/chips?date=2026-03-06&adjust=qfq HTTP/1.1" 200 OK +INFO: 127.0.0.1:58392 - "GET /api/auth/me HTTP/1.1" 200 OK +INFO: 127.0.0.1:58400 - "GET /api/preferences HTTP/1.1" 200 OK +INFO: 127.0.0.1:58410 - "GET /api/stocks/facets HTTP/1.1" 200 OK +INFO: 127.0.0.1:58409 - "GET /api/stocks?search=%E5%8D%AB%E6%98%9F&sort=symbol&order=asc&limit=100&offset=0 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58414 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:58417 - "GET /api/watchlist HTTP/1.1" 200 OK +INFO: 127.0.0.1:58415 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:58422 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58424 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58431 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK +INFO: 127.0.0.1:58432 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:58435 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58441 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58444 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58447 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58450 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58698 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58707 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58709 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58766 - "GET /api/trades?ts_code=002648.SZ HTTP/1.1" 200 OK +INFO: 127.0.0.1:58767 - "GET /api/screener/preview/002648.SZ?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:58777 - "GET /api/trades?ts_code=600118.SH HTTP/1.1" 200 OK +INFO: 127.0.0.1:58769 - "GET /api/screener/preview/002648.SZ?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK +INFO: 127.0.0.1:58778 - "GET /api/screener/preview/600118.SH?limit=500&adjust=qfq&timeframe=1d HTTP/1.1" 200 OK +INFO: 127.0.0.1:58782 - "GET /api/screener/preview/600118.SH?limit=800&adjust=qfq&timeframe=1d&end=2024-07-24 HTTP/1.1" 200 OK diff --git a/frontend/src/indicators/chips.ts b/frontend/src/indicators/chips.ts new file mode 100644 index 0000000..6706a86 --- /dev/null +++ b/frontend/src/indicators/chips.ts @@ -0,0 +1,132 @@ +// 个股筹码峰(Tushare cyq_chips / cyq_perf,同花顺式右侧横向线状直方图) +// +// 数据不在图表窗口上计算,而是一次性截面(某交易日各价位筹码占比 + 获利比例/平均成本), +// 经 extendData 注入、纯 draw 绘制: +// - 每个价位一根横线,从主图右缘向左延伸,宽度 ∝ 占比/最大占比(火焰山形态) +// - 分界价 = 选中K线的收盘价(refClose):下方为获利盘(红线)、上方为套牢盘(蓝线) +// - 平均成本画黄色虚线;右上角小字块:截面日期 / 获利比例 / 平均成本 / 90% 成本区间 +// figures 为空、calc 返回空行:指标不产生序列值,也不影响主图价格刻度(筹码是覆盖层)。 +import type { IndicatorTemplate } from 'klinecharts'; + +export const CHIPS_NAME = 'pv-chips'; + +/** 经 extendData 注入的筹码截面(父组件按 当前窗口末根/十字线 所在周期取数) */ +export interface ChipsExt { + rows: { price: number; percent: number }[]; // percent 为 0~1 占比 + date?: string; // YYYYMMDD 截面日 + refClose?: number | null; // 获利/套牢分界价:选中K线收盘(缺省时 draw 退回图表末根收盘) + winner?: number | null; // 获利比例 % + avg?: number | null; // 平均成本 + costLow?: number | null; // 90% 成本下沿(cost_5pct) + costHigh?: number | null; // 90% 成本上沿(cost_95pct) + error?: string | null; // 无数据/出错的提示文案(仍要画出来,用户才知道为何空) +} + +// 同花顺筹码峰配色:获利盘红、套牢盘蓝 +const UP = '#FE354B'; +const DOWN = '#2F7BFF'; +const AVG_COLOR = '#F5C518'; +const LABEL_COLOR = '#9AA0AA'; +const VALUE_COLOR = '#E8EAED'; + +/** 同花顺筹码峰同款:横线最大占主图宽度的比例与上限(px) */ +const MAX_WIDTH_RATIO = 0.22; +const MAX_WIDTH_PX = 150; +/** 横条高度上限(px):极度放大时单价位占比条不至于铺满半个主图 */ +const BAR_HEIGHT_CAP = 24; + +function fmtDate(d?: string): string { + return d && d.length === 8 ? `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}` : '—'; +} + +export const chipsIndicator: IndicatorTemplate, unknown, ChipsExt> = { + name: CHIPS_NAME, + shortName: '筹码', + figures: [], // 纯 draw 绘制,无序列图形 + calc: (d) => d.map(() => ({})), + // 返回 true(isCover):跳过框架 figure 渲染(本来也没有 figures),全部自己画 + draw: ({ ctx, chart, indicator, bounding, yAxis }) => { + const ext = indicator.extendData; + if (!ext) return true; + + // 右上角统计小字块(画线工具栏是 HTML 层,占顶部 ~36px,文字块从 44px 起) + const lines: { label: string; value: string; color: string }[] = []; + if (ext.error) { + lines.push({ label: '筹码', value: ext.error, color: LABEL_COLOR }); + } else if (ext.rows.length) { + const w = ext.winner; + lines.push({ label: `筹码 ${fmtDate(ext.date)}`, value: '', color: LABEL_COLOR }); + lines.push({ + label: '获利比例', + value: w == null ? '—' : `${w.toFixed(2)}%`, + color: w == null ? VALUE_COLOR : w >= 50 ? UP : DOWN, + }); + lines.push({ label: '平均成本', value: ext.avg == null ? '—' : ext.avg.toFixed(2), color: AVG_COLOR }); + lines.push({ + label: '90%成本', + value: ext.costLow == null || ext.costHigh == null ? '—' : `${ext.costLow.toFixed(2)}~${ext.costHigh.toFixed(2)}`, + color: VALUE_COLOR, + }); + } + if (lines.length) { + ctx.font = '11px sans-serif'; + ctx.textBaseline = 'top'; + ctx.textAlign = 'right'; + let y = bounding.top + 44; + for (const ln of lines) { + const text = ln.value ? `${ln.label} ${ln.value}` : ln.label; + ctx.fillStyle = ln.color; + ctx.fillText(text, bounding.right - 4, y); + y += 14; + } + } + if (!ext.rows.length) return true; + + // 分界价:选中K线的收盘价(父组件随截面注入;缺省退回图表末根收盘), + // <=分界价为获利盘(红)、>分界价为套牢盘(蓝)——同花顺同款 + const list = chart.getDataList(); + const close = ext.refClose ?? (list.length ? list[list.length - 1].close : undefined); + + // 价位升序,便于求相邻像素间距(直方图的“条高”) + const rows = [...ext.rows].sort((a, b) => a.price - b.price); + const maxP = rows.reduce((m, r) => Math.max(m, r.percent), 0); + if (maxP <= 0) return true; + const maxW = Math.min(bounding.width * MAX_WIDTH_RATIO, MAX_WIDTH_PX); + + // 相邻价位的中位像素间距 → 条高(连续火焰形态;缩到极小时并成实心轮廓) + const pitches: number[] = []; + for (let i = 1; i < rows.length; i++) { + const p = Math.abs(yAxis.convertToPixel(rows[i].price) - yAxis.convertToPixel(rows[i - 1].price)); + if (p > 0) pitches.push(p); + } + pitches.sort((a, b) => a - b); + const pitch = pitches.length ? pitches[pitches.length >> 1] : 4; + const barH = Math.min(BAR_HEIGHT_CAP, Math.max(1, pitch * 0.92)); + + for (const r of rows) { + const y = yAxis.convertToPixel(r.price); + if (y < bounding.top - barH || y > bounding.bottom + barH) continue; // 视口外跳过 + const w = Math.max(1, (r.percent / maxP) * maxW); + ctx.fillStyle = close == null ? 'rgba(154,160,170,0.5)' + : r.price <= close ? 'rgba(254,53,75,0.55)' : 'rgba(47,123,255,0.55)'; + ctx.fillRect(bounding.right - w, y - barH / 2, w, barH); + } + + // 平均成本虚线(横贯直方图区) + if (ext.avg != null) { + const y = yAxis.convertToPixel(ext.avg); + if (y >= bounding.top && y <= bounding.bottom) { + ctx.save(); + ctx.strokeStyle = AVG_COLOR; + ctx.lineWidth = 1; + ctx.setLineDash([4, 3]); + ctx.beginPath(); + ctx.moveTo(bounding.right - maxW, y + 0.5); + ctx.lineTo(bounding.right, y + 0.5); + ctx.stroke(); + ctx.restore(); + } + } + return true; + }, +}; diff --git a/frontend/src/indicators/index.ts b/frontend/src/indicators/index.ts new file mode 100644 index 0000000..94ffea0 --- /dev/null +++ b/frontend/src/indicators/index.ts @@ -0,0 +1,23 @@ +// 自定义指标注册表:通达信导入的主图指标都在这里追加一项。 +// 依赖预计算序列的指标(知行)不在此处闭包数据,而是导出模板工厂、由 DetailKLine 注入 +// 按时间戳取值的访问器(g(),与 pv-boll 等后端序列同款管道)。注意 g 及其闭包的 +// PV/allData 是组件实例级的(script setup 每次挂载重建),知行模板必须随挂载重注册 +// 换绑到当前实例——否则永远读首挂实例的数据,切股后画的是第一只股票的线; +// 筹码峰等“截面数据 + 纯 draw”指标不闭包实例数据(经 extendData 注入),注册一次即可。 +import { registerIndicator } from 'klinecharts'; +import { chipsIndicator } from './chips'; +import { makeZhixingIndicator, ZHIXING_NAME, type ZhixingGetter } from './zhixing'; + +let staticRegistered = false; + +export function registerCustomIndicators(get: ZhixingGetter) { + // 知行:同名重注册即替换,把 calc 换绑到本次挂载实例的 g(与 DetailKLine 内 + // pv-boll/pv-macd 每挂必注册同款;幂等闸曾导致知行只认第一只股票) + registerIndicator(makeZhixingIndicator(get)); + if (staticRegistered) return; + staticRegistered = true; + registerIndicator(chipsIndicator); +} + +export { CHIPS_NAME, type ChipsExt } from './chips'; +export { ZHIXING_NAME, ZHIXING_TREND_COLOR, DKX_PERIODS, computeZhixingSeries } from './zhixing'; diff --git a/frontend/src/indicators/zhixing.md b/frontend/src/indicators/zhixing.md new file mode 100644 index 0000000..4282f42 --- /dev/null +++ b/frontend/src/indicators/zhixing.md @@ -0,0 +1,173 @@ +# 知行指标移植文档(通达信 → 任意平台) + +> 供后续导入到其他项目/平台使用的完整规格:公式原文、参数、数值口径、参考实现、 +> 校验向量、显示规范。框架无关部分可直接拷贝;klinecharts v10 集成见第八节。 +> 本仓库实现:`zhixing.ts`(算法+模板)/ `index.ts`(注册表)/ `DetailKLine.vue`(数据管道)。 + +## 一、原始通达信公式 + +``` +知行短期趋势线:EMA(EMA(C,10),10),COLORE61851,LINETHICK1; +MA1:=MA(CLOSE,60); +MA2:=EMA(CLOSE,13); +Z1:=STRCAT(HYBLOCK,' '); +Z2:=STRCAT(Z1,DYBLOCK); +Z3:=STRCAT(Z2,' '); +DRAWTEXT_FIX(ISLASTBAR,0,0,0,STRCAT(Z3,GNBLOCK)),COLOR00C0C0; +知行多空线:(MA(CLOSE,M1)+MA(CLOSE,M2)+MA(CLOSE,M3)+MA(CLOSE,M4))/4; +``` + +**参数表(通达信公式管理器里配置,公式文本不带——移植时最容易丢的就是这个):** + +| 参数 | 缺省值 | +|------|--------| +| M1 | 14 | +| M2 | 28 | +| M3 | 57 | +| M4 | 114 | + +## 二、逐行语义 + +| 行 | 作用 | 移植处理 | +|----|------|----------| +| 知行短期趋势线 | EMA 嵌套两层,主图线 | 画线,#E61851 | +| MA1 / MA2 | `:=` 隐藏赋值,且未被后续引用 → 死代码 | 不画 | +| Z1~Z3 + DRAWTEXT_FIX | 左上角固定文本:行业板块+地域板块+概念板块(通达信本地板块数据) | 无本地板块数据源,用「行业+地域」近似做成图例行;概念板块缺失 | +| 知行多空线 | 4 条 MA 的等权平均,主图线 | 画线,周期 14/28/57/114 | + +## 三、数值口径(与通达信逐位一致) + +**EMA(通达信式)**:`E[i] = (2·X[i] + (N−1)·E[i−1]) / (N+1)`,首根以自身为种子 +(`E[0] = X[0]`)。等价于系数 `k = 2/(N+1)` 的 `E[i] = k·X[i] + (1−k)·E[i−1]`。 +无限记忆,但衰减因子 `(N−1)/(N+1)`(N=10 时 ≈0.82/根),截断起点的影响几十根后可忽略。 + +**MA(通达信式,严格窗口)**:窗口不满输出空值(null/NaN),**不是** min_periods=1 的渐进均值—— +很多图表库内置 MA 是渐进口径,直接用会整条线偏掉。 + +**多空线**:`dkx[i] = (MA14[i] + MA28[i] + MA57[i] + MA114[i]) / 4`,四个 MA 任一为空则为空。 +即前 113 根没有多空线(通达信从上市日起算,同样如此)。 + +**复权口径**:序列必须与图表展示用同一复权基准(前复权/不复权)的收盘价计算,否则数值对不上。 + +## 四、参考实现(框架无关 TypeScript,可直接拷贝) + +```ts +export const DKX_PERIODS = [14, 28, 57, 114]; + +function ema(src: number[], n: number): number[] { + const out = new Array(src.length); + const k = 2 / (n + 1); + let prev = NaN; + for (let i = 0; i < src.length; i++) { + prev = Number.isNaN(prev) ? src[i] : src[i] * k + prev * (1 - k); + out[i] = prev; + } + return out; +} + +function sma(src: number[], n: number): (number | null)[] { + const out = new Array(src.length).fill(null); + let sum = 0; + for (let i = 0; i < src.length; i++) { + sum += src[i]; + if (i >= n) sum -= src[i - n]; + if (i >= n - 1) out[i] = sum / n; + } + return out; +} + +/** closes 为整段历史收盘价(建议从上市日或至少 ≥ 需求长度+114 根起) */ +export function computeZhixingSeries(closes: number[]) { + const trend = ema(ema(closes, 10), 10); + const mas = DKX_PERIODS.map((p) => sma(closes, p)); + const dkx = new Array(closes.length).fill(null); + for (let i = 0; i < closes.length; i++) { + const m = mas.map((a) => a[i]); + if (m.every((v) => v != null)) dkx[i] = m.reduce((s, v) => s + (v as number), 0) / m.length; + } + return { trend, dkx }; +} +``` + +## 五、参考实现(Python / numpy) + +```python +import numpy as np + +DKX_PERIODS = (14, 28, 57, 114) + +def ema_tdx(x: np.ndarray, n: int) -> np.ndarray: + out = np.empty(len(x)) + k = 2.0 / (n + 1) + prev = np.nan + for i, v in enumerate(x): + prev = v if np.isnan(prev) else v * k + prev * (1 - k) + out[i] = prev + return out + # pandas 等价写法(adjust=False 且首根种子=自身,与通达信一致): + # s.ewm(span=n, adjust=False).mean() + +def sma_tdx(x: np.ndarray, n: int) -> np.ndarray: + c = np.concatenate(([0.0], np.cumsum(x))) + out = np.full(len(x), np.nan) + out[n - 1:] = (c[n:] - c[:-n]) / n # 窗口不满保持 NaN + return out + +def zhixing(close: np.ndarray): + trend = ema_tdx(ema_tdx(close.astype(float), 10), 10) + stack = np.vstack([sma_tdx(close.astype(float), p) for p in DKX_PERIODS]) + dkx = np.full(len(close), np.nan) + ok = ~np.isnan(stack).any(axis=0) + dkx[ok] = stack[:, ok].mean(axis=0) + return trend, dkx +``` + +## 六、校验向量(移植后跑一遍,全对即口径正确) + +**趋势线** `closes = [10,11,12,13,14,15]`: + +``` +EMA(C,10) = 10.000000, 10.181818, 10.512397, 10.964688, 11.516563, 12.149915 +EMA(EMA(C,10),10) = 10.000000, 10.033058, 10.120210, 10.273752, 10.499717, 10.799753 +``` + +**MA 严格窗口** `sma([1,2,3,4,5,6], 3)`: + +``` +null, null, 2.000000, 3.000000, 4.000000, 5.000000 +``` + +**多空线预热** `closes[i] = i+1`(线性爬坡,116 根):索引 0~112 全为 null; +索引 113 起有值,闭式解 `dkx[i] = (i+1) − (13+27+56+113)/8 = i+1 − 26.125`: + +``` +dkx[113] = 87.875000 dkx[114] = 88.875000 dkx[115] = 89.875000 +``` + +## 七、数据与预热要求 + +- MA114 需预热 113 根:**喂给算法的历史长度 = 可视区最左端再往左 ≥113 根**。 + 分页加载的图表不要只算当前窗口(首屏窗口常见 240 根,左边会缺一大段), + 应在「全部已加载历史」上整段计算,左滑前插后整体重算。 +- EMA 理论上要上市日起算;实际截断到几百根即可(衰减 0.82/根,60 根后残差 < 1e−5)。 +- 建议 closes 传入前先按 timestamp 升序、剔除停牌空K。 + +## 八、klinecharts v10 集成要点(本仓库方案) + +- 注册:`registerIndicator(makeZhixingIndicator(get))`,模板工厂注入「(key, timestamp) → 预计算值」 + 访问器;序列由 DetailKLine 在 allData 上预计算写入 `PV['zx_trend']/PV['zx_dkx']`, + `calc` 里按 bar.timestamp 取值(**不要用图表相对索引**,翻页后会错位)。 +- 回调参数的 `Indicator` 不带 E 泛型,`extendData` 是 unknown,取值需 `as` 收窄。 +- `createTooltipDataSource` 的 `legends` 会整体替换默认图例行,线值图例须自己拼回; + `calcParams: DKX_PERIODS` 让图例显示「知行(14,28,57,114)」(通达信式)。 +- 长周期指标 + 分页窗口的完整坑位说明见 + `memory/custom-indicator-framework.md`。 + +## 九、移植检查清单 + +1. M1~M4 = **14/28/57/114**(不是 BBI 的 3/6/12/24) +2. EMA 首根种子=自身;MA 窗口不满=null(两条都与常见库默认不同) +3. 计算输入与显示用同一复权基准的收盘价 +4. 预热 ≥113 根覆盖到可视区最左端之外 +5. 跑第六节校验向量,逐位一致 +6. 颜色:趋势线 #E61851、多空线 #F5C518、板块文本 #00C0C0 diff --git a/frontend/src/indicators/zhixing.ts b/frontend/src/indicators/zhixing.ts new file mode 100644 index 0000000..92e6867 --- /dev/null +++ b/frontend/src/indicators/zhixing.ts @@ -0,0 +1,109 @@ +// 通达信《知行》主图指标前端移植 +// +// 知行短期趋势线:EMA(EMA(C,10),10),COLORE61851,LINETHICK1; +// 知行多空线:(MA(CLOSE,M1)+MA(CLOSE,M2)+MA(CLOSE,M3)+MA(CLOSE,M4))/4; +// +// - M1~M4 = 14/28/57/114(用户通达信公式参数表里的缺省值,公式文本不带) +// - 原文 MA1:=MA(CLOSE,60) / MA2:=EMA(CLOSE,13) 为隐藏赋值(:=)且未被引用,不绘制 +// - DRAWTEXT_FIX 的 HYBLOCK/DYBLOCK/GNBLOCK 是通达信本地板块数据;web 端用后端 +// info 的 行业+地域 近似前两者(经 extendData 注入图例行),概念板块无数据源暂缺 +// +// 序列不是在图表窗口上现算,而是由 DetailKLine 在 allData(全部已加载历史)上预计算、 +// 经 PV 按时间戳取值(与 pv-boll 同款管道):MA114 预热 113 根,图表首吐窗口 240 根, +// 窗口现算左边会缺一大段;allData 首屏 500 根(左滑翻页还会前插)足够覆盖。 +import type { IndicatorTemplate, IndicatorTooltipData } from 'klinecharts'; + +export const ZHIXING_NAME = 'pv-zhixing'; +/** 多空线 4 个 MA 周期(M1~M4,通达信参数表缺省值) */ +export const DKX_PERIODS = [14, 28, 57, 114]; +/** 板块文本颜色(通达信原文 COLOR00C0C0) */ +export const ZHIXING_BLOCK_COLOR = '#00C0C0'; +/** 趋势线颜色(通达信原文 COLORE61851,同时用作开关按钮主色) */ +export const ZHIXING_TREND_COLOR = '#E61851'; +export const ZHIXING_DKX_COLOR = '#F5C518'; + +export interface ZhixingRow { trend?: number; dkx?: number } +export interface ZhixingExt { blocks?: string } + +/** 序列取值函数:(指标 key, 时间戳) → 值(DetailKLine 的 g() 按时间戳定位 allData 索引) */ +export type ZhixingGetter = (key: 'zx_trend' | 'zx_dkx', ts: number) => number | undefined; + +/** 通达信 EMA:E=(2*X+(N-1)*prev)/(N+1),首根以自身为种子。无限记忆,但衰减因子 + * (N-1)/(N+1)≈0.82/根,allData 截断的影响几十根后即可忽略,左滑前插会整体重算自愈 */ +function ema(src: number[], n: number): number[] { + const out = new Array(src.length); + const k = 2 / (n + 1); + let prev = NaN; + for (let i = 0; i < src.length; i++) { + prev = Number.isNaN(prev) ? src[i] : src[i] * k + prev * (1 - k); + out[i] = prev; + } + return out; +} + +/** 通达信 MA:窗口不满为 null(忠实原公式;主图 MA 指标是 min_periods=1,口径不同) */ +function sma(src: number[], n: number): (number | null)[] { + const out = new Array(src.length).fill(null); + let sum = 0; + for (let i = 0; i < src.length; i++) { + sum += src[i]; + if (i >= n) sum -= src[i - n]; + if (i >= n - 1) out[i] = sum / n; + } + return out; +} + +/** 在整段已加载历史上计算知行序列(与 allData 按索引对齐),build / 左滑前插后各跑一次 */ +export function computeZhixingSeries(closes: number[]): { trend: (number | null)[]; dkx: (number | null)[] } { + const trend = ema(ema(closes, 10), 10); + const mas = DKX_PERIODS.map((p) => sma(closes, p)); + const dkx = new Array(closes.length).fill(null); + for (let i = 0; i < closes.length; i++) { + const m = mas.map((a) => a[i]); + if (m.every((v) => v != null)) dkx[i] = m.reduce((s, v) => s + (v as number), 0) / m.length; + } + return { trend, dkx }; +} + +/** 指标模板工厂:注册时注入按时间戳取预计算序列的访问器(实例级 g 闭包,随每次 + * DetailKLine 挂载重注册换绑,与 pv-boll 同款;幂等注册会绑死首挂实例导致切股错线) */ +export function makeZhixingIndicator(get: ZhixingGetter): IndicatorTemplate { + return { + name: ZHIXING_NAME, + shortName: '知行', + figures: [ + { key: 'trend', title: '趋势线', type: 'line', styles: () => ({ color: ZHIXING_TREND_COLOR }) }, + { key: 'dkx', title: '多空线', type: 'line', styles: () => ({ color: ZHIXING_DKX_COLOR }) }, + ], + calc: (d) => d.map((bar) => { + const row: ZhixingRow = {}; + const t = get('zx_trend', bar.timestamp); + const k = get('zx_dkx', bar.timestamp); + if (t != null) row.trend = t; + if (k != null) row.dkx = k; + return row; + }), + // 通达信 DRAWTEXT_FIX(ISLASTBAR,0,0,0,STRCAT(HYBLOCK,DYBLOCK,GNBLOCK)) 的近似: + // 自定义 legends 会整体替换图例行,须把两条线的值一并补回(颜色随各自线色)。 + // calcParamsText 从 calcParams 拼,保留「知行(14,28,57,114)」的通达信式图例 + createTooltipDataSource: ({ indicator, crosshair }): IndicatorTooltipData => { + const di = crosshair.dataIndex; + const row = (typeof di === 'number' && di >= 0 ? indicator.result[di] : undefined) ?? {}; + const fmt = (v?: number) => (v == null ? '—' : v.toFixed(2)); + const legends: IndicatorTooltipData['legends'] = []; + // 回调参数的 Indicator 未带 E 泛型(extendData 为 unknown),需收窄 + const blocks = (indicator.extendData as ZhixingExt | undefined)?.blocks; + if (blocks) { + legends.push({ title: { text: '板块', color: '#9AA0AA' }, value: { text: blocks, color: ZHIXING_BLOCK_COLOR } }); + } + legends.push({ title: { text: '趋势线', color: ZHIXING_TREND_COLOR }, value: { text: fmt(row.trend), color: ZHIXING_TREND_COLOR } }); + legends.push({ title: { text: '多空线', color: ZHIXING_DKX_COLOR }, value: { text: fmt(row.dkx), color: ZHIXING_DKX_COLOR } }); + return { + name: '知行', + calcParamsText: `(${(indicator.calcParams as number[]).join(',')})`, + legends, + features: [], + }; + }, + }; +} diff --git a/frontend_dev.log b/frontend_dev.log new file mode 100644 index 0000000..cf0b8c5 --- /dev/null +++ b/frontend_dev.log @@ -0,0 +1,9 @@ +$ vite + + VITE v6.4.3 ready in 598 ms + + ➜ Local: http://localhost:5173/ + ➜ Network: use --host to expose +00:44:18 [vite] (client) hmr update /src/style.css, /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue +00:44:23 [vite] (client) hmr update /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue, /src/style.css +00:44:24 [vite] (client) hmr update /src/style.css, /@fs/D:\Project\stock\frontend\src\components\DetailKLine.vue