尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

给自动交易程序增加节日过滤规则,非交易日跳过行情检测。

给自动交易程序增加节日过滤规则,非交易日跳过行情检测。
📅 发布时间:2026/6/21 4:21:40

自动交易程序:增加节日过滤规则,非交易日跳过行情检测

一、实际应用场景描述

在 A 股自动交易系统的实际运行中,交易日历(Trading Calendar) 管理是最基础却最容易被忽视的环节。一个没有节日过滤的交易程序,会在非交易日(周末、法定节假日、调休补班日) 不断尝试获取行情、计算信号、甚至下单,导致一系列严重问题。

典型场景

场景 问题表现

国庆长假后第一个交易日 程序在假期期间持续运行,积累了 7 天的"待处理信号",开盘瞬间集中下单,造成价格冲击

周末运行策略 周六早上定时任务触发,API 返回空数据或上周五的缓存数据,策略基于过时信息计算信号

春节假期 9 天假期中程序每天尝试连接交易接口,触发风控告警,账户被标记为"异常登录"

调休工作日(如春节前补班) 程序误判为假期跳过,实际是交易日,错失当天交易机会

境外市场节假日不同步 港股通标的在 A 股休市时仍可交易,过滤规则需区分市场

二、引入痛点

痛点 具体表现

🔴 无效 API 调用 非交易日调用行情接口,消耗配额、产生空结果,部分 API 返回错误导致程序崩溃

🔴 信号失真 基于非交易日数据(如前一日收盘价)计算的"信号",在下一个交易日开盘时已经失效

🔴 集中下单冲击 假期积累的待处理信号在开盘瞬间释放,大资金造成显著价格冲击

🔴 风控告警 券商系统检测到"非交易时段频繁登录/下单尝试",可能冻结账户

🔴 调休日误判 简单用

"weekday()" 判断无法处理"周末调休上班"等特殊情况

🟡 多市场日历差异 A 股、港股、美股交易日历不同,需分别处理

🟡 夜盘/盘前竞价 部分策略需要在盘前竞价阶段运行,交易日历需精确到时段

三、核心逻辑讲解

3.1 A 股交易日历规则

A 股非交易日类型:

┌─────────────────────────────────────────────────────┐

│ 类型 │ 示例 │

├───────────────────┼───────────────────────────────┤

│ 周末 │ 周六、周日 │

│ 法定节假日 │ 元旦、春节、清明、五一、端午、 │

│ │ 中秋、国庆 │

│ 调休补班日 │ 周末上班但股市不开市 │

│ 临时休市 │ 极端天气、重大事件等 │

│ 日内非交易时段 │ 9:30 前 / 11:30~13:00 / 15:00后│

└─────────────────────────────────────────────────────┘

3.2 节日过滤核心设计

┌──────────────────────────────────────────────────────────┐

│ 自动交易程序:交易日历过滤模块 │

├──────────────────────────────────────────────────────────┤

│ │

│ 程序启动 / 定时触发 │

│ │ │

│ ▼ │

│ ┌──────────────────────────────────┐ │

│ │ ★ 第一步:获取交易日历 │ │

│ │ 调用 exchange_cal.get('A股') │ │

│ │ 返回今日是否为交易日 │ │

│ └──────────────────────────────────┘ │

│ │ │

│ ▼ 是交易日? │

│ ┌────┴────┐ │

│ │ │ │

│ 是┘ └否 │

│ │ │ │

│ ▼ ▼ │

│ 执行完整 记录日志: │

│ 行情检测 "YYYY-MM-DD 非交易日,跳过" │

│ 信号计算 计算下次交易日 │

│ 下单逻辑 休眠至下一交易日开盘前 │

│ │

└──────────────────────────────────────────────────────────┘

3.3 交易日历数据来源

来源 优点 缺点

交易所官方公告 最权威 需爬取/解析 PDF

第三方金融数据库 接口友好、数据完整 依赖外部服务

本地静态文件 零延迟、无配额限制 需每年更新

在线 API(推荐) 自动更新、支持多市场 需处理网络异常

3.4 核心判断逻辑

# 伪代码

def should_trade_today(date, market='A股'):

"""返回 (是否交易, 原因)"""

# 1. 周末检查(快速过滤 5/7 的非交易日)

if date.weekday() in (5, 6): # 周六、周日

return False, "weekend"

# 2. 查询交易日历

cal = get_trading_calendar(market, year=date.year)

if not cal.is_trading_day(date):

return False, "holiday"

# 3. 检查日内交易时段(如需要)

if not is_within_trading_hours(now()):

return False, "after_hours"

return True, "trading_day"

四、项目结构

trading_calendar_filter/

├── README.md

├── requirements.txt

├── config.yaml # 全局配置(含交易日历来源)

├── data/

│ └── trading_calendar.csv # 交易日历数据(静态备份)

├── src/

│ ├── calendar_provider.py # ★ 交易日历数据提供者

│ ├── trading_day_checker.py # ★ 交易日判断器

│ ├── trading_engine.py # 交易引擎(集成日历过滤)

│ ├── backtester.py # 回测引擎(含日历感知)

│ └── visualizer.py # 可视化工具

├── main.py # 主入口

└── update_calendar.py # 更新交易日历脚本

五、完整代码(模块化 + 清晰注释)

"requirements.txt"

pandas>=1.5

numpy>=1.21

pyyaml>=6.0

matplotlib>=3.5

requests>=2.28

"config.yaml"

# 交易日历与交易时段配置

# ★ 交易日历数据源

calendar:

# 数据来源:local_file / online_api / hybrid(推荐)

source: "hybrid"

local_file: "data/trading_calendar.csv"

# 在线 API 地址(示例,实际替换为真实接口)

api_url: "https://api.example.com/trading_calendar"

api_key: ""

# 支持的市场

markets:

- name: "A股"

code: "CN"

timezone: "Asia/Shanghai"

- name: "港股"

code: "HK"

timezone: "Asia/Hong_Kong"

- name: "美股"

code: "US"

timezone: "America/New_York"

# ★ 交易时段配置

trading_hours:

A股:

pre_market: "09:15-09:25" # 竞价

morning: "09:30-11:30" # 上午

afternoon: "13:00-15:00" # 下午

# 是否包含盘前竞价时段

include_pre_market: false

# 日内检查粒度(秒)

check_interval: 60

# 策略参数

strategy:

initial_capital: 1000000

max_positions: 5

take_profit_pct: 0.08

stop_loss_pct: -0.05

# 日志

logging:

level: "INFO"

file: "logs/trading.log"

"src/calendar_provider.py"(★ 核心模块)

"""

calendar_provider.py

★ 交易日历数据提供者

职责:

1. 从本地文件/在线 API 获取交易日历

2. 缓存到本地,减少 API 调用

3. 支持多市场(A 股、港股、美股)

"""

import pandas as pd

import numpy as np

from pathlib import Path

from typing import Optional, Set, Dict

import json

import logging

from datetime import date

logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')

logger = logging.getLogger(__name__)

class TradingCalendarProvider:

"""

交易日历数据提供者

支持三种模式:

- local_file: 从本地 CSV 读取(离线可用)

- online_api: 从在线 API 获取(自动更新)

- hybrid: 优先本地,缺失时 fallback 到 API(推荐)

"""

def __init__(

self,

source: str = "hybrid",

local_file: str = "data/trading_calendar.csv",

api_url: str = "",

cache_dir: str = "data/.cache"

):

"""

参数:

source: 数据源模式 (local_file / online_api / hybrid)

local_file: 本地交易日历文件路径

api_url: 在线 API 地址

cache_dir: 缓存目录

"""

self.source = source

self.local_file = Path(local_file)

self.api_url = api_url

self.cache_dir = Path(cache_dir)

self.cache_dir.mkdir(parents=True, exist_ok=True)

# 内存缓存: {market: {date_str: is_trading}}

self._memory_cache: Dict[str, Dict[str, bool]] = {}

logger.info(f"交易日历提供者初始化: 模式={source}")

def get_calendar(

self,

market: str = "A股",

year: int = None

) -> pd.DataFrame:

"""

获取指定市场、指定年份的交易日历

返回 DataFrame:

date is_trading holiday_name

2024-01-01 False 元旦

2024-01-02 True NaN

"""

year = year or date.today().year

cache_key = f"{market}_{year}"

# 尝试从本地加载

if self.source in ("local_file", "hybrid"):

df = self._load_local(market, year)

if df is not None:

logger.debug(f"从本地加载: {cache_key}")

return df

# Fallback 到 API

if self.source in ("online_api", "hybrid"):

df = self._fetch_online(market, year)

if df is not None:

self._save_local(df, market, year)

logger.info(f"从 API 获取并缓存: {cache_key}")

return df

# 都失败 → 用简易规则生成(仅周末 + 主要节假日)

logger.warning(f"无法获取交易日历,使用简易规则生成")

return self._generate_simple_calendar(year)

def is_trading_day(

self,

date: date,

market: str = "A股"

) -> bool:

"""

★ 核心方法:判断某日期是否为交易日

参数:

date: 要查询的日期

market: 市场名称

返回:

bool: True = 交易日

"""

date_str = date.isoformat()

cache_key = f"{market}"

# 检查内存缓存

if cache_key in self._memory_cache:

if date_str in self._memory_cache[cache_key]:

return self._memory_cache[cache_key][date_str]

# 获取日历

cal = self.get_calendar(market, date.year)

# 查询

if date_str in cal.index:

result = bool(cal.loc[date_str, 'is_trading'])

else:

# 不在日历中 → 用简易规则

result = self._simple_is_trading(date)

# 写入缓存

if cache_key not in self._memory_cache:

self._memory_cache[cache_key] = {}

self._memory_cache[cache_key][date_str] = result

return result

def get_next_trading_day(

self,

date: date,

market: str = "A股"

) -> date:

"""获取下一个交易日"""

from datetime import timedelta

candidate = date + timedelta(days=1)

max_search = 30 # 最多往前找 30 天

for i in range(max_search):

if self.is_trading_day(candidate, market):

return candidate

candidate += timedelta(days=1)

# 找不到 → 返回 30 天后的日期

logger.warning(f"在 30 天内未找到 {market} 的下一个交易日")

return date + timedelta(days=30)

def get_trading_days_between(

self,

start: date,

end: date,

market: str = "A股"

) -> pd.DatetimeIndex:

"""获取两个日期之间的所有交易日"""

# 确保日历覆盖区间

cal = self.get_calendar(market, start.year)

if end.year != start.year:

cal_next = self.get_calendar(market, end.year)

cal = pd.concat([cal, cal_next])

mask = (cal.index >= start.isoformat()) & (cal.index <= end.isoformat())

trading = cal[mask & cal['is_trading']]

return pd.DatetimeIndex(pd.to_datetime(trading.index))

def _load_local(

self,

market: str,

year: int

) -> Optional[pd.DataFrame]:

"""从本地文件加载"""

path = self.cache_dir / f"{market}_{year}.csv"

if not path.exists() and self.local_file.exists():

path = self.local_file

if not path.exists():

return None

try:

df = pd.read_csv(path, parse_dates=['date']).set_index('date')

return df

except Exception as e:

logger.error(f"加载本地交易日历失败: {e}")

return None

def _fetch_online(

self,

market: str,

year: int

) -> Optional[pd.DataFrame]:

"""从在线 API 获取"""

if not self.api_url:

return None

try:

import requests

resp = requests.get(

f"{self.api_url}/calendar",

params={'market': market, 'year': year},

timeout=10

)

if resp.status_code == 200:

data = resp.json()

records = data.get('data', [])

df = pd.DataFrame([{

'date': r['date'],

'is_trading': r['is_trading'],

'holiday_name': r.get('holiday', '')

} for r in records]).set_index('date')

return df

except Exception as e:

logger.error(f"在线获取交易日历失败: {e}")

return None

def _save_local(self, df: pd.DataFrame, market: str, year: int):

"""保存到本地缓存"""

path = self.cache_dir / f"{market}_{year}.csv"

df.to_csv(path)

def _generate_simple_calendar(self, year: int) -> pd.DataFrame:

"""

简易交易日历生成器

仅处理:

- 周末(周六日非交易)

- 主要法定节假日(元旦、春节、清明、五一、端午、中秋、国庆)

注意:不处理调休补班,精度有限,仅作降级方案

"""

import holidays

from datetime import date, timedelta

start = date(year, 1, 1)

end = date(year, 12, 31)

dates = []

current = start

while current <= end:

dates.append(current)

current += timedelta(days=1)

records = []

for d in dates:

# 周末

if d.weekday() in (5, 6):

is_td = False

hname = 'weekend'

else:

# 法定节假日(使用 holidays 库)

is_td = True

hname = ''

try:

cn_holidays = holidays.China(years=year)

if d in cn_holidays:

is_td = False

hname = str(cn_holidays[d])

except:

pass

# 手动补充主要节假日(降级方案)

if is_td and hname == '':

is_td, hname = self._check_major_holidays(d)

records.append({

'date': d.isoformat(),

'is_trading': is_td,

'holiday_name': hname

})

return pd.DataFrame(records).set_index('date')

def _check_major_holidays(self, d: date) -> tuple[bool, str]:

"""检查是否为主要法定节假日(降级方案)"""

# 元旦

if d.month == 1 and d.day <= 3:

return False, "元旦"

# 五一

if d.month == 5 and 1 <= d.day <= 5:

return False, "劳动节"

# 国庆

if d.month == 10 and 1 <= d.day <= 7:

return False, "国庆节"

# 中秋(简化:农历八月十五附近,实际需要农历转换)

# 此处省略农历计算,实际项目中应使用 lunardate 库

return True, ""

def _simple_is_trading(self, d: date) -> bool:

"""简易判断(内存缓存未命中时)"""

if d.weekday() in (5, 6):

return False

return True # 简化:非周末即视为交易日

def print_calendar_summary(self, market: str = "A股", year: int = None):

"""打印交易日历摘要"""

cal = self.get_calendar(market, year)

total = len(cal)

trading = cal['is_trading'].sum()

non_trading = total - trading

print(f"\n{'='*60}")

print(f" 交易日历摘要: {market} {year or '最新'}")

print(f"{'='*60}")

print(f" 总天数: {total}")

print(f" 交易日: {trading} ({trading/total*100:.1f}%)")

print(f" 非交易日: {non_trading} ({non_trading/total*100:.1f}%)")

# 列出节假日

holidays = cal[~cal['is_trading'] & (cal['holiday_name'] != '')]

if len(holidays) > 0:

print(f"\n 节假日明细(前 10 条):")

for idx, row in holidays.head(10).iterrows():

print(f" {idx}: {row['holiday_name']}")

if len(holidays) > 10:

print(f" ... 共 {len(holidays)} 个非交易日")

print(f"{'='*60}\n")

"src/trading_day_checker.py"(★ 核心模块)

"""

trading_day_checker.py

★ 交易日判断器:集成日内时段检查

在交易日历基础上,增加:

1. 日内交易时段判断(开盘前/交易中/收盘后)

2. 竞价时段处理

3. 连续交易时段(如需要)

"""

import pandas as pd

import numpy as np

from datetime import datetime, time, date, timedelta

from typing import Optional, Tuple

from enum import Enum

import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')

logger = logging.getLogger(__name__)

class MarketSession(Enum):

"""市场交易时段"""

PRE_MARKET = "pre_market" # 盘前竞价

MORNING = "morning" # 上午交易

MIDDAY_BREAK = "midday_break" # 午间休市

AFTERNOON = "afternoon" # 下午交易

AFTER_HOURS = "after_hours" # 盘后

CLOSED = "closed" # 休市

class TradingDayChecker:

"""

★ 交易日 + 交易时段检查器

集成功能:

1. 交易日历查询(委托给 CalendarProvider)

2. 日内交易时段判断

3. 距离下次开盘倒计时

"""

def __init__(

self,

calendar_provider: "TradingCalendarProvider",

market: str = "A股",

trading_hours: Optional[Dict] = None

):

"""

参数:

calendar_provider: 交易日历提供者

market: 市场名称

trading_hours: 交易时段配置,如:

{

'pre_market': '09:15-09:25',

'morning': '09:30-11:30',

'afternoon': '13:00-15:00',

'include_pre_market': False

}

"""

self.provider = calendar_provider

self.market = market

# 解析交易时段

self.sessions = self._parse_trading_hours(trading_hours or {})

self.include_pre_market = trading_hours.get('include_pre_market', False)

logger.info(f"交易日检查器初始化: 市场={market}")

def _parse_trading_hours(self, config: Dict) -> list:

"""解析交易时段配置为时间区间列表"""

sessions = []

if config.get('include_pre_market') and 'pre_market' in config:

start, end = config['pre_market'].split('-')

sessions.append((MarketSession.PRE_MARKET, self._parse_time(start), self._parse_time(end)))

if 'morning' in config:

start, end = config['morning'].split('-')

sessions.append((MarketSession.MORNING, self._parse_time(start), self._parse_time(end)))

if 'afternoon' in config:

start, end = config['afternoon'].split('-')

sessions.append((MarketSession.AFTERNOON, self._parse_time(start), self._parse_time(end)))

return sessions

def _parse_time(self, t_str: str) -> time:

"""解析 'HH:MM' 为 time 对象"""

h, m = map(int, t_str.split(':'))

return time(h, m)

def check_now(self, now: Optional[datetime] = None) -> Tuple[bool, MarketSession, str]:

"""

★ 核心方法:检查当前时刻是否应该运行交易逻辑

参数:

now: 当前时间(默认取系统时间)

返回:

Tuple[是否交易日, 当前时段, 说明信息]

"""

now = now or datetime.now()

today = now.date()

# === 第一步:检查是否为交易日 ===

if not self.provider.is_trading_day(today, self.market):

# 非交易日 → 计算距离下一个交易日还有多久

next_td = self.provider.get_next_trading_day(today, self.market)

days_until = (next_td - today).days

reason = f"非交易日,距离下次交易还有 {days_until} 天 ({next_td})"

logger.debug(reason)

return False, MarketSession.CLOSED, reason

# === 第二步:检查日内交易时段 ===

current_time = now.time()

for session, start, end in self.sessions:

if start <= current_time <= end:

reason = f"交易时段: {session.value}"

return True, session, reason

# 不在任何交易时段内

# 判断是盘前还是盘后

morning_start = self.sessions[0][1] if self.sessions else time(9, 30)

if current_time < morning_start:

reason = "盘前等待开盘"

else:

reason = "盘后/午间休市"

return False, MarketSession.AFTER_HOURS, reason

def should_run_strategy(self, now: Optional[datetime] = None) -> Tuple[bool, str]:

"""

★ 策略层调用入口:判断当前是否应该执行行情检测和交易逻辑

返回:

(should_run, reason)

"""

is_trading, session, reason = self.check_now(now)

if not is_trading:

return False, reason

# 如果策略不包含盘前竞价,需要额外检查

if not self.include_pre_market and session == MarketSession.PRE_MARKET:

return False, "竞价时段(策略未启用竞价)"

return True, reason

def get_seconds_to_next_open(self, now: Optional[datetime] = None) -> int:

"""

计算距离下次开盘还有多少秒

用于:非交易时段让程序休眠,避免空轮询

"""

now = now or datetime.now()

today = now.date()

current_time = now.time()

# 情况 1:今天就是交易日,但还没开盘

if self.provider.is_trading_day(today, self.market):

morning_start = self.sessions[0][1] if self.sessions else time(9, 30)

if current_time < morning_start:

next_open = datetime.combine(today, morning_start)

return max(0, int((next_open - now).total_seconds()))

# 情况 2:今天不是交易日,或已经收盘

next_td = self.provider.get_next_trading_day(today, self.market)

morning_start = self.sessions[0][1] if self.sessions else time(9, 30)

next_open = datetime.combine(next_td, morning_start)

return max(0, int((next_open - now).total_seconds()))

def print_today_status(self):

"""打印今日交易状态"""

today = date.today()

is_td = self.provider.is_trading_day(today, self.market)

print(f"\n{'='*50}")

print(f" 今日交易状态: {today}")

print(f"{'='*50}")

print(f" 是否交易日: {'✅ 是' if is_td else '❌ 否'}")

if is_

本文代码仅供学习与技术交流,不构成任何投资建议,股市有风险,入市需谨慎!

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

相关新闻

  • Claude工作流实战:50条覆盖认知-操作-集成的工程化技巧
  • WSL2+llama.cpp部署Qwen 3.6-35B-A3B全指南
  • 2026年比较好的提升机链钩/山东提升机链轮实力工厂推荐 - 品牌宣传支持者

最新新闻

  • 嵌入式GUI开发:emWin高级特性实战指南
  • P89LPC93x单片机SPI接口配置与驱动开发实战指南
  • i.MX27 IP摄像头开发全流程:从环境搭建到固件烧录实战指南
  • 嵌入式DES加密库实战:从Feistel结构到CBC/CFB模式集成
  • 传感器失效下的鲁棒最优实验设计:从理论到工程实践
  • 2026年浙江杭州行政诉讼律师推荐精选:5家专业实力律师团队 - 本地品牌推荐

日新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号