Kaynağa Gözat

feat(AGENTS): 添加技能系统文档,列出可用技能及其描述

- 新增 AGENTS.md 文件,详细列出可用技能及其功能描述
- 提供技能使用说明,指导用户如何有效调用技能
- 包含多种技能,如算法艺术、品牌指南、文档协作等,增强系统的功能性与灵活性

feat(Multi-factor): 添加多因子股票交易策略

- Multi-factor.py
maxfeng 4 hafta önce
ebeveyn
işleme
20b1c2f7b7
3 değiştirilmiş dosya ile 335 ekleme ve 30 silme
  1. 129 0
      AGENTS.md
  2. 53 30
      Lib/future/trading_training_tool.py
  3. 153 0
      Lib/stock/Multi-factor.py

+ 129 - 0
AGENTS.md

@@ -0,0 +1,129 @@
+# AGENTS
+
+<skills_system priority="1">
+
+## Available Skills
+
+<!-- SKILLS_TABLE_START -->
+<usage>
+When users ask you to perform tasks, check if any of the available skills below can help complete the task more effectively. Skills provide specialized capabilities and domain knowledge.
+
+How to use skills:
+- Invoke: Bash("openskills read <skill-name>")
+- The skill content will load with detailed instructions on how to complete the task
+- Base directory provided in output for resolving bundled resources (references/, scripts/, assets/)
+
+Usage notes:
+- Only use skills listed in <available_skills> below
+- Do not invoke a skill that is already loaded in your context
+- Each skill invocation is stateless
+</usage>
+
+<available_skills>
+
+<skill>
+<name>algorithmic-art</name>
+<description>Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>brand-guidelines</name>
+<description>Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>canvas-design</name>
+<description>Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>doc-coauthoring</name>
+<description>Guide users through a structured workflow for co-authoring documentation. Use when user wants to write documentation, proposals, technical specs, decision docs, or similar structured content. This workflow helps users efficiently transfer context, refine content through iteration, and verify the doc works for readers. Trigger when user mentions writing docs, creating proposals, drafting specs, or similar documentation tasks.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>docx</name>
+<description>"Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>frontend-design</name>
+<description>Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>internal-comms</name>
+<description>A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>mcp-builder</name>
+<description>Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>pdf</name>
+<description>Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>pptx</name>
+<description>"Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying or editing content, (3) Working with layouts, (4) Adding comments or speaker notes, or any other presentation tasks"</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>skill-creator</name>
+<description>Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>slack-gif-creator</name>
+<description>Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like "make me a GIF of X doing Y for Slack."</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>template</name>
+<description>Replace with description of the skill and when Claude should use it.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>theme-factory</name>
+<description>Toolkit for styling artifacts with a theme. These artifacts can be slides, docs, reportings, HTML landing pages, etc. There are 10 pre-set themes with colors/fonts that you can apply to any artifact that has been creating, or can generate a new theme on-the-fly.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>web-artifacts-builder</name>
+<description>Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use for complex artifacts requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX artifacts.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>webapp-testing</name>
+<description>Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.</description>
+<location>global</location>
+</skill>
+
+<skill>
+<name>xlsx</name>
+<description>"Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas"</description>
+<location>global</location>
+</skill>
+
+</available_skills>
+<!-- SKILLS_TABLE_END -->
+
+</skills_system>

+ 53 - 30
Lib/future/trading_training_tool.py

@@ -215,7 +215,7 @@ def get_kline_data_with_future(contract_code, trade_date, days_before=100, days_
         # 获取完整的数据范围
         start_date, end_date = get_trade_day_range(trade_date, days_before, days_after)
         if start_date is None or end_date is None:
-            return None, None, None
+            return None, None
 
         # 获取K线数据
         price_data = get_price(
@@ -227,7 +227,17 @@ def get_kline_data_with_future(contract_code, trade_date, days_before=100, days_
         )
 
         if price_data is None or len(price_data) == 0:
-            return None, None, None
+            return None, None
+
+        # 若该标的存在开收高低完全一致的日期,则视为异常并直接过滤
+        same_all_mask = (
+            (price_data['close'] == price_data['open']) &
+            (price_data['close'] == price_data['high']) &
+            (price_data['close'] == price_data['low'])
+        )
+        if same_all_mask.any():
+            print(f"{contract_code} 数据异常:存在开盘、收盘、最高、最低完全一致的交易日,跳过该标的。")
+            return None, None
 
         # 计算均线
         price_data['ma5'] = price_data['close'].rolling(window=5).mean()
@@ -249,7 +259,7 @@ def get_kline_data_with_future(contract_code, trade_date, days_before=100, days_
 
     except Exception as e:
         print(f"获取K线数据时出错: {str(e)}")
-        return None, None, None
+        return None, None
 
 
 def plot_partial_kline(data, trade_idx, trade_price, direction, contract_code, trade_date, order_time, save_path=None):
@@ -781,8 +791,8 @@ def main():
         print("没有未处理的开仓交易,退出")
         return
 
-    # 4. 随机选择一个交易(按标的类型分组随机抽取,避免同类连续出现
-    print("\n=== 步骤4: 随机选择交易 ===")
+    # 4. 构建候选交易列表(按标的类型分组轮询,避免同类集中
+    print("\n=== 步骤4: 构建候选交易列表 ===")
 
     # 按标的类型分组(提取合约代码的核心字母部分)
     def get_contract_type(contract_code):
@@ -803,36 +813,49 @@ def main():
     for contract_type in trades_by_type:
         random.shuffle(trades_by_type[contract_type])
 
-    # 从各组中轮流抽取,确保类型分散
-    selected_trade = None
-    available_types = list(trades_by_type.keys())
+    def build_trade_queue(trade_groups):
+        type_order = list(trade_groups.keys())
+        random.shuffle(type_order)
+        queue = []
+        while True:
+            added = False
+            for contract_type in type_order:
+                if trade_groups[contract_type]:
+                    queue.append(trade_groups[contract_type].pop(0))
+                    added = True
+            if not added:
+                break
+        return queue
 
-    # 随机打乱类型顺序,然后从第一个有交易的类型中抽取
-    random.shuffle(available_types)
-    for contract_type in available_types:
-        if trades_by_type[contract_type]:
-            selected_trade = trades_by_type[contract_type].pop(0)
-            break
+    trade_queue = build_trade_queue(trades_by_type)
+    print(f"候选交易数量: {len(trade_queue)}")
 
-    if selected_trade is None:
-        # 如果上述方法失败,回退到简单随机选择
-        random.shuffle(open_trades)
-        selected_trade = random.choice(open_trades)
+    # 5. 依次尝试获取K线数据,若失败则自动尝试下一候选
+    print("\n=== 步骤5: 获取K线数据 ===")
+    selected_trade = None
+    kline_data = None
+    trade_idx = None
+
+    for i, candidate_trade in enumerate(trade_queue):
+        print(f"尝试候选 {i + 1}/{len(trade_queue)}: {candidate_trade['contract_code']} {candidate_trade['trade_date']}")
+        kline_data, trade_idx = get_kline_data_with_future(
+            candidate_trade['contract_code'],
+            candidate_trade['trade_date'],
+            CONFIG['history_days'],
+            CONFIG['future_days']
+        )
 
-    # print(f"选中交易: {selected_trade['contract_code']} - {selected_trade['trade_date']} - {selected_trade['direction']}")
-    print(f"剩余未处理交易: {len(open_trades) - 1} 个")
+        if kline_data is None or trade_idx is None:
+            print("获取K线数据失败,尝试下一个候选。")
+            continue
 
-    # 5. 获取K线数据
-    print("\n=== 步骤5: 获取K线数据 ===")
-    kline_data, trade_idx = get_kline_data_with_future(
-        selected_trade['contract_code'],
-        selected_trade['trade_date'],
-        CONFIG['history_days'],
-        CONFIG['future_days']
-    )
+        selected_trade = candidate_trade
+        remaining = len(trade_queue) - (i + 1)
+        print(f"成功获取K线数据,剩余候选 {remaining} 个")
+        break
 
-    if kline_data is None or trade_idx is None:
-        print("获取K线数据失败,退出")
+    if selected_trade is None:
+        print("所有候选交易均无法获取有效K线数据,退出")
         return
 
     # 6. 显示部分K线图

+ 153 - 0
Lib/stock/Multi-factor.py

@@ -0,0 +1,153 @@
+# 克隆自聚宽文章:https://www.joinquant.com/post/65807
+# 标题:11年26倍,简单的多因子策略
+# 作者:foolmouse
+# https://www.joinquant.com/view/community/detail/625cc5268324506f6746fc0c6c605c44
+# 很简单的几个随手可得的因子:
+
+# ROA高。
+# 归母净利润增长快。
+# PB排名小且小于2。
+# 就这些,没有其他花里胡哨诘屈聱牙的高频因子,没有复杂计算,只是简单的排序,我觉得比较满意的几点:
+
+# 没有现在满社区的小市值暴露(虽然我也有小市值仓位,但是不会是重仓)。
+# 所有因子都是理论基础很扎实的几个基础因子,即使暂时会失效长期看应该有效的。
+# 没有太多参数(只有一个PB小于2,这个存在后视镜,是根据结果优化的。去掉也行但是2015年股灾回撤比较大,如果实盘我会考虑指数估值什么的大盘择时,但是聚宽没有这个因子因此没用)
+# 在牛市后半程会跑输,但是大部分熊市后会赚回来的。
+
+
+'''
+1.市净率小于2;
+2.负债比例高于市场平均值;
+3.企业的流动资产至少是流动负债的1.2倍;
+4.每年四次调仓,即在1/4/7/10月调仓;
+5.可加入止损(十天HS300跌幅达10%清仓);
+'''
+
+## 初始化函数,设定要操作的股票、基准等等
+def initialize(context):
+    # 设定指数
+    g.stockindex = '000300.XSHG' 
+    # 设定沪深300作为基准
+    set_benchmark('000300.XSHG')
+    # True为开启动态复权模式,使用真实价格交易
+    set_option('use_real_price', True) 
+    # 设定成交量比例
+    set_option('order_volume_ratio', 1)
+    # 股票类交易手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
+    set_order_cost(OrderCost(open_tax=0, close_tax=0.001, \
+                             open_commission=0.0003, close_commission=0.0003,\
+                             close_today_commission=0, min_commission=5), type='stock')
+    # 最大持仓数量
+    g.stocknum = 10
+
+    ## 自动设定调仓月份(如需使用自动,注销下段)
+    """
+    f = 4  # 调仓频率
+    log.info(range(1,13,12/f))
+    g.Transfer_date = range(1,13,12/f)
+    """
+    ## 手动设定调仓月份(如需使用手动,注释掉上段)
+    # g.Transfer_date = (3,9)
+    
+    #根据大盘止损,如不想加入大盘止损,注释下句即可
+    # run_daily(dapan_stoploss, time='open') 
+    
+    ## 按月调用程序
+    run_weekly(trade, weekday=1, time='open')
+
+## 交易函数
+def trade(context):
+    ## 获得Buylist
+    Buylist = check_stocks(context)
+    value = context.portfolio.total_value
+    hold_count = min(len(Buylist),g.stocknum)
+    per_value = value/hold_count if hold_count>0 else 0
+    ## 卖出
+    if len(context.portfolio.positions) > 0:
+        for stock in context.portfolio.positions.keys():
+            if stock not in Buylist:
+                order_target_value(stock, 0)
+            else:
+                order_target_value(stock,per_value)
+    ## 买入
+    if len(Buylist) > 0:
+        for stock in Buylist:
+           if stock not in context.portfolio.positions.keys():
+               order_target_value(stock,per_value)
+    
+## 选股函数
+def check_stocks(context):
+    # 获取所有股票
+    yesterday = context.previous_date
+    security = get_all_securities("stock", yesterday).index.tolist()
+    # 排除st,科创,新股
+    security = filter_st_stock(security)
+    security = filter_kcbj_stock(security)
+    security = filter_new_stock(context,security)
+    # 获取因子值
+    Stocks = get_fundamentals(query(
+            valuation.code,
+            valuation.pb_ratio,
+            indicator.roa,
+            indicator.inc_net_profit_to_shareholders_year_on_year
+        ).filter(
+            valuation.code.in_(security),
+            valuation.pb_ratio<2
+        ))
+    
+    stock_count = len(Stocks)
+    # 计算roa靠前的
+    Stocks = Stocks.sort_values(by='roa',ascending=False).iloc[:int(stock_count/10)]
+    # 计算增长率考前的
+    stock_count = len(Stocks)
+    Stocks = Stocks.sort_values(by='inc_net_profit_to_shareholders_year_on_year',ascending=False).iloc[:int(stock_count/10)]
+    # 计算PB最小的
+    Stocks = Stocks.sort_values(by='pb_ratio',ascending=True).iloc[:10]
+
+    Codes = Stocks.code
+
+    return list(Codes)
+# 过滤停牌股票
+def filter_paused_stock(stock_list):
+    current_data = get_current_data()
+    return [stock for stock in stock_list if not current_data[stock].paused]
+
+
+# 过滤ST及其他具有退市标签的股票
+def filter_st_stock(stock_list):
+    current_data = get_current_data()
+    return [stock for stock in stock_list
+            if not current_data[stock].is_st
+            and 'ST' not in current_data[stock].name
+            and '*' not in current_data[stock].name
+            and '退' not in current_data[stock].name]
+
+
+# 过滤科创北交股票
+def filter_kcbj_stock(stock_list):
+    for stock in stock_list[:]:
+        if stock[0] == '4' or stock[0] == '8' or stock[:2] == '68':
+            stock_list.remove(stock)
+    return stock_list
+
+
+# 过滤涨停的股票
+def filter_limitup_stock(context, stock_list):
+    last_prices = history(1, unit='1m', field='close', security_list=stock_list)
+    current_data = get_current_data()
+    return [stock for stock in stock_list if stock in context.portfolio.positions.keys()
+            or last_prices[stock][-1] < current_data[stock].high_limit]
+
+
+# 过滤跌停的股票
+def filter_limitdown_stock(context, stock_list):
+    last_prices = history(1, unit='1m', field='close', security_list=stock_list)
+    current_data = get_current_data()
+    return [stock for stock in stock_list if (stock in context.portfolio.positions.keys()
+            or last_prices[stock][-1] > current_data[stock].low_limit)]
+
+
+# 过滤次新股
+def filter_new_stock(context, stock_list):
+    yesterday = context.previous_date
+    return [stock for stock in stock_list if not yesterday - get_security_info(stock).start_date < datetime.timedelta(days=375)]