據(jù)分析實戰(zhàn):從爬蟲采集到回測驗證全流程)
簡介面向金融數(shù)據(jù)分析初學者與有一定基礎的Python開發(fā)者這份資源以股票市場數(shù)據(jù)為實戰(zhàn)場景系統(tǒng)串聯(lián)起數(shù)據(jù)采集、清洗、分析與建模全流程。內容涉及Python爬蟲編寫與反爬應對、Pandas處理缺失值和格式統(tǒng)一、NumPy/SciPy進行統(tǒng)計計算、Matplotlib/Seaborn繪制K線與箱型圖以及利用Scikit-learn構建回歸、決策樹或隨機森林等預測模型并延伸至Tushare實時數(shù)據(jù)獲取與Flask部署等環(huán)節(jié)。資源包為7z壓縮格式整體僅4.47MB便于下載后按主題查閱。目前已有3654人學習適合希望快速上手股票大數(shù)據(jù)分析并用于課程設計或個人量化研究參考的讀者。1. 從數(shù)據(jù)到?jīng)Q策這份股票大數(shù)據(jù)分析Python實戰(zhàn)包能解決什么用Python分析股票數(shù)據(jù)卡住大多數(shù)人的不是算法而是第一步數(shù)據(jù)從哪來、怎么清洗、怎么才能保證算出來的指標沒被復權除權搞亂。我拆過幾個模擬項目后發(fā)現(xiàn)一份能落地的“股票大數(shù)據(jù)分析Python實戰(zhàn)包”真正起作用的是其中那條從爬蟲采集、數(shù)據(jù)清洗到策略驗證的完整鏈路。這個資源包的核心就是把“python 爬蟲”和“股票數(shù)據(jù)分析”焊在一起讓你不用從頭去翻接口文檔、不用反復調試數(shù)據(jù)缺失的坑跟著代碼走就能把日線數(shù)據(jù)拉下來、算成特征、跑出信號。適合手里有Python基礎但沒完整做過量化數(shù)據(jù)處理的從業(yè)者也適合想拿真實行情練手Pandas和爬蟲的同學。它解決的不是“股票會不會漲”這種玄學問題而是“行情數(shù)據(jù)到手之后怎么處理才能用來做決策”的工程問題——這部分恰恰是很多教程跳過的環(huán)節(jié)。2. 環(huán)境與數(shù)據(jù)獲取先把可復現(xiàn)的數(shù)據(jù)管道搭起來2.1 建立隔離的Python分析環(huán)境拿到資源包后我習慣先做一件事絕不直接往全局環(huán)境里裝依賴。原因很簡單股票數(shù)據(jù)分析涉及的庫版本沖突很常見——Pandas 2.x 和 1.x 的 API 差異、NumPy 版本不兼容都可能讓昨天的代碼今天跑出完全不同的結果。資源包里一般會帶 requirements.txt但在安裝之前先用虛擬環(huán)境把依賴隔離出來。python -m venv stock_venv source stock_venv/bin/activate # Windows 下為 stock_venv\Scripts\activate pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple邏輯說明venv創(chuàng)建獨立解釋器環(huán)境source激活后pip安裝的包只屬于該項目。用清華鏡像安裝是為了避免默認源在大陸訪問緩慢的問題實測速度能差好幾倍。參數(shù)說明stock_venv是虛擬環(huán)境目錄名可以按項目改如果資源包沒有requirements.txt手動安裝時建議先把pandas、numpy、requests、matplotlib這四件套裝好其他的按需逐步補。環(huán)境裝好后先跑一下資源包自帶的入口腳本。一般是一個main.py或run_analysis.py它做的事情是驗證數(shù)據(jù)源連通性。這個步驟很關鍵——如果數(shù)據(jù)接口的 token 過期或網(wǎng)絡不通后面所有分析都是空轉。我一般會在這個階段先確認 Python 版本股票分析相關的庫對 3.8 以下支持并不好建議用 3.9 以上。2.2 用 Pandas 對接行情接口從 Token 到 DataFrame這個實戰(zhàn)包通常以某財經(jīng)數(shù)據(jù)平臺的開放接口作為默認數(shù)據(jù)源代碼風格是requests或官方 SDK 封裝。我先講講接口對接里的關鍵token。數(shù)據(jù)接口通過 token 識別身份如果資源包里帶了 token 變量看下是不是寫死在某配置文件里。我一般習慣把 token 放到環(huán)境變量里避免代碼提交到公開倉庫時泄露。import os import pandas as pd from requests import Session TOKEN os.getenv(STOCK_API_TOKEN) sess Session() sess.headers.update({Authorization: fBearer {TOKEN}}) def fetch_daily(code, start_date, end_date): url https://api.example-stock-data.com/daily params { ts_code: code, start_date: start_date, end_date: end_date, fields: trade_date,open,high,low,close,vol } resp sess.get(url, paramsparams, timeout10) resp.raise_for_status() data resp.json() df pd.DataFrame(data[items], columnsdata[fields]) return df邏輯說明這里用Session復用 TCP 連接避免每次請求都重新握手token 從環(huán)境變量讀取不硬編碼在代碼里。timeout10是請求超時秒數(shù)數(shù)據(jù)量大或網(wǎng)絡差時建議調到 20。參數(shù)說明ts_code是股票代碼參數(shù)通常格式為000001.SZ或600000.SHfields字段列表按需取別一股腦拉全字段——接口響應體越大解析越慢token 額度也浪費得越快。拿到DataFrame后先做兩件小事看一眼dtypes確認trade_date被解析成了datetime64而不是字符串再看一眼isna().sum()確認沒有整列缺失。這兩步能省后面大量排錯時間。原始數(shù)據(jù)格式通常是{items: [], fields: []}這種結構資源包里一般已經(jīng)封裝好了解析函數(shù)但你要知道它內部做了什么——我見過不少人拿到的列是字符串時間排序按字典序排結果圖全亂了。2.3 增量更新與歷史全量數(shù)據(jù)完整性的保障股票數(shù)據(jù)接口最麻煩的問題不是拉一次而是“怎么持續(xù)更新”。比如一個模擬項目的目標是分析最近三年的日線數(shù)據(jù)首次全量拉取可能要跑很久之后每天只需補充最新一天的數(shù)據(jù)。def update_daily(code, csv_pathdaily.csv): try: old pd.read_csv(csv_path, parse_dates[trade_date]) except FileNotFoundError: old pd.DataFrame() latest old[trade_date].max() if len(old) else None end pd.Timestamp.today().strftime(%Y%m%d) start (latest pd.Timedelta(days1)).strftime(%Y%m%d) if latest else 20200101 new fetch_daily(code, start, end) merged pd.concat([old, new], ignore_indexTrue).drop_duplicates(subset[trade_date]).sort_values(trade_date) merged.to_csv(csv_path, indexFalse) return merged邏輯說明這一段先讀本地已有 CSV取最大日期作為增量更新的起點避免重復拉取。drop_duplicates按trade_date去重防止某次請求超時重試后產(chǎn)生重復行。參數(shù)說明csv_path是本地緩存文件路徑如果你改用數(shù)據(jù)庫比如 SQLite邏輯完全一樣只是讀寫那兩行換成語句。start用字符串拼接是因為接口通常要求YYYYMMDD格式Pandas 的Timestamp不能直接傳。增量更新的好處不止是省流量——更重要的是保持本地數(shù)據(jù)和接口狀態(tài)一致。如果某天接口的數(shù)據(jù)源本身出了問題比如某只股票停牌當天沒返回記錄增量邏輯不會主動發(fā)現(xiàn)。所以我一般每周做一次全量校驗把本地最后一行的trade_date和接口最新的trade_date對比差超過一個交易日就觸發(fā)重新拉取。資源包里這個邏輯可能寫得比較簡單建議拿到后按實際需求補強。3. 爬蟲實戰(zhàn)把請求頻率、斷點續(xù)爬和字段抽取做成工程3.1 為什么直接爬網(wǎng)頁通常不是好選擇“python 爬蟲”這個詞容易讓人想到爬網(wǎng)頁 HTML 然后解析表格但在股票數(shù)據(jù)場景里直接爬網(wǎng)頁是最差的選擇原因有兩點。第一財經(jīng)網(wǎng)站的頁面結構變動頻繁今天能解析的表格明天可能就換了 CSS 類名維護成本極高。第二大量股票數(shù)據(jù)頁面是通過 JavaScript 異步加載的直接requests.get拿到的 HTML 里根本沒有數(shù)據(jù)要么用 Selenium 這種重型工具要么破解 XHR 接口地址兩個方向都是血淚教訓。但這個資源包里一般不會只用官方接口因為有些高頻因子和盤中數(shù)據(jù)接口不給普通用戶開放。這時候就要寫爬蟲去抓公開的網(wǎng)頁接口——注意是抓 JSON 接口不是抓 HTML。常見做法是用開發(fā)者工具打開任意財經(jīng)網(wǎng)站的數(shù)據(jù)頁在“網(wǎng)絡”標簽里過濾XHR請求找到真正返回數(shù)據(jù)的那條鏈接。它通常是個帶一堆參數(shù)的 GET 請求返回的是 JSON解析起來比 HTML 安穩(wěn)得多。3.2 用 Requests Session 封裝一個行情讀取器抓 JSON 接口的爬蟲核心就三件事帶好 Headers、控制頻率、做好重試。我自己封裝的時候是這么寫的import time import random from requests import Session class MarketReader: def __init__(self, base_url, min_interval1.0): self.sess Session() self.sess.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64), Referer: https://example-finance-site.com, Accept: application/json, text/plain, */* }) self.base_url base_url self.min_interval min_interval self._last_req 0 def get_json(self, path, params): now time.time() wait self._last_req self.min_interval - now if wait 0: time.sleep(wait random.uniform(0.1, 0.5)) resp self.sess.get(f{self.base_url}{path}, paramsparams, timeout15) self._last_req time.time() resp.raise_for_status() return resp.json() def fetch_kline(self, symbol, perioddaily): data self.get_json(/api/kline, { symbol: symbol, period: period, limit: 1000 }) records data.get(data, {}).get(items, []) return [{date: r[0], close: r[2], volume: r[5]} for r in records]邏輯說明min_interval用來控制兩次請求的最小間隔默認 1 秒對多數(shù)公開接口都夠用。random.uniform加一個隨機抖動避免請求節(jié)奏太規(guī)律被限流。_last_req記錄上一次請求時間用于計算是否需要等待。參數(shù)說明這里的period支持daily和weekly接口不同參數(shù)名可能不同具體要看抓到的 XHR 請求定義。很多翻車的爬蟲都是因為沒帶Referer或者UA太假被拒絕。這個封裝里 UA 用的是 Chrome 的標準格式Referer 指向頁面地址——這兩個缺失或不對接口大概率返回 403。帶上之后通常能順利拿到 JSON。實際運行時建議把timeout設大一點有些接口在拉長周期數(shù)據(jù)時服務端要算很久15 秒比較穩(wěn)妥。3.3 多線程拉取與斷點續(xù)爬應對大范圍歷史數(shù)據(jù)單線程拉全量歷史數(shù)據(jù)很慢。比如你要拉滬深兩市共 5000 多只股票的周線數(shù)據(jù)單線程按 1 秒 1 次算要跑一個多小時而且中間斷一次就前功盡棄。資源包里一般會帶多線程方案但我要提醒一句多線程不是越多越好接口限流是按 token 或 IP 算的開 20 個線程如果被限流反而比單線程更慢。我一般把線程數(shù)壓在 5 到 8 個同時加一個進度記錄文件做到“斷點續(xù)爬”。import threading import os import json from queue import Queue def worker(q, results, lock): while not q.empty(): try: symbol q.get(timeout1) data reader.fetch_kline(symbol, perioddaily) with lock: results[symbol] data done_list.append(symbol) save_progress(done_list) except Exception as e: print(ffailed: {symbol}: {e}) finally: q.task_done() def save_progress(done_list): with open(progress.json, w) as f: json.dump(done_list, f) q Queue() all_symbols load_symbols(symbols.txt) # 每行一個股票代碼 for s in all_symbols: q.put(s) done_list load_progress() if os.path.exists(progress.json) else [] results {} lock threading.Lock() for _ in range(6): t threading.Thread(targetworker, args(q, results, lock)) t.start() q.join()邏輯說明Queue是線程安全的多個 worker 從隊列里取任務不會重復處理。lock保護results字典和progress.json的寫入避免多線程同時寫文件導致 JSON 損壞。save_progress每完成一個股票就記錄一次程序崩了之后重新運行時從done_list里剔除已完成項即可。參數(shù)說明symbols.txt是股票代碼清單每行一個如果資源包自帶清單更好沒有的話可以用akshare或接口的stock_basic接口拉取。這里有一個重要的點斷點續(xù)爬的前提是“寫盤夠快且夠頻繁”。save_progress每次完整重寫 JSON 文件在 5000 只股票的場景下文件不超過 100KB開銷可以忽略。如果數(shù)據(jù)量更大建議改成 SQLite 記錄進度。多線程踩坑的重點是千萬別讓兩個線程同時寫同一個文件lock就是干這個用的。另外q.empty()判斷在并發(fā)下不完全可靠但任務分發(fā)完成后消費者不會再往里放任務所以這里的場景下夠用。4. 因子計算與預處理讓原始行情變成可分析的特征4.1 復權因子處理避免算出假信號原始行情里的close是未復權價遇到分紅送股價格會出現(xiàn)斷崖式跳空。如果沒有做復權處理任何基于價格的指標都會失真。最常見的坑是某只股票 10 送 10除權日價格從 20 元變成 10 元均線系統(tǒng)會“檢測”出一個巨大下跌信號——但實際上這只是權益變動不是市場下跌。資源包里的數(shù)據(jù)如果來自統(tǒng)一接口一般會提供adj_factor字段或qfq前復權選項。前復權會把歷史價格按最新股本調整保證歷史走勢連續(xù)。處理邏輯是把原始價格乘上復權因子再除以最新因子值或者直接用接口的adj參數(shù)。我拿到數(shù)據(jù)后會先做一個“除權跳空檢測”def detect_gap(df, threshold0.2): df df.sort_values(trade_date) pct df[close].pct_change() gap_days df[pct.abs() threshold].index.tolist() return gap_days邏輯說明pct_change計算相鄰交易日的漲跌幅閾值為 20%。如果某天漲跌幅絕對值超過 20%注冊制股票除外幾乎可以斷定是未復權數(shù)據(jù)里的除權跳空。注意這個檢測只用于輔助判斷數(shù)據(jù)質量不能替代正式的復權因子處理。參數(shù)說明threshold按市場調整A 股普通股票設 0.2 夠用科創(chuàng)板/創(chuàng)業(yè)板個股日內波動大可以調到 0.3但再高就可能漏檢。4.2 高頻與低頻數(shù)據(jù)的重采樣對齊股票分析中經(jīng)常遇到不同頻率的數(shù)據(jù)要合并——比如把日線數(shù)據(jù)和分鐘線數(shù)據(jù)對齊到周線。Pandas 的resample能搞定但有個隱含陷阱交易日的“日”和自然日的“日”不是一回事。如果用resample(W)按自然周聚合法定節(jié)假日會導致某些周只有 2 個交易日周均線跟交易所口徑對不上。正確做法是先用交易日歷生成有效交易日的DatetimeIndex再對齊import pandas as pd def resample_trading(df, ruleW-FRI): df df.set_index(trade_date) df df.resample(rule).agg({ open: first, high: max, low: min, close: last, vol: sum }).dropna() return df邏輯說明W-FRI表示按周聚合錨定在周五一周內的交易日數(shù)據(jù)合并到周五這個索引上。open取周內第一個交易日的開盤價close取最后一個的收盤價high取周內最高low取最低vol求和。dropna去掉沒有交易數(shù)據(jù)的自然周。參數(shù)說明如果數(shù)據(jù)源沒有交易日歷字段可以用exchange_calendars庫生成或者直接用df.index里去重后的日期做后續(xù)匹配。這里容易翻車的點是resample默認是按日歷日頻率遇到周末和節(jié)假日會創(chuàng)建空數(shù)據(jù)行dropna才能清掉。但注意agg字典里的first和last在空數(shù)據(jù)上會返回NaN不要用fillna去填否則會把不存在的交易周也算進去。4.3 構造常用技術指標的 Pandas 實現(xiàn)資源包最實用的一部分就是指標計算代碼。均線、RSI、MACD 這類常用指標的 Pandas 實現(xiàn)并不復雜難在邊界條件。我以 RSI 為例——它經(jīng)常被當作買賣信號但不同平臺的計算差異導致結果不同def compute_rsi(close, period14): delta close.diff() gain delta.clip(lower0) loss -delta.clip(upper0) avg_gain gain.rolling(windowperiod, min_periodsperiod).mean() avg_loss loss.rolling(windowperiod, min_periodsperiod).mean() rs avg_gain / avg_loss rsi 100 - (100 / (1 rs)) return rsi邏輯說明diff計算相鄰收盤價差clip(lower0)把負值歸零得到上漲幅度clip(upper0)取負并取絕對值得到下跌幅度。rolling(windowperiod, min_periodsperiod)要求窗口內至少 14 個有效值前 13 個交易日輸出NaN。參數(shù)說明period是回看天數(shù)默認 14 是經(jīng)典參數(shù)有的平臺用 SMMA平滑移動平均而非簡單平均算avg_gain這是不同平臺 RSI 數(shù)值不一致的主要原因。如果做跨平臺策略校驗務必確認對方用的是哪種平滑方式。這里有一個很玄學的點RSI 的公式里avg_loss如果為 0連續(xù)上漲rs會變成無窮大rsi會返回NaN。資源包里如果沒處理這個邊界回測時就會出現(xiàn)信號丟失。修正方法很簡單rs計算前先把avg_loss為 0 的行替換為極大值。我經(jīng)手過的模擬項目X里這個坑導致周末回測結果和實盤完全對不上查了半天才發(fā)現(xiàn)是 RSI 計算在極端行情下靜默失敗。拿到資源包后建議先跑一遍內置的自檢函數(shù)確認指標邊界條件都處理過。5. 常見問題與排查這五類錯我?guī)缀趺看味紩纫槐?.1 現(xiàn)象回測結果里出現(xiàn)巨額收益但實際走勢沒有原因數(shù)據(jù)里包含了未復權價格或者復權因子計算錯誤。某只股票經(jīng)歷多次分紅后未復權價格長期看起來“陰跌不止”但策略按價格差算收益時會出現(xiàn)一個假的大額盈利。解決拿到數(shù)據(jù)后先做除權跳空檢測見 4.1再對應復權因子檢查。資源包如果帶了復權接口直接調用接口的adj參數(shù)拉前復權數(shù)據(jù)不要自己在本地做除法——除數(shù)用錯一步全錯。5.2 現(xiàn)象K 線圖的日期明明是周一卻顯示的是周五的數(shù)據(jù)原因數(shù)據(jù)的時區(qū)是 UTC世界標準時間或者美東時間而本地日期是北京時間。某些國際數(shù)據(jù)接口返回的trade_date帶時區(qū)偏移to_pandas后沒做tz_convert導致日期整體偏移。特別是凌晨時段的數(shù)據(jù)UTC 日期比北京時間早 8 小時周末和交易日的對齊全亂。解決拉數(shù)據(jù)后統(tǒng)一執(zhí)行df[trade_date] df[trade_date].dt.tz_convert(Asia/Shanghai)或者在讀取時指定parse_dates并傳入utcTrue。如果接口返回的是無時區(qū)字符串但實際代表北京時間直接解析即可千萬別畫蛇添足。5.3 現(xiàn)象爬蟲跑到一半開始大量返回 403 或 HTTP 429原因請求頻率太快或者 UA/Referer 被目標服務器識別為“非正常訪客”。很多公開接口的限流策略是按 IPUA 組合計算的即使你單線程運行如果請求間隔小于 0.5 秒也可能觸發(fā)封禁。解決把min_interval調到 1 秒以上UA 換成真實的 Chrome UA 字符串Referer 設為頁面地址。如果已經(jīng)被封等 10 分鐘到半小時再試不要立刻重啟爬蟲——那只會讓封禁時間延長。我在模擬項目X中就是沒注意_last_req的初始值為 0導致第一次請求前沒有等待幾百次請求后直接被斷連。5.4 現(xiàn)象回測優(yōu)化的策略在實盤里完全失效原因前視偏差。最常見的是用當天的收盤價算出信號然后當天就成交——但真實情況下你得等收盤后才能知道當天的收盤價交易指令最早也只能在次日開盤執(zhí)行。數(shù)據(jù)在時間軸上“偷看”了未來信息回測自然好看實盤自然翻車。解決信號生成后必須shift(1)讓信號延遲一天生效或者嚴格按“次日開盤價”成交。資源包如果自帶回測引擎先確認它的成交價格是不是用了close.shift(1)。這個是最隱蔽的坑很多初學者完全意識不到。5.5 現(xiàn)象兩次運行同一段代碼結果出現(xiàn)細微差異原因浮點精度。Pandas 的rolling.mean()用累加方式計算大數(shù)據(jù)量下會出現(xiàn)微小的精度差異另外resample聚合時多線程或并行模式下可能有誤差。解決做關鍵價格比較時用round(..., 6)或者用np.isclose做比較判斷。數(shù)據(jù)量在幾萬行時這個誤差可能只有 1e-10但累積到信號判斷的閾值邊緣就可能造成一次交易的開與不開。不要相信“浮點誤差是小事”在策略回測里這就是大事。6. 從靜態(tài)分析到簡單回測驗證你的股票策略是否真的可靠6.1 用向量化回測快速驗證 MA 均線策略資源包加載了干凈數(shù)據(jù)、算好了指標之后最值得做的一件事就是跑一個最小可用的回測驗證數(shù)據(jù)的時序邏輯是否自洽。我常用雙均線策略做“冒煙測試”——如果連這個策略都跑不出合理結果那數(shù)據(jù)或指標一定有問題。def backtest_ma(df, short5, long20): df df.copy().sort_values(trade_date) df[ma_short] df[close].rolling(short).mean() df[ma_long] df[close].rolling(long).mean() df[signal] (df[ma_short] df[ma_long]).astype(int) df[position] df[signal].shift(1) # 次日開盤執(zhí)行 df[ret] df[close].pct_change() df[strategy_ret] df[position] * df[ret] df[cum_ret] (1 df[strategy_ret]).cumprod() - 1 return df邏輯說明signal是當日收盤后的判斷position用shift(1)延遲到次日生效避免前視偏差。strategy_ret是持倉收益乘以當日漲跌幅cumprod累積得到凈值曲線。參數(shù)說明short和long分別是短、長均線窗口5和20是經(jīng)典參數(shù)。注意這里沒有考慮手續(xù)費和漲跌停限制結果偏樂觀但作為冒煙測試足夠。跑完看兩件事第一回測期末的累計凈值是不是在合理區(qū)間——如果翻了 100 倍得懷疑數(shù)據(jù)或邏輯第二position序列有沒有頻繁切換——一段趨勢行情里切了十幾次多半是數(shù)據(jù)有重復行或時間戳混亂。這兩點過了才放心做更復雜的策略驗證。6.2 用回測結果反推數(shù)據(jù)質量回測里經(jīng)常會發(fā)現(xiàn)隱藏的數(shù)據(jù)質量問題。比如某天close是 0pct_change會變成無窮大導致整條累積凈值線直接斷裂比如停牌日的價格沒有NaN而是重復了上一交易日導致rolling(5)計算出看似正常的均線但實際少了一天。我一般會在回測前加一個數(shù)據(jù)質量斷言assert df[close].isna().sum() 0, 存在空收盤價 assert (df[close] 0).sum() 0, 存在非正收盤價 assert df[vol].dtype in (int64, float64), 成交量類型異常這三條斷言能把大部分臟數(shù)據(jù)擋在回測之前。資源包如果自帶了內置檢查雙擊運行一下即可如果沒有建議自己加上成本極低收益極高。做完這個再跑策略心里踏實得多。6.3 可視化呈現(xiàn)把信號與凈值曲線畫出來回測結果最終要看圖才能定位問題。我習慣把收盤價、均線、買賣信號點和凈值曲線畫在一張圖上一眼就能看出信號是否出現(xiàn)在合理的位置——如果買點全出現(xiàn)在大漲之后而不是啟動之初說明信號邏輯有未來函數(shù)或滯后問題。import matplotlib.pyplot as plt def plot_strategy(df): fig, ax plt.subplots(2, 1, figsize(12, 8), sharexTrue) ax[0].plot(df[trade_date], df[close], labelClose, lw1) ax[0].plot(df[trade_date], df[ma_short], labelfMA{5}, lw1) ax[0].plot(df[trade_date], df[ma_long], labelfMA{20}, lw1) buy_idx df[df[signal].diff() 1].index sell_idx df[df[signal].diff() -1].index ax[0].scatter(df.loc[buy_idx, trade_date], df.loc[buy_idx, close], marker^, colorr, s50) ax[0].scatter(df.loc[sell_idx, trade_date], df.loc[sell_idx, close], markerv, colorg, s50) ax[0].legend() ax[1].plot(df[trade_date], df[cum_ret], labelStrategy Return, lw1) ax[1].legend() plt.tight_layout() plt.show()邏輯說明signal.diff() 1表示信號從 0 變成 1 的轉折點也就是買入信號diff() -1是賣出信號。從信號點和價格的位置關系能直觀判斷是否有滯后或超前。參數(shù)說明figsize控制圖幅buy_idx和sell_idx分別存儲買賣點索引。如果信號點全部出現(xiàn)在均線交叉后的第 N 根 K 線說明shift層級可能不對?;販y不是終點而是數(shù)據(jù)質量的試金石。從那以后我每次拿到新的數(shù)據(jù)資源都強制走一遍“跳空檢測 → 時區(qū)對齊 → 冒煙策略 → 畫圖目測”這個流程不跑完這一步就不碰后續(xù)的任何高級模型。這套流程幫我擋下了至少三次因為數(shù)據(jù)沒復權導致的假信號也讓我少走了很多彎路。希望這份實戰(zhàn)筆記里提到的坑和參數(shù)能讓你在跑自己的股票數(shù)據(jù)分析項目時更順一點。本文還有配套的精品資源點擊獲取