Skip to content
Back to skills

2480 Design 9331dc5b

ASecurity

``` ┌─────────────────────────────────────────────────────────────────┐ │ Config Layer │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ config/trade_calendar.csv │ │ │ │ - cal_date, is_open, pretrade_date │ │ │ │ - 2000-01-01 ~ 2030-12-31 │ │ │ └─────────────────────────────────────────────────────────┘ │ └────────────────────

  • 9 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 31, 2026
developmenttypescriptpythongovueexpresstestingapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 31, 2026

npx -y skills add tools-only/X-Skills --skill 2480-design_9331dc5b --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2480 Design 9331dc5b?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2480 Design 9331dc5b
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2480-design-9331dc5b/badge)](https://www.skillsdirectory.com/skills/tools-only-2480-design-9331dc5b)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
# Design: optimize-plugin-dependencies

## Architecture Overview

```
┌─────────────────────────────────────────────────────────────────┐
│                        Config Layer                              │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  config/trade_calendar.csv                               │   │
│  │  - cal_date, is_open, pretrade_date                      │   │
│  │  - 2000-01-01 ~ 2030-12-31                               │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Core Services Layer                          │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  TradeCalendarService (Singleton)                        │   │
│  │  - get_trading_days(n) -> List[str]                      │   │
│  │  - is_trading_day(date) -> bool                          │   │
│  │  - get_prev_trading_day(date) -> str                     │   │
│  │  - get_next_trading_day(date) -> str                     │   │
│  │  - get_trading_days_between(start, end) -> List[str]     │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  PluginManager (Enhanced)                                │   │
│  │  - check_dependencies(plugin_name) -> DependencyResult   │   │
│  │  - execute_with_dependencies(plugin_name, **kwargs)      │   │
│  │  - get_dependency_graph() -> Dict                        │   │
│  │  - get_plugins_by_category(category) -> List[Plugin]     │   │
│  │  - batch_trigger_sync(plugin_names) -> List[Task]        │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                       Plugin Layer                               │
│                                                                  │
│  Category: stock                                                 │
│  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐  │
│  │ stock_basic  │      │ daily        │      │ adj_factor   │  │
│  │ role: basic  │◀─────│ role: primary│─ ─ ─▶│ role: derived│  │
│  │ deps: []     │      │ deps: [basic]│      │ deps: [basic]│  │
│  └──────────────┘      │ opt: [adj]   │      └──────────────┘  │
│                        └──────────────┘                         │
│                                                                  │
│  Category: etf_fund (ETF/基金)                                   │
│  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐  │
│  │ etf_basic    │      │ etf_daily    │      │ etf_adj      │  │
│  │ role: basic  │◀─────│ role: primary│─ ─ ─▶│ role: derived│  │
│  │ deps: []     │      │ deps: [basic]│      │ deps: [basic]│  │
│  └──────────────┘      │ opt: [adj]   │      └──────────────┘  │
│                        └──────────────┘                         │
│                                                                  │
│  Category: index                                                 │
│  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐  │
│  │ index_basic  │      │ index_daily  │      │ index_weight │  │
│  │ role: basic  │◀─────│ role: primary│      │ role: aux    │  │
│  │ deps: []     │      │ deps: [basic]│      │ deps: [basic]│  │
│  └──────────────┘      └──────────────┘      └──────────────┘  │
│                                                                  │
│  Legend: ──▶ required dependency   ─ ─▶ optional dependency     │
└─────────────────────────────────────────────────────────────────┘
```

## Component Design

### 1. TradeCalendarService

**位置**: `src/stock_datasource/core/trade_calendar.py`

```python
class TradeCalendarService:
    """全局交易日历服务(单例模式)"""
    
    _instance = None
    _calendar_df: pd.DataFrame = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._load_calendar()
        return cls._instance
    
    def _load_calendar(self):
        """从 config/trade_calendar.csv 加载交易日历"""
        
    def get_trading_days(self, n: int = 30, end_date: str = None) -> List[str]:
        """获取最近 n 个交易日"""
        
    def is_trading_day(self, date: str) -> bool:
        """判断是否为交易日"""
        
    def get_prev_trading_day(self, date: str) -> str:
        """获取上一个交易日"""
        
    def get_next_trading_day(self, date: str) -> str:
        """获取下一个交易日"""
        
    def get_trading_days_between(self, start: str, end: str) -> List[str]:
        """获取两个日期之间的所有交易日"""
        
    def refresh_calendar(self):
        """手动刷新交易日历(从 TuShare 更新 CSV)"""
```

**设计决策**:
- 使用单例模式确保全局唯一实例
- 启动时加载 CSV 到内存,避免频繁 IO
- 提供 `refresh_calendar()` 方法支持手动更新

### 2. 插件分类与角色

**位置**: `src/stock_datasource/core/base_plugin.py`

```python
from enum import Enum

class PluginCategory(str, Enum):
    """插件分类"""
    STOCK = "stock"      # 股票相关
    INDEX = "index"      # 指数相关
    ETF_FUND = "etf_fund"  # ETF/基金相关(合并为一类)
    SYSTEM = "system"    # 系统数据

class PluginRole(str, Enum):
    """插件角色"""
    PRIMARY = "primary"    # 主数据(如 daily 行情)
    BASIC = "basic"        # 基础数据(如 stock_basic)
    DERIVED = "derived"    # 衍生数据(如复权因子)
    AUXILIARY = "auxiliary"  # 辅助数据(如指数权重)

class BasePlugin:
    def get_category(self) -> PluginCategory:
        """获取插件分类,子类应覆盖"""
        return PluginCategory.STOCK
    
    def get_role(self) -> PluginRole:
        """获取插件角色,子类应覆盖"""
        return PluginRole.PRIMARY
    
    def get_dependencies(self) -> List[str]:
        """获取必需依赖"""
        return []
    
    def get_optional_dependencies(self) -> List[str]:
        """获取可选依赖(如复权因子)
        
        可选依赖在同步主数据时默认关联同步,用户可选择关闭
        """
        return []
    
    def has_data(self) -> bool:
        """检查插件是否已有数据(供依赖检查使用)"""
        schema = self.get_schema()
        table_name = schema.get('table_name')
        if table_name and self.db:
            result = self.db.execute_query(f"SELECT 1 FROM {table_name} LIMIT 1")
            return result is not None and not result.empty
        return False
```

### 3. 插件依赖检查增强

**位置**: `src/stock_datasource/core/plugin_manager.py`

```python
@dataclass
class DependencyCheckResult:
    satisfied: bool
    missing_plugins: List[str]
    missing_data: Dict[str, str]  # {plugin_name: reason}
    optional_dependencies: List[str]  # 可选依赖列表
    
class PluginManager:
    def check_dependencies(self, plugin_name: str) -> DependencyCheckResult:
        """检查插件依赖是否满足
        
        检查内容:
        1. 依赖插件是否已注册
        2. 依赖插件的数据是否已存在
        """
        
    def execute_with_dependencies(
        self, 
        plugin_name: str, 
        auto_run_deps: bool = False,
        include_optional: bool = True,  # 是否包含可选依赖
        **kwargs
    ) -> Dict[str, Any]:
        """执行插件,可选自动执行依赖
        
        Args:
            plugin_name: 插件名称
            auto_run_deps: 是否自动执行未满足的依赖
            include_optional: 是否同步可选依赖(如复权因子)
            **kwargs: 插件参数
        """
        
    def get_dependency_graph(self) -> Dict[str, List[str]]:
        """获取所有插件的依赖关系图"""
    
    def get_plugins_by_category(self, category: PluginCategory) -> List[BasePlugin]:
        """按分类获取插件列表"""
        return [p for p in self.plugins.values() if p.get_category() == category]
    
    def get_plugins_by_role(self, role: PluginRole) -> List[BasePlugin]:
        """按角色获取插件列表"""
        return [p for p in self.plugins.values() if p.get_role() == role]
    
    def batch_trigger_sync(
        self, 
        plugin_names: List[str],
        task_type: str = "incremental",
        include_optional: bool = True
    ) -> List[Dict]:
        """批量触发同步任务
        
        自动按依赖顺序排序执行
        """
```

### 4. 插件依赖声明更新

| 插件文件 | 分类 | 角色 | 必需依赖 | 可选依赖 |
|---------|------|------|---------|---------|
| `tushare_stock_basic` | stock | basic | `[]` | `[]` |
| `tushare_daily` | stock | primary | `["tushare_stock_basic"]` | `["tushare_adj_factor"]` |
| `tushare_daily_basic` | stock | derived | `["tushare_stock_basic"]` | `[]` |
| `tushare_adj_factor` | stock | derived | `["tushare_stock_basic"]` | `[]` |
| `tushare_etf_basic` | etf_fund | basic | `[]` | `[]` |
| `tushare_etf_fund_daily` | etf_fund | primary | `["tushare_etf_basic"]` | `["tushare_etf_fund_adj"]` |
| `tushare_etf_fund_adj` | etf_fund | derived | `["tushare_etf_basic"]` | `[]` |
| `tushare_index_basic` | index | basic | `[]` | `[]` |
| `tushare_index_daily` | index | primary | `["tushare_index_basic"]` | `[]` |
| `tushare_index_weight` | index | auxiliary | `["tushare_index_basic"]` | `[]` |
| `tushare_idx_factor_pro` | index | derived | `["tushare_index_basic"]` | `[]` |

### 5. API 设计

**位置**: `src/stock_datasource/modules/datamanage/router.py`

```python
# 插件筛选
@router.get("/plugins")
async def get_plugins(
    category: Optional[str] = None,  # stock, index, etf_fund
    role: Optional[str] = None,      # primary, basic, derived, auxiliary
) -> List[PluginInfo]:
    """获取插件列表,支持按分类和角色筛选"""

# 依赖检查
@router.get("/plugins/{name}/dependencies")
async def get_plugin_dependencies(name: str) -> DependencyCheckResponse:
    """获取插件依赖详情,包含可选依赖"""

# 批量同步
@router.post("/sync/batch")
async def batch_trigger_sync(request: BatchSyncRequest) -> List[SyncTask]:
    """批量触发同步任务
    
    Request:
        plugin_names: List[str]  # 插件名称列表
        task_type: str           # incremental, full, backfill
        include_optional: bool   # 是否包含可选依赖(默认 true)
        trade_dates: List[str]   # 可选,指定日期
    """

# 按分类批量同步
@router.post("/sync/category/{category}")
async def sync_by_category(
    category: str,
    task_type: str = "incremental",
    include_optional: bool = True
) -> List[SyncTask]:
    """按分类触发所有插件同步"""
```

### 6. 前端设计

**PluginInfo 扩展**:
```typescript
interface PluginInfo {
  name: string
  version: string
  description: string
  category: 'stock' | 'index' | 'etf_fund' | 'system'
  role: 'primary' | 'basic' | 'derived' | 'auxiliary'
  dependencies: string[]
  optional_dependencies: string[]
  is_enabled: boolean
  // ... 其他字段
}
```

**前端功能**:
1. 插件列表顶部添加分类筛选 Tabs(全部/股票/指数/ETF基金)
2. 添加角色标签显示(主数据/基础/衍生/辅助)
3. 支持多选插件进行批量同步
4. 同步对话框添加"包含可选依赖"开关

## Data Flow

### 交易日历查询流程

```
1. 模块/插件请求交易日期
   │
   ▼
2. TradeCalendarService.get_trading_days()
   │
   ▼
3. 从内存缓存的 DataFrame 查询
   │
   ▼
4. 返回交易日期列表
```

### 插件执行流程(带依赖检查)

```
1. 用户触发插件执行(含可选依赖选项)
   │
   ▼
2. PluginManager.check_dependencies()
   │
   ├─ 依赖满足 ──────────────────────┐
   │                                  │
   ├─ 依赖不满足 + auto_run=true     │
   │   │                              │
   │   ▼                              │
   │  递归执行依赖插件                │
   │   │                              │
   │   ▼                              │
   │  依赖执行完成                    │
   │   │                              │
   │   └──────────────────────────────┤
   │                                  │
   ├─ 依赖不满足 + auto_run=false    │
   │   │                              │
   │   ▼                              │
   │  返回错误,提示缺失依赖          │
   │                                  │
   ▼                                  ▼
3. 执行目标插件
   │
   ├─ include_optional=true
   │   │
   │   ▼
   │  同时触发可选依赖(如复权因子)
   │
   ▼
4. 返回执行结果
```

### 批量同步流程

```
1. 用户选择多个插件 + 点击批量同步
   │
   ▼
2. 后端接收 plugin_names 列表
   │
   ▼
3. 构建依赖图,拓扑排序
   │
   ▼
4. 按顺序创建同步任务
   │
   ├─ 基础数据插件优先
   │   │
   │   ▼
   ├─ 主数据插件
   │   │
   │   ▼
   └─ 衍生/辅助数据插件
   │
   ▼
5. 返回任务列表
```

## Configuration

### trade_calendar.csv 格式

```csv
cal_date,is_open,pretrade_date
2026-12-31,1,20261230
2026-12-30,1,20261229
...
2000-01-04,1,20000103
2000-01-03,0,19991231
```

### 配置目录结构

```
src/stock_datasource/config/
├── __init__.py
├── trade_calendar.csv      # 交易日历数据
└── settings.py             # 其他全局配置(可选)
```

## Error Handling

### 依赖检查错误

```python
class DependencyNotSatisfiedError(Exception):
    """依赖未满足异常"""
    def __init__(self, plugin_name: str, missing: List[str], missing_data: Dict[str, str] = None):
        self.plugin_name = plugin_name
        self.missing = missing
        self.missing_data = missing_data or {}
        super().__init__(
            f"Plugin '{plugin_name}' dependencies not satisfied. "
            f"Missing: {', '.join(missing)}. "
            f"Please run the dependent plugins first."
        )
```

### 交易日历错误

```python
class TradeCalendarError(Exception):
    """交易日历异常"""
    pass

class CalendarNotFoundError(TradeCalendarError):
    """交易日历文件不存在"""
    pass

class InvalidDateError(TradeCalendarError):
    """无效的日期"""
    pass
```

## Migration Plan

1. **Phase 1**: 创建 `TradeCalendarService`,保持现有接口兼容 ✅
2. **Phase 2**: 更新 `datamanage/service.py` 使用新服务 ✅
3. **Phase 3**: 更新插件依赖声明 ✅
4. **Phase 4**: 增强 `PluginManager` 依赖检查 ✅
5. **Phase 5**: 调整插件分类(增加 cn_stock/hk_stock)
6. **Phase 6**: 添加定时调度服务后端
7. **Phase 7**: 添加定时调度 API
8. **Phase 8**: 前端调度管理和批量操作 UI
9. **Phase 9**: 添加操作说明和帮助提示
10. **Phase 10**: 清理和测试

## 定时调度设计 🆕

### 调度服务架构

```
┌─────────────────────────────────────────────────────────────────┐
│                     ScheduleService                              │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  ScheduleConfig (持久化到 runtime_config.json)           │   │
│  │  - enabled: bool (是否启用定时调度)                       │   │
│  │  - cron_expression: str (执行时间)                        │   │
│  │  - include_optional_deps: bool (是否包含可选依赖)          │   │
│  │  - skip_non_trading_days: bool (是否跳过非交易日)          │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  PluginScheduleConfig (每个插件独立配置)                   │   │
│  │  - plugin_name: str                                      │   │
│  │  - schedule_enabled: bool (是否加入定时任务)              │   │
│  │  - full_scan_enabled: bool (是否全量扫描)                 │   │
│  └─────────────────────────────────────────────────────────┘   │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │  ScheduleExecutor (后台线程)                              │   │
│  │  - 解析 cron 表达式,等待执行时间                          │   │
│  │  - 检查是否为交易日                                        │   │
│  │  - 获取启用的插件列表                                      │   │
│  │  - 按依赖排序,创建批量任务                                │   │
│  └─────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────┘
```

### 数据模型

```python
# schemas.py 新增

class ScheduleConfig(BaseModel):
    """全局调度配置"""
    enabled: bool = False                     # 是否启用定时调度
    cron_expression: str = "0 18 * * 1-5"     # Cron: 工作日18:00
    include_optional_deps: bool = True        # 包含可选依赖
    skip_non_trading_days: bool = True        # 跳过非交易日
    last_run_at: Optional[datetime] = None    # 上次执行时间
    next_run_at: Optional[datetime] = None    # 下次执行时间

class PluginScheduleConfig(BaseModel):
    """插件调度配置"""
    plugin_name: str
    schedule_enabled: bool = True             # 是否加入定时任务
    full_scan_enabled: bool = False           # 是否全量扫描
    category: str                             # 分类
    role: str                                 # 角色
    dependencies: List[str] = []              # 依赖列表

class ScheduleExecutionRecord(BaseModel):
    """调度执行记录"""
    execution_id: str
    started_at: datetime
    completed_at: Optional[datetime]
    status: str                               # running, completed, failed
    total_plugins: int
    completed_plugins: int
    failed_plugins: int
    task_ids: List[str]                       # 关联的同步任务ID
```

### API 设计

```python
# router.py 新增

# ============ 定时调度 ============

@router.get("/schedule/config", response_model=ScheduleConfig)
async def get_schedule_config():
    """获取全局调度配置"""

@router.put("/schedule/config", response_model=ScheduleConfig)
async def update_schedule_config(request: ScheduleConfigRequest):
    """更新全局调度配置
    
    - enabled: 是否启用定时调度
    - cron_expression: Cron 表达式(如 "0 18 * * 1-5")
    - include_optional_deps: 是否包含可选依赖
    - skip_non_trading_days: 是否跳过非交易日
    """

@router.get("/schedule/plugins", response_model=List[PluginScheduleConfig])
async def get_plugin_schedule_configs(
    category: Optional[str] = None  # 按分类筛选
):
    """获取所有插件的调度配置"""

@router.put("/schedule/plugins/{name}", response_model=PluginScheduleConfig)
async def update_plugin_schedule_config(
    name: str,
    request: PluginScheduleConfigRequest
):
    """更新单个插件的调度配置
    
    - schedule_enabled: 是否加入定时任务
    - full_scan_enabled: 是否全量扫描
    """

@router.post("/schedule/trigger", response_model=ScheduleExecutionRecord)
async def trigger_schedule_now():
    """立即触发一次调度执行(不等待 cron 时间)"""

@router.get("/schedule/history", response_model=List[ScheduleExecutionRecord])
async def get_schedule_history(days: int = 7, limit: int = 50):
    """获取调度执行历史"""
```

### 前端界面设计

```vue
<!-- SchedulePanel.vue - 调度管理面板 -->
<template>
  <el-card>
    <template #header>
      <div class="flex justify-between items-center">
        <span class="text-lg font-bold">定时调度配置</span>
        <el-switch v-model="config.enabled" @change="updateConfig" />
      </div>
    </template>
    
    <!-- 配置项 -->
    <el-form label-width="140px">
      <el-form-item label="执行时间">
        <el-time-picker v-model="executeTime" format="HH:mm" />
        <el-select v-model="frequency" class="ml-2">
          <el-option label="每天" value="daily" />
          <el-option label="仅工作日" value="weekday" />
        </el-select>
      </el-form-item>
      
      <el-form-item label="包含可选依赖">
        <el-switch v-model="config.include_optional_deps" />
        <el-tooltip content="如复权因子等衍生数据">
          <el-icon class="ml-2"><QuestionFilled /></el-icon>
        </el-tooltip>
      </el-form-item>
      
      <el-form-item label="跳过非交易日">
        <el-switch v-model="config.skip_non_trading_days" />
      </el-form-item>
    </el-form>
    
    <!-- 操作按钮 -->
    <div class="flex gap-2 mt-4">
      <el-button type="primary" @click="triggerNow">立即执行一次</el-button>
      <el-button @click="showHistory">查看执行历史</el-button>
    </div>
    
    <!-- 操作说明 -->
    <el-alert type="info" :closable="false" class="mt-4">
      <template #title>操作说明</template>
      <ul class="list-disc ml-4 text-sm">
        <li>定时调度会在每个交易日自动执行增量同步</li>
        <li>插件按依赖顺序执行:基础数据 → 主数据 → 衍生数据</li>
        <li>开启"全量扫描"会重新获取全部历史数据(耗时较长)</li>
        <li>建议仅在数据异常时开启全量扫描</li>
      </ul>
    </el-alert>
  </el-card>
</template>
```

### 插件分类调整

```python
class PluginCategory(str, Enum):
    """插件分类 - 按市场划分"""
    CN_STOCK = "cn_stock"    # A股相关(原 stock)
    HK_STOCK = "hk_stock"    # 港股相关(新增)
    INDEX = "index"          # 指数相关
    ETF_FUND = "etf_fund"    # ETF/基金相关
    SYSTEM = "system"        # 系统数据

# 分类映射(兼容旧值)
CATEGORY_ALIASES = {
    "stock": "cn_stock",  # 兼容旧分类
}

# 前端显示名称
CATEGORY_LABELS = {
    "cn_stock": "A股",
    "hk_stock": "港股",
    "index": "指数",
    "etf_fund": "ETF基金",
    "system": "系统",
}
```

## Testing Strategy

### 单元测试

- `test_trade_calendar_service.py`: 交易日历服务测试
- `test_plugin_dependencies.py`: 插件依赖检查测试
- `test_plugin_category.py`: 插件分类筛选测试
- `test_batch_sync.py`: 批量同步测试
- `test_schedule_service.py`: 定时调度服务测试 🆕
- `test_schedule_executor.py`: 调度执行器测试 🆕

### 集成测试

- 测试完整的插件执行流程(含依赖检查)
- 测试自动执行依赖功能
- 测试可选依赖关联同步
- 测试批量同步按依赖顺序执行
- 测试定时调度配置持久化 🆕
- 测试调度执行(模拟非交易日跳过)🆕

Files in this skill

  • README.md898 B
  • skill.md26.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.