Pārlūkot izejas kodu

更新了一般牛差的逻辑,但是还没有完全完成

maxfeng 1 mēnesi atpakaļ
vecāks
revīzija
5554ee46ab

+ 174 - 0
Lib/Options/README_ETF_MA_Analysis.md

@@ -0,0 +1,174 @@
+# ETF月线级别均线分析工具
+
+## 概述
+
+基于 `deep_itm_bull_spread_strategy.py` 的ETF获取逻辑,创建了一个专门的ETF价格与移动平均线距离分析工具。该工具用于分析ETF月收盘价与移动平均线的距离关系。
+
+## 功能特性
+
+### 1. ETF数据获取
+- **支持的ETF**:
+  - 50ETF (510050.XSHG) - 上证50ETF
+  - 300ETF (510300.XSHG) - 沪深300ETF  
+  - 创业板ETF (159915.XSHE) - 创业板ETF
+
+### 2. 技术分析
+- **移动平均线计算**:5日、10日、20日均线
+- **月线级别分析**:将日线数据转换为月线数据
+- **距离计算**:计算月收盘价与各均线的绝对距离和百分比距离
+
+### 3. 数据可视化
+- 生成距离趋势图表
+- 显示每个ETF的距离变化曲线
+- 包含统计信息标注
+
+### 4. 数据导出
+- 导出详细的CSV分析结果
+- 包含价格、均线、距离等完整数据
+
+## 文件说明
+
+### 主要文件
+- `etf_monthly_ma_analysis.py` - 完整的分析工具(包含分析功能和直接运行代码)
+- `README_ETF_MA_Analysis.md` - 本说明文档
+
+### 生成文件
+运行后会生成以下文件:
+- `ETF月线距离分析_开始日期_结束日期.png` - 分析图表
+- `ETF_月线分析_ETF名称_开始日期_结束日期.csv` - 每个ETF的详细数据
+
+## 使用方法
+
+### 直接运行(推荐)
+
+```bash
+# 在聚宽环境中直接运行
+python etf_monthly_ma_analysis.py
+```
+
+文件中已经预设了分析参数,直接运行即可开始分析。如需修改时间范围,编辑文件底部的参数:
+
+```python
+# 修改这里的参数来自定义分析
+analyzer = ETFMonthlyMAAnalysis(
+    start_date='2024-01-01',  # 修改开始日期
+    end_date='2024-10-31'     # 修改结束日期
+)
+```
+
+### 自定义使用
+
+如果需要在其他脚本中使用,可以导入并自定义:
+
+```python
+from etf_monthly_ma_analysis import ETFMonthlyMAAnalysis
+
+# 创建分析器
+analyzer = ETFMonthlyMAAnalysis(
+    start_date='2024-06-01', 
+    end_date='2024-12-31'
+)
+
+# 运行完整分析
+analyzer.run_complete_analysis(
+    save_chart=True,  # 保存图表
+    export_csv=True   # 导出CSV
+)
+```
+
+## 输出示例
+
+### 控制台输出
+```
+==========================================
+详细分析结果
+==========================================
+
+50ETF 分析结果:
+------------------------------
+MA5:
+  平均距离: 0.0234 元
+  距离标准差: 0.0456 元
+  平均百分比距离: 1.23%
+  高于均线次数: 6
+  低于均线次数: 4
+  高于均线比例: 60.0%
+
+MA10:
+  平均距离: 0.0156 元
+  ...
+```
+
+### 图表输出
+- 多子图显示每个ETF的距离趋势
+- 包含统计信息文本框
+- 支持保存为PNG文件
+
+### CSV数据格式
+| 年月 | close | MA5 | MA10 | MA20 | Distance_MA5 | Distance_MA5_Pct | ... |
+|------|-------|-----|------|------|--------------|------------------|-----|
+| 2024-01 | 2.845 | 2.834 | 2.821 | 2.808 | 0.011 | 0.39% | ... |
+| 2024-02 | 2.892 | 2.856 | 2.843 | 2.835 | 0.036 | 1.26% | ... |
+
+## 配置参数
+
+### 时间范围
+```python
+analyzer = ETFMonthlyMAAnalysis(
+    start_date='2024-01-01',  # 开始日期
+    end_date='2024-12-31'     # 结束日期
+)
+```
+
+### 移动平均线周期
+可在类初始化时修改 `self.ma_periods = [5, 10, 20]`
+
+### ETF符号
+可在类初始化时修改 `self.etf_symbols` 字典
+
+## 技术原理
+
+### 数据获取
+- 使用聚宽的 `get_price()` 函数获取ETF日线数据
+- 包含开盘价、最高价、最低价、收盘价、成交量
+
+### 月线转换
+- 使用 pandas 的 `resample('M')` 方法
+- 月开盘价:取月内第一个交易日开盘价
+- 月收盘价:取月内最后一个交易日收盘价
+- 月最高/最低价:取月内极值
+
+### 距离计算
+- **绝对距离**:`距离 = 月收盘价 - 移动平均线`
+- **百分比距离**:`百分比距离 = (月收盘价 - 移动平均线) / 移动平均线 × 100%`
+
+### 统计分析
+- 计算距离的均值、标准差、最大值、最小值
+- 统计正向/负向距离的次数
+- 计算高于均线的比例
+
+## 注意事项
+
+1. **环境要求**:需要在聚宽(JoinQuant)环境中运行
+2. **数据依赖**:依赖聚宽的 `jqdata` 包和 `get_price()` 函数
+3. **时间范围**:建议选择至少6个月以上的时间范围以获得有意义的分析结果
+4. **市场假期**:工具会自动处理交易日历,跳过市场假期
+
+## 应用场景
+
+1. **趋势分析**:观察ETF价格相对于均线的长期趋势
+2. **买卖时机**:通过距离分析判断潜在的买卖机会
+3. **风险评估**:通过距离的波动性评估风险
+4. **策略回测**:为期权策略提供基础的技术分析数据
+
+## 扩展功能
+
+可以基于此工具进一步开发:
+- 添加更多技术指标(RSI、MACD等)
+- 增加更多均线周期
+- 添加自动化交易信号生成
+- 集成到现有的期权策略中
+
+## 版本历史
+
+- v1.0 - 初始版本,基于深度实值牛差策略的ETF获取逻辑

+ 426 - 0
Lib/Options/etf_monthly_ma_analysis.py

@@ -0,0 +1,426 @@
+# ETF月线级别均线分析工具
+# 功能:
+# 1. 获取50ETF、300ETF、创业板ETF的价格数据
+# 2. 计算月线级别的5、10、20日均线
+# 3. 计算当月收盘价与各均线的距离
+# 4. 生成可视化图表和数据导出
+
+import jqdata
+from jqdata import *
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import datetime
+from typing import Dict, List, Tuple
+
+# 设置中文字体
+plt.rcParams['font.sans-serif'] = ['SimHei']
+plt.rcParams['axes.unicode_minus'] = False
+
+class ETFMonthlyMAAnalysis:
+    """ETF月线级别均线分析器"""
+    
+    def __init__(self, start_date='2024-01-01', end_date='2024-12-31'):
+        """
+        初始化分析器
+        :param start_date: 开始日期
+        :param end_date: 结束日期
+        """
+        self.start_date = start_date
+        self.end_date = end_date
+        
+        # ETF配置 - 基于现有策略的符号定义
+        self.etf_symbols = {
+            '50ETF': '510050.XSHG',      # 上证50ETF
+            '300ETF': '510300.XSHG',     # 沪深300ETF (用户提到的500ETF应该是指这个)
+            '创业板ETF': '159915.XSHE'    # 创业板ETF
+        }
+        
+        # 移动平均线配置
+        self.ma_periods = [5, 10, 20]  # 5日、10日、20日均线
+        
+        # 存储数据
+        self.price_data = {}
+        self.monthly_data = {}
+        self.analysis_results = {}
+        
+        print(f"初始化ETF月线分析器")
+        print(f"分析时间范围: {start_date} 到 {end_date}")
+        print(f"分析ETF: {list(self.etf_symbols.keys())}")
+        print(f"均线周期: {self.ma_periods}日")
+    
+    def fetch_etf_prices(self) -> Dict[str, pd.DataFrame]:
+        """
+        获取所有ETF的价格数据
+        返回: {etf_name: price_dataframe}
+        """
+        print(f"\n开始获取ETF价格数据...")
+        
+        for etf_name, symbol in self.etf_symbols.items():
+            try:
+                print(f"  获取{etf_name}({symbol})价格数据...")
+                
+                # 使用现有策略的get_price方法获取数据
+                price_data = get_price(
+                    symbol, 
+                    start_date=self.start_date, 
+                    end_date=self.end_date, 
+                    fields=['close', 'open', 'high', 'low', 'volume'],
+                    frequency='daily'
+                )
+                
+                if price_data.empty:
+                    print(f"    警告: {etf_name}无价格数据")
+                    continue
+                
+                # 确保索引为日期格式
+                if not isinstance(price_data.index, pd.DatetimeIndex):
+                    price_data.index = pd.to_datetime(price_data.index)
+                
+                self.price_data[etf_name] = price_data
+                
+                print(f"    成功获取{etf_name}数据: {len(price_data)}条记录")
+                print(f"    数据范围: {price_data.index[0].strftime('%Y-%m-%d')} 到 {price_data.index[-1].strftime('%Y-%m-%d')}")
+                print(f"    价格范围: {price_data['close'].min():.3f} - {price_data['close'].max():.3f}")
+                
+            except Exception as e:
+                print(f"    错误: 获取{etf_name}价格数据失败 - {e}")
+                continue
+        
+        print(f"\nETF价格数据获取完成,成功获取{len(self.price_data)}个ETF的数据")
+        return self.price_data
+    
+    def calculate_monthly_ma(self) -> Dict[str, pd.DataFrame]:
+        """
+        计算月线级别的移动平均线
+        返回: {etf_name: monthly_data_with_ma}
+        """
+        print(f"\n开始计算月线级别的移动平均线...")
+        
+        for etf_name, price_df in self.price_data.items():
+            try:
+                print(f"  处理{etf_name}的月线数据...")
+                
+                # 转换为月线数据 - 取每月最后一个交易日的数据
+                monthly_df = price_df.resample('M').agg({
+                    'open': 'first',    # 月初开盘价
+                    'high': 'max',      # 月内最高价
+                    'low': 'min',       # 月内最低价
+                    'close': 'last',    # 月末收盘价
+                    'volume': 'sum'     # 月成交量
+                })
+                
+                # 计算移动平均线
+                for period in self.ma_periods:
+                    ma_column = f'MA{period}'
+                    monthly_df[ma_column] = monthly_df['close'].rolling(window=period).mean()
+                    print(f"    计算{period}日均线完成")
+                
+                # 计算当月收盘价与各均线的距离
+                for period in self.ma_periods:
+                    ma_column = f'MA{period}'
+                    distance_column = f'Distance_MA{period}'
+                    monthly_df[distance_column] = monthly_df['close'] - monthly_df[ma_column]
+                    
+                    # 计算距离的百分比
+                    pct_distance_column = f'Distance_MA{period}_Pct'
+                    monthly_df[pct_distance_column] = (monthly_df['close'] - monthly_df[ma_column]) / monthly_df[ma_column] * 100
+                
+                # 添加月份标识
+                monthly_df['年月'] = monthly_df.index.strftime('%Y-%m')
+                
+                self.monthly_data[etf_name] = monthly_df
+                
+                print(f"    {etf_name}月线数据计算完成: {len(monthly_df)}个月")
+                
+                # 显示最近几个月的数据示例
+                if len(monthly_df) > 0:
+                    latest_data = monthly_df.tail(3)
+                    print(f"    最近数据示例:")
+                    for idx, row in latest_data.iterrows():
+                        month_str = idx.strftime('%Y-%m')
+                        close_price = row['close']
+                        ma5_dist = row.get('Distance_MA5', np.nan)
+                        ma10_dist = row.get('Distance_MA10', np.nan)
+                        ma20_dist = row.get('Distance_MA20', np.nan)
+                        print(f"      {month_str}: 收盘{close_price:.3f}, 距MA5={ma5_dist:.3f}, 距MA10={ma10_dist:.3f}, 距MA20={ma20_dist:.3f}")
+                
+            except Exception as e:
+                print(f"    错误: 处理{etf_name}月线数据失败 - {e}")
+                continue
+        
+        print(f"\n月线移动平均线计算完成,处理了{len(self.monthly_data)}个ETF")
+        return self.monthly_data
+    
+    def analyze_distance_statistics(self) -> Dict[str, Dict]:
+        """
+        分析距离统计信息
+        返回: {etf_name: statistics}
+        """
+        print(f"\n开始分析距离统计信息...")
+        
+        for etf_name, monthly_df in self.monthly_data.items():
+            try:
+                print(f"  分析{etf_name}的距离统计...")
+                
+                stats = {}
+                
+                # 对每个均线周期进行统计
+                for period in self.ma_periods:
+                    distance_col = f'Distance_MA{period}'
+                    pct_distance_col = f'Distance_MA{period}_Pct'
+                    
+                    if distance_col in monthly_df.columns:
+                        distance_data = monthly_df[distance_col].dropna()
+                        pct_distance_data = monthly_df[pct_distance_col].dropna()
+                        
+                        if len(distance_data) > 0:
+                            stats[f'MA{period}'] = {
+                                '绝对距离_均值': distance_data.mean(),
+                                '绝对距离_标准差': distance_data.std(),
+                                '绝对距离_最大值': distance_data.max(),
+                                '绝对距离_最小值': distance_data.min(),
+                                '百分比距离_均值': pct_distance_data.mean(),
+                                '百分比距离_标准差': pct_distance_data.std(),
+                                '百分比距离_最大值': pct_distance_data.max(),
+                                '百分比距离_最小值': pct_distance_data.min(),
+                                '正向距离次数': (distance_data > 0).sum(),
+                                '负向距离次数': (distance_data < 0).sum(),
+                                '数据点总数': len(distance_data)
+                            }
+                            
+                            print(f"    MA{period}统计: 均值={stats[f'MA{period}']['绝对距离_均值']:.3f}, "
+                                  f"正向{stats[f'MA{period}']['正向距离次数']}次, "
+                                  f"负向{stats[f'MA{period}']['负向距离次数']}次")
+                
+                self.analysis_results[etf_name] = stats
+                
+            except Exception as e:
+                print(f"    错误: 分析{etf_name}距离统计失败 - {e}")
+                continue
+        
+        print(f"\n距离统计分析完成")
+        return self.analysis_results
+    
+    def plot_distance_chart(self, save_path: str = None):
+        """
+        绘制距离图表
+        :param save_path: 保存路径,如果为None则直接显示
+        """
+        print(f"\n开始绘制距离图表...")
+        
+        # 设置图表布局:每个ETF一个子图
+        n_etfs = len(self.monthly_data)
+        if n_etfs == 0:
+            print("没有数据可以绘制")
+            return
+        
+        fig, axes = plt.subplots(n_etfs, 1, figsize=(14, 6 * n_etfs))
+        if n_etfs == 1:
+            axes = [axes]  # 确保axes是列表
+        
+        # 颜色配置
+        colors = ['red', 'blue', 'green']
+        ma_labels = [f'距MA{period}日线' for period in self.ma_periods]
+        
+        for idx, (etf_name, monthly_df) in enumerate(self.monthly_data.items()):
+            ax = axes[idx]
+            
+            # 准备数据
+            months = monthly_df.index
+            month_labels = [dt.strftime('%Y-%m') for dt in months]
+            
+            # 绘制距离线
+            for i, period in enumerate(self.ma_periods):
+                distance_col = f'Distance_MA{period}'
+                if distance_col in monthly_df.columns:
+                    distance_data = monthly_df[distance_col]
+                    ax.plot(months, distance_data, 
+                           label=ma_labels[i], 
+                           color=colors[i], 
+                           linewidth=2, 
+                           marker='o', 
+                           markersize=4)
+            
+            # 添加零线
+            ax.axhline(y=0, color='black', linestyle='--', alpha=0.5, linewidth=1)
+            
+            # 设置标题和标签
+            ax.set_title(f'{etf_name} 月收盘价与移动平均线距离', fontsize=14, fontweight='bold')
+            ax.set_xlabel('月份', fontsize=12)
+            ax.set_ylabel('距离 (元)', fontsize=12)
+            ax.legend(fontsize=10)
+            ax.grid(True, alpha=0.3)
+            
+            # 设置x轴标签
+            if len(months) > 12:
+                # 如果月份太多,只显示部分标签
+                step = max(1, len(months) // 12)
+                ax.set_xticks(months[::step])
+                ax.set_xticklabels([dt.strftime('%Y-%m') for dt in months[::step]], rotation=45)
+            else:
+                ax.set_xticklabels(month_labels, rotation=45)
+            
+            # 添加统计信息文本
+            if etf_name in self.analysis_results:
+                stats_text = []
+                for period in self.ma_periods:
+                    ma_key = f'MA{period}'
+                    if ma_key in self.analysis_results[etf_name]:
+                        mean_dist = self.analysis_results[etf_name][ma_key]['绝对距离_均值']
+                        positive_count = self.analysis_results[etf_name][ma_key]['正向距离次数']
+                        negative_count = self.analysis_results[etf_name][ma_key]['负向距离次数']
+                        stats_text.append(f'MA{period}: 均值{mean_dist:.3f}, 正{positive_count}负{negative_count}')
+                
+                if stats_text:
+                    ax.text(0.02, 0.98, '\n'.join(stats_text), 
+                           transform=ax.transAxes, 
+                           fontsize=9, 
+                           verticalalignment='top',
+                           bbox=dict(boxstyle='round,pad=0.3', facecolor='lightgray', alpha=0.8))
+        
+        plt.tight_layout()
+        
+        if save_path:
+            plt.savefig(save_path, dpi=300, bbox_inches='tight')
+            print(f"图表已保存到: {save_path}")
+        else:
+            plt.show()
+        
+        print("距离图表绘制完成")
+    
+    def export_results_to_csv(self, output_dir: str = "./"):
+        """
+        导出结果到CSV文件
+        :param output_dir: 输出目录
+        """
+        print(f"\n开始导出结果到CSV文件...")
+        
+        for etf_name, monthly_df in self.monthly_data.items():
+            try:
+                # 准备导出数据
+                export_df = monthly_df.copy()
+                
+                # 重新排列列的顺序,把重要信息放在前面
+                column_order = ['年月', 'close']
+                
+                # 添加均线列
+                for period in self.ma_periods:
+                    column_order.append(f'MA{period}')
+                
+                # 添加距离列
+                for period in self.ma_periods:
+                    column_order.append(f'Distance_MA{period}')
+                    column_order.append(f'Distance_MA{period}_Pct')
+                
+                # 添加其他列
+                other_columns = [col for col in export_df.columns if col not in column_order]
+                column_order.extend(other_columns)
+                
+                # 重新排序并导出
+                export_df = export_df.reindex(columns=column_order)
+                
+                # 构建文件名
+                filename = f"{output_dir}/ETF_月线分析_{etf_name}_{self.start_date}_{self.end_date}.csv"
+                export_df.to_csv(filename, encoding='utf-8-sig', index=True)
+                
+                print(f"  {etf_name}数据已导出到: {filename}")
+                
+            except Exception as e:
+                print(f"  错误: 导出{etf_name}数据失败 - {e}")
+        
+        print("CSV导出完成")
+    
+    def run_complete_analysis(self, save_chart: bool = True, export_csv: bool = True):
+        """
+        运行完整的分析流程
+        :param save_chart: 是否保存图表
+        :param export_csv: 是否导出CSV
+        """
+        print("="*60)
+        print("开始ETF月线级别均线距离分析")
+        print("="*60)
+        
+        try:
+            # 1. 获取ETF价格数据
+            print(f"\n步骤1: 获取ETF价格数据")
+            self.fetch_etf_prices()
+            
+            if not self.price_data:
+                print("错误: 没有成功获取任何ETF价格数据")
+                return
+            
+            # 2. 计算月线均线
+            print(f"\n步骤2: 计算月线级别移动平均线")
+            self.calculate_monthly_ma()
+            
+            if not self.monthly_data:
+                print("错误: 没有成功计算任何月线数据")
+                return
+            
+            # 3. 分析距离统计
+            print(f"\n步骤3: 分析距离统计信息")
+            self.analyze_distance_statistics()
+            
+            # 4. 绘制图表
+            print(f"\n步骤4: 绘制距离图表")
+            if save_chart:
+                chart_filename = f"ETF月线距离分析_{self.start_date}_{self.end_date}.png"
+                self.plot_distance_chart(save_path=chart_filename)
+            else:
+                self.plot_distance_chart()
+            
+            # 5. 导出CSV
+            if export_csv:
+                print(f"\n步骤5: 导出分析结果")
+                self.export_results_to_csv()
+            
+            # 6. 输出总结
+            print(f"\n" + "="*60)
+            print("分析总结")
+            print("="*60)
+            
+            for etf_name, stats in self.analysis_results.items():
+                print(f"\n{etf_name}分析结果:")
+                for ma_period, stat_data in stats.items():
+                    print(f"  {ma_period}:")
+                    print(f"    平均距离: {stat_data['绝对距离_均值']:.3f} 元")
+                    print(f"    平均距离百分比: {stat_data['百分比距离_均值']:.2f}%")
+                    print(f"    正向/负向比例: {stat_data['正向距离次数']}/{stat_data['负向距离次数']}")
+            
+            print(f"\n分析完成! 总共处理了{len(self.monthly_data)}个ETF,{sum(len(df) for df in self.monthly_data.values())}个月度数据点")
+            
+        except Exception as e:
+            print(f"分析过程中发生错误: {e}")
+            import traceback
+            traceback.print_exc()
+
+# =============================================================================
+# 直接运行分析 - 可以修改下面的参数来自定义分析
+# =============================================================================
+
+print("="*60)
+print("ETF月线级别均线距离分析工具")
+print("="*60)
+print("功能:分析ETF价格与5、10、20日均线的距离关系")
+print("ETF标的:50ETF、300ETF、创业板ETF")
+print()
+
+# 分析参数配置(可根据需要修改)
+START_DATE = '2022-01-01'  # 开始日期
+END_DATE = '2024-12-31'    # 结束日期
+
+print(f"分析时间范围: {START_DATE} 到 {END_DATE}")
+print()
+
+# 创建分析器实例
+analyzer = ETFMonthlyMAAnalysis(
+    start_date=START_DATE, 
+    end_date=END_DATE
+)
+
+# 运行完整分析
+analyzer.run_complete_analysis(
+    save_chart=False,  # 保存图表到文件
+    export_csv=False   # 导出CSV数据
+)

+ 5 - 0
Lib/fund/README.md

@@ -211,3 +211,8 @@
 - 保持统一的缩进和列表样式
 - 使用标题区分不同层级的内容
 
+## Kmeans
+https://www.joinquant.com/algorithm/index/edit?algorithmId=efadbad9b1c9b57c556faca968310a6f&backtest=0
+Lib/Options/etf_monthly_ma_analysis.py
+
+配套研究:https://www.joinquant.com/research?target=research&url=/user/75474983526/notebooks/Funds/Kmeans.ipynb

+ 108 - 0
Lib/fund/kmeans.py

@@ -0,0 +1,108 @@
+# 克隆自聚宽文章:https://www.joinquant.com/post/61157
+# 标题:致敬市场(13)——K-means 指数增强
+# 作者:Gyro^.^
+
+import numpy as np
+import pandas as pd
+import datetime as dt
+from sklearn.cluster import KMeans
+
+def initialize(context):
+    # setting system
+    log.set_level('order', 'error')
+    set_option('use_real_price', True)
+    set_option('avoid_future_data', True)
+    run_monthly(iUpdate, 1, 'before_open')
+    run_daily(iTrader, 'every_bar')
+    run_daily(iReport, 'after_close')
+
+def iUpdate(context):
+    # paramers
+    nday = 243
+    n_cluster = 24
+    n_class = 5
+    # all funds
+    dt_now = context.current_dt.date()
+    all_fund = get_all_securities('fund', dt_now)
+    # onlist 1-year
+    dt_1y = dt_now - dt.timedelta(days=365)
+    funds = all_fund[all_fund.start_date < dt_1y].index.tolist()
+    # filter, liquity
+    hm = history(nday, '1d', 'money', funds).mean()
+    funds = hm[hm > 1e6].index.tolist()
+    # history return
+    h = history(nday, '1d', 'close', funds).dropna(axis=1)
+    r = np.log(h).diff()[1:]
+    # annual return
+    ar = 100*nday*r.mean()
+    # K-means
+    cluster = KMeans(n_clusters=n_cluster).fit(r.T)
+    # labels
+    c = pd.Series(cluster.labels_, r.columns)
+    # choice
+    df = pd.DataFrame(columns=['Cluster', 'Return', 'Name'])
+    for k in range(n_cluster):
+        # class
+        choice = []
+        for s in c.index:
+            if c[s] == k:
+                choice.append(s) 
+        log.info(k, len(choice))
+        # high return
+        cr = ar[choice].sort_values(ascending=False).head(n_class)
+        for s in cr.index:
+            if s in context.portfolio.positions:
+                df.loc[s] = [k, cr[s], get_security_info(s).display_name]
+                break
+        else:
+            s = cr.index[0]
+            df.loc[s] = [k, cr[s], get_security_info(s).display_name]
+    # report
+    pd.set_option('display.max_rows', None)
+    log.info('Funds', len(df), '\n', df)
+    # results
+    g.funds = df.index.tolist()
+    g.position_size = 0.95/n_cluster * context.portfolio.total_value
+
+def iTrader(context):
+    # load data
+    choice  = g.funds
+    position_size = g.position_size
+    lm_value = 0.7*position_size
+    hm_value = 1.5*position_size
+    cash_size = 0.05*context.portfolio.total_value
+    cdata = get_current_data()
+    # sell
+    for s in context.portfolio.positions:
+        if cdata[s].paused or \
+            cdata[s].last_price >= cdata[s].high_limit or cdata[s].last_price <= cdata[s].low_limit:
+            continue # 过滤三停
+        if s not in choice:
+            log.info('sell', s, cdata[s].name)
+            order_target(s, 0, MarketOrderStyle(0.99*cdata[s].last_price))
+    # buy
+    for s in choice:
+        if cdata[s].paused or \
+            cdata[s].last_price >= cdata[s].high_limit or cdata[s].last_price <= cdata[s].low_limit:
+            continue # 过滤三停
+        if context.portfolio.available_cash < cash_size:
+            break # 现金耗尽
+        if s not in context.portfolio.positions:
+            log.info('buy', s, cdata[s].name)
+            order_target_value(s, position_size, MarketOrderStyle(1.01*cdata[s].last_price))
+
+def iReport(context):
+    # table of positions
+    ptable = pd.DataFrame(columns=['amount', 'value', 'weight', 'name'])
+    tvalue = context.portfolio.total_value
+    cdata = get_current_data()
+    for s in context.portfolio.positions:
+        ps = context.portfolio.positions[s]
+        ptable.loc[s] = [ps.total_amount, int(ps.value), 100*ps.value/tvalue, cdata[s].name]
+    ptable = ptable.sort_values(by='weight', ascending=False)
+    # report portfolio
+    pd.set_option('display.max_rows', None)
+    log.info('  positions', len(ptable), '\n', ptable.head())
+    log.info('  total value %.2f, cash %.2f', \
+        context.portfolio.total_value/10000, context.portfolio.available_cash/10000)
+# end