{ "cells": [ { "cell_type": "code", "execution_count": 1, "id": "79a7758178bafdd3", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:46:06.987506Z", "start_time": "2025-04-03T12:46:06.259551Z" }, "jupyter": { "source_hidden": true } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "/mnt/d/PyProject/NewStock\n" ] } ], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", "\n", "import gc\n", "import os\n", "import sys\n", "sys.path.append('/mnt/d/PyProject/NewStock/')\n", "print(os.getcwd())\n", "import pandas as pd\n", "from main.factor.factor import get_rolling_factor, get_simple_factor\n", "from main.utils.factor import read_industry_data\n", "from main.utils.factor_processor import calculate_score\n", "from main.utils.utils import read_and_merge_h5_data, merge_with_industry_data\n", "\n", "import warnings\n", "\n", "warnings.filterwarnings(\"ignore\")\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "4a481c60", "metadata": {}, "outputs": [], "source": [ "# 设置使用核心\n", "import os\n", "os.environ[\"MODIN_CPUS\"] = \"4\"\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "a79cafb06a7e0e43", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:47:00.212859Z", "start_time": "2025-04-03T12:46:06.998047Z" }, "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "daily data\n", "daily basic\n", "inner merge on ['ts_code', 'trade_date']\n", "stk limit\n", "left merge on ['ts_code', 'trade_date']\n", "money flow\n", "left merge on ['ts_code', 'trade_date']\n", "cyq perf\n", "left merge on ['ts_code', 'trade_date']\n", "\n", "RangeIndex: 8692146 entries, 0 to 8692145\n", "Data columns (total 33 columns):\n", " # Column Dtype \n", "--- ------ ----- \n", " 0 ts_code object \n", " 1 trade_date datetime64[ns]\n", " 2 open float64 \n", " 3 close float64 \n", " 4 high float64 \n", " 5 low float64 \n", " 6 vol float64 \n", " 7 amount float64 \n", " 8 pct_chg float64 \n", " 9 turnover_rate float64 \n", " 10 pe_ttm float64 \n", " 11 circ_mv float64 \n", " 12 total_mv float64 \n", " 13 volume_ratio float64 \n", " 14 is_st bool \n", " 15 up_limit float64 \n", " 16 down_limit float64 \n", " 17 buy_sm_vol float64 \n", " 18 sell_sm_vol float64 \n", " 19 buy_lg_vol float64 \n", " 20 sell_lg_vol float64 \n", " 21 buy_elg_vol float64 \n", " 22 sell_elg_vol float64 \n", " 23 net_mf_vol float64 \n", " 24 his_low float64 \n", " 25 his_high float64 \n", " 26 cost_5pct float64 \n", " 27 cost_15pct float64 \n", " 28 cost_50pct float64 \n", " 29 cost_85pct float64 \n", " 30 cost_95pct float64 \n", " 31 weight_avg float64 \n", " 32 winner_rate float64 \n", "dtypes: bool(1), datetime64[ns](1), float64(30), object(1)\n", "memory usage: 2.1+ GB\n", "None\n" ] } ], "source": [ "from main.utils.utils import read_and_merge_h5_data\n", "\n", "print('daily data')\n", "df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/daily_data.h5', key='daily_data',\n", " columns=['ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'vol', 'amount', 'pct_chg'],\n", " df=None)\n", "\n", "print('daily basic')\n", "df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/daily_basic.h5', key='daily_basic',\n", " columns=['ts_code', 'trade_date', 'turnover_rate', 'pe_ttm', 'circ_mv', 'total_mv', 'volume_ratio',\n", " 'is_st'], df=df, join='inner')\n", "\n", "print('stk limit')\n", "df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/stk_limit.h5', key='stk_limit',\n", " columns=['ts_code', 'trade_date', 'pre_close', 'up_limit', 'down_limit'],\n", " df=df)\n", "print('money flow')\n", "df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/money_flow.h5', key='money_flow',\n", " columns=['ts_code', 'trade_date', 'buy_sm_vol', 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol',\n", " 'buy_elg_vol', 'sell_elg_vol', 'net_mf_vol'],\n", " df=df)\n", "print('cyq perf')\n", "df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/cyq_perf.h5', key='cyq_perf',\n", " columns=['ts_code', 'trade_date', 'his_low', 'his_high', 'cost_5pct', 'cost_15pct',\n", " 'cost_50pct',\n", " 'cost_85pct', 'cost_95pct', 'weight_avg', 'winner_rate'],\n", " df=df)\n", "print(df.info())" ] }, { "cell_type": "code", "execution_count": 4, "id": "cac01788dac10678", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:47:10.527104Z", "start_time": "2025-04-03T12:47:00.488715Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "industry\n" ] } ], "source": [ "print('industry')\n", "industry_df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/industry_data.h5', key='industry_data',\n", " columns=['ts_code', 'l2_code', 'in_date'],\n", " df=None, on=['ts_code'], join='left')\n", "\n", "\n", "def merge_with_industry_data(df, industry_df):\n", " # 确保日期字段是 datetime 类型\n", " df['trade_date'] = pd.to_datetime(df['trade_date'])\n", " industry_df['in_date'] = pd.to_datetime(industry_df['in_date'])\n", "\n", " # 对 industry_df 按 ts_code 和 in_date 排序\n", " industry_df_sorted = industry_df.sort_values(['in_date', 'ts_code'])\n", "\n", " # 对原始 df 按 ts_code 和 trade_date 排序\n", " df_sorted = df.sort_values(['trade_date', 'ts_code'])\n", "\n", " # 使用 merge_asof 进行向后合并\n", " merged = pd.merge_asof(\n", " df_sorted,\n", " industry_df_sorted,\n", " by='ts_code', # 按 ts_code 分组\n", " left_on='trade_date',\n", " right_on='in_date',\n", " direction='backward'\n", " )\n", "\n", " # 获取每个 ts_code 的最早 in_date 记录\n", " min_in_date_per_ts = (industry_df_sorted\n", " .groupby('ts_code')\n", " .first()\n", " .reset_index()[['ts_code', 'l2_code']])\n", "\n", " # 填充未匹配到的记录(trade_date 早于所有 in_date 的情况)\n", " merged['l2_code'] = merged['l2_code'].fillna(\n", " merged['ts_code'].map(min_in_date_per_ts.set_index('ts_code')['l2_code'])\n", " )\n", "\n", " # 保留需要的列并重置索引\n", " result = merged.reset_index(drop=True)\n", " return result\n", "\n", "\n", "# 使用示例\n", "df = merge_with_industry_data(df, industry_df)\n", "# print(mdf[mdf['ts_code'] == '600751.SH'][['ts_code', 'trade_date', 'l2_code']])" ] }, { "cell_type": "code", "execution_count": 5, "id": "c4e9e1d31da6dba6", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:47:10.719252Z", "start_time": "2025-04-03T12:47:10.541247Z" }, "jupyter": { "source_hidden": true } }, "outputs": [], "source": [ "from main.factor.factor import *\n", "\n", "def calculate_indicators(df):\n", " \"\"\"\n", " 计算四个指标:当日涨跌幅、5日移动平均、RSI、MACD。\n", " \"\"\"\n", " df = df.sort_values('trade_date')\n", " df['daily_return'] = (df['close'] - df['pre_close']) / df['pre_close'] * 100\n", " # df['5_day_ma'] = df['close'].rolling(window=5).mean()\n", " delta = df['close'].diff()\n", " gain = delta.where(delta > 0, 0)\n", " loss = -delta.where(delta < 0, 0)\n", " avg_gain = gain.rolling(window=14).mean()\n", " avg_loss = loss.rolling(window=14).mean()\n", " rs = avg_gain / avg_loss\n", " df['RSI'] = 100 - (100 / (1 + rs))\n", "\n", " # 计算MACD\n", " ema12 = df['close'].ewm(span=12, adjust=False).mean()\n", " ema26 = df['close'].ewm(span=26, adjust=False).mean()\n", " df['MACD'] = ema12 - ema26\n", " df['Signal_line'] = df['MACD'].ewm(span=9, adjust=False).mean()\n", " df['MACD_hist'] = df['MACD'] - df['Signal_line']\n", "\n", " # 4. 情绪因子1:市场上涨比例(Up Ratio)\n", " df['up_ratio'] = df['daily_return'].apply(lambda x: 1 if x > 0 else 0)\n", " df['up_ratio_20d'] = df['up_ratio'].rolling(window=20).mean() # 过去20天上涨比例\n", "\n", " # 5. 情绪因子2:成交量变化率(Volume Change Rate)\n", " df['volume_mean'] = df['vol'].rolling(window=20).mean() # 过去20天的平均成交量\n", " df['volume_change_rate'] = (df['vol'] - df['volume_mean']) / df['volume_mean'] * 100 # 成交量变化率\n", "\n", " # 6. 情绪因子3:波动率(Volatility)\n", " df['volatility'] = df['daily_return'].rolling(window=20).std() # 过去20天的日收益率标准差\n", "\n", " # 7. 情绪因子4:成交额变化率(Amount Change Rate)\n", " df['amount_mean'] = df['amount'].rolling(window=20).mean() # 过去20天的平均成交额\n", " df['amount_change_rate'] = (df['amount'] - df['amount_mean']) / df['amount_mean'] * 100 # 成交额变化率\n", "\n", " # df = sentiment_panic_greed_index(df)\n", " # df = sentiment_market_breadth_proxy(df)\n", " # df = sentiment_reversal_indicator(df)\n", "\n", " return df\n", "\n", "\n", "def generate_index_indicators(h5_filename):\n", " df = pd.read_hdf(h5_filename, key='index_data')\n", " df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')\n", " df = df.sort_values('trade_date')\n", "\n", " # 计算每个ts_code的相关指标\n", " df_indicators = []\n", " for ts_code in df['ts_code'].unique():\n", " df_index = df[df['ts_code'] == ts_code].copy()\n", " df_index = calculate_indicators(df_index)\n", " df_indicators.append(df_index)\n", "\n", " # 合并所有指数的结果\n", " df_all_indicators = pd.concat(df_indicators, ignore_index=True)\n", "\n", " # 保留trade_date列,并将同一天的数据按ts_code合并成一行\n", " df_final = df_all_indicators.pivot_table(\n", " index='trade_date',\n", " columns='ts_code',\n", " values=['daily_return', \n", " 'RSI', 'MACD', 'Signal_line', 'MACD_hist', \n", " # 'sentiment_panic_greed_index',\n", " 'up_ratio_20d', 'volume_change_rate', 'volatility',\n", " 'amount_change_rate', 'amount_mean'],\n", " aggfunc='last'\n", " )\n", "\n", " df_final.columns = [f\"{col[1]}_{col[0]}\" for col in df_final.columns]\n", " df_final = df_final.reset_index()\n", "\n", " return df_final\n", "\n", "\n", "# 使用函数\n", "h5_filename = '/mnt/d/PyProject/NewStock/data/index_data.h5'\n", "index_data = generate_index_indicators(h5_filename)\n", "index_data = index_data.dropna()\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "a735bc02ceb4d872", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:47:10.821169Z", "start_time": "2025-04-03T12:47:10.751831Z" } }, "outputs": [], "source": [ "import talib\n", "import numpy as np" ] }, { "cell_type": "code", "execution_count": 7, "id": "53f86ddc0677a6d7", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:47:15.944254Z", "start_time": "2025-04-03T12:47:10.826179Z" }, "jupyter": { "source_hidden": true }, "scrolled": true }, "outputs": [], "source": [ "from main.utils.factor import get_act_factor\n", "\n", "\n", "def read_industry_data(h5_filename):\n", " # 读取 H5 文件中所有的行业数据\n", " industry_data = pd.read_hdf(h5_filename, key='sw_daily', columns=[\n", " 'ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'pe', 'pb', 'vol'\n", " ]) # 假设 H5 文件的键是 'industry_data'\n", " industry_data = industry_data.sort_values(by=['ts_code', 'trade_date'])\n", " industry_data = industry_data.reindex()\n", " industry_data['trade_date'] = pd.to_datetime(industry_data['trade_date'], format='%Y%m%d')\n", "\n", " grouped = industry_data.groupby('ts_code', group_keys=False)\n", " industry_data['obv'] = grouped.apply(\n", " lambda x: pd.Series(talib.OBV(x['close'].values, x['vol'].values), index=x.index)\n", " )\n", " industry_data['return_5'] = grouped['close'].apply(lambda x: x / x.shift(5) - 1)\n", " industry_data['return_20'] = grouped['close'].apply(lambda x: x / x.shift(20) - 1)\n", "\n", " industry_data = get_act_factor(industry_data, cat=False)\n", " industry_data = industry_data.sort_values(by=['trade_date', 'ts_code'])\n", "\n", " # # 计算每天每个 ts_code 的因子和当天所有 ts_code 的中位数的偏差\n", " # factor_columns = ['obv', 'return_5', 'return_20', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4'] # 因子列\n", " # \n", " # for factor in factor_columns:\n", " # if factor in industry_data.columns:\n", " # # 计算每天每个 ts_code 的因子值与当天所有 ts_code 的中位数的偏差\n", " # industry_data[f'{factor}_deviation'] = industry_data.groupby('trade_date')[factor].transform(\n", " # lambda x: x - x.mean())\n", "\n", " industry_data['return_5_percentile'] = industry_data.groupby('trade_date')['return_5'].transform(\n", " lambda x: x.rank(pct=True))\n", " industry_data['return_20_percentile'] = industry_data.groupby('trade_date')['return_20'].transform(\n", " lambda x: x.rank(pct=True))\n", "\n", " # cs_rank_intraday_range(industry_data)\n", " # cs_rank_close_pos_in_range(industry_data)\n", "\n", " industry_data = industry_data.drop(columns=['open', 'close', 'high', 'low', 'pe', 'pb', 'vol'])\n", "\n", " industry_data = industry_data.rename(\n", " columns={col: f'industry_{col}' for col in industry_data.columns if col not in ['ts_code', 'trade_date']})\n", "\n", " industry_data = industry_data.rename(columns={'ts_code': 'cat_l2_code'})\n", " return industry_data\n", "\n", "\n", "industry_df = read_industry_data('/mnt/d/PyProject/NewStock/data/sw_daily.h5')\n" ] }, { "cell_type": "code", "execution_count": 8, "id": "dbe2fd8021b9417f", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:47:15.969344Z", "start_time": "2025-04-03T12:47:15.963327Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['ts_code', 'open', 'close', 'high', 'low', 'amount', 'circ_mv', 'total_mv', 'is_st', 'up_limit', 'down_limit', 'buy_sm_vol', 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol', 'buy_elg_vol', 'sell_elg_vol', 'net_mf_vol', 'his_low', 'his_high', 'cost_5pct', 'cost_15pct', 'cost_50pct', 'cost_85pct', 'cost_95pct', 'weight_avg', 'in_date']\n" ] } ], "source": [ "origin_columns = df.columns.tolist()\n", "origin_columns = [col for col in origin_columns if\n", " col not in ['turnover_rate', 'pe_ttm', 'volume_ratio', 'vol', 'pct_chg', 'l2_code', 'winner_rate']]\n", "origin_columns = [col for col in origin_columns if col not in index_data.columns]\n", "origin_columns = [col for col in origin_columns if 'cyq' not in col]\n", "print(origin_columns)" ] }, { "cell_type": "code", "execution_count": 9, "id": "85c3e3d0235ffffa", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T12:47:16.089879Z", "start_time": "2025-04-03T12:47:15.990101Z" } }, "outputs": [], "source": [ "fina_indicator_df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/fina_indicator.h5', key='fina_indicator',\n", " columns=['ts_code', 'ann_date', 'undist_profit_ps', 'ocfps', 'bps', 'roa', 'roe'],\n", " df=None)\n", "cashflow_df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/cashflow.h5', key='cashflow',\n", " columns=['ts_code', 'ann_date', 'n_cashflow_act'],\n", " df=None)\n", "balancesheet_df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/balancesheet.h5', key='balancesheet',\n", " columns=['ts_code', 'ann_date', 'money_cap', 'total_liab'],\n", " df=None)\n", "top_list_df = read_and_merge_h5_data('/mnt/d/PyProject/NewStock/data/top_list.h5', key='top_list',\n", " columns=['ts_code', 'trade_date', 'reason'],\n", " df=None)\n", "\n", "top_list_df = top_list_df.sort_values(by='trade_date', ascending=False).drop_duplicates(subset=['ts_code', 'trade_date'], keep='first').sort_values(by='trade_date')\n" ] }, { "cell_type": "code", "execution_count": 10, "id": "92d84ce15a562ec6", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T13:08:01.612695Z", "start_time": "2025-04-03T12:47:16.121802Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "使用 'ann_date' 作为财务数据生效日期。\n", "警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n", "使用 'ann_date' 作为财务数据生效日期。\n", "警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n", "使用 'ann_date' 作为财务数据生效日期。\n", "警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n", "使用 'ann_date' 作为财务数据生效日期。\n", "警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n", "开始计算因子: AR, BR (原地修改)...\n", "因子 AR, BR 计算成功。\n", "因子 AR, BR 计算流程结束。\n", "使用 'ann_date' 作为财务数据生效日期。\n", "使用 'ann_date' 作为财务数据生效日期。\n", "使用 'ann_date' 作为财务数据生效日期。\n", "使用 'ann_date' 作为财务数据生效日期。\n", "警告: 从 financial_data_subset 中移除了 366 行,因为其 'ts_code' 或 'ann_date' 列存在空值。\n", "计算 BBI...\n", "--- 计算日级别偏离度 (使用 pct_chg) ---\n", "--- 计算日级别动量基准 (使用 pct_chg) ---\n", "日级别动量基准计算完成 (使用 pct_chg)。\n", "日级别偏离度计算完成 (使用 pct_chg)。\n", "--- 计算日级别行业偏离度 (使用 pct_chg 和行业基准) ---\n", "--- 计算日级别行业动量基准 (使用 pct_chg 和 cat_l2_code) ---\n", "错误: 计算日级别行业动量基准需要以下列: ['pct_chg', 'cat_l2_code', 'trade_date', 'ts_code']。\n", "错误: 计算日级别行业偏离度需要以下列: ['pct_chg', 'daily_industry_positive_benchmark', 'daily_industry_negative_benchmark']。请先运行 daily_industry_momentum_benchmark(df)。\n", "Index(['ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'vol',\n", " 'amount', 'pct_chg', 'turnover_rate', 'pe_ttm', 'circ_mv', 'total_mv',\n", " 'volume_ratio', 'is_st', 'up_limit', 'down_limit', 'buy_sm_vol',\n", " 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol', 'buy_elg_vol',\n", " 'sell_elg_vol', 'net_mf_vol', 'his_low', 'his_high', 'cost_5pct',\n", " 'cost_15pct', 'cost_50pct', 'cost_85pct', 'cost_95pct', 'weight_avg',\n", " 'winner_rate', 'l2_code', 'undist_profit_ps', 'ocfps', 'roa', 'roe',\n", " 'AR', 'BR', 'AR_BR', 'log_circ_mv', 'cashflow_to_ev_factor',\n", " 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20',\n", " 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol',\n", " 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'flow_divergence_diff',\n", " 'flow_divergence_ratio', 'total_buy_vol', 'lg_elg_buy_prop',\n", " 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change',\n", " 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness',\n", " 'floating_chip_proxy', 'cost_support_15pct_change',\n", " 'cat_winner_price_zone', 'flow_chip_consistency',\n", " 'profit_taking_vs_absorb', '_is_positive', '_is_negative',\n", " 'cat_is_positive', '_pos_returns', '_neg_returns', '_pos_returns_sq',\n", " '_neg_returns_sq', 'upside_vol', 'downside_vol', 'vol_ratio',\n", " 'return_skew', 'return_kurtosis', 'volume_change_rate',\n", " 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike',\n", " 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike',\n", " 'vol_std_5', 'atr_14', 'atr_6', 'obv'],\n", " dtype='object')\n", "Calculating lg_flow_mom_corr_20_60...\n", "Finished lg_flow_mom_corr_20_60.\n", "Calculating lg_flow_accel...\n", "Finished lg_flow_accel.\n", "Calculating profit_pressure...\n", "Finished profit_pressure.\n", "Calculating underwater_resistance...\n", "Finished underwater_resistance.\n", "Calculating cost_conc_std_20...\n", "Finished cost_conc_std_20.\n", "Calculating profit_decay_20...\n", "Finished profit_decay_20.\n", "Calculating vol_amp_loss_20...\n", "Finished vol_amp_loss_20.\n", "Calculating vol_drop_profit_cnt_5...\n", "Finished vol_drop_profit_cnt_5.\n", "Calculating lg_flow_vol_interact_20...\n", "Finished lg_flow_vol_interact_20.\n", "Calculating cost_break_confirm_cnt_5...\n", "Finished cost_break_confirm_cnt_5.\n", "Calculating atr_norm_channel_pos_14...\n", "Finished atr_norm_channel_pos_14.\n", "Calculating turnover_diff_skew_20...\n", "Finished turnover_diff_skew_20.\n", "Calculating lg_sm_flow_diverge_20...\n", "Finished lg_sm_flow_diverge_20.\n", "Calculating pullback_strong_20_20...\n", "Finished pullback_strong_20_20.\n", "Calculating vol_wgt_hist_pos_20...\n", "Finished vol_wgt_hist_pos_20.\n", "Calculating vol_adj_roc_20...\n", "Finished vol_adj_roc_20.\n", "Calculating cs_rank_net_lg_flow_val...\n", "Finished cs_rank_net_lg_flow_val.\n", "Calculating cs_rank_flow_divergence...\n", "Finished cs_rank_flow_divergence.\n", "Calculating cs_rank_ind_adj_lg_flow...\n", "Finished cs_rank_ind_adj_lg_flow.\n", "Calculating cs_rank_elg_buy_ratio...\n", "Finished cs_rank_elg_buy_ratio.\n", "Calculating cs_rank_rel_profit_margin...\n", "Finished cs_rank_rel_profit_margin.\n", "Calculating cs_rank_cost_breadth...\n", "Finished cs_rank_cost_breadth.\n", "Calculating cs_rank_dist_to_upper_cost...\n", "Finished cs_rank_dist_to_upper_cost.\n", "Calculating cs_rank_winner_rate...\n", "Finished cs_rank_winner_rate.\n", "Calculating cs_rank_intraday_range...\n", "Finished cs_rank_intraday_range.\n", "Calculating cs_rank_close_pos_in_range...\n", "Finished cs_rank_close_pos_in_range.\n", "Calculating cs_rank_opening_gap...\n", "Error calculating cs_rank_opening_gap: Missing 'pre_close' column. Assigning NaN.\n", "Calculating cs_rank_pos_in_hist_range...\n", "Finished cs_rank_pos_in_hist_range.\n", "Calculating cs_rank_vol_x_profit_margin...\n", "Finished cs_rank_vol_x_profit_margin.\n", "Calculating cs_rank_lg_flow_price_concordance...\n", "Finished cs_rank_lg_flow_price_concordance.\n", "Calculating cs_rank_turnover_per_winner...\n", "Finished cs_rank_turnover_per_winner.\n", "Calculating cs_rank_ind_cap_neutral_pe (Placeholder - requires statsmodels)...\n", "Finished cs_rank_ind_cap_neutral_pe (Placeholder).\n", "Calculating cs_rank_volume_ratio...\n", "Finished cs_rank_volume_ratio.\n", "Calculating cs_rank_elg_buy_sell_sm_ratio...\n", "Finished cs_rank_elg_buy_sell_sm_ratio.\n", "Calculating cs_rank_cost_dist_vol_ratio...\n", "Finished cs_rank_cost_dist_vol_ratio.\n", "Calculating cs_rank_size...\n", "Finished cs_rank_size.\n", "\n", "RangeIndex: 4554725 entries, 0 to 4554724\n", "Columns: 181 entries, ts_code to cs_rank_size\n", "dtypes: bool(10), datetime64[ns](1), float64(165), int64(3), object(2)\n", "memory usage: 5.8+ GB\n", "None\n", "['ts_code', 'trade_date', 'open', 'close', 'high', 'low', 'vol', 'amount', 'pct_chg', 'turnover_rate', 'pe_ttm', 'circ_mv', 'total_mv', 'volume_ratio', 'is_st', 'up_limit', 'down_limit', 'buy_sm_vol', 'sell_sm_vol', 'buy_lg_vol', 'sell_lg_vol', 'buy_elg_vol', 'sell_elg_vol', 'net_mf_vol', 'his_low', 'his_high', 'cost_5pct', 'cost_15pct', 'cost_50pct', 'cost_85pct', 'cost_95pct', 'weight_avg', 'winner_rate', 'cat_l2_code', 'undist_profit_ps', 'ocfps', 'roa', 'roe', 'AR', 'BR', 'AR_BR', 'log_circ_mv', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'flow_divergence_diff', 'flow_divergence_ratio', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'price_cost_divergence', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_flow_divergence', 'cs_rank_ind_adj_lg_flow', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_opening_gap', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_ind_cap_neutral_pe', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size']\n" ] } ], "source": [ "\n", "import numpy as np\n", "from main.factor.factor import *\n", "\n", "def filter_data(df):\n", " # df = df.groupby('trade_date').apply(lambda x: x.nlargest(1000, 'act_factor1'))\n", " df = df[~df['is_st']]\n", " df = df[~df['ts_code'].str.endswith('BJ')]\n", " df = df[~df['ts_code'].str.startswith('30')]\n", " df = df[~df['ts_code'].str.startswith('68')]\n", " df = df[~df['ts_code'].str.startswith('8')]\n", " df = df[df['trade_date'] >= '2019-01-01']\n", " if 'in_date' in df.columns:\n", " df = df.drop(columns=['in_date'])\n", " df = df.reset_index(drop=True)\n", " return df\n", "\n", "gc.collect()\n", "\n", "df = filter_data(df)\n", "df = df.sort_values(by=['ts_code', 'trade_date'])\n", "\n", "# df = price_minus_deduction_price(df, n=120)\n", "# df = price_deduction_price_diff_ratio_to_sma(df, n=120)\n", "# df = cat_price_vs_sma_vs_deduction_price(df, n=120)\n", "# df = cat_reason(df, top_list_df)\n", "# df = cat_is_on_top_list(df, top_list_df)\n", "\n", "# df = ts_turnover_rate_acceleration_5_20(df)\n", "# df = ts_vol_sustain_10_30(df)\n", "# df = cs_turnover_rate_relative_strength_20(df)\n", "# df = cs_amount_outlier_10(df)\n", "\n", "df = add_financial_factor(df, fina_indicator_df, factor_value_col='undist_profit_ps')\n", "df = add_financial_factor(df, fina_indicator_df, factor_value_col='ocfps')\n", "df = add_financial_factor(df, fina_indicator_df, factor_value_col='roa')\n", "df = add_financial_factor(df, fina_indicator_df, factor_value_col='roe')\n", "\n", "calculate_arbr(df, N=26)\n", "df['log_circ_mv'] = np.log(df['circ_mv'])\n", "df = calculate_cashflow_to_ev_factor(df, cashflow_df, balancesheet_df)\n", "df = caculate_book_to_price_ratio(df, fina_indicator_df)\n", "\n", "df = turnover_rate_n(df, n=5)\n", "df = variance_n(df, n=20)\n", "df = bbi_ratio_factor(df)\n", "df = daily_deviation(df)\n", "df = daily_industry_deviation(df)\n", "df, _ = get_rolling_factor(df)\n", "df, _ = get_simple_factor(df)\n", "\n", "df = df.rename(columns={'l1_code': 'cat_l1_code'})\n", "df = df.rename(columns={'l2_code': 'cat_l2_code'})\n", "\n", "lg_flow_mom_corr(df, N=20, M=60)\n", "lg_flow_accel(df)\n", "profit_pressure(df)\n", "underwater_resistance(df)\n", "cost_conc_std(df, N=20)\n", "profit_decay(df, N=20)\n", "vol_amp_loss(df, N=20)\n", "vol_drop_profit_cnt(df, N=20, M=5)\n", "lg_flow_vol_interact(df, N=20)\n", "cost_break_confirm_cnt(df, M=5)\n", "atr_norm_channel_pos(df, N=14)\n", "turnover_diff_skew(df, N=20)\n", "lg_sm_flow_diverge(df, N=20)\n", "pullback_strong(df, N=20, M=20)\n", "vol_wgt_hist_pos(df, N=20)\n", "vol_adj_roc(df, N=20)\n", "\n", "cs_rank_net_lg_flow_val(df)\n", "cs_rank_flow_divergence(df)\n", "cs_rank_industry_adj_lg_flow(df) # Needs cat_l2_code\n", "cs_rank_elg_buy_ratio(df)\n", "cs_rank_rel_profit_margin(df)\n", "cs_rank_cost_breadth(df)\n", "cs_rank_dist_to_upper_cost(df)\n", "cs_rank_winner_rate(df)\n", "cs_rank_intraday_range(df)\n", "cs_rank_close_pos_in_range(df)\n", "cs_rank_opening_gap(df) # Needs pre_close\n", "cs_rank_pos_in_hist_range(df) # Needs his_low, his_high\n", "cs_rank_vol_x_profit_margin(df)\n", "cs_rank_lg_flow_price_concordance(df)\n", "cs_rank_turnover_per_winner(df)\n", "cs_rank_ind_cap_neutral_pe(df) # Placeholder - needs external libraries\n", "cs_rank_volume_ratio(df) # Needs volume_ratio\n", "cs_rank_elg_buy_sell_sm_ratio(df)\n", "cs_rank_cost_dist_vol_ratio(df) # Needs volume_ratio\n", "cs_rank_size(df) # Needs circ_mv\n", "\n", "\n", "# df = df.merge(index_data, on='trade_date', how='left')\n", "\n", "print(df.info())\n", "print(df.columns.tolist())" ] }, { "cell_type": "code", "execution_count": 11, "id": "b87b938028afa206", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T13:08:03.658725Z", "start_time": "2025-04-03T13:08:02.469611Z" } }, "outputs": [], "source": [ "from scipy.stats import ks_2samp, wasserstein_distance\n", "\n", "\n", "def remove_shifted_features(train_data, test_data, feature_columns, ks_threshold=0.05, wasserstein_threshold=0.1,\n", " importance_threshold=0.05):\n", " dropped_features = []\n", "\n", " # **统计数据漂移**\n", " numeric_columns = train_data.select_dtypes(include=['float64', 'int64']).columns\n", " numeric_columns = [col for col in numeric_columns if col in feature_columns]\n", " for feature in numeric_columns:\n", " ks_stat, p_value = ks_2samp(train_data[feature], test_data[feature])\n", " wasserstein_dist = wasserstein_distance(train_data[feature], test_data[feature])\n", "\n", " if p_value < ks_threshold or wasserstein_dist > wasserstein_threshold:\n", " dropped_features.append(feature)\n", "\n", " print(f\"检测到 {len(dropped_features)} 个可能漂移的特征: {dropped_features}\")\n", "\n", " # **应用阈值进行最终筛选**\n", " filtered_features = [f for f in feature_columns if f not in dropped_features]\n", "\n", " return filtered_features, dropped_features\n", "\n" ] }, { "cell_type": "code", "execution_count": 12, "id": "f4f16d63ad18d1bc", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T13:08:03.670700Z", "start_time": "2025-04-03T13:08:03.665739Z" } }, "outputs": [], "source": [ "import numpy as np\n", "import statsmodels.api as sm # 用于中性化回归\n", "from tqdm import tqdm # 可选,用于显示进度条\n", "\n", "# --- 常量 ---\n", "epsilon = 1e-10 # 防止除零\n", "\n", "# --- 1. 中位数去极值 (MAD) ---\n", "\n", "def cs_mad_filter(df: pd.DataFrame,\n", " features: list,\n", " k: float = 3.0,\n", " scale_factor: float = 1.4826):\n", " \"\"\"\n", " 对指定特征列进行截面 MAD 去极值处理 (原地修改)。\n", "\n", " 方法: 对每日截面数据,计算 median 和 MAD,\n", " 将超出 [median - k * scale * MAD, median + k * scale * MAD] 范围的值\n", " 替换为边界值 (Winsorization)。\n", " scale_factor=1.4826 使得 MAD 约等于正态分布的标准差。\n", "\n", " Args:\n", " df (pd.DataFrame): 输入 DataFrame,需包含 'trade_date' 和 features 列。\n", " features (list): 需要处理的特征列名列表。\n", " k (float): MAD 的倍数,用于确定边界。默认为 3.0。\n", " scale_factor (float): MAD 的缩放因子。默认为 1.4826。\n", "\n", " WARNING: 此函数会原地修改输入的 DataFrame 'df'。\n", " \"\"\"\n", " print(f\"开始截面 MAD 去极值处理 (k={k})...\")\n", " if not all(col in df.columns for col in features):\n", " missing = [col for col in features if col not in df.columns]\n", " print(f\"错误: DataFrame 中缺少以下特征列: {missing}。跳过去极值处理。\")\n", " return\n", "\n", " grouped = df.groupby('trade_date')\n", "\n", " for col in tqdm(features, desc=\"MAD Filtering\"):\n", " try:\n", " # 计算截面中位数\n", " median = grouped[col].transform('median')\n", " # 计算截面 MAD (Median Absolute Deviation from Median)\n", " mad = (df[col] - median).abs().groupby(df['trade_date']).transform('median')\n", "\n", " # 计算上下边界\n", " lower_bound = median - k * scale_factor * mad\n", " upper_bound = median + k * scale_factor * mad\n", "\n", " # 原地应用 clip\n", " df[col] = np.clip(df[col], lower_bound, upper_bound)\n", "\n", " except KeyError:\n", " print(f\"警告: 列 '{col}' 可能不存在或在分组中出错,跳过此列的 MAD 处理。\")\n", " except Exception as e:\n", " print(f\"警告: 处理列 '{col}' 时发生错误: {e},跳过此列的 MAD 处理。\")\n", "\n", " print(\"截面 MAD 去极值处理完成。\")\n", "\n", "\n", "# --- 2. 行业市值中性化 ---\n", "\n", "from tqdm import tqdm\n", "\n", "def cs_neutralize_market_cap_numpy(df: pd.DataFrame,\n", " features: list,\n", " market_cap_col: str = 'circ_mv'):\n", " \"\"\"\n", " 对 DataFrame 中的指定特征进行截面市值中性化 (NumPy 优化)。\n", "\n", " Args:\n", " df (pd.DataFrame): 包含数据的 DataFrame,需要有 'trade_date' 和 market_cap_col 列。\n", " features (list): 需要进行市值中性化的特征列名列表。\n", " market_cap_col (str): 包含市值数据的列名,默认为 'circ_mv'。\n", " \"\"\"\n", " print(\"开始截面市值中性化 (NumPy 优化)...\")\n", " required_cols = features + ['trade_date', market_cap_col]\n", " if not all(col in df.columns for col in required_cols):\n", " missing = [col for col in required_cols if col not in df.columns]\n", " print(f\"错误: DataFrame 中缺少必需列: {missing}。无法进行中性化。\")\n", " return\n", "\n", " df_copy = df\n", " log_cap_col = '_log_market_cap'\n", " df_copy[log_cap_col] = np.log1p(df_copy[market_cap_col])\n", "\n", " # 创建一个 DataFrame 来存储所有日期的残差结果\n", " residuals_container = pd.DataFrame(index=df_copy.index, columns=features, dtype=float)\n", "\n", " for date, group_df in tqdm(df_copy.groupby('trade_date'), desc=\"Neutralizing by Date (NumPy)\"):\n", " # 准备 X 矩阵 (自变量):常数项和对数市值\n", " X_daily = np.concatenate([np.ones((len(group_df), 1)), group_df[[log_cap_col]].values], axis=1)\n", "\n", " for feature_col in features:\n", " Y_daily = group_df[feature_col].values\n", "\n", " # 处理 NaN:只对有效数据对进行回归\n", " valid_mask_y = ~np.isnan(Y_daily)\n", " valid_mask_x = ~np.isnan(X_daily).any(axis=1)\n", " valid_mask = valid_mask_y & valid_mask_x\n", "\n", " current_feature_indices = group_df.index[valid_mask]\n", "\n", " if np.sum(valid_mask) < X_daily.shape[1] + 1:\n", " # 有效数据不足,此特征在此日期保持 NaN\n", " continue\n", "\n", " Y_valid = Y_daily[valid_mask]\n", " X_valid = X_daily[valid_mask, :]\n", "\n", " try:\n", " # 使用 np.linalg.lstsq 进行 OLS 计算\n", " beta, sum_sq_resid, rank, s = np.linalg.lstsq(X_valid, Y_valid, rcond=None)\n", "\n", " # 计算预测值 Y_hat = X_valid @ beta\n", " Y_hat_valid = X_valid @ beta\n", "\n", " # 计算残差 residuals = Y_valid - Y_hat_valid\n", " residuals_valid = Y_valid - Y_hat_valid\n", "\n", " # 将计算得到的残差放回 residuals_container\n", " residuals_container.loc[current_feature_indices, feature_col] = residuals_valid\n", "\n", " except np.linalg.LinAlgError:\n", " pass\n", " except Exception as e:\n", " pass\n", "\n", " # 将所有计算得到的残差更新回原始的 df (原地修改)\n", " for feature_col in features:\n", " df[feature_col] = residuals_container[feature_col]\n", "\n", " # 清理临时列\n", " df.drop(columns=[log_cap_col], inplace=True, errors='ignore')\n", " print(\"截面市值中性化完成 (NumPy 优化)。\")\n", "\n", "# --- 3. Z-Score 标准化 ---\n", "\n", "def cs_zscore_standardize(df: pd.DataFrame, features: list, epsilon: float = 1e-10):\n", " \"\"\"\n", " 对指定特征列进行截面 Z-Score 标准化 (原地修改)。\n", " 方法: Z = (value - cross_sectional_mean) / (cross_sectional_std + epsilon)\n", "\n", " Args:\n", " df (pd.DataFrame): 输入 DataFrame,需包含 'trade_date' 和 features 列。\n", " features (list): 需要处理的特征列名列表。\n", " epsilon (float): 防止除以零的小常数。\n", "\n", " WARNING: 此函数会原地修改输入的 DataFrame 'df'。\n", " \"\"\"\n", " print(\"开始截面 Z-Score 标准化...\")\n", " if not all(col in df.columns for col in features):\n", " missing = [col for col in features if col not in df.columns]\n", " print(f\"错误: DataFrame 中缺少以下特征列: {missing}。跳过标准化处理。\")\n", " return\n", "\n", " grouped = df.groupby('trade_date')\n", "\n", " for col in tqdm(features, desc=\"Standardizing\"):\n", " try:\n", " # 使用 transform 计算截面均值和标准差\n", " mean = grouped[col].transform('mean')\n", " std = grouped[col].transform('std')\n", "\n", " # 计算 Z-Score 并原地赋值\n", " df[col] = (df[col] - mean) / (std + epsilon)\n", "\n", " except KeyError:\n", " print(f\"警告: 列 '{col}' 可能不存在或在分组中出错,跳过此列的标准化处理。\")\n", " except Exception as e:\n", " print(f\"警告: 处理列 '{col}' 时发生错误: {e},跳过此列的标准化处理。\")\n", "\n", " print(\"截面 Z-Score 标准化完成。\")\n", "\n", "def fill_nan_with_daily_median(df: pd.DataFrame, feature_columns: list[str]) -> pd.DataFrame:\n", " \"\"\"\n", " 对指定特征列进行每日截面中位数填充缺失值 (NaN)。\n", "\n", " 参数:\n", " df (pd.DataFrame): 包含多日数据的DataFrame,需要包含 'trade_date' 和 feature_columns 中的列。\n", " feature_columns (list[str]): 需要进行缺失值填充的特征列名称列表。\n", "\n", " 返回:\n", " pd.DataFrame: 包含缺失值填充后特征列的DataFrame。在输入DataFrame的副本上操作。\n", " \"\"\"\n", " processed_df = df.copy() # 在副本上操作,保留原始数据\n", "\n", " # 确保 trade_date 是 datetime 类型以便正确分组\n", " processed_df['trade_date'] = pd.to_datetime(processed_df['trade_date'])\n", "\n", " def _fill_daily_nan(group):\n", " # group 是某一个交易日的 DataFrame\n", "\n", " # 遍历指定的特征列\n", " for feature_col in feature_columns:\n", " # 检查列是否存在于当前分组中\n", " if feature_col in group.columns:\n", " # 计算当日该特征的中位数\n", " median_val = group[feature_col].median()\n", "\n", " # 使用当日中位数填充该特征列的 NaN 值\n", " # inplace=True 会直接修改 group DataFrame\n", " group[feature_col].fillna(median_val, inplace=True)\n", " # else:\n", " # print(f\"Warning: Feature column '{feature_col}' not found in daily group for {group['trade_date'].iloc[0]}. Skipping.\")\n", "\n", " return group\n", "\n", " # 按交易日期分组,并应用每日填充函数\n", " # group_keys=False 避免将分组键添加到结果索引中\n", " filled_df = processed_df.groupby('trade_date', group_keys=False).apply(_fill_daily_nan)\n", "\n", " return filled_df" ] }, { "cell_type": "code", "execution_count": 13, "id": "40e6b68a91b30c79", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T13:08:04.694262Z", "start_time": "2025-04-03T13:08:03.694904Z" } }, "outputs": [], "source": [ "def remove_outliers_label_percentile(label: pd.Series, lower_percentile: float = 0.01, upper_percentile: float = 0.99,\n", " log=True):\n", " if not (0 <= lower_percentile < upper_percentile <= 1):\n", " raise ValueError(\"Percentile values must satisfy 0 <= lower_percentile < upper_percentile <= 1.\")\n", "\n", " # Calculate lower and upper bounds based on percentiles\n", " lower_bound = label.quantile(lower_percentile)\n", " upper_bound = label.quantile(upper_percentile)\n", "\n", " # Filter out values outside the bounds\n", " filtered_label = label[(label >= lower_bound) & (label <= upper_bound)]\n", "\n", " # Print the number of removed outliers\n", " if log:\n", " print(f\"Removed {len(label) - len(filtered_label)} outliers.\")\n", " return filtered_label\n", "\n", "\n", "def calculate_risk_adjusted_target(df, days=5):\n", " df = df.sort_values(by=['ts_code', 'trade_date'])\n", "\n", " df['future_close'] = df.groupby('ts_code')['close'].shift(-days)\n", " df['future_open'] = df.groupby('ts_code')['open'].shift(-1)\n", " df['future_return'] = (df['future_close'] - df['future_open']) / df['future_open']\n", "\n", " df['future_volatility'] = df.groupby('ts_code')['future_return'].rolling(days, min_periods=1).std().reset_index(\n", " level=0, drop=True)\n", " sharpe_ratio = df['future_return'] * df['future_volatility']\n", " sharpe_ratio.replace([np.inf, -np.inf], np.nan, inplace=True)\n", "\n", " return sharpe_ratio\n", "\n", "\n", "def calculate_score(df, days=5, lambda_param=1.0):\n", " def calculate_max_drawdown(prices):\n", " peak = prices.iloc[0] # 初始化峰值\n", " max_drawdown = 0 # 初始化最大回撤\n", "\n", " for price in prices:\n", " if price > peak:\n", " peak = price # 更新峰值\n", " else:\n", " drawdown = (peak - price) / peak # 计算当前回撤\n", " max_drawdown = max(max_drawdown, drawdown) # 更新最大回撤\n", "\n", " return max_drawdown\n", "\n", " def compute_stock_score(stock_df):\n", " stock_df = stock_df.sort_values(by=['trade_date'])\n", " future_return = stock_df['future_return']\n", " # 使用已有的 pct_chg 字段计算波动率\n", " volatility = stock_df['pct_chg'].rolling(days).std().shift(-days)\n", " max_drawdown = stock_df['close'].rolling(days).apply(calculate_max_drawdown, raw=False).shift(-days)\n", " score = future_return - lambda_param * max_drawdown\n", " return score\n", "\n", " # # 确保 DataFrame 按照股票代码和交易日期排序\n", " # df = df.sort_values(by=['ts_code', 'trade_date'])\n", "\n", " # 对每个股票分别计算 score\n", " df['score'] = df.groupby('ts_code').apply(compute_stock_score).reset_index(level=0, drop=True)\n", "\n", " return df['score']\n", "\n", "\n", "def remove_highly_correlated_features(df, feature_columns, threshold=0.9):\n", " numeric_features = df[feature_columns].select_dtypes(include=[np.number]).columns.tolist()\n", " if not numeric_features:\n", " raise ValueError(\"No numeric features found in the provided data.\")\n", "\n", " corr_matrix = df[numeric_features].corr().abs()\n", " upper = corr_matrix.where(np.triu(np.ones(corr_matrix.shape), k=1).astype(bool))\n", " to_drop = [column for column in upper.columns if any(upper[column] > threshold)]\n", " remaining_features = [col for col in feature_columns if col not in to_drop\n", " or 'act' in col or 'af' in col]\n", " return remaining_features\n", "\n", "\n", "def cross_sectional_standardization(df, features):\n", " df_sorted = df.sort_values(by='trade_date') # 按时间排序\n", " df_standardized = df_sorted.copy()\n", "\n", " for date in df_sorted['trade_date'].unique():\n", " # 获取当前时间点的数据\n", " current_data = df_standardized[df_standardized['trade_date'] == date]\n", "\n", " # 只对指定特征进行标准化\n", " scaler = StandardScaler()\n", " standardized_values = scaler.fit_transform(current_data[features])\n", "\n", " # 将标准化结果重新赋值回去\n", " df_standardized.loc[df_standardized['trade_date'] == date, features] = standardized_values\n", "\n", " return df_standardized\n", "\n", "\n", "def neutralize_manual_revised(df: pd.DataFrame, features: list, industry_col: str, mkt_cap_col: str) -> pd.DataFrame:\n", " \"\"\"\n", " 手动实现简单回归以提升速度,通过构建 Series 确保索引对齐。\n", " 对特征在行业内部进行市值中性化。\n", "\n", " Args:\n", " df: 输入的 DataFrame,包含特征、行业分类和市值列。\n", " features: 需要进行中性化的特征列名列表。\n", " industry_col: 行业分类列的列名。\n", " mkt_cap_col: 市值列的列名。\n", "\n", " Returns:\n", " 中性化后的 DataFrame。\n", " \"\"\"\n", "\n", " df[mkt_cap_col] = pd.to_numeric(df[mkt_cap_col], errors='coerce')\n", " df_cleaned = df.dropna(subset=[mkt_cap_col]).copy()\n", " df_cleaned = df_cleaned[df_cleaned[mkt_cap_col] > 0].copy()\n", "\n", " if df_cleaned.empty:\n", " print(\"警告: 清理市值异常值后 DataFrame 为空。\")\n", " return df # 返回原始或空df,取决于清理前的状态\n", "\n", " processed_df = df\n", "\n", " for col in features:\n", " if col not in df_cleaned.columns:\n", " print(f\"警告: 特征列 '{col}' 不存在于清理后的 DataFrame 中,已跳过。\")\n", " # 对于原始 df 中该列不存在的,在结果 df 中也保持原样(可能全是NaN)\n", " processed_df[col] = df[col] if col in df.columns else np.nan\n", " continue\n", "\n", " # 跳过对控制变量本身进行中性化\n", " if col == mkt_cap_col or col == industry_col:\n", " print(f\"警告: 特征列 '{col}' 是控制变量或内部使用的列,跳过中性化。\")\n", " # 在结果 df 中也保持原样\n", " processed_df[col] = df[col] if col in df.columns else np.nan\n", " continue\n", "\n", " residual_series = pd.Series(index=df_cleaned.index, dtype=float)\n", "\n", " # 在分组前处理特征列的 NaN,只对有因子值的行进行回归计算\n", " df_subset_factor = df_cleaned.dropna(subset=[col]).copy()\n", "\n", " if not df_subset_factor.empty:\n", " for industry, group in df_subset_factor.groupby(industry_col):\n", " x = group[mkt_cap_col] # 市值对数\n", " y = group[col] # 因子值\n", "\n", " # 确保有足够的数据点 (>1) 且市值对数有方差 (>0) 进行回归计算\n", " # 检查 np.var > 一个很小的正数,避免浮点数误差导致的零方差判断问题\n", " if len(group) > 1 and np.var(x) > 1e-9:\n", " try:\n", " beta = np.cov(y, x)[0, 1] / np.var(x)\n", " alpha = np.mean(y) - beta * np.mean(x)\n", "\n", " # 计算残差\n", " resid = y - (alpha + beta * x)\n", "\n", " # 将计算出的残差存储到 residual_series 中,通过索引自动对齐\n", " residual_series.loc[resid.index] = resid\n", "\n", " except Exception as e:\n", " # 捕获可能的计算异常,例如np.cov或np.var因为极端数据报错\n", " print(f\"警告: 在行业 {industry} 计算回归时发生错误: {e}。该组残差将设为原始值或 NaN。\")\n", " # 此时该组的残差会保持 residual_series 初始化时的 NaN 或后续处理\n", " # 也可以选择保留原始值:residual_series.loc[group.index] = group[col]\n", "\n", " else:\n", " residual_series.loc[group.index] = group[col] # 保留原始因子值\n", " processed_df.loc[residual_series.index, col] = residual_series\n", "\n", "\n", " else:\n", " processed_df[col] = np.nan # 或 df[col] if col in df.columns else np.nan\n", "\n", " return processed_df\n", "\n", "\n", "import gc\n", "\n", "gc.collect()\n", "\n", "\n", "def mad_filter(df, features, n=3):\n", " for col in features:\n", " median = df[col].median()\n", " mad = np.median(np.abs(df[col] - median))\n", " upper = median + n * mad\n", " lower = median - n * mad\n", " df[col] = np.clip(df[col], lower, upper) # 截断极值\n", " return df\n", "\n", "\n", "def percentile_filter(df, features, lower_percentile=0.01, upper_percentile=0.99):\n", " for col in features:\n", " # 按日期分组计算上下百分位数\n", " lower_bound = df.groupby('trade_date')[col].transform(\n", " lambda x: x.quantile(lower_percentile)\n", " )\n", " upper_bound = df.groupby('trade_date')[col].transform(\n", " lambda x: x.quantile(upper_percentile)\n", " )\n", " # 截断超出范围的值\n", " df[col] = np.clip(df[col], lower_bound, upper_bound)\n", " return df\n", "\n", "\n", "from scipy.stats import iqr\n", "\n", "\n", "def iqr_filter(df, features):\n", " for col in features:\n", " df[col] = df.groupby('trade_date')[col].transform(\n", " lambda x: (x - x.median()) / iqr(x) if iqr(x) != 0 else x\n", " )\n", " return df\n", "\n", "\n", "def quantile_filter(df, features, lower_quantile=0.01, upper_quantile=0.99, window=60):\n", " df = df.copy()\n", " for col in features:\n", " # 计算 rolling 统计量,需要按日期进行 groupby\n", " rolling_lower = df.groupby('trade_date')[col].transform(lambda x: x.rolling(window=min(len(x), window)).quantile(lower_quantile))\n", " rolling_upper = df.groupby('trade_date')[col].transform(lambda x: x.rolling(window=min(len(x), window)).quantile(upper_quantile))\n", "\n", " # 对数据进行裁剪\n", " df[col] = np.clip(df[col], rolling_lower, rolling_upper)\n", " \n", " return df\n", "\n", "def select_top_features_by_rankic(df: pd.DataFrame, feature_columns: list, n: int, target_column: str = 'future_return') -> list:\n", " \"\"\"\n", " 计算给定特征与目标列的 RankIC,并返回 RankIC 绝对值最高的 n 个特征。\n", "\n", " Args:\n", " df: 包含特征列和目标列的 Pandas DataFrame。\n", " feature_columns: 包含所有待评估特征列名的列表。\n", " n: 希望选取的 RankIC 绝对值最高的特征数量。\n", " target_column: 目标列的名称,用于计算 RankIC。默认为 'future_return'。\n", "\n", " Returns:\n", " 包含 RankIC 绝对值最高的 n 个特征列名的列表。\n", " \"\"\"\n", " numeric_columns = df.select_dtypes(include=['float64', 'int64']).columns\n", " numeric_columns = [col for col in numeric_columns if col in feature_columns]\n", " if target_column not in df.columns:\n", " raise ValueError(f\"目标列 '{target_column}' 不存在于 DataFrame 中。\")\n", "\n", " rankic_scores = {}\n", " for feature in numeric_columns:\n", " if feature not in df.columns:\n", " print(f\"警告: 特征列 '{feature}' 不存在于 DataFrame 中,已跳过。\")\n", " continue\n", "\n", " # 计算特征与目标列的 RankIC (斯皮尔曼相关系数)\n", " # dropna() 是为了处理缺失值,确保相关性计算不失败\n", " valid_data = df[[feature, target_column]].dropna()\n", " if len(valid_data) > 1: # 确保有足够的数据点进行相关性计算\n", " # 计算斯皮尔曼相关性\n", " correlation = valid_data[feature].corr(valid_data[target_column], method='spearman')\n", " rankic_scores[feature] = abs(correlation) # 使用绝对值来衡量相关性强度\n", " else:\n", " rankic_scores[feature] = 0 # 数据不足,RankIC设为0或跳过\n", "\n", " # 将 RankIC 分数转换为 Series 便于排序\n", " rankic_series = pd.Series(rankic_scores)\n", "\n", " # 按 RankIC 绝对值降序排序,选取前 n 个特征\n", " # handle case where n might be larger than available features\n", " n_actual = min(n, len(rankic_series))\n", " top_features = rankic_series.sort_values(ascending=False).head(n_actual).index.tolist()\n", " top_features = [col for col in feature_columns if col in top_features or col not in numeric_columns]\n", " return top_features\n", "\n", "def create_deviation_within_dates(df, feature_columns):\n", " groupby_col = 'cat_l2_code' # 使用 trade_date 进行分组\n", " new_columns = {}\n", " ret_feature_columns = feature_columns[:]\n", "\n", " # 自动选择所有数值型特征\n", " num_features = [col for col in feature_columns if 'cat' not in col and 'index' not in col]\n", "\n", " # num_features = ['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'cat_vol_spike', 'obv', 'maobv_6', 'return_5', 'return_10', 'return_20', 'std_return_5', 'std_return_15', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'act_factor5', 'act_factor6', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'alpha_022', 'alpha_003', 'alpha_007', 'alpha_013']\n", " num_features = [col for col in num_features if 'cat' not in col and 'industry' not in col]\n", " num_features = [col for col in num_features if 'limit' not in col]\n", " num_features = [col for col in num_features if 'cyq' not in col]\n", "\n", " # 遍历所有数值型特征\n", " for feature in num_features:\n", " if feature == 'trade_date': # 不需要对 'trade_date' 计算偏差\n", " continue\n", "\n", " # grouped_mean = df.groupby(['trade_date'])[feature].transform('mean')\n", " # deviation_col_name = f'deviation_mean_{feature}'\n", " # new_columns[deviation_col_name] = df[feature] - grouped_mean\n", " # ret_feature_columns.append(deviation_col_name)\n", "\n", " grouped_mean = df.groupby(['trade_date', groupby_col])[feature].transform('mean')\n", " deviation_col_name = f'deviation_mean_{feature}'\n", " new_columns[deviation_col_name] = df[feature] - grouped_mean\n", " ret_feature_columns.append(deviation_col_name)\n", "\n", " # 将新计算的偏差特征与原始 DataFrame 合并\n", " df = pd.concat([df, pd.DataFrame(new_columns)], axis=1)\n", "\n", " # for feature in ['obv', 'return_20', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4']:\n", " # df[f'deviation_industry_{feature}'] = df[feature] - df[f'industry_{feature}']\n", "\n", " return df, ret_feature_columns\n" ] }, { "cell_type": "code", "execution_count": 14, "id": "47c12bb34062ae7a", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T14:57:50.841165Z", "start_time": "2025-04-03T14:49:25.889057Z" } }, "outputs": [], "source": [ "days = 5\n", "validation_days = 120\n", "\n", "import gc\n", "\n", "gc.collect()\n", "\n", "df = df.sort_values(by=['ts_code', 'trade_date'])\n", "df['future_return'] = df.groupby('ts_code', group_keys=False)['close'].apply(lambda x: x.shift(-days) / x - 1)\n", "# df['future_return'] = (df.groupby('ts_code')['close'].shift(-days) - df.groupby('ts_code')['open'].shift(-1)) / \\\n", "# df.groupby('ts_code')['open'].shift(-1)\n", "\n", "df['cat_up_limit'] = df['pct_chg'] > 5\n", "df['label'] = df.groupby('ts_code')['cat_up_limit'].rolling(window=5, min_periods=1).max().groupby('ts_code').shift(-5).fillna(0).astype(int).reset_index(level=0, drop=True)\n", "\n", "filter_index = df['future_return'].between(df['future_return'].quantile(0.01), df['future_return'].quantile(0.99))\n", "\n", "# for col in [col for col in df.columns]:\n", "# train_data[col] = train_data[col].astype('str')\n", "# test_data[col] = test_data[col].astype('str')" ] }, { "cell_type": "code", "execution_count": 15, "id": "29221dde", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "191\n" ] } ], "source": [ "feature_columns = [col for col in df.head(10).merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left').merge(index_data, on='trade_date', how='left').columns]\n", "feature_columns = [col for col in feature_columns if col not in ['trade_date',\n", " 'ts_code',\n", " 'label']]\n", "feature_columns = [col for col in feature_columns if 'future' not in col]\n", "feature_columns = [col for col in feature_columns if 'label' not in col]\n", "feature_columns = [col for col in feature_columns if 'score' not in col]\n", "feature_columns = [col for col in feature_columns if 'gen' not in col]\n", "feature_columns = [col for col in feature_columns if 'is_st' not in col]\n", "feature_columns = [col for col in feature_columns if 'pe_ttm' not in col]\n", "# feature_columns = [col for col in feature_columns if 'volatility' not in col]\n", "feature_columns = [col for col in feature_columns if 'circ_mv' not in col]\n", "feature_columns = [col for col in feature_columns if 'code' not in col]\n", "feature_columns = [col for col in feature_columns if col not in origin_columns]\n", "feature_columns = [col for col in feature_columns if not col.startswith('_')]\n", "# feature_columns = [col for col in feature_columns if col not in ['ts_code', 'trade_date', 'vol_std_5', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_007', 'consecutive_up_limit', 'mv_volatility', 'volume_growth', 'mv_growth', 'arbr']]\n", "feature_columns = [col for col in feature_columns if col not in ['intraday_lg_flow_corr_20', \n", " 'cap_neutral_cost_metric', \n", " 'hurst_net_mf_vol_60', \n", " 'complex_factor_deap_1', \n", " 'lg_buy_consolidation_20',\n", " 'cs_rank_ind_cap_neutral_pe',\n", " 'cs_rank_opening_gap',\n", " 'cs_rank_ind_adj_lg_flow']]\n", "feature_columns = [col for col in feature_columns if col not in ['roa', 'roe']]\n", "print(len(feature_columns))" ] }, { "cell_type": "code", "execution_count": 16, "id": "03ee5daf", "metadata": {}, "outputs": [], "source": [ "# df = fill_nan_with_daily_median(df, feature_columns)\n", "for feature_col in [col for col in feature_columns if col in df.columns]:\n", " # median_val = df[feature_col].median()\n", " df[feature_col].fillna(0, inplace=True)" ] }, { "cell_type": "code", "execution_count": 17, "id": "b76ea08a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " ts_code trade_date log_circ_mv\n", "0 000001.SZ 2019-01-02 16.574219\n", "1 000001.SZ 2019-01-03 16.583965\n", "2 000001.SZ 2019-01-04 16.633371\n", "['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'winner_rate', 'undist_profit_ps', 'ocfps', 'AR', 'BR', 'AR_BR', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size', 'cat_up_limit', 'industry_obv', 'industry_return_5', 'industry_return_20', 'industry__ema_5', 'industry__ema_13', 'industry__ema_20', 'industry__ema_60', 'industry_act_factor1', 'industry_act_factor2', 'industry_act_factor3', 'industry_act_factor4', 'industry_act_factor5', 'industry_act_factor6', 'industry_rank_act_factor1', 'industry_rank_act_factor2', 'industry_rank_act_factor3', 'industry_return_5_percentile', 'industry_return_20_percentile', '000852.SH_MACD', '000905.SH_MACD', '399006.SZ_MACD', '000852.SH_MACD_hist', '000905.SH_MACD_hist', '399006.SZ_MACD_hist', '000852.SH_RSI', '000905.SH_RSI', '399006.SZ_RSI', '000852.SH_Signal_line', '000905.SH_Signal_line', '399006.SZ_Signal_line', '000852.SH_amount_change_rate', '000905.SH_amount_change_rate', '399006.SZ_amount_change_rate', '000852.SH_amount_mean', '000905.SH_amount_mean', '399006.SZ_amount_mean', '000852.SH_daily_return', '000905.SH_daily_return', '399006.SZ_daily_return', '000852.SH_up_ratio_20d', '000905.SH_up_ratio_20d', '399006.SZ_up_ratio_20d', '000852.SH_volatility', '000905.SH_volatility', '399006.SZ_volatility', '000852.SH_volume_change_rate', '000905.SH_volume_change_rate', '399006.SZ_volume_change_rate']\n", "去除极值\n", "开始截面 MAD 去极值处理 (k=3.0)...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "MAD Filtering: 100%|██████████| 131/131 [00:12<00:00, 10.28it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "截面 MAD 去极值处理完成。\n", "开始截面 MAD 去极值处理 (k=3.0)...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "MAD Filtering: 100%|██████████| 131/131 [00:11<00:00, 11.00it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "截面 MAD 去极值处理完成。\n", "开始截面 MAD 去极值处理 (k=3.0)...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "MAD Filtering: 0it [00:00, ?it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "截面 MAD 去极值处理完成。\n", "开始截面 MAD 去极值处理 (k=3.0)...\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "MAD Filtering: 0it [00:00, ?it/s]\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "截面 MAD 去极值处理完成。\n", "feature_columns: ['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'winner_rate', 'undist_profit_ps', 'ocfps', 'AR', 'BR', 'AR_BR', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size', 'cat_up_limit', 'industry_obv', 'industry_return_5', 'industry_return_20', 'industry__ema_5', 'industry__ema_13', 'industry__ema_20', 'industry__ema_60', 'industry_act_factor1', 'industry_act_factor2', 'industry_act_factor3', 'industry_act_factor4', 'industry_act_factor5', 'industry_act_factor6', 'industry_rank_act_factor1', 'industry_rank_act_factor2', 'industry_rank_act_factor3', 'industry_return_5_percentile', 'industry_return_20_percentile', '000852.SH_MACD', '000905.SH_MACD', '399006.SZ_MACD', '000852.SH_MACD_hist', '000905.SH_MACD_hist', '399006.SZ_MACD_hist', '000852.SH_RSI', '000905.SH_RSI', '399006.SZ_RSI', '000852.SH_Signal_line', '000905.SH_Signal_line', '399006.SZ_Signal_line', '000852.SH_amount_change_rate', '000905.SH_amount_change_rate', '399006.SZ_amount_change_rate', '000852.SH_amount_mean', '000905.SH_amount_mean', '399006.SZ_amount_mean', '000852.SH_daily_return', '000905.SH_daily_return', '399006.SZ_daily_return', '000852.SH_up_ratio_20d', '000905.SH_up_ratio_20d', '399006.SZ_up_ratio_20d', '000852.SH_volatility', '000905.SH_volatility', '399006.SZ_volatility', '000852.SH_volume_change_rate', '000905.SH_volume_change_rate', '399006.SZ_volume_change_rate']\n", "df最小日期: 2019-01-02\n", "df最大日期: 2025-05-30\n", "2057465\n", "train_data最小日期: 2020-01-02\n", "train_data最大日期: 2022-12-30\n", "1781706\n", "test_data最小日期: 2023-01-03\n", "test_data最大日期: 2025-05-30\n", " ts_code trade_date log_circ_mv\n", "0 000001.SZ 2019-01-02 16.574219\n", "1 000001.SZ 2019-01-03 16.583965\n", "2 000001.SZ 2019-01-04 16.633371\n" ] } ], "source": [ "split_date = '2023-01-01'\n", "train_data = df[filter_index & (df['trade_date'] <= split_date) & (df['trade_date'] >= '2020-01-01')]\n", "test_data = df[(df['trade_date'] >= split_date)]\n", "\n", "print(df[['ts_code', 'trade_date', 'log_circ_mv']].head(3))\n", "\n", "industry_df = industry_df.sort_values(by=['trade_date'])\n", "index_data = index_data.sort_values(by=['trade_date'])\n", "\n", "# train_data = train_data.merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n", "# train_data = train_data.merge(index_data, on='trade_date', how='left')\n", "# test_data = test_data.merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n", "# test_data = test_data.merge(index_data, on='trade_date', how='left')\n", "\n", "train_data, test_data = train_data.replace([np.inf, -np.inf], np.nan), test_data.replace([np.inf, -np.inf], np.nan)\n", "\n", "# feature_columns_new = feature_columns[:]\n", "# train_data, _ = create_deviation_within_dates(train_data, [col for col in feature_columns if col in train_data.columns])\n", "# test_data, _ = create_deviation_within_dates(test_data, [col for col in feature_columns if col in train_data.columns])\n", "\n", "# feature_columns = [\n", "# 'undist_profit_ps', \n", "# 'AR_BR',\n", "# 'pe_ttm',\n", "# 'alpha_22_improved', \n", "# 'alpha_003', \n", "# 'alpha_007', \n", "# 'alpha_013', \n", "# 'cat_up_limit', \n", "# 'cat_down_limit', \n", "# 'up_limit_count_10d', \n", "# 'down_limit_count_10d', \n", "# 'consecutive_up_limit', \n", "# 'vol_break', \n", "# 'weight_roc5', \n", "# 'price_cost_divergence', \n", "# 'smallcap_concentration', \n", "# 'cost_stability', \n", "# 'high_cost_break_days', \n", "# 'liquidity_risk', \n", "# 'turnover_std', \n", "# 'mv_volatility', \n", "# 'volume_growth', \n", "# 'mv_growth', \n", "# 'lg_flow_mom_corr_20_60', \n", "# 'lg_flow_accel', \n", "# 'profit_pressure', \n", "# 'underwater_resistance', \n", "# 'cost_conc_std_20', \n", "# 'profit_decay_20', \n", "# 'vol_amp_loss_20', \n", "# 'vol_drop_profit_cnt_5', \n", "# 'lg_flow_vol_interact_20', \n", "# 'cost_break_confirm_cnt_5', \n", "# 'atr_norm_channel_pos_14', \n", "# 'turnover_diff_skew_20', \n", "# 'lg_sm_flow_diverge_20', \n", "# 'pullback_strong_20_20', \n", "# 'vol_wgt_hist_pos_20', \n", "# 'vol_adj_roc_20',\n", "# 'cashflow_to_ev_factor',\n", "# 'ocfps',\n", "# 'book_to_price_ratio',\n", "# 'turnover_rate_mean_5',\n", "# 'variance_20',\n", "# 'bbi_ratio_factor'\n", "# ]\n", "# feature_columns = [col for col in feature_columns if col in train_data.columns]\n", "# feature_columns = [col for col in feature_columns if not col.startswith('_')]\n", "\n", "numeric_columns = df.select_dtypes(include=['float64', 'int64']).columns\n", "numeric_columns = [col for col in numeric_columns if col in feature_columns]\n", "# feature_columns = select_top_features_by_rankic(df, numeric_columns, n=10)\n", "print(feature_columns)\n", "\n", "# train_data = fill_nan_with_daily_median(train_data, feature_columns)\n", "# test_data = fill_nan_with_daily_median(test_data, feature_columns)\n", "\n", "train_data = train_data.dropna(subset=[col for col in feature_columns if col in train_data.columns])\n", "train_data = train_data.dropna(subset=['label'])\n", "train_data = train_data.reset_index(drop=True)\n", "# print(test_data.tail())\n", "test_data = test_data.dropna(subset=[col for col in feature_columns if col in train_data.columns])\n", "# test_data = test_data.dropna(subset=['label'])\n", "test_data = test_data.reset_index(drop=True)\n", "\n", "transform_feature_columns = feature_columns\n", "transform_feature_columns = [col for col in transform_feature_columns if col in feature_columns and not col.startswith('cat') and col in train_data.columns]\n", "# transform_feature_columns.remove('undist_profit_ps')\n", "print('去除极值')\n", "cs_mad_filter(train_data, transform_feature_columns)\n", "# print('中性化')\n", "# cs_neutralize_market_cap_numpy(train_data, transform_feature_columns)\n", "# print('标准化')\n", "# cs_zscore_standardize(train_data, transform_feature_columns)\n", "\n", "cs_mad_filter(test_data, transform_feature_columns)\n", "# cs_neutralize_market_cap_numpy(test_data, transform_feature_columns)\n", "# cs_zscore_standardize(test_data, transform_feature_columns)\n", "\n", "mad_filter_feature_columns = [col for col in feature_columns if col not in transform_feature_columns and not col.startswith('cat') and col in train_data.columns]\n", "cs_mad_filter(train_data, mad_filter_feature_columns)\n", "cs_mad_filter(test_data, mad_filter_feature_columns)\n", "\n", "\n", "print(f'feature_columns: {feature_columns}')\n", "\n", "\n", "print(f\"df最小日期: {df['trade_date'].min().strftime('%Y-%m-%d')}\")\n", "print(f\"df最大日期: {df['trade_date'].max().strftime('%Y-%m-%d')}\")\n", "print(len(train_data))\n", "print(f\"train_data最小日期: {train_data['trade_date'].min().strftime('%Y-%m-%d')}\")\n", "print(f\"train_data最大日期: {train_data['trade_date'].max().strftime('%Y-%m-%d')}\")\n", "print(len(test_data))\n", "print(f\"test_data最小日期: {test_data['trade_date'].min().strftime('%Y-%m-%d')}\")\n", "print(f\"test_data最大日期: {test_data['trade_date'].max().strftime('%Y-%m-%d')}\")\n", "\n", "cat_columns = [col for col in feature_columns if col.startswith('cat')]\n", "for col in cat_columns:\n", " train_data[col] = train_data[col].astype('category')\n", " test_data[col] = test_data[col].astype('category')\n", "\n", "print(df[['ts_code', 'trade_date', 'log_circ_mv']].head(3))\n" ] }, { "cell_type": "code", "execution_count": null, "id": "3ff2d1c5", "metadata": {}, "outputs": [], "source": [ "from sklearn.preprocessing import StandardScaler\n", "from sklearn.linear_model import LogisticRegression\n", "import matplotlib.pyplot as plt # 保持 matplotlib 导入,尽管LightGBM的绘图功能已移除\n", "from sklearn.decomposition import PCA\n", "import datetime # 用于日期计算\n", "from catboost import CatBoostClassifier\n", "from catboost import Pool\n", "import lightgbm as lgb\n", "\n", "def train_model(train_data_df, feature_columns,\n", " print_info=True, # 调整参数名,更通用\n", " validation_days=180, use_pca=False, split_date=None,\n", " target_column='label', type='light'): # 增加目标列参数\n", "\n", " print('train data size: ', len(train_data_df))\n", " print(train_data_df[['ts_code', 'trade_date', 'log_circ_mv']])\n", " # 确保数据按时间排序\n", " train_data_df = train_data_df.sort_values(by='trade_date')\n", "\n", " # 识别数值型特征列\n", " numeric_feature_columns = train_data_df[feature_columns].select_dtypes(include=['float64', 'int64']).columns.tolist()\n", "\n", " # 去除标签为空的样本\n", " initial_len = len(train_data_df)\n", " train_data_df = train_data_df.dropna(subset=[target_column])\n", "\n", " if print_info:\n", " print(f'原始样本数: {initial_len}, 去除标签为空后样本数: {len(train_data_df)}')\n", "\n", " # 提取特征和标签,只取数值型特征用于线性回归\n", " \n", " if split_date is None:\n", " all_dates = train_data_df['trade_date'].unique() # 获取所有唯一的 trade_date\n", " split_date = all_dates[-validation_days] # 划分点为倒数第 validation_days 天\n", " train_data_split = train_data_df[train_data_df['trade_date'] < split_date] # 训练集\n", " val_data_split = train_data_df[train_data_df['trade_date'] >= split_date] # 验证集\n", " \n", " X_train = train_data_split[feature_columns]\n", " y_train = train_data_split[target_column]\n", " \n", " X_val = val_data_split[feature_columns]\n", " y_val = val_data_split['label']\n", "\n", "\n", " # # 标准化数值特征 (使用 StandardScaler 对训练集fit并transform, 对验证集只transform)\n", " scaler = StandardScaler()\n", " # X_train = scaler.fit_transform(X_train)\n", "\n", " # 训练线性回归模型\n", " # model = LogisticRegression(random_state=42)\n", " \n", " # # 使用处理后的特征和样本权重进行训练\n", " # model.fit(X_train, y_train)\n", "\n", "\n", " if type == 'cat':\n", " params = {\n", " 'loss_function': 'Logloss', # 适用于二分类\n", " 'eval_metric': 'Logloss', # 评估指标\n", " 'iterations': 1500,\n", " 'learning_rate': 0.01,\n", " 'depth': 10, # 控制模型复杂度\n", " 'l2_leaf_reg': 50, # L2 正则化\n", " 'verbose': 5000,\n", " 'early_stopping_rounds': 300,\n", " # 'od_type': 'Iter', # Overfitting detector type\n", " # 'od_wait': 300, # Number of iterations to wait after the bes\n", " 'one_hot_max_size': 50,\n", " 'class_weights': [0.6, 1.2],\n", " 'task_type': 'GPU',\n", " 'has_time': True,\n", " 'random_seed': 7\n", " }\n", " cat_features = [i for i, col in enumerate(feature_columns) if col.startswith('cat')]\n", " train_pool = Pool(data=X_train, label=y_train, cat_features=cat_features)\n", " val_pool = Pool(data=X_val, label=y_val, cat_features=cat_features)\n", "\n", "\n", " model = CatBoostClassifier(**params)\n", " model.fit(train_pool,\n", " eval_set=val_pool, \n", " plot=True, \n", " use_best_model=True\n", " )\n", " elif type == 'light':\n", " params = {\n", " 'objective': 'binary',\n", " 'metric': 'average_precision',\n", " 'learning_rate': 0.01,\n", " 'is_unbalance': True,\n", " 'num_leaves': 2048,\n", " 'min_data_in_leaf': 1024,\n", " 'max_depth': 32,\n", " 'max_bin': 1024,\n", " 'feature_fraction': 0.5,\n", " 'bagging_fraction': 0.5,\n", " 'bagging_freq': 1,\n", " 'lambda_l1': 50,\n", " 'lambda_l2': 50,\n", " 'verbosity': -1,\n", " 'num_threads' : 8\n", " }\n", " categorical_feature = [col for col in feature_columns if 'cat' in col]\n", " train_dataset = lgb.Dataset(\n", " X_train, label=y_train,\n", " categorical_feature=categorical_feature\n", " )\n", " val_dataset = lgb.Dataset(\n", " X_val, label=y_val,\n", " categorical_feature=categorical_feature\n", " )\n", "\n", " evals = {}\n", " callbacks = [lgb.log_evaluation(period=1000),\n", " lgb.callback.record_evaluation(evals),\n", " lgb.early_stopping(100, first_metric_only=True)\n", " ]\n", " # 训练模型\n", " model = lgb.train(\n", " params, train_dataset, num_boost_round=1000,\n", " valid_sets=[train_dataset, val_dataset], valid_names=['train', 'valid'],\n", " callbacks=callbacks\n", " )\n", "\n", " # 打印特征重要性(如果需要)\n", " if True:\n", " lgb.plot_metric(evals)\n", " lgb.plot_importance(model, importance_type='split', max_num_features=20)\n", " plt.show()\n", "\n", "\n", " return model, scaler, None # 返回训练好的模型、scaler 和 pca 对象" ] }, { "cell_type": "code", "execution_count": 106, "id": "c6eb5cd4-e714-420a-ac48-39af3e11ee81", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T15:03:18.426481Z", "start_time": "2025-04-03T15:02:19.926352Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "train data size: 36400\n", " ts_code trade_date log_circ_mv\n", "0 600306.SH 2020-01-02 11.552040\n", "1 603269.SH 2020-01-02 11.324801\n", "2 002633.SZ 2020-01-02 11.759023\n", "3 603991.SH 2020-01-02 11.181150\n", "4 000691.SZ 2020-01-02 11.677910\n", "... ... ... ...\n", "36395 600615.SH 2022-12-30 12.027909\n", "36396 603829.SH 2022-12-30 12.034572\n", "36397 603037.SH 2022-12-30 12.035767\n", "36398 002767.SZ 2022-12-30 11.896427\n", "36399 600561.SH 2022-12-30 11.858571\n", "\n", "[36400 rows x 3 columns]\n", "原始样本数: 36400, 去除标签为空后样本数: 36400\n" ] }, { "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "0a4d14d383d0499e81773abe038f7d1d", "version_major": 2, "version_minor": 0 }, "text/plain": [ "MetricVisualizer(layout=Layout(align_self='stretch', height='500px'))" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "0:\tlearn: 0.6888297\ttest: 0.6894367\tbest: 0.6894367 (0)\ttotal: 30.9ms\tremaining: 30.9s\n", "Stopped by overfitting detector (300 iterations wait)\n", "\n", "bestTest = 0.5057357077\n", "bestIteration = 665\n", "\n", "Shrink model to first 666 iterations.\n" ] } ], "source": [ "\n", "gc.collect()\n", "\n", "use_pca = False\n", "type = 'cat'\n", "# feature_contri = [2 if feat.startswith('act_factor') or 'buy' in feat or 'sell' in feat else 1 for feat in feature_columns]\n", "# light_params['feature_contri'] = feature_contri\n", "# print(f'feature_contri: {feature_contri}')\n", "model, scaler, pca = train_model(train_data\n", " .dropna(subset=['label']).groupby('trade_date', group_keys=False)\n", " .apply(lambda x: x.nsmallest(50, 'total_mv'))\n", " .merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n", " .merge(index_data, on='trade_date', how='left'), feature_columns, type=type)\n" ] }, { "cell_type": "code", "execution_count": 107, "id": "5d1522a7538db91b", "metadata": { "ExecuteTime": { "end_time": "2025-04-03T15:04:39.656944Z", "start_time": "2025-04-03T15:04:39.298483Z" } }, "outputs": [], "source": [ "score_df = test_data.groupby('trade_date', group_keys=False).apply(lambda x: x.nsmallest(300, 'total_mv'))\n", "# score_df = fill_nan_with_daily_median(score_df, ['pe_ttm'])\n", "# score_df = score_df[score_df['pe_ttm'] > 0]\n", "score_df = score_df.merge(industry_df, on=['cat_l2_code', 'trade_date'], how='left')\n", "score_df = score_df.merge(index_data, on='trade_date', how='left')\n", "# score_df = score_df.groupby('trade_date', group_keys=False).apply(lambda x: x.nsmallest(50, 'total_mv')).reset_index()\n", "numeric_columns = score_df.select_dtypes(include=['float64', 'int64']).columns\n", "numeric_columns = [col for col in feature_columns if col in numeric_columns]\n", "# score_df.loc[:, numeric_columns] = scaler.transform(score_df[numeric_columns])\n", "# score_df = cross_sectional_standardization(score_df, numeric_columns)\n", "\n", "if type == 'cat':\n", " score_df['score'] = model.predict_proba(score_df[feature_columns])[:, 1]\n", "elif type == 'light':\n", " score_df['score'] = model.predict(score_df[feature_columns])\n", "score_df['score_ranks'] = score_df.groupby('trade_date')['score'].rank(ascending=True)\n", "\n", "score_df = score_df.groupby('trade_date', group_keys=False).apply(\n", " lambda x: x[x['score'] >= x['score'].quantile(0.90)] # 计算90%分位数作为阈值,筛选分数>=阈值的行\n", ").reset_index(drop=True) # drop=True 避免添加旧索引列\n", "# save_df = score_df.groupby('trade_date', group_keys=False).apply(lambda x: x.nlargest(1, 'score')).reset_index()\n", "save_df = score_df.groupby('trade_date', group_keys=False).apply(lambda x: x.nsmallest(2, 'total_mv')).reset_index()\n", "save_df = save_df.sort_values(['trade_date', 'score'])\n", "save_df[['trade_date', 'score', 'ts_code']].to_csv('predictions_test.tsv', index=False)\n" ] }, { "cell_type": "code", "execution_count": 108, "id": "09b1799e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "191\n", "['vol', 'pct_chg', 'turnover_rate', 'volume_ratio', 'winner_rate', 'undist_profit_ps', 'ocfps', 'AR', 'BR', 'AR_BR', 'cashflow_to_ev_factor', 'book_to_price_ratio', 'turnover_rate_mean_5', 'variance_20', 'bbi_ratio_factor', 'daily_deviation', 'lg_elg_net_buy_vol', 'flow_lg_elg_intensity', 'sm_net_buy_vol', 'total_buy_vol', 'lg_elg_buy_prop', 'flow_struct_buy_change', 'lg_elg_net_buy_vol_change', 'flow_lg_elg_accel', 'chip_concentration_range', 'chip_skewness', 'floating_chip_proxy', 'cost_support_15pct_change', 'cat_winner_price_zone', 'flow_chip_consistency', 'profit_taking_vs_absorb', 'cat_is_positive', 'upside_vol', 'downside_vol', 'vol_ratio', 'return_skew', 'return_kurtosis', 'volume_change_rate', 'cat_volume_breakout', 'turnover_deviation', 'cat_turnover_spike', 'avg_volume_ratio', 'cat_volume_ratio_breakout', 'vol_spike', 'vol_std_5', 'atr_14', 'atr_6', 'obv', 'maobv_6', 'rsi_3', 'return_5', 'return_20', 'std_return_5', 'std_return_90', 'std_return_90_2', 'act_factor1', 'act_factor2', 'act_factor3', 'act_factor4', 'rank_act_factor1', 'rank_act_factor2', 'rank_act_factor3', 'cov', 'delta_cov', 'alpha_22_improved', 'alpha_003', 'alpha_007', 'alpha_013', 'vol_break', 'weight_roc5', 'smallcap_concentration', 'cost_stability', 'high_cost_break_days', 'liquidity_risk', 'turnover_std', 'mv_volatility', 'volume_growth', 'mv_growth', 'momentum_factor', 'resonance_factor', 'log_close', 'cat_vol_spike', 'up', 'down', 'obv_maobv_6', 'std_return_5_over_std_return_90', 'std_return_90_minus_std_return_90_2', 'cat_af2', 'cat_af3', 'cat_af4', 'act_factor5', 'act_factor6', 'active_buy_volume_large', 'active_buy_volume_big', 'active_buy_volume_small', 'buy_lg_vol_minus_sell_lg_vol', 'buy_elg_vol_minus_sell_elg_vol', 'ctrl_strength', 'low_cost_dev', 'asymmetry', 'lock_factor', 'cat_vol_break', 'cost_atr_adj', 'cat_golden_resonance', 'mv_turnover_ratio', 'mv_adjusted_volume', 'mv_weighted_turnover', 'nonlinear_mv_volume', 'mv_volume_ratio', 'mv_momentum', 'lg_flow_mom_corr_20_60', 'lg_flow_accel', 'profit_pressure', 'underwater_resistance', 'cost_conc_std_20', 'profit_decay_20', 'vol_amp_loss_20', 'vol_drop_profit_cnt_5', 'lg_flow_vol_interact_20', 'cost_break_confirm_cnt_5', 'atr_norm_channel_pos_14', 'turnover_diff_skew_20', 'lg_sm_flow_diverge_20', 'pullback_strong_20_20', 'vol_wgt_hist_pos_20', 'vol_adj_roc_20', 'cs_rank_net_lg_flow_val', 'cs_rank_elg_buy_ratio', 'cs_rank_rel_profit_margin', 'cs_rank_cost_breadth', 'cs_rank_dist_to_upper_cost', 'cs_rank_winner_rate', 'cs_rank_intraday_range', 'cs_rank_close_pos_in_range', 'cs_rank_pos_in_hist_range', 'cs_rank_vol_x_profit_margin', 'cs_rank_lg_flow_price_concordance', 'cs_rank_turnover_per_winner', 'cs_rank_volume_ratio', 'cs_rank_elg_buy_sell_sm_ratio', 'cs_rank_cost_dist_vol_ratio', 'cs_rank_size', 'cat_up_limit', 'industry_obv', 'industry_return_5', 'industry_return_20', 'industry__ema_5', 'industry__ema_13', 'industry__ema_20', 'industry__ema_60', 'industry_act_factor1', 'industry_act_factor2', 'industry_act_factor3', 'industry_act_factor4', 'industry_act_factor5', 'industry_act_factor6', 'industry_rank_act_factor1', 'industry_rank_act_factor2', 'industry_rank_act_factor3', 'industry_return_5_percentile', 'industry_return_20_percentile', '000852.SH_MACD', '000905.SH_MACD', '399006.SZ_MACD', '000852.SH_MACD_hist', '000905.SH_MACD_hist', '399006.SZ_MACD_hist', '000852.SH_RSI', '000905.SH_RSI', '399006.SZ_RSI', '000852.SH_Signal_line', '000905.SH_Signal_line', '399006.SZ_Signal_line', '000852.SH_amount_change_rate', '000905.SH_amount_change_rate', '399006.SZ_amount_change_rate', '000852.SH_amount_mean', '000905.SH_amount_mean', '399006.SZ_amount_mean', '000852.SH_daily_return', '000905.SH_daily_return', '399006.SZ_daily_return', '000852.SH_up_ratio_20d', '000905.SH_up_ratio_20d', '399006.SZ_up_ratio_20d', '000852.SH_volatility', '000905.SH_volatility', '399006.SZ_volatility', '000852.SH_volume_change_rate', '000905.SH_volume_change_rate', '399006.SZ_volume_change_rate']\n", "[]\n" ] } ], "source": [ "print(len(feature_columns))\n", "print(feature_columns)\n", "print([col for col in feature_columns if 'total_mv' in col])" ] }, { "cell_type": "code", "execution_count": 109, "id": "e53b209a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5595 2057465\n", " ts_code trade_date turnover_rate\n", "0 000001.SZ 2023-01-03 1.1307\n", "1 000001.SZ 2023-01-04 1.1284\n", "2 000001.SZ 2023-01-05 0.8582\n", "3 000001.SZ 2023-01-06 0.6162\n", "4 000001.SZ 2023-01-09 0.5450\n", "... ... ... ...\n", "1781701 605599.SH 2025-05-26 0.6188\n", "1781702 605599.SH 2025-05-27 1.2576\n", "1781703 605599.SH 2025-05-28 2.0432\n", "1781704 605599.SH 2025-05-29 2.0954\n", "1781705 605599.SH 2025-05-30 1.4392\n", "\n", "[1781706 rows x 3 columns]\n" ] } ], "source": [ "print(len(train_data[train_data['pct_chg'] > 7]), len(train_data))\n", "print(test_data[['ts_code', 'trade_date', 'turnover_rate']])" ] }, { "cell_type": "code", "execution_count": 110, "id": "364e821a", "metadata": {}, "outputs": [], "source": [ "from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, roc_curve\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "def calculate_binary_classification_metrics(df: pd.DataFrame, score_col: str, label_col: str, future_return_col: str = None, total_mv_col: str = None, n_mv_bins: int = 10, threshold: float = 0.5):\n", " \"\"\"\n", " 计算二分类模型的评估指标,可选择计算 score 和 future_return 的相关性,\n", " 并可选择计算 score 在按总市值 (total_mv) 分为 n 份后的每个分组上的预测性能(ROC AUC)。\n", "\n", " Args:\n", " df: 包含 score (预测概率或置信度), label (真实二分类标签), 可选的 future_return 和 total_mv 的 Pandas DataFrame。\n", " score_col: 包含模型预测 score 的列名。\n", " label_col: 包含真实二分类标签 (0 或 1) 的列名。\n", " future_return_col: (可选) 包含未来收益率的列名,用于计算相关性。\n", " total_mv_col: (可选) 包含总市值的列名,用于按市值分 n 份分析预测性能。\n", " n_mv_bins: (可选) 将总市值分为多少份,默认为 5。\n", " threshold: 将 score 转换为预测类别的阈值,默认为 0.5。\n", "\n", " Returns:\n", " 一个包含以下评估指标的字典:\n", " - accuracy: 准确率\n", " - precision: 精确率\n", " - recall: 召回率\n", " - f1: F1 分数\n", " - roc_auc: ROC AUC 值\n", " - fpr: ROC 曲线的假正率 (False Positive Rate)\n", " - tpr: ROC 曲线的真正率 (True Positive Rate)\n", " - thresholds: ROC 曲线的阈值\n", " - score_return_correlation: (如果 future_return_col 提供) score 和 future_return 的皮尔逊相关系数\n", " - mv_roc_auc: (如果 total_mv_col 提供) 一个字典,包含按总市值分为 n 份后的每个市值分组对应的 ROC AUC 值\n", " \"\"\"\n", " y_true = df[label_col].values\n", " y_score = df[score_col].values\n", " y_pred = (y_score >= threshold).astype(int)\n", "\n", " metrics = {}\n", " metrics['accuracy'] = accuracy_score(y_true, y_pred)\n", " metrics['precision'] = precision_score(y_true, y_pred)\n", " metrics['recall'] = recall_score(y_true, y_pred)\n", " metrics['f1'] = f1_score(y_true, y_pred)\n", " metrics['roc_auc'] = roc_auc_score(y_true, y_score)\n", " metrics['fpr'], metrics['tpr'], metrics['thresholds'] = roc_curve(y_true, y_score)\n", "\n", " if future_return_col in df.columns:\n", " metrics['score_return_correlation'] = df[score_col].corr(df[future_return_col])\n", "\n", " if total_mv_col in df.columns and n_mv_bins > 1:\n", " metrics['mv_roc_auc'] = {}\n", " df['mv_quantile'] = pd.cut(df[total_mv_col], bins=n_mv_bins, labels=False, duplicates='drop')\n", " for i in range(df['mv_quantile'].nunique()):\n", " mv_group = df[df['mv_quantile'] == i]\n", " if len(mv_group) > 0 and len(np.unique(mv_group[label_col])) > 1 and len(np.unique(mv_group[score_col])) > 1:\n", " roc_auc_mv = roc_auc_score(mv_group[label_col], mv_group[score_col])\n", " lower_bound = df[total_mv_col][df['mv_quantile'] == i].min()\n", " upper_bound = df[total_mv_col][df['mv_quantile'] == i].max()\n", " metrics['mv_roc_auc'][f'{lower_bound:.0e}-{upper_bound:.0e}'] = roc_auc_mv\n", " else:\n", " lower_bound = df[total_mv_col][df['mv_quantile'] == i].min()\n", " upper_bound = df[total_mv_col][df['mv_quantile'] == i].max()\n", " metrics['mv_roc_auc'][f'{lower_bound:.0e}-{upper_bound:.0e}'] = np.nan\n", " print(f'{lower_bound:.0e}-{upper_bound:.0e}')\n", " df.drop(columns=['mv_quantile'], inplace=True)\n", "\n", " return metrics\n", "\n", "def plot_roc_curve(metrics: dict):\n", " plt.figure(figsize=(8, 6))\n", " plt.plot(metrics['fpr'], metrics['tpr'], color='darkorange', lw=2, label=f'ROC curve (AUC = {metrics[\"roc_auc\"]:.2f})')\n", " plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')\n", " plt.xlabel('False Positive Rate')\n", " plt.ylabel('True Positive Rate')\n", " plt.title('Receiver Operating Characteristic (ROC)')\n", " plt.legend(loc=\"lower right\")\n", " plt.grid(True)\n", " plt.show()\n" ] }, { "cell_type": "code", "execution_count": 111, "id": "1f6e6336", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6e+04-9e+04\n", "9e+04-1e+05\n", "1e+05-1e+05\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "1e+05-1e+05\n", "1e+05-2e+05\n", "2e+05-2e+05\n", "2e+05-2e+05\n", "2e+05-2e+05\n", "2e+05-3e+05\n", "3e+05-3e+05\n", "二分类评估指标:\n", "accuracy: 0.6687\n", "precision: 0.4667\n", "recall: 0.0134\n", "f1: 0.0260\n", "roc_auc: 0.6166\n", "fpr: (array of length 7520)\n", "tpr: (array of length 7520)\n", "thresholds: (array of length 7520)\n", "score_return_correlation: -0.0419\n", "mv_roc_auc: {'6e+04-9e+04': np.float64(0.6129972565157751), '9e+04-1e+05': np.float64(0.5481528934443733), '1e+05-1e+05': np.float64(0.5819692706968757), '1e+05-2e+05': np.float64(0.5802354633555421), '2e+05-2e+05': np.float64(0.610526564518331), '2e+05-3e+05': np.float64(0.6141327685032996), '3e+05-3e+05': np.float64(0.6069017365995996)}\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAArcAAAIkCAYAAAAEbwOaAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjMsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvZiW1igAAAAlwSFlzAAAPYQAAD2EBqD+naQAAm+1JREFUeJzs3Xd0FOXbxvHvphda6L13BEIvIr33LkoXkCJF1J9YEQUbIkgvgtKkhqYgIiAgdrpIRzpIDRDSy877R15WlwQSUnayyfU5xyN7z8zOvZlNcmX2mWcshmEYiIiIiIikAy5mNyAiIiIiklIUbkVEREQk3VC4FREREZF0Q+FWRERERNINhVsRERERSTcUbkVEREQk3VC4FREREZF0Q+FWRERERNINhVsREZF05MqVKwQHBztsf6dOnXLYvkQSQ+FWREQknbh37x6DBg1ix44dDtnfhQsX6N27N8eOHXPI/kQSQ+FWJIX9/vvvlClTxvZfrVq1GDp0KH///bcp/axdu5bGjRubsu/EuHfvHq+99hrVqlWjRo0ajBs3joiICLPbSrLGjRuzdu1a0/YfEBBA06ZNqVSpEgMHDuTy5ctppre06LXXXuO1115L1X307t2b6dOnp+o+7nvzzTepXbs27dq1A7D7WVSlShW6d+/Ojz/+aLfN6dOn6d27N5UqVaJRo0YsWbIkzvM+7H1VuHBhxo0bxwsvvEBYWFjqv0CRRFC4FUklH3/8MatXr+add97h3Llz9OzZk7t37zq8j0aNGjF79myH7zexXnrpJXbs2MG4ceN47bXX2LBhA++//77ZbcVr4cKFCZ6hmj17No0aNXJQR/Y2btzI2LFjeeaZZ5g9ezbBwcG88MILGIZhSj/JcenSJYcEwuHDhzN8+PAkb7927Vp+//33R67z7rvv0r179yTvI7F++uknjh8/zpgxY+zqffr0ISAggClTppA9e3YGDx7MH3/8AcT+cdmvXz9CQkKYPn06bdu2ZcKECWzcuNG2fULvq5YtW1K1alXmzp2b6q9RJFEMEUlRv/32m1G6dGnjzz//tNVOnjxplC5d2li3bp15jaVBx48fN0qXLm1s2rTJVvvyyy+NcuXKGbdv3zavsYdo1KiRsWbNGrPbiJfVajUaNmxojBs3zlY7ffq0Ubp0aWPnzp2GYaTt/h90//sorevVq5cxbdo0s9swDMMw+vXrZ2zcuNGuVrp0aWP+/Pm2xxEREcZTTz1lvPbaa4ZhGMbixYuN8uXLG1evXrWtM2TIEKNDhw6GYSTufWUYhnH16lWjZs2aRkRERGq8NJHHojO3Ig5QqlQpvL29uXr1qtmtpCl79uwBoH79+rZa3bp1iYmJYf/+/Wa15ZT++usvrly5QqtWrWy1EiVK4Ovrqwt+MoA7d+5w+PBhmjRp8sj1PDw8KFOmDNeuXQNivwfLlClDnjx5bOs8+eSTHDt2jODg4ES/r/LkyUPp0qX5+eefU/iViTw+hVsRB7h37x5hYWFkz57dVtuyZQvt2rWjUqVKdOzYkV9//dVum9OnT9O/f38qVapEw4YN+eyzz4iOjrYtj4iI4P3336du3brUqFGDF198kcDAwDj7ftiY2969ezN27Fi72sSJE+ncubPt8Z07dxgzZgw1atSgTp06jB07ltDQUNvyS5cuUaZMGS5dukRAQADt2rXj7bffTvTX5eLFi2TNmpVMmTLZavnz57c99/3n/+KLL2jRogVVq1Zl6NChtl/M9yX0tbw/rvLq1au89NJL1KpVy24samhoKGPHjqVu3bpUq1aNAQMGcPHiRbvXWKZMGS5fvszrr79uexyf+Ma13n+OEydO8Oqrr1K1alXq16/PunXr7Nb76quvaNCgAbVr1+bjjz/m+eeftwv+j3Ly5EkAihcvblefM2dOnOM/d+5cnnzySWrUqMH48ePthi3s3r2bzp07U7lyZRo3bszChQvttr3/foqMjGTSpEk0bNiQ9evX262zcOFCmjRpQuXKlenQoQO//PKL3fJHvbenT59OmTJl6NOnD/DvmNEHx8Um95g/uF585s2bR+PGjalcuTKdO3e2G35wv68//viDGTNm2B5funQpzvM8bMytYRjMnTuXBg0aUKVKFXr16sVff/0Vby8JOX36NKVKlcLLyyvBdW/evImfnx8Q+z14/3vuvnz58gGx79vHeV9VrVqVEydOJKl/kZSkcCuSygIDA3n33Xdxd3e3BZXff/+dUaNG0bRpUxYsWEDFihUZNGiQ7aKza9eu0bNnTwzDYM6cOYwaNYpFixbZjZ0dN24cW7Zs4e2332bSpEmcPHnyscYOtmrVip07d9rVdu7cSZs2bWyPR4wYwZEjR/jkk08YN24cP/zwA++8806c51qwYAFz5syhbdu2tG7dOtE9hISE4O3tbVe7/zgkJMRWmzFjBgMHDmTSpEmcPXuWoUOH2gJZQl/L++7cucMzzzyDm5sbI0aMIGvWrLZlH3zwAdu3b+fdd99l5syZhIWF8dZbbwGQO3duAgICCAgIIFeuXAwfPtz2+HG9+uqrWCwWZs6cSfXq1Rk7dqztD5K9e/fy4YcfMnr0aCZMmEBAQAAlS5Zk2rRpiXru+8/z39cFULNmTbtgsmjRIrZv387777/PwIEDWbp0qe19cPHiRYYNG0aFChVYsGABAwcO5OOPP2bv3r1x9jdixAgOHDhA//79qVixoq3+9ddf8/HHH9OvXz8WLFhAlSpVGDlypG1qqoTe2927dycgIIB3330XwPa1/u97OyWOeUK2bdvGp59+Sp8+fZg7dy5ly5blhRdeIDIy0q6vChUq2HoOCAggd+7cid7H5MmTmT17NgMGDGDOnDlkzZqVAQMGcPv27UQ/x303btwgV65cj1wnLCyMRYsWcfToUZo2bQrE/z3o4+NjW5bY9xXEfq9cv379sXsXSWluZjcgkl517drV9u/s2bPz6aefkjdvXiA2rDVq1IhRo0YBUK1aNbZu3cqmTZsYOXIky5YtwzAMZsyYYTuree/ePW7cuAHEnlFZt24d06dPp1mzZgBER0czbNgwLl68SKFChRLsr2XLlkyYMIGjR49Svnx5Ll68yJkzZ2wfP/7xxx/88ccfrFu3jvLlywOxwWTixIm8//77eHh42J7r559/ZvXq1bazQclxP7RaLBZbbcCAAXTr1g2I/cXbt29f9u/fT7Vq1RL8Wt63Y8cO3njjDfr27Rtnn3Xr1qVz585UrVoViP2I//6ZNg8PD1t48/DwoECBAnZh7nHky5ePjz/+GIg987dp0ybOnj1L9uzZOXToEOXKlaNjx45A7EU8wcHB+Pv7J+q574cuV1fXR65369YttmzZgq+vLw0bNuTrr7/m+PHjNGrUCKvVytixY2nfvj2enp6ULVuWWbNmceDAAapXr257jsuXL1OhQgWWLFmCi4v9OZL8+fPz2Wef0aJFCyD2a7Z8+XLOnDlDpUqVEnxv58mThzx58tg+IYjva50Sxzwhly5dwt3dne7du+Pj40OFChVo1qwZMTExdn35+vqSO3fux35PhISEsHDhQoYOHWo7S128eHHGjx/PP//889jfSy4uLg+9cHDixIlMnDgRiD0eQ4YMsRtm8KD/fg8m9n0FEBMTE+f9IGIGhVuRVDJp0iRKlCjBsGHDqFatmu2XPcR+hHznzp04H22fP38egKNHj1KmTBm7j+vv/wK8v71hGPGeqT1//nyiwm327NmpXbs2O3bsoHz58uzYsQN/f3/bR5T3P17s1KlTnG0vX75MsWLFbI9ffPHFJAXbTJky2Q1zAGzTCf33td8PnfBvqLhw4QLVqlVL8Gt5X6lSpejdu3e8fTRt2pR169axcOFCDh48yI0bN7BarY/9ehLSq1cv27/vD1G5/3F88eLFOXPmDH///TeZMmXi8OHDdsc8Ib6+vkDsEIv/fu2GDx9OnTp16NmzJwCdO3e2rXu/j/s9FClShJCQEKZOncrevXs5evQoMTExhIeH2+3L3d2dN954I94gU716dXbs2MFbb73F/v37bcfh/nFN6L2dGClxzBPSvHlz5s2bR9u2balbty5VqlShRYsWcc5yJtXff/9NZGQk1apVs9Vy5cqV6DP1D8qVK9dDz5r269ePjh07MmPGDI4dO2b3cyO+78H7jzNlypTo9xXEnj1+nDPXIqlF4VYklRQtWpTy5cszaNAg3n//fUaMGEHRokVty5999tk40wNlzpwZIN4zMDdv3uTcuXN2QW/+/PnkzJnTbr3EBNv7WrduzcqVK3nhhRfYuXNnnCEFrq6uBAQE2J1FBeKM0atUqVKi9/lgr0FBQdy7d8/22q9cuWJbFp/4zuw+6mt53xNPPBFvGIuJiaFXr14EBgbSrVs3unXrhtVq5fnnn0/Sa3qUwoULP3RZyZIlcXFxsR2Dhg0b8swzzyT6uYsUKQLEDi0oV66crf7zzz/bnVV81Ptj27ZtjBw5kjZt2vDss89SqVKleIeh5M6d2zYu80Eff/wxK1asoEePHowaNYpq1arx5JNP2pYn9N5O7Jm/5BzzxMifPz9btmxh9+7d7Nu3jylTpjBr1iw2bNhgF/KS6mFnWf/880+yZ89OwYIFH+v5SpcuzenTpwkODo7TX+7cuSlXrhyjRo2iffv2bNiwwfbJUqFCheL8UfDPP/9gsVgoUKBAot9XEPtpz/2z6SJm0ucHIqmsW7du5MyZ024OyFKlSnHjxg3KlStn+2/79u3s2rULgPLly3PixAm7W2guW7aMF154ARcXF0qVKgXEfhR9f/scOXKwYMGCeC+aeZhmzZpx4sQJLly4wL59+2jZsqVdj/c/Zry/DxcXFxYsWJBi8/XWqlULwPa6IfaXpru7O1WqVLHV7s/JCXDo0CEA2x8KCX0tE3Lq1CkOHTrEhAkTGDx4ME899ZQtYD/Iw8PD9rF0UjwqaL3//vu8+OKL7N69mx9//JG5c+faDf1ISI0aNfDy8mLbtm222pEjRwgNDaVChQq22qM+Xl63bh1Vq1blk08+oWPHjhQoUOCx3k8QOxa1f//+jBkzhhYtWsR5ryT03r7P09MTwO4iyvuSe8wTY/Xq1fz++++0bt2at99+m6VLl3Lp0qU4F655eHjE22NCSpQogbu7u9145oiICHr27Jmku4tlypSJGjVqsHnz5oeuU7p0aZo3b87nn39uex/XqlWLkydP2s3k8vPPP1OhQgV8fX0T/b46f/48Fy5coGbNmo/du0hK05lbkVTm4eHBwIEDmThxIsOHD6dAgQK88MIL9O/fnylTplCvXj0OHDjAzJkzmTp1KhB7VmrFihUMHz6cQYMGcf36dZYuXUqPHj2A2LMtHTp04L333iMkJITcuXMzb948Tp06xbhx4xLdW9asWalbty7jx4+nUqVKdh8p1q5dmxo1avDKK68watQovLy8mDJlCtHR0QleuJJYJUuWpHHjxrz33nsYhkFERATTpk2jW7duZMmShaCgICD2Iqh8+fKRI0cOPv74YypVqmQbi5rQ1zIxXwOLxcK3336Lm5sbv/zyC59//jkQG6zc3P79MVm5cmU2bNhAsWLFCAoKwsXFhYYNG6bI18LV1ZVNmzZRsGBBsmfPTmhoKAUKFEh0wM2UKRNDhgxhxowZ+Pr6UqJECT7++GNKly5NnTp1EvUcfn5+7Nu3j127dhEREcH8+fO5fPnyY4U3Pz8/fv75Z+rUqcPZs2dtY5fvh6mE3tv3lSxZEl9fXz7//HNq167N0aNHadGiBTlz5kz2MU+MmzdvMmPGDCIjI8mbNy/fffcdQJwzqpUrV+a7776jbt26REZGcvfuXdq2bZvg82fKlIk+ffrw+eefkzlzZkqXLs3y5cvx8fGxG8L0OIYMGcLIkSNp1arVQ88uDxs2jI4dO/Ltt9/Srl07OnTowJw5cxg6dCgvvvgie/fuZceOHXz22We2PhPzvvrggw8YNGiQ3feLiGkcP7WuSPoW300cwsPDjSeffNJ45513bLVNmzYZbdq0MZ544gmjZcuWcSbXP3HihNG3b1+jYsWKRqNGjYzp06cbkZGRtuWhoaHG+PHjjdq1axv+/v7GgAEDjNOnT8fpZ82aNUajRo0e2u/69euN0qVLG8uXL4+z7NatW8Yrr7xiVKtWzahevboxatQou8neL168aJQuXdq4ePFior428bl3757x2muvGVWrVjWqV69uvPvuu7aJ4O8//5IlS4yWLVsaTzzxhPHcc88Zly5dsnuOhL6WY8aMMcaMGfPQHlatWmU0bNjQeOKJJ4wePXoYAQEBRunSpY3ffvvNbr3r168bgwYNMipVqmRUq1bNWLVqVZzniu9GCQ/7Ov13H9u3bzcqVqxo1KxZ0yhbtqxRunRpw9/f39i8eXMCX0F7X3zxhdGgQQOjYsWKxuDBg+2OV3y9/fcmBNevXzcGDhxoVK5c2XjyySeNTz75xBg2bJjRq1cv2/oJvZ/27dtndOzY0XjiiSeM5s2bG+vWrTNq1apld6ODhN7b9/3www9G8+bNjfLlyxtNmjQxrl27ZluW3GOe0HqRkZHGJ598YjRq1Mj2WuI73iEhIcZLL71kVKlSxfD39zemT58eZ52H3eghJibGmDVrllGvXj3D39/f6NOnj3H06NEEe36UsWPHGiNGjDBiYmIMw4h7EwfDMIxhw4YZbdq0MaxWq2EYsTdl6N27t/HEE08YDRs2NJYuXRrneR/1vlq4cKHRrl27eI+hiBkshuGE92UUkQzh0qVLNGnShICAgCTPUOAMQkJCqFOnDi+//DIVK1bEzc2NwMBApk6dSoECBZgxY4bZLYqTiIyMZMCAAXTq1MluzurUcuHCBXr16sXy5cspUKBAqu9PJDEUbkUkzcoo4RZibxjw9ddfc/nyZaKiovDz86NmzZqMHDnSdlGPSGIEBwfj7e2dqOm7UsKdO3fIli2bQ/YlkhgKtyIiIiKSbmi2BBERERFJNxRuRURERCTdULgVERERkXRD4VZERERE0g3NtgwcOHAAwzBwd3c3uxURERERiUdUVBQWi8XuDpbx0ZlbYu/x7chJIwzDIDIy0qH7lJSlY+jcdPycn46h89MxdG5mHL/E5jWduQXbGVtHzaMZGhrKsWPHKFmyJD4+Pg7Zp6QsHUPnpuPn/HQMnZ+OoXMz4/gdPnw4UevpzK2IiIiIpBsKtyIiIiKSbijcioiIiEi6oXArIiIiIumGwq2IiIiIpBsKtyIiIiKSbijcioiIiEi6oXArIiIiIumGwq2IiIiIpBsKtyIiIiKSbijcioiIiEi6oXArIiIiIumGwq2IiIiIpBsKtyIiIiKSbijcioiIiEi6YXq4tVqtjBgxgunTpyd6m5iYGD7++GNq1apFo0aN+Pbbb1OxQxERERFxFm5m7jwiIoK3336b77//ntKlSyd6u88++4wNGzYwffp03N3dGTFiBEWKFKFChQqp2K2IiIiIpHWmnrkdN24c7u7uVKlSJdHbREZGsnTpUoYPH07NmjWpUqUKffr0YdmyZanYqYiIiIjYRIfhFhlodhfxMvXM7ZAhQyhSpAi9e/dO9DZnz54lNDSUp556ylarUqUKX3/9dbJ6MQyD0NDQZD1HYoWFhdn9X5yPjqFz0/FzfjqGzk/H0IlEheK+/xNcz36NJfQal67F4GoxqJQ1mNCItwmtOcYhbRiGgcViSXA9U8NtkSJFHnube/fu4erqSqFChWy1LFmycO3atWT1EhUVxbFjx5L1HI/r3LlzDt2fpDwdQ+em4+f8dAydn45hGmFY8Qo9S6a7h/AKPUvWm7uxYOAZftlutW+OlKbfyo5UyHODH4Yswu3IfP7K3N5hbXp4eCS4jqnhNinc3NzivDBvb+9kn3V1d3enZMmSyXqOxAoLC+PcuXMULVoUb29vh+xTUpaOoXPT8XN+OobOT8cwDYiJwuXqr3h93SrBVSOjXXn92yZM/rEuALvPFuGTnxsx7ONXKVe4XGp3CsDp06cTtZ7ThVs/Pz/CwsIIDg4mU6ZMQOzZ3MQk+UexWCz4+PikRIuJ5u3t7fB9SsrSMXRuOn7OT8fQ+ekYOlhkMGwbCseWJnqTc3fz0mNpF34/m8tWa9euJLVfbI574coOO36JGZIAThhuCxUqRM6cOdm3bx8NGjQA4PDhw+TLl8/kzkRERETSkJhIOLMRdr8O4YEQdjPhbfLVgcKNIHdVyFGO9T9a6P/c19y5Ew6Au7sLkyY1Z8CAJzh+/Hgqv4CkSZPh1mq1EhwcjK+vL66urnbLXFxcaN26NdOmTaNatWpERUWxcOFCmjZtalK3IiIiImnEzb/gh5FwcUfi1vfOCVmKQIsvIVdFWzkiIppXX93KtGl/2GrFi/uxcmVXqlfP77CL8JMiTYbbK1eu0KRJE9avX0+5cnHHcYwcOZJBgwZRv359DMOgSJEiDBkyxIRORUREREwUdBHOfgsnA+DCtoTXz1UJbvwJVUdBg0ngEjcKhoVFUb/+QvbuvWKrdetWns8/b0fWrF4p2X2qSBPhdsmSJXaPCxYsyIkTJx66fubMmVm2bBn79u0jMjKSmjVr4u7untptioiIiJjHMCD8NhycCb+MTdw2WYpC1qLw5ATIXxcSMW7V29udmjXzs3fvFTw9XZkypQVDhlRP9JhXs6WJcJsULi4u1KhRw+w2RERERFJHVBj88SH8Nh7cM0FUcOK3bbUYyvUES9Lu1/Xppy24cSOUN954Cn//vEl6DrM4bbgVERERSXfObYGtgyHovH09oWDbcAoUrA+5/R870J48eYvjx2/Svn0ZW83Ly41Vq7o91vOkFQq3IiIiImY6tyX2IrDbJxNe180H8lSDbCWhyUxwT94cwV999SeDB2/EMGDv3kGUK5cr4Y3SOIVbEREREUc7thz2T4GrexJet1AjaPFF7NjZFBIaGsXIkZtZsOCArfb22zsICOieYvswi8KtiIiIiCNEhcCOF+Hw/Eev5+YDPX76/yEGKX8R17FjN+jePYC//rpuq/XtW5mZM1un+L7MoHArIiIikhpu/AlHFsK+KeCZFSLuPnr9fkchR+reynbRooMMG/YtoaFRAPj4uDNrVmv69vVP1f06ksKtiIiISEpb3QQu/PDv44cF2z6HIGfFVDlD+18hIZG88MK3LFp0yFZ74oncrFzZlfLlnX+c7X8p3IqIiIikBGtM7NRdv7wDhjX+dbIWhxLt4Mnx4JHZYa116LCC7dvP2h4PHFiFqVNb4eOT/u4ToHArIiIikhxRobC8TuwwhPg0mQWlu4KPeWdI33qrPjt2nMPHx525c9vy7LMVE97ISSncioiIiCTFjT9hceWHL89cCHrtBZ/cjuvpIRo2LMq8eW156qkilC6dw+x2UpXCrYiIiEhiGQb8+CrsnfTwdQo3hlZLIVM+x/X1HwcPXuXzz/cxfXprXFz+Hcs7YEBVU/pxNIVbERERkYRc3Anbh8OtIw9fp+Zr8NSHjuooDsMwmDNnL6NHbyEiIoYiRbLx6qtPmtaPWRRuRURERB5kGHDlF9j5Elz949Hrdt0KRZo6pq+HuHs3nEGDvmH16qO22tq1x3jppTq4uT3e7XidncKtiIiICEBkMJzdHDvbQeCxR6+buTD0PgDe2R3T2yPs3XuFp58O4MyZ27baiBE1+eSTZhku2ILCrYiIiGRkwVdgxyg4GZC49Z/5BfLVTvV5aRPDMAymT/+DV175nqio2KnHsmXz4osv2tOpU+reDCItU7gVERGRjCU6An5+G/ZPAWv0o9f1zQf1J0K5Z8GSds6C3r4dxoABX7Nu3XFbrWbNAqxc2ZWiRbOZ11gaoHArIiIi6Z9hhT8mwk9vAMaj1y3SHKqMgKLNwdXDIe09rgkTfrQLti+/XIcPPmiCh4eriV2lDQq3IiIikn4ZBkzPAlHBj16v2kvw1Efg6hx37Hr33UZs2nSKGzdCWbiwA+3alTG7pTRD4VZERETSn4OzYPsLj16ncFNo81WauMlCQqxWw27O2kyZPFi37mkyZfKgUKGsJnaW9ijcioiIiPOLCoEl1SD4Uuy/H6bm61BleOxY2jRwUVhi/PzzBYYO3cSGDT0oVszPVi9Xzrzb+aZlCrciIiLinMJuwdz8EBOZ8LqFGkL3HaneUkqyWg0mTvyZt976gZgYgx491rB7d3+Nq02Awq2IiIg4l9MbYEPHhNfzKwM9fwdP5/vY/vr1EPr0WceWLX/bap6ergQFRZAzp4+JnaV9CrciIiKSthkG7HoZDi+AyKBHr1u8DbRdCe6+juktFezadY5nnlnDP//EXgRnscCbbz7FO+80zJA3ZXhcCrciIiKSdv29Eda3e/Q67QKgdBfH9JOKYmKsfPDBbsaN24XVGjtdWZ48vixd2pmmTYub3J3zULgVERGRtMUw4ORq2Pj0w9fJVhK6bIFs6SP0Xb0aTK9ea9m+/ayt1rhxMb76qjN582YysTPno3ArIiIiace9SzCvUPzL/ErBs7+Dl1/8y53YgQP/2IKti4uFd95pwJtvPoWrq4YhPC6FWxERETFfVAgsrQGBx+Jf/vQuKFjfsT05UKtWpXjllTp89dVhli3rQsOGRc1uyWkp3IqIiIhpLEFn4Y8FsG9K/Ct0+yF2Gi8nmZM2sW7dCiV7dm8s/3ldH3zQhFdffZJcuZz3Yri0QOFWREREHC8qhGo7qz98efs1UKqz4/pxoC1bTtOr1zree68hQ4fWsNXd3V0VbFOAwq2IiIg4hmGFbUPhyCJ8YiLiX6dQI+i2Pd2dqQWIjrby9ts/8NFHPwMwevQW6tQphL9/XpM7S18UbkVERCR13TgMqxpB+K2Hr1P1Raj+MmQu6LC2HOnixbs888wafv75oq3WtGlxChbMYmJX6ZPCrYiIiKSO89shoOlDF4dmKo1b2W54NJjgwKYcb+PGk/Ttu57AwDAA3Nxc+PDDJrz0Uh1cXNLfGWqzKdyKiIhIyrFGw4XtsKblw9cp1IjQFis5dvoC5cqVw8Nx3TlUZGQMb7yxnU8//dVWK1w4KytXdqV27fR5hjotULgVERGR5Im4C7++B/smP3q97juhUIPYf4eGpnpbZrp8OYguXVbx+++XbbUOHcrwxRcdyJ7d28TO0j+FWxEREUmawJPwZZmE13vmV8hfO/X7SUN8fNy5ejUYAHd3Fz75pBkjR9aym/pLUofCrYiIiCRe+B04NAd+ev3R6xVuDDVfhyIPH3Obnvn5ebNiRVf69l3P0qWdqFGjgNktZRgKtyIiIvJohgG/vAO/jX/0ejXGQI1XwTu7Y/pKQ/7+OxAfH3fy5ctsq9WuXZAjR4bh5qZb6DqSwq2IiIjEb99nsHN0wut1+Q6Ktkj1dtKq1auPMHDgN1Stmo9t23rj6vpvmFWwdTyFWxEREflXWCBc2gVfP+LuYD65oXBTaD4P3DPuHbXCw6N56aUtzJ69F4CdO88xbdrvjB5dx+TOMjaFWxERkYwuIgh+ehMOznj0emWfgebzwd3HMX2lYadO3aJ79wAOHrxqq/Xo8QQDBlQ1sSsBhVsREZGMK/wObO4FZzY9er2+hyHnEw5pyRksX36Y55/fSHBwJABeXm5Mm9aSgQOrajaENEDhVkREJKO5dwnmFXr0OtVehpLtoWB9x/TkBEJDoxg1ajPz5x+w1cqUycGqVd2oVCmPiZ3JfyncioiIZBQxUbC2JVz4If7lTzwHjWeAu24y8KDg4Ejq1FnAX39dt9V6967ErFltyJQpvd5jzTkp3IqIiKR3EXdhYXkIvhL/8lpvQK23FGofIVMmD558shB//XUdb283Zs1qQ79+/ma3JfFQuBUREUnPDANmZIt/WetlUO4Zh7bjzKZMacHduxG8/XZ9ypfPZXY78hAKtyIiIulR0Hn4vGj8y+q+B7XfAl389FB//XWdM2du0779v7cX9vZ2Z/nyLiZ2JYmhcCsiIpJeGAasaQHnt8a/3K8MPHfcsT05GcMw+OKLA4wYsRkXFwt79z5P2bI5zW5LHoNumyEiIuLswu/Ar+NhssvDg22hRtDvL4e25Wzu3Yugd+91DBz4DWFh0YSERDF+/I9mtyWPSWduRUREnNXpDbCh48OX5ygPtcdC2acd1pKzOnToKt27B3Dy5C1bbciQakyenHFvK+ysFG5FREScjWHA0upwff9DVrDA6ChwcXVoW87IMAzmzt3Hiy9+R0REDACZM3swf357unevYHJ3khQKtyIiIs7CsMKPr8HeT+JfXqYHNJkB3jkc25eTuns3nOef38iqVUdstapV87FyZVdKlsxuYmeSHAq3IiIizuDoUtjcO/5lfQ5BrkqO7cfJGYZB27bL+emnC7baiBE1+eSTZnh6Kh45Mx09ERGRtComEn4YAX/Oi395/rrQ4ydN6ZUEFouFd99tSNOmi8mSxZMvvuhA587lzG5LUoDCrYiISFpzdQ98VfPR67wQCF5+juknnWrcuBjz57enUaOiFCumr2V6oXArIiKSVvz0Fvz+/qPXGXwFMuVzTD/pyB9/XGbhwoPMnNkay3/OdD/3XBUTu5LUoHArIiJipqgwWNcaLu58+Dol2kPbVeDm6aiu0g3DMJgy5TfGjNlGdLSVEiX8ePnluma3JalI4VZERMQsRxbDd30fvrznH5C3huP6SWcCA8Po128933xz0lbbuPEUo0fXwcVF45TTK4VbERERR/vnD1hW6+HLh/wDvnkd10869MsvF+nRI4CLF4NstTFjnmT8+EYKtumcwq2IiIgjHfsKvu0Vt168DXT8RjMfJJPVavDJJz/z5ps/EBNjAJAzpw9LlnSiZcuSJncnjqBwKyIi4giGAT+/Bb9/YF/3zAq9D0DWYub0lY7cuBFCnz7r+e6707Za/fpFWLasMwUKZDGxM3EkhVsREZHUtn8a7BgVt975WyjWyvH9pFPjx/9oC7YWC7z55lO8805D3NxcTO5MHEnhVkREJDWtbgIXfohb774DCjV0eDvp2fvvN+a7705z924ES5d2olmzEma3JCZQuBUREUkNj7pobOAZDUNIAVarYXdxWObMnqxf3wM/Py/y5ctsYmdiJp2nFxERSUnWaJjhF3+wHX4HXjYUbFPA9u1nqFhxNufP37Grly+fS8E2g1O4FRERSSknA2CKO0TcibtsVFjsxWOSLDExVt55ZwfNmi3h6NEb9OixhqioGLPbkjREwxJERESS495l2D0mdoqv+Dy9GwrWc2xP6dSVK/fo2XMtO3ees9WyZPEkJCSKbNlczWtM0hSFWxERkaQwDJj8iA9A67wDdcc5rJ30bsuW0/TuvY4bN0IBcHW1MH58I8aMqaebMogdhVsREZHHldBtc7v9AIUbOa6fdCw62srYsTv48MOfbLUCBTKzYkVX6tUrbGJnklYp3IqIiDyOh03t1XAy+A8HV3fH95ROXboUxDPPrOGnny7Yaq1bl2LRoo7kzOljYmeSlincioiIJIY1BqY85Nfmy4Zje8kgDh68agu2bm4ufPBBY15+ua6GIcgjKdyKiIgk5NxWWNM8bv35i5C5oOP7ySDati3N6NG1WbPmGCtWdKFOnUJmtyROQOFWRETkYR51I4YXAsHLz7H9pHPXr4eQK5cPFsu/Z2Y/+qgpb71Vn+zZvU3sTJyJ5rkVERH5r5hI2PQsfGqJP9gWbxc7DEHBNkWtX3+cMmVmMH/+fru6h4ergq08FlPD7c2bNxk2bBhVqlShc+fOHD9+PFHb/f333zz99NP4+/tTu3Zt3nnnHcLDw1O5WxERSfeOLoHPPOH48viXP/MrdPrasT2lc5GRMbz44nd06rSSO3fCGTnyOw4fvmZ2W+LETAu3hmEwfPhwAgMDCQgIoHfv3gwbNoyQkJAEt33hhRdo2rQpW7duZe7cufz222/MmDHDAV2LiEi6FHQRpvrC5j7xL++1P/Zsbf7aju0rnbt0KYQmTZYxdervtlrbtqUpXFh3cpOkM23M7f79+zlw4ACbNm2iRIkSlChRgo0bN7Jt2zY6dOjw0O0CAwM5e/YsPXv2xMfHh1y5ctGoUSP+/vtvB3YvIiLpxsqGcGlX3HrRFtB+Dbj7OryljGDduhMMHrybkJBoADw9XZkypQVDhlS3G3Mr8rhMC7dHjx4lf/78lCxZ0larUqUKhw4demS4zZo1K3nz5mX+/PkMHDiQCxcusHnzZl544YVk9WMYBqGhocl6jsQKCwuz+784Hx1D56bj5/xS4hha7p7Be1nFOHWrbz7Cu/4MPnkgCohyzO+GjCI8PJrXX9/BvHkHbbWSJf1YvLgdlSvn0felkzDj56hhGIn6w8e0cHvv3j0KF7a/s0jWrFk5duzYI7dzdXVl4sSJ9OvXj5kzZwLQpk0bunXrlqx+oqKiEtx3Sjt37pxD9ycpT8fQuen4Ob/HPYauUffIcvs3ih99Pf7nK/M2t/J1gPOBQGDyGxQ7V66E8sorezl5MshWa9EiP2+8UQkPj0COHdPX3Nk4+ueoh4dHguuYFm7d3Nzw9PS0q3l5eSV49jQsLIzXX3+doUOH8txzz3Hr1i3eeustJkyYwNtvv53kftzd3e3OIqemsLAwzp07R9GiRfH21hWgzkjH0Lnp+Dm/xz2Glpt/4r26ziPXCR10k9xu3uROqSYljty5wwgN3QOAl5crL79cnhdfrI+Pj+425mzM+Dl6+vTpRK1nWrj18/Pj5s2bdrXg4OAEE/mvv/5KcHAwI0eOBCBTpky88sor9OzZk9dffx03t6S9JIvF4vBvLm9vb31DOzkdQ+em4+f8EjyGd8/BoooQFfzwdbpugyJN0Dsh9fn4+LByZTeGDNnI/PmtcXW9iY+Pj74PnZgjf44mdiy2abMl+Pv7c+rUKYKC/v1o4vDhw+TLl++R28XExBAcHGw3xuPGjRvExMRgGLr9oYiIAJd/iZ2ndn6x+IPtEwOgx8+xMyAUaeL4/jKI48dvcvWq/de/bt1CHDw4hCeeyGVSV5LemRZuS5UqRfHixZk8eTJWq5UjR47w/fff07hxY6xWK0FBQcTExMTZrnz58ri4uPDKK6/w7bffsnjxYt59912aNGmCu7u7Ca9ERETSlD2TYMWT8S/rsiU20LaYDwXqOravDGbx4kNUqzaPXr3WEhNjtVvm4qLZECT1mHoThw8//JDt27dTt25dunfvTvv27alfvz5XrlyhRo0anDx5Ms42BQoUYO7cudy4cYO33nqLKVOmULlyZd577z0TXoGIiKQpP78DP/4vbr3BpNhQW7S543vKYEJCIunffwN9+64nNDSK7dvPMmfOXrPbkgzEtDG3EHsWdsuWLezZswc/Pz8qVaoEQMGCBTlx4sRDt3vyySd58smH/FUuIiIZ0y/j4LcHTnR0+R6KNjOlnYzoyJHrdO8ewNGjN2y1AQOq0L9/FRO7kozG1HALsYPLGzRoYHYbIiLirK7uha9qxK33Pgi5Kzu8nYzIMAy++OIAI0ZsJiws9qYMvr7uzJ3blp49K5ncnWQ0podbERGRpPAMOYfP7OrxLxx4FrIWdWg/GdW9exEMHbqJr746bKtVqpSHVau6UqZMThM7k4xK4VZERJzLvUv4zCvEEw9bPuIeeGRyZEcZ1t274dSsOZ+TJ2/ZakOGVGPy5BZ4e+sibzGHqReUiYiIPJb902FeofiXPft77EVjCrYOkzWrF089FXu30cyZPVi5siuzZ7dVsBVT6cytiIikfRFBMCNr/MvarYbSXR3bj9hMm9aK8PBoxo1rSMmS2c1uR0ThVkRE0jjDGm+wPVXxMwrUG6S7WznQ/v3/cPlyEO3albHVfHzcWbq0s4ldidjTsAQREUm7YiJhsqt9zeJCaP8LBOWoZ05PGZBhGMyY8Qd16izg2WfXcurUrYQ3EjGJwq2IiKRNt47BZ572tWKt4aUY8MphTk8Z0J074XTtupoRIzYTGRlDcHAkH330k9ltiTyUhiWIiEjaEfwPbO4FF36Iu6xQQ+i8yeEtZWR//HGZp58O4Ny5O7ba6NG1+eijpuY1JZIAhVsRETHfjT9h8SNuuOCeCbrvcFw/GZxhGEyZ8htjxmwjOtoKgJ+fFwsXdqR9+zIJbC1iLoVbERExj2GF9e3hzCPOyD53EvxKOa6nDC4wMIx+/dbzzTcnbbU6dQqyYkVXChd+yIwVImmIwq2IiJhjdTO4sC3+ZfU+gBr/Axf9mnIkwzBo0WIpe/desdVefbUuEyY0xt3d9RFbiqQd+qkhIiKOExMF0zNDTET8y3v+AXlrOLYnsbFYLHzwQWNatFhKjhw+LF7ckVatdNZcnIvCrYiIOMatY7CwfPzLcvlDj926u1ga0KxZCRYsaE/z5iUoUCCL2e2IPDaFWxERSV0xkTArJ0Tei7useFvosB5c9JG3GX788TzLlh1m9uw2WCwWW71//yomdiWSPAq3IiKSeo6vhE094tYz5YfnL8F/ApU4jtVq8OGHuxk7didWq0G5cjkZNaq22W2JpAjdxEFERFLHgpLxB9u2K2HwZQVbk1y7FkzLlkt5660dWK0GAJs3n8YwDJM7E0kZOnMrIiIpyzBgcjznTrIWhwGnFWpN9MMPZ+nZcy1XrwYDsYfinXca8NZb9e2GJYg4M4VbERFJWV/Ec3W9ZkEwVUyMlfHjf+S993Zx/wRt3ryZWLasM40aFTO3OZEUpnArIiIpIywQZueKvTHDf71k1dlaE125co+ePdeyc+c5W6158xIsWdKJ3Ll9zWtMJJUo3IqISPJYo2F1U7i0K+4yBVvTvffeLluwdXGxMH58I157rR4uLjoukj4p3IqISNIFnoQvy8S/7IVABds0YOLEZmzbdobw8GiWL+/CU08VMbslkVSlcCsiIo/v3PewpkX8y+q9DzVfV7A1SUyMFVfXfy/oy5LFk2++eYZcuXzJmdPHxM5EHENTgYmIyOOZlin+YJu7KoyOglpvKNia5NtvT1GhwiwuXrxrVy9XLpeCrWQYCrciIpI4YbfgUwtEhcRd1vsA9N4HLvpA0AxRUTG8+upW2rRZxokTt3jmmTVERcWY3ZaIKfRTSEREEmdWzri1Qecgi8Zwmun8+Tv06LGG3367ZKvlyOFDeHg07u66rbFkPAq3IiKSsIDmcWuaCcF0GzYcp3//Ddy+HQ6Au7sLEyc2Y9SoWropg2RYCrciIhK/kGuwvj1c/SPuspd1q1YzRUbGMGbMVj777HdbrVixbKxc2ZUaNQqY2JmI+RRuRUQkrh2jYf9n8S8bFe7QVsTe2bO3efrpAPbsuWKrdelSjvnz25Mtm5eJnYmkDQq3IiLyr+gIWNcWLmyLu8zVA4bdAjdPx/clNocOXbMFWw8PVyZPbs6wYTU0DEHk/ynciohIrGPL4NueceudN0Oxlo7vR+LVsWNZRoyoyebNp1m1qitVquQzuyWRNEVTgYmICGwfEX+w7fmHgq3Jrl4NxjDsxzh/8kkz9u17XsFWJB4KtyIiGZk1Onbu2oMz7OvVXoaXYiBvDXP6EgBWrPiL0qWn8+WXB+3qnp5uZMmi4SEi8VG4FRHJiAwDvnkaprjHXdbnEDScBBb9ijBLWFgUgwd/wzPPrOHevUiGD/+Wo0dvmN2WiFPQmFsRkYxomi9Eh8WtD78Lnlkc34/YnDhxk+7dA/jzz2u2Wpcu5SlcOKuJXYk4D4VbEZGMZqo3RD8wnVfZZ6DNMnP6EZulS/9kyJCNhIREAeDt7cbMma3p189fsyGIJJLCrYhIRhLQPG6w1Z3GTBcSEsmIEZvtxtaWL5+LVau6UqFCbvMaE3FCCrciIhlByDWYkzduXXcaM93p04F06LDCbkxt//7+TJ/eCl9fDxM7E3FOCrciIundue9hTYu49ZHBju9F4siWzYu7d2PPpvv6ujN7dht6965sclcizkuXwoqIpFcxUbDzpfiD7aAL4O7r+J4kjpw5fVi+vAtVquRl797nFWxFkklnbkVE0qMji+C7fnHrZXpA2+UOb0f+dfjwNfLkyUTu3P/+cfHUU0XYu/d5XFw09lkkuXTmVkQkvTm8IP5g23yBgq2JDMNg3rx91Kw5n96912G12o93VrAVSRkKtyIi6cnCCvD9QPta6W6x42srPmdOT0JQUATPPruWwYM3Eh4ezfff/82CBfvNbkskXdKwBBGR9OLTeM789doHeao6vhexOXDgH7p3D+D06UBbbfjwGhpbK5JKFG5FRJzdmU2wrm3c+qDzkKWw4/sRIHYYwqxZe3jppe+JjIwBIGtWTxYsaE+XLuVN7k4k/VK4FRFxZl+Uhdsn4tZHR4GLfsSb5c6dcAYO/Jo1a47ZajVq5GfFiq4UL+5nYmci6Z9+8omIOKs5+SHkn7h13XHMVIGBYVSvPo+zZ+/Yai++WIuPP26Gh4ereY2JZBBJDrdr1qxh/fr1XLhwgQULFrB8+XJcXV159dVXcXNTZhYRSVUrG8YNtgNOQ7YSprQj/8qe3ZsGDYpy9uxB/Py8WLiwI+3blzG7LZEMI0mzJSxatIixY8eSLVs2rl+/TnR0NNWqVePrr79m2rRpKd2jiIjcF34HZuaAS7vs66OjFGzTkBkzWtG3b2UOHBisYCviYEkKt0uWLOH1119n+vTpGEbsPH2tW7fmrbfeYsOGDSnaoIiI/L/902GmH4QH2tdfitH4WhP9+utFNm48aVfz9fVg4cKOFCmSzZymRDKwJP00vHnzJmXLlo1Tz5EjB3fv3k12UyIi8oC1reHs5rj14XfBoinLzWC1Gnz66S+88cYP+Pi4s3//85Qokd3stkQyvCT9RPT392fx4sVER0cDYLFYiIqK4quvvsLf3z8l+xMRydiuHYidv/bBYNtgUuyFY55ZzOkrg7t5M5R27Zbz6qvbiI62EhQUweTJv5rdloiQxDO3r732Gv369aNBgwYAjB07litXrhAZGcmiRYtStEERkQzr295wbGnceqdNULy14/sRAHbvPs8zz6zh8uV7QOzEFG+88RTjxjU0tzERAZIYbsuWLct3333H0qVLOXkydpxRvXr16NWrF35+mr9PRCTZjn0VN9j65oUh8Uz9JQ5htRp89NFPjB27g5iY2OtNcuXy4auvOtOsmS7mE0krknwFQrZs2Rg+fHhK9iIiIuG34YvSEHbTvv70LihY35yehOvXQ+jVay1bt56x1Ro1KspXX3UmX77MJnYmIg9K0ctr9+/fT0hICE899VRKPq2ISPoXdgtm5Yx/Wf/jkF3TSZnFajVo3nwJhw5dA2KHIYwd24C3366Pq6su5hNJa5L0XdmkSRNOnToVp37t2jVee+21ZDclIpKhnPv+4cG2114FW5O5uFj46KOmAOTNm4lt2/owblxDBVuRNCpJZ24vX75MZGRknHrevHkJDg5OdlMiIhnG+e2wpkXcer0PoOZruo1uGtGyZUm+/LIDrVqVJE+eTGa3IyKPkOhwe+XKFS5fvmx7fPToUUJDQ22Po6OjWbVqFRUrVkzZDkVE0qsrv0FAU/tahX7Q8ktT2pFYW7f+zdq1x5g1qw2W//xx0a+fv3lNiUiiJTrcrl27lhkzZgCx89q+/fbbdss9PDyoXLkyEyZMSNkORUTSoy0D4a8F9rWu26BIE3P6EaKjrYwbt5MPPtiNYcATT+TmhRdqmt2WiDymRIfb4cOH22ZHKFu2LGvWrKFChQqp1piISLp0409YXDluvf0aBVsTXboUxLPPrmH37gu22vbtZxk2rIbd2VsRSft0M3IREUcI/gfm5o9/Wf2JUKqzY/sRm82bT9G79zpu3QoDwNXVwocfNuHll+sq2Io4oSSF28WLF1O0aNEUbkVEJJ2a6gPRYXHr2ctC38PgovMMZoiKiuGtt35g4sRfbLVChbKwcmVX6tQpZGJnIpIcSfqJWrPmw8cgXbp0iYIFCya5IRGRdOXvjfEH2+47oFBDh7cjsS5cuEuPHgH8+uslW619+zJ8+WUHsmf3NrEzEUmuJIXbO3fuMGnSJA4ePEhY2L8/tGNiYrhx4wZHjhxJsQZFRJzWvcuwvp19rdFUqDrSnH7EZty4nbZg6+7uwsSJzRg1qpaGIYikA0magXrcuHEcOHCAWrVqcfXqVQYPHkyrVq24fv06Y8aMSekeRUScj2HAvAc+xRp8RcE2jZg8uQXFimWjaNFs/Pzzc7z4Ym0FW5F0Iklnbn/99VcmT57Mk08+yebNmylWrBjdu3cnPDyc3bt306dPn5TuU0TEeVzaDSvr29fqfQiZ8pnTjxAdbcXN7d/zOdmyebFp07Pky5eZbNm8TOxMRFJaku8daBgGAJUrV7YNQ2jRogV79+5Nmc5ERJzRqXVxgy1ALd2a3Cxr1x6jfPmZXL4cZFcvVy6Xgq1IOpSkcFu3bl0++OADTp48Se3atdmwYQNXrlzht99+w9fXN6V7FBFxDl93ga/jmdLrZcPxvQgREdGMGPEtXbqs4tSpQJ59di3R0Vaz2xKRVJakcPvWW2+RN29ejhw5QpcuXQgKCqJJkybMmjWLXr16pXSPIiJp33f94dRa+1rHbxRsTXL6dCB1637BjBl7bLW8eTMRERFtYlci4ghJGnObI0cOvvjiC9vjr7/+mt9++w0/Pz+qVq2aYs2JiDiFmEg4stC+1msf5NHPQzOsXPkXgwZ9w717kQB4eroydWpLnn++mi4aE8kAUmTmcF9fX5o0ib1t5LVr18iTJ09KPK2ISNoX3+10X7KCQpTDhYVFMXr0FubO3WerlS6dg1WrulK5cl4TOxMRR0rUsATDMJg9ezbNmjWjYsWKNGjQgAkTJhAaGmpb5/Lly4wdO5ZmzZqlWrMiImnKua1xg22tNxVsTXDixE1q115gF2x79arEvn3PK9iKZDCJCreLFy9m2rRplC9fnlGjRtGyZUvWr1/Pq6++ytmzZ3nttddo0aIFO3bs4JVXXkn0zm/evMmwYcOoUqUKnTt35vjx44/VfHh4OC1atGDBggWPtZ2ISLIdWQRrmtvXar0J9SaY008Gd/jwdf788xoA3t5uLFjQnsWLO5Ipk4fJnYmIoyVqWMKKFSt47rnn+N///merNWzYkP79+7Njxw5y5szJ66+/Trdu3fDwSNwPEsMwGD58OAABAQH8+eefDBs2jG+++SbRMy5MmjSJzJkz069fv0StLyKSIq7tg+/62dfarYbSXU1pR6Br1/IMHVqdXbvOs2pVVypUyG12SyJikkSF24sXL9KgQQO7Ws2aNQEYOXIk/fv3T3SovW///v0cOHCATZs2UaJECUqUKMHGjRvZtm0bHTp0SHD7X3/9lZUrVxIQEICrq+tj7VtEJMn++R2W1bav1ftAwdbBrly5F6c2eXILYmKs+PrqbK1IRpaocBsdHU2mTJnsavcDZcOGDR872AIcPXqU/PnzU7JkSVutSpUqHDp0KMFwGxwczBtvvEGtWrU4cuQIISEhyZ6lwTAMuzHEqSksLMzu/+J8dAydW1KPn8vlH/H6upVdLaLlKmKKtQEH/fwQWLLkL0aP3sqYMU9QtGhRu2UWC4SGarovZ6Cfo87NjONnGEaiZjxJ9GwJH3zwQZyACzBhwgS7YQQWi4XZs2cn+Hz37t2jcOHCdrWsWbNy7NixBLedOXMmV69epW7duly6dIk5c+ZQrlw5pkyZgotL0m66FhUVlah9p6Rz5845dH+S8nQMndvjHL/SB57H5+5+u9rVQn25HF4cHPyzI6MKDY3m44//YtOmSwB89NFhnngiG0WLxv3dJM5DP0edm6OPX2JOqCYq3NaoUQOAkJCQRNUTw83NDU9PT7ual5dXgmdPIyMjWb58Of369WPMmDEAPPPMMzRq1Ijt27cnebYGd3d3u7PIqSksLIxz585RtGhRvL29HbJPSVk6hs7tcY+fz+y41wFEVf0fWWqNI0tqNChxHD58nUGDvuHEiUBbrVWrAtSqVZbs2TOb2JkklX6OOjczjt/p06cTtV6iwu2SJUuS1Ux8/Pz8uHnzpl0tODg4wUR++/ZtwsLCaNy4sa2WK1cuChQowJkzZ5Lcj8ViwcfHJ8nbJ4W3t7fD9ykpS8fQuSV4/AwDVjeOW++1D/c8VXFPvdbk/xmGweef72fUqO8ID48dbpApkwczZjTniScge/bM+h50cvo56twcefwSexOWpH2GnwL8/f05deoUQUFBttrhw4fJly/fI7fLnj17nDO8ERERXL9+XTePEJGUNdkFLu60r40K053HHCQoKIJnn13L4MEbbcHW3z8v+/c/T7du5UzuTkTSKtPCbalSpShevDiTJ0/GarVy5MgRvv/+exo3bozVaiUoKIiYmJg427m7u9O+fXsmT57MkSNHuHjxImPHjsXT09N2lzQRkWSJvAefPnCGwOIKo6PBzcucnjKYY8duUK3aPFas+MtWe+GFGvz66wBKlcphYmciktaZFm4BPvzwQ7Zv307dunXp3r077du3p379+ly5coUaNWpw8uTJeLd76623qFu3LsOGDaNNmzb8/fffzJs3j8yZNe5KRJLp2gGYHs9I2lFh4KJpBx0lRw4fQkIiAciSxZPVq7sxY0ZrvLxS5K7xIpKOmfpTonz58mzZsoU9e/bg5+dHpUqVAChYsCAnTpx46Haenp6MGTPGdkGZiEiy3TwCi56If9mLkeCqEbaOlDu3L8uWdeG117axbFkXihf3M7slEXESpv8J7OPjE+cGESIiDrXzZdg3OW69cBPoujV28lRJVXv3XqFIkazkyvXvzBQNGxbl118HJPoiEhERMHlYgoiIqQwDvu4Sf7B9+kfotk3BNpUZhsHUqb9Rt+4C+vZdj9Vq2C1XsBWRx2X6mVsREYeLiaLcnmfx2RnPuP4uW6Boc8f3lAEFBobx3HMb2LAhdhja5s2nWbz4EP36+ZvbmIg4NYVbEclwvL8siCUqOO6CodfAJ7fjG8qAfvvtEk8/HcCFC3dttVdeqUPPnhVN7EpE0oNkhduoqCiuXLlCvnz5CAoKImfOnCnVl4hIyouJhM88ifNBd55q8Ozvmg3BAaxWg08//YU33viB6GgrADlyeLNoUUfatCltcncikh4kKdyGhYUxbtw4vv32W2JiYli7di0LFizg1KlTzJs3j9y5deZDRNKYawdgaTw3X3gpBiy6/MARbt4MpV+/9WzadMpWq1evMMuXd6FgQd3IWERSRpJ+on/00Uf8+uuvvPrqq1itsX95Dxo0CBcXFz7++OMUbVBEJNkeEmxDB1xVsHWQ69dD8PefYxdsX3+9Hjt29FWwFZEUlaSf6lu3buXtt9+md+/etlrp0qUZPXo0P/30U4o1JyKSLFGhsK59nGAbXbIr+xr8Dh668Yuj5MrlQ6NGxWz//u67nnzwQRPc3PTHhYikrCQNS4iOjsbT0zNOPSIiAsMw4tlCRMTBDAOm+catN/ucyJLPwrFjju8pA7NYLMye3QYPDxfGj29M/vz6w0JEUkeSwm2jRo345JNP7MbWnjx5kqlTp9KoUaMUa05EJEnunIEFJeLWm82FSgMhNNTxPWUwu3ad4969SNq2/fcisUyZPFiwoIOJXYlIRpCkz4PeeOMNsmXLRseOHQHo2rUrHTp0IFu2bLz++usp2Z+IyOP5+e24wdbNB16yQqXnzekpA4mJsfLee7to3HgxvXqt5ezZ22a3JCIZTJLO3GbNmpUlS5bwxx9/cPJk7CTopUqVolatWinanIjIY5mZA8ID49ZHhTi+lwzo6tVgevZcyw8/nAXg7t0Ipk//g8mTW5jcmYhkJMma57ZmzZrUrFkzpXoREUm6r7vEDbZdt0KRpub0k8Fs23aGnj3Xcv167B8SLi4W3n23Ia+/Xs/cxkQkw0lSuO3atStt27alZcuW5M2bN6V7EhFJvJgo+O09OLXWvj70BvjoxjKpLTrayrvv7uT993dz/3ri/Pkzs2xZZxo0KGpqbyKSMSUp3NasWZPVq1czceJE/P39ad26NS1bttQdykTEsS7thpX149ZfjABXD8f3k8FcvhzEs8+u5ccfz9tqLVuWZPHijuTKFc9MFSIiDpCkC8peffVVNm3axJYtW2jVqhU7duygadOm9O3bl9WrV6d0jyIicUUExR9sn7+oYOsAMTFWmjZdYgu2rq4WPvqoCZs2PatgKyKmStbs2YUKFaJ3794sWLCAb775Bk9PT8aOHZtSvYmIxC/wJMzIal8r1hqGXofMBc3pKYNxdXVh0qRmABQqlIUff+zPmDH1cHGxmNyZiGR0ybqg7Pjx4+zYsYOdO3dy4sQJateurdvvikjqCrsFX5axr9V+G558z5x+MrA2bUqzaFFH2rQpRY4cPma3IyICJDHcvvPOO+zatYubN29Sq1YtunfvTrNmzciSRfcHF5FUdOZbWNfGvlawgYKtA2zceJKNG08ye3YbLJZ/z8726VPZxK5EROJKUrg9c+YMgwcPpkWLFmTPnj2lexIRievST3GDbcWB0Pxzc/rJICIjY3j99W1MnvwbAP7+eRkypLrJXYmIPFySwu2SJUtSug8RkYcLvQ4rn7KvVeirYJvKzp27w9NPB/DHH5dttR9/PM/gwdXszt6KiKQlyRpzKyKS6gwDZuexr7VeBuWeMaefDGLdumM899zX3LkTDoCHhyuTJjVj+PCaCrYikqYp3IpI2jb5gUldGkxSsE1FERHR/O9/W5k+/Q9brXhxP1at6kq1avlN7ExEJHEUbkUkbdozCX78X9x69Zcd30sG8fffgTz9dAD79v1jq3XvXoF589qSNauXiZ2JiCReosJtkyZNmDNnDqVKlQKgcePGj/xYavv27SnTnYhkPLeOwsIK8S972XBsLxnMuHG7bMHW09OVzz5rqfG1IuJ0EhVuhw8fTu7cuW2PR4wYkWoNiUgGdnA2bB8Wt+7iDi+GO76fDGbatJbs3n0eDw9XVq3qhr9/XrNbEhF5bIkKt506dXrkYxGRZAs6H3+wfSEQvPwc308GEBUVg7u7q+2xn583mzf3pGDBLGTO7GliZyIiSZes2++KiKSYz4vaP244OXYYgoJtqli27DDlys3kn3/u2dXLlculYCsiTk3hVkTM920v+8ctvoRqo83pJZ0LDY1i4MCv6dlzLX//fZuePdcSE2M1uy0RkRSj2RJExFwn18Cxr+xrT/QzpZX07tixG3TvHsBff1231QoXzkpkZAze3jrXISLpQ5J+ms2YMYMbN27Eqc+fP5927doluykRySDObYVvutrXRoaa00s6t2jRQapX/9wWbH183Fm4sAMLF3bE29vd5O5ERFJOksLtzJkzuX79epx61apVOXv2bLKbEpEMIPgfWNPcvjboPLh7m9NPOhUcHEnfvuvp128DoaFRADzxRG727h1E377+5jYnIpIKkjQswTCMeOc9PHPmDLly5Up2UyKSzhkGzH3gbledv4Ushc3pJ506fPga3bsHcPz4TVtt4MAqTJ3aCh8fna0VkfQp0eF20aJFLF68GACLxcKQIUNwd//3h2NMTAw3b97kvffeS/kuRSR9efCWujVfg2KtzOklHTty5IYt2GbK5MHcuW159tmKJnclIpK6Eh1ua9WqRZYsWTAMgzfeeIPu3btToEAB23JPT08qVKhAkSJFUqVREUknNj1r/7hUZ3jqQ3N6Sed69HiCHTvO8vvvl1m1qhulS+cwuyURkVSX6HBbtmxZypYtC8Abb7xBo0aNqFDhIbfIFBF5UGQwTM8ct95+jeN7SacuXrxLoUJZ7WpTp8aeEffy0uQ4IpIxJOmCsk6dOuHnp4nVRSSR7p6LG2wtLvBihCntpDeGYTB79h5KlZrOV1/9abfMy8tNwVZEMpQk/cT78EN9hCgiiXT3HMwvFrc+OhriuTBVHs/du+EMGvQNq1cfBWDIkE3UqlWQkiWzm9yZiIg59Oe8iKSeyOC4wbbyUGg6y5x+0pm9e6/w9NMBnDlz21YbMKAKhQplMbErERFzKdyKSOowrHGHIlQcqGCbAgzDYPr0P3jlle+Jioq9dW62bF58+WUHOnYsa3J3IiLmSlS43bNnDxUqVMDHx8f2+FFq1KiR/M5ExLlNdrV/7P8CNJlhTi/pyO3bYTz33NesX3/cVqtVqwArVnSlaNFs5jUmIpJGJCrc9u7dmzVr1thmR+jdu/dD17VYLBw7dixluhMR5/RpPGNpFWyT7c8/r9G+/XLOn79rq738ch0++KAJHh6uj9hSRCTjSFS4PX78+CMfi4jYHF0at/aS1fF9pEN58vgSHh4NQPbs3ixa1JG2bUub3JWISNqSpKnAREQeavMDn+y8GKFZEVJInjyZ+Oqrzjz1VGEOHhysYCsiEg9dUCYiKefcVvvHw26Cq4c5vaQDv/xykTJlcpAjh4+t1qRJcRo3LoZFfzCIiMRLZ25FJGXcPQdrmtvXvHW716SwWg0++ugn6tf/kv79N2AYht1yBVsRkYdLcri9du0ad+7cAeCff/5h0aJF7NixI6X6EhFncmJ13Pls+x01pxcnd/16CK1bf8Xrr28nJsbgm29OsmLFX2a3JSLiNJIUbrds2ULTpk05dOgQgYGBdOzYkUmTJjFs2DDmzZuX0j2KSFr2dRfY2N2+VrQF5ChnTj9ObNeuc/j7z2HLlr+B2KHKY8fWp1u3CiZ3JiLiPJIUbqdOnUr//v158skn2bJlC9myZeP3339n9OjRrFixIqV7FJG06s4ZOLXWvtbiS+jynTn9OKmYGCvjx++icePF/PNPMBA7M8LWrb15991GuLlpBJmISGIl6YKyy5cvU79+fdzc3Dh48CBNmzbFx8eHGjVqMGuW7j4kku5FR8CqRvDPr/b1506BX0lzenJSV68G06vXWrZvP2urNWlSjKVLO5M3byYTOxMRcU5JOh2QL18+du/ezfnz5/n555+pVq0aAOfOnSNv3rwp2qCIpDERd2GqV9xg22Smgu1junLlHv7+c2zB1sXFwnvvNWTLll4KtiIiSZSkcPv8888zb948WrZsSaZMmahXrx5r167lgw8+oHv37gk/gYg4p0s/woxscettVoD/MIe34+zy5ctE48bFbP/+4Yc+vP12A1xdNQxBRCSpkjQsoXPnzpQtW5YrV65Qp04dPDw8yJw5M2PHjqVdu3Yp3aOIpAWhN2FlA/tapoIw+KI5/aQDFouFuXPbkjmzB+PHNyZ3bl+zWxIRcXpJvolD+fLlKV++vO1xs2bNUqQhEUmD9n4Ku16xr1V7GRpOMqcfJ/Xdd6exWg1aty5lq2XO7MncuTopICKSUpIcbiMjI/n222/566+/MAyDihUr0rp1azw8dDcikXRlTSs498DsBw0+heovmdOPE4qOtvL22z/w0Uc/4+fnxYEDgylSJJvZbYmIpEtJCre3bt2iX79+nDlzhvz58wOwYsUKvvjiCxYuXEj27NlTtEkRMckv78YNtqW7Kdg+hosX7/LMM2v4+efY4Ru3b4czd+4+PvigicmdiYikT0m6amH8+PEAbN68ma1bt7J161Y2b94MwIQJE1KuOxExz8kA+HWcfW3oNWi3ypR2nNHGjSfx959rC7Zubi58+mlz3n+/scmdiYikX0kKt7t27eKVV16hcOHCtlrhwoUZPXq0bsErkl58083+8aAL4JPbnF6cTGRkDK+88j3t2i0nMDAMgCJFsvLTT/156aU6WCwWkzsUEUm/kjQswcfHh1u3bsWpBwYG4u3tneymRMRkcwvYP26/DrIUMqcXJ3Pu3B169Ajg998v22odO5bliy/a4+enn48iIqktSeG2Y8eOfPzxx1gsFmrVqgXA77//zsSJE+nWrVsCW4tImnZ8BQRf+fexlx+U6mhaO84kOtpK06aL+fvv2wC4u7swaVJzRoyoqbO1IiIOkqRwO3r0aG7dusUbb7xhV+/YsSOjR49OkcZExAQ7X4F9n9rXhsX9lEbi5+bmwuTJLejQYQXFi/uxcmVXqlfPb3ZbIiIZSpLCrZubGx999BEvvPACR44cwTAMnnjiCQoV0seWIk5rc184uti+1mYF6IzjY2nfvgyLF3ekffsyZM3qZXY7IiIZzmOF20uXLnHx4kVy5cpFiRIlKFSokAKtSHoQGRw32HbbDoV1Vf+jrFp1hB9+OMvs2W3shh307l3ZxK5ERDK2RIXb4OBg/ve//7Fz504Mw8BisVC2bFkmT55MsWLFUrtHEUlt0zPbPx5+BzyzmtKKMwgPj2b06O+YM2cfANWr52fgwKomdyUiIpDIqcAmTpzIiRMnmD59Ot9++y1ffvklXl5evPrqq6ndn4iktl/H2z+u/ZaC7SOcPHmL2rXn24ItwG+/XTKxIxER+a9EnbndtWsXo0ePpmnTpgAUL16cnDlz0q5dO27fvo2fn1+qNikiqWT/NPhlrH3tyfHxryt89dWfDB68kZCQKAC8vNyYPr0VAwZUMbkzERG5L1Hh9tq1axQvXtyuVqxYMQzD4Pr16wq3Is7o03guFBt+1/F9OIHQ0ChGjdrM/PkHbLWyZXOyalVXKlbMY2JnIiLyoERfUObmZr+qq6trijcjIg4QcRdmZItb77UXPLM4vJ207tixG3TvHsBff1231fr2rczMma3x9fUwsTMREYlPosNtjx494p2EvFu3bnZ1i8XCwYMHU6Q5EUkF8QXbYTfBO4fDW3EG48btsgVbHx93Zs1qTd++/uY2JSIiD5WocDt8+PDU7kNEUtu1A7D0gSv6LS4wOlpz2T7CrFmt+eWXi2TL5sXKlV0pXz6X2S2JiMgjKNyKZASbnoXjy+PWX4pxfC9pXGRkDB4e/w67ypHDh61be1O4cFZ8fNxN7ExERBIjUVOBiYgTW9v6IcHW6vhe0jDDMJg/fz9ly87g6tVgu2Vly+ZUsBURcRIKtyLp2Xf94Oxm+1rHb+BlQ0MR/uPevQh69lzLoEHfcPbsHXr1WktMjMK/iIgzMjXc3rx5k2HDhlGlShU6d+7M8ePHH/s5zp49i7+/P5cuaRJ1ETvWGDiyyL72YiSUaGtOP2nUwYNXqVZtHsuX/2WrlS6dg+hohVsREWeU6NkSUpphGLaxvAEBAfz5558MGzaMb775Bl9f30Q9R0xMDGPGjCEsLCw1WxVxPoYVpjzw7f3CbXDVR+v3GYbB558fYMyYHURExI49zpzZg/nz29O9ewWTuxMRkaQyLdzu37+fAwcOsGnTJkqUKEGJEiXYuHEj27Zto0OHDol6jjlz5ijYijwoJhI+87SvVR4KXtlMaSctuns3gtdf38+2bf/YatWq5WPlyq6UKJHdxM5ERCS5TAu3R48eJX/+/JQsWdJWq1KlCocOHUpUuD1y5Ajz589n+fLliQ7Dj2IYBqGhocl+nsS4H8gVzJ1XWj2GlsCjeK+sEaceWncSOOj9ndYdOHCVXr02cO5ckK02dGhV3n+/AZ6ebg77OSDJk1a/ByXxdAydmxnHzzCMeO+58CDTwu29e/coXLiwXS1r1qwcO3YswW0jIyMZM2YMI0eOpGzZsinST1RUVKL2nZLOnTvn0P1JyktLx9D73gnK7+sZp76v4V5w8Hs7Ldux45It2GbO7M7YsZVo1CgfZ86cMrkzSYq09D0oSaNj6Nwcffw8PBK+M6Rp4dbNzQ1PT/uPTr28vBJ11mTy5MnkypWLfv36pVg/7u7udmeRU1NYWBjnzp2jaNGieHt7O2SfkrLS3DEMu4HPwup2JWumgoT3Ok45zYpgp1y5chw/HsHBg/+wZEl7ypTJY3ZLkgRp7ntQHpuOoXMz4/idPn06UeslK9xevnyZ8+fPU7lyZa5cuUKBAgXw8fFJ1LZ+fn7cvHnTrhYcHJxgIt+zZw/r169nw4YNiTo1nVgWiyXRvacUb29vh+9TUlaaOYazi9o/fnICLrXfJA10ZrqzZ29TrJifXW3q1BacOnWSMmXypI3jJ0mWZr4HJcl0DJ2bI49fYnNfkqYCCwwMpFevXjRp0oSBAwdy8eJF5s2bR6tWrThz5kyinsPf359Tp04RFPTvuLfDhw+TL1++R263du1agoODadOmDdWrV6d69dizVe3bt2fevHlJeTkizu3TB77ZnxwPtd80p5c0xDAMPv30F0qXnsGKFX/ZLfP2dsfdXdN8i4ikR0n66T5+/Hju3LnDzJkzsVpj54J89dVXKVGiBB9//HGinqNUqVIUL16cyZMnY7VaOXLkCN9//z2NGzfGarUSFBRETEzcW4P+73//47vvvmP9+vW2/wDmzZtHjx49kvJyRJzXxmfsH7u4Q+23zOklDbl1K5T27VfwyitbiY628vzz33D27G2z2xIREQdIUrj9+eefefXVV2nSpImtlitXLp5//nn27t2b6Of58MMP2b59O3Xr1qV79+60b9+e+vXrc+XKFWrUqMHJkyfjbJM9e3YKFixo9x9A3rx5yZIlS1JejohzirgLJ1bY10YGx79uBvLzzxfw95/Lxo3//vwYNqwGBQvq54OISEaQpDG37u7uhIeHx6nfuHEDLy+vRD9P+fLl2bJlC3v27MHPz49KlSoBULBgQU6cOJHo53mcdUXShbBbMCunfW3EPXBN+CrS9MpqNZg48WfeeusHYmIMAHLm9GHJkk60bOmYi0VFRMR8SQq3rVu35oMPPsDFxQWLxUJgYCA7duzg008/pXXr1o/1XD4+PjRo0CApbYhkTOF34gbbuu+CRyZT2kkLbtwIoU+f9Xz33b9X0tavX4RlyzpToIDO2IqIZCRJCrf/+9//CA4OZsSIERiGwYABA4DYi7peeeWVFG1QRB4w0/7Kf6qMgDpjzeklDdi37wrt26/gypV7AFgs8OabT/HOOw1xc9NFYyIiGU2Swq2HhwcffvghL7zwAqdOxU58XrJkSQoVKpSizYnIA3aMtn9ctCU0nmZOL2lEvnyZiYqKvfg0Tx5fli7tTNOmxU3uSkREzJKseW7/e0GXiKSyb7rDydX2tS6bzeklDcmfPzNLl3bmk09+YcmSTuTNm3GHZ4iISBLDbdmyZR85ka6jb2Mrku4dmhM32I5M+G5+6dGOHWepXDkv2bP/e0ec5s1L0KxZ8RS9sYuIiDinJIXbxYsX2/5ttVr5559/2Lx5M0eOHGH8+PEp1pyIAEHnYdtQ+9pzp8A9Y92uMibGyrvv7mLChB9p374M69Y9bRdmFWxFRASSGG5r1qwZp9apUyfmzp3L1q1bady4cbIbExHAGg2fF7Wv9TkEfhlraqsrV+7x7LNr2LXrPAAbNpxgzZpjdO1a3uTOREQkrUnWmNsH9e/fn/r166fkU4pkXDGR8Jmnfa31MshVyZx+TLJly2l6917HjRuxwzBcXS2MH9+Izp3LmdyZiIikRSkabnfv3o2Li6beEUm2PybC7jFx6+WeiVtLp6Kjrbz99g989NHPtlqBAplZsaIr9eoVNrEzERFJy5IUbhs3bhxnfFt4eDiBgYGMHj36IVuJSKIEXYwbbP3KwHPHzenHBBcv3uWZZ9bw888XbbU2bUqxcGFHcub0MbEzERFJ65IUbkeMGBGn5urqSpkyZShTpkyymxLJsAwrfP7AWckGk6D6y+b0Y4Lz5+9Qteo8AgPDAHBzc+HDD5vw0kt1cHHRRWMiIvJoSQq3nTp1Suk+RARg7gPzRrddBWW6mdOLSQoXzkqTJsVYvfooRYpkZcWKrtSurfm0RUQkcVJ0zK2IJMOWgRDyz7+Ps5XMcMEWYqf0+vzzduTO7cv48Y3w88tYU56JiEjyJOnqr3bt2rFhw4aU7kUk44oKgb8W2NeeO2lOLw62fv1xNm8+ZVfLmtWLGTNaK9iKiMhjS1K49fPz49SpUwmvKCIJu3cZpj1wy9ih1yGd35QgIiKaUaM206nTSnr1WsfFi3fNbklERNKBJIXbUaNGsWbNGv7666+U7kckY7FGw7wHxpPWfRd8cpnTj4P8/XcgTz75BdOm/QFAYGAYX3xxwOSuREQkPUjSmNuLFy/SrFkznn32Wbp160bFihXtlnfs2DElehNJ36JC4p6xLdUF6ow1px8HWb36CAMHfkNQUAQAHh6uTJnSgqFDq5vcmYiIpAdJCrfTpk0DIGfOnOzYsYMdO3bYllksFoVbkYRc/gVWPGlfy1sD2geY048DhIdH89JLW5g9e6+tVrJkdlat6kqVKvlM7ExERNKTJIXbH374IaX7EMk49kyCH/9nX/PIDD3/MKcfBzh58hbdu6/m0KFrttozzzzB3LltyZzZ8xFbioiIPB5NBSbiSBs6w+l19jWLK4wIMqcfB4iMjKFp08VcvBj7Gr283Jg2rSUDB1aNc6dDERGR5ErUBWUzZszgxo0bqd2LSPo2r3DcYPvUx/BStDn9OIiHhytTp7YEoEyZHPz++0AGDaqmYCsiIqkiUWduZ86cSaNGjciVK31fwS2Sao6vhHsX7Wsv3AavbKa042idOpVj6dJOdOhQlkyZPMxuR0RE0rFEnbk1DENnWUSS6ptusKmHfW3IP+k22C5efIghQzZiGIZdvWfPSgq2IiKS6hI95vbll1/Gy8srwfUsFgtr165NVlMi6YJhwOR4/n7scwh88zq+n1QWEhLJ8OGbWbjwIAC1ahWgf/8q5jYlIiIZTqLDbZEiRfDz80vNXkTSlxX14tZ6/gG5Kjm+l1T211/X6d59NceO3bTV9u//R+FWREQcLtHhdsSIEVSoUCE1exFJPya7gmG1r70QCF7p6w9EwzD44osDDB++mfDw2AvjfH3dmTOnLb16pb8QLyIiaZ+mAhNJScFXYG6BuPWXrJDOxq3fuxfB0KGb+Oqrw7ZapUp5WLWqK2XK5DSxMxERycgSFW4XL15MsWLFUrsXEecWExl/sB14Jt0F20OHrtK9ewAnT96y1QYPrsaUKS3w9nY3sTMREcnoEhVua9asmdp9iDi/5U/Grb1sxK2lA++996Mt2GbO7MHnn7fj6aefMLkrERERDUsQSRk3/oRre/99XKEvtFxoWjupbe7ctvz++yXy5MnEypVdKVkyu9ktiYiIAAq3Isl36SdY+ZR9rcWX5vSSSiIiovH0/PfHRc6cPmzf3oeiRbPZ1UVERMyWqJs4iMhDhAfGDbbN5qabMbaGYTBt2u+ULTuT69dD7JaVKZNTwVZERNIchVuRpIgOp8LvnfH5spB9vdFnUOl5U1pKabdvh9GlyypGjfqOc+fu0Lv3OqzW9DmGWERE0g+ddhF5XIEn8fmyTNx6xYFQdZTj+0kFv/9+iR491nDu3B1brWLF3MTEWHFxcTWvMRERkQQo3Io8jjtnIL5g23AKVHvR4e2kNMMwmDLlN8aM2UZ0dOxNKLJn92bhwg60axfP6xYREUljFG5FEivwBHxZ1q4UXao7bu1XmtRQyrp1K5R+/TawceNJW61u3UIsX96FwoWzmtiZiIhI4inciiRGdEScYHu1UG+yNJ2TLr6JfvnlIj16BHDxYpCtNmbMk4wf3wh3dw1DEBER55Eefi+LpL6pXnYPoyoO5XKOAWQxqZ2Udvp0oC3Y5szpw+LFHWnVqpTJXYmIiDw+hVuRhKxtbf84T3Wi6k2CY8fM6ScV9OlTmR07zvH334EsX96FAgXSS2wXEZGMRuFW5FH++BjObrav9doDoaHm9JNCTp8OjHNXsVmzWuPu7oqbm2YIFBER56XfYiIPc+U32P2afW34XXN6SSExMVYmTPiRMmVmEBBw1G6Zt7e7gq2IiDg9/SYTic+v78HyOva14XfA03k/rr92LZgWLZby9ts7sFoNBgz4mgsXnDusi4iIPEjDEkQe9Mu78Os4+1q10eDpvNNhbd9+hp4913LtWuwtdF1cLLz8ch0KFMhscmciIiIpS+FW5L9+mxA32PY7AjnKm9JOcsXEWHnvvV2MH/8jxv/fOTdfvkwsW9aFhg2LmtqbiIhIalC4Ffmvn9+2fzzoAmQpZE4vyXTlyj169lzLzp3nbLXmzUuwZEkncuf2Na8xERGRVKRwK3Lfn/PsHw8657TB9tdfL9Khwwpu3Iid1cHV1cL48Y0YM6YeLi4Wk7sTERFJPQq3IgDntsDWwf8+9sgCWYqY108yFS6c1TYMoUCBzKxY0ZV69Qqb25SIiIgDaLYEkVtHYU1L+9qAU+b0kkIKFMjC4sUdadu2NAcPDlGwFRGRDEPhVjK2iCBYWMG+1mkj+OQ2p58k2rLlNHfuhNvVWrUqxddf9yBnTh+TuhIREXE8hVvJuC7uhBkPTO/VZjkUb2NGN0kSFRXD//73PS1bfsXAgV9j3B+L8P8sFo2vFRGRjEVjbiXjMazwmSdYo+3r/i9A2R7m9JQE58/f4emnA/j998sArFlzjI0bT9KuXRmTOxMRETGPwq1kLNHhMNU7br1wU2gyw/H9JNH69cfp33+DbSiCu7sLn3zSjLZtS5vcmYiIiLkUbiXjuH0Kvogn/DnRXLYREdGMGbONqVN/t9WKFcvGypVdqVGjgImdiYiIpA0Kt5IxhN6IJ9ha4GWrKe0kxZkzt+nefTX79v1jq3XpUo7589uTLZuXiZ2JiIikHQq3kv6tbQ1nN9vXSneDdqvM6ScJTp8OpFq1eQQFRQDg4eHKlCktGDq0ui4aExER+Q+FW0m/DAMmP2RCECcKtgAlSvjRtGlx1q49RsmS2Vm1qitVquQzuy0REZE0R+FW0q8/Po5bazIT/Ic5vpdkslgsLFjQnkKFsvDee43IksXT7JZERETSJIVbSZ8MA3563b42OgpcnOMtv2LFX/j5edGiRUlbLVs2Lz77rOUjthIRERHn+E0v8rhm+tk/HnHPKYJtWFgUo0Z9x+ef7ydnTh8OHhxMgQJZzG5LRETEaegOZZL+7J8KEXftax6ZzOnlMRw/fpOaNefz+ef7Abh5M5SvvjpsclciIiLOJe2fyhJ5HNcOwI4X7Wsvpf3pvhYvPsTQoZsIDY0CwNvbjVmz2tCvn7+5jYmIiDgZhVtJP24chqVV7WsDTkManiorJCSS4cM3s3DhQVutQoVcrFrVjfLlc5nXmIiIiJNSuJX0wbDC4kr2tRpjIFsJc/pJhCNHrtO9ewBHj96w1Z57zp/p01vj4+NuYmciIiLOS+FWnJ9hhcmu9rWnd0PBeub0kwjh4dE0bbqEq1eDAfD1dWfOnLb06lUpgS1FRETkUXRBmTi/B4NthX5pOtgCeHm5MW1a7LRelSrlYd++5xVsRUREUoDO3Ipz+/SB8bQu7tDyS3N6eUzdulVg+XKDDh3K4O2tYQgiIiIpQWduxXk9GGwBRkc6vo8EGIbB3Ll7GTp0Y5xlPXo8oWArIiKSgnTmVpxTfME2DU75FRQUwaBB37Bq1REA6tQpRJ8+lU3uSkREJP1SuBXn88u7cWsvxaS5Kb/27/+H7t1X8/fft221I0eum9iRiIhI+qdwK87lxGr4dZx97SVrmgq2hmEwY8YfvPLKViIjYwDImtWTL77oQOfO5UzuTkREJH1TuBXnYVhhY3f72rBbaSrY3rkTzoABX7N27TFbrUaN/Kxc2ZVixfxM7ExERCRjULgV52CNhikPXHg19AZ4Zzenn3j88cdlnn46gHPn7thqL71Umw8/bIqHh+vDNxQREZEUo3ArzuHBYFvtJfDJaU4vDzF+/I+2YOvn58WiRR1p166MuU2JiIhkMKZOBXbz5k2GDRtGlSpV6Ny5M8ePH0/Udnfu3GHkyJFUqVKFihUrMmTIEG7fvp3whuKcLvxg/zhbSWjwiTm9PMKCBe3Jly8TdeoU5ODBIQq2IiIiJjAt3BqGwfDhwwkMDCQgIIDevXszbNgwQkJCEtz21VdfJTw8nLVr17J+/XrOnTvHRx995ICuxeFu/gWrm9jXBpwCi/lTNIeHx9g9zp3bl507+7FrVz8KF85qUlciIiIZm2kJYf/+/Rw4cIAJEyZQokQJOnXqRLFixdi2bdsjtwsKCsLX15epU6dSrFgxSpQoQceOHTl48KBjGhfHCf4HFlW0r/U7ak4v/2G1Gkye/Dvdu+/k5s1Qu2WlS+fA3V3ja0VERMxi2pjbo0ePkj9/fkqWLGmrValShUOHDtGhQ4eHbpclSxamTJliVzt16hTFixdPVj+GYRAaGprwiikgLCzM7v8Sj5gofObltytF1vmAaO8i4KDjFJ8bN0J5/vlv+f77swAMGLCRdeu64eKSdmZskITpe9D56Rg6Px1D52bG8TMMA0siZkgyLdzeu3ePwoUL29WyZs3KsWPHHrJF/I4fP87mzZtZtGhRsvqJiop67H0n17lz5xy6P2eR+fYflD40zK52I18nLng2Bwcfo//av/8Wb765nxs3IoDYGcgKF3bn6NFjuLoq3DojfQ86Px1D56dj6Nwcffw8PDwSXMe0cOvm5oanp6ddzcvL67HOnoaFhfHyyy/TuXNnatSokax+3N3d7c4ip6awsDDOnTtH0aJF8fb2dsg+nYXnxva4XtxuV7P6lcW341LMuv2B1WowadJvjB//G1arAUCuXN68804levSooWPohPQ96Px0DJ2fjqFzM+P4nT59OlHrmRZu/fz8uHnzpl0tODg4UYkcYk9NjxkzBi8vL95+++1k92OxWPDx8Un28zwOb29vh+8zTfuiNNw+ZV8r3haXTt9g1lfp2rVgevdex9atZ2y1Ro2K8vnnrbhz56KOoZPT8XN+OobOT8fQuTny+CVmSAKYeEGZv78/p06dIigoyFY7fPgw+fLlS9T2EydO5NChQ8yaNSvOGWBxQp9a4gbbPn9Cp2/M6QfYseMs/v5zbcHWxcXCu+82ZOvW3uTLl8m0vkREROThTAu3pUqVonjx4kyePBmr1cqRI0f4/vvvady4MVarlaCgIGJiYuLddv78+axYsYKpU6eSKVMmQkJCEjWFmKRRWwbErY0MgVwV49Yd6OzZO1y9GgxA3ryZ2L69D2PHNsDV1fxpyERERCR+pt6h7MMPP2Tw4MF899133Lt3jw4dOlC/fn0uXbpEkyZNWL9+PeXKxR1pOW/ePEJDQ3n66aft6idOnHBU65KS/vrC/vGoMHDzMqeX/+jf358dO85x/XoIS5Z0InduX7NbEhERkQSYGm7Lly/Pli1b2LNnD35+flSqVAmAggULPjKo/vHHH45qUVLbg7fVfckaOw2BCU6cuEmZMv/e0tdisfD55+3w8HDVVF8iIiJOwvTPV318fGjQoIEt2EoGsmM0WKP/fezmZUqwjY628uab2ylXbiZr19pPNebl5aZgKyIi4kRMD7eSQZ1aC/s/s68NuebwNi5dCqJx40V88MFPGAY899wGLl8OSnhDERERSZNMHZYgGdTVvfB1F/vaixHgmrhp4FLKt9+eok+fddy6FXt3FVdXC2+++RT58mV2aB8iIiKSchRuxbGiQuGrB2640fewQ4NtVFQMb775A5988outVrhwVlas6EKdOoUc1oeIiIikPIVbcZzQmzA7l32t6zbI+YTDWjh//g49eqzht98u2Wrt25fhyy87kD277pAjIiLi7BRuxTGu7oGvatrXar4ORZo4rIVdu87RqdNKbt8OB8Dd3YWJE5sxalStRN/1RERERNI2hVtJfVf3xg22+WrBUx84tI3ixf1sIbZYsWysXNmVGjUKOLQHERERSV2aLUFS177P4o6xrTISnv3N4a0UKpSVxYs70rVrefbvH6xgKyIikg7pzK2knq+7wqk19rXG06HKcIfs/ptvTlC/fhGyZv33bmdt2pSmTZvSDtm/iIiIOJ7O3Erq+OmtuMG2y/cOCbbh4dGMGPEt7duv4PnnN2IYRqrvU0RERNIGhVtJeeF34Pf37Wt9DkHRZqm+69OnA6lbdwEzZuwBYNWqI3z//d+pvl8RERFJGzQsQVJW4An4sqx9beg18Mmd6rteseIvnn/+G+7diwTA09OVadNa0bx5iVTft4iIiKQNCreScq7tg6XV7WtPfZTqwTYsLIoXX/yOefP222plyuRg1apuVKqUJ1X3LSIiImmLwq2kjJNr4Juu9rVab0DNMam62+PHb9K9+2oOH75uq/XqVYnZs9uQKZNjb+crIiIi5lO4leQ7vy1usPV/Aeq9H//6KeT48ZtUrz6PkJAoALy93Zg5szX9+vnrpgwiIiIZlC4ok+QJuQoBD1wo1n0HNJmR6rsuUyYHzZrFjqctXz4Xe/YMon//Kgq2IiIiGZjO3ErS3TwCi56wr/X4GQrUdcjuLRYLX3zRnlKlsvPOOw3w9dUwBBERkYxOZ24laa4fjBtsnxiQasHWMAy+/PIAW7faT+vl5+fNxInNFGxFREQE0JlbSYpjX8G3vexrufyhxfxU2V1wcCTDhm1iyZI/yZ3bl4MHB5MvX+ZU2ZeIiIg4N525lcSLCoVPLXGDbduV0OdAquzyzz+vUb36PJYs+ROA69dDCAg4mir7EhEREeenM7eSOIYVpvnGrXffAYUapvzuDIPPP9/PyJGbiYiIASBzZg/mzWtHjx5PJLC1iIiIZFQKt5I4cwvGrfU/AdlLp/iugoIiGDx4IytW/GWrVamSl1WrulGyZPYU35+IiIikHwq38miGAZMfGL3inROG3UiV3R048A/duwdw+nSgrTZ8eA0++aQ5Xl56u4qIiMijKS3Io62sH7eWSsE2NDSKFi2WcuNGKABZs3qyYEF7unQpnyr7ExERkfRHF5TJw134AS7/ZF97MSLVdufj4860aa0AqFEjP/v3D1awFRERkceiM7cSv1tHYXUT+9pLVkjlu3/16PEErq4WOnQoi4eHa6ruS0RERNIfnbmV+C2sYP+435EUDbaGYfDZZ7/xwgub4izr1q2Cgq2IiIgkic7cSlwrnrJ/XPddyJFywwMCA8Po338DX399AoAnnyzMs89WTLHnFxERkYxL4VbsTfWG6PB/H/vmgzpjU+zpf/31Ij16rOHChbu22qlTt1Ls+UVERCRjU7iVf31Vyz7YAgy+nCJPbbUafPrpL7zxxg9ER1sByJHDm8WLO9G6dakU2YeIiIiIwq3EmuoD0WH2tVFhKTLO9ubNUPr2Xc+3356y1erVK8zy5V0oWDBLsp9fRERE5D6FW4FNPeMG29FR4JL8t8fu3ed55pk1XL58D4jNyq+/Xo93322Em5uuZxQREZGUpXCb0Z0MgOPL7Gujo8ElZWYr+OCDn2zBNlcuH5Yu7Uzz5iVS5LlFREREHqRTZxlZdAR8082+NjoqxYItwMKFHciTx5eGDYty6NAQBVsRERFJVTpzm5FN9bJ/3OOnZA9FCA2NwsfH3fY4T55M/PTTcxQrlg1XV/0tJSIiIqlLaSOj+vSBC8VKdoICTyb56WJirLz77k4qVJjFrVuh9k9dMruCrYiIiDiEEkdGE3gybrD1yg4d1ib5Kf/55x7Nmi1h3LhdnDt3h/79N2AYRjIbFREREXl8GpaQkRhW+LJM3Pqwm0l+yq1b/6ZXr3Vcvx4CgIuLhVq1CmAYKXq3XhEREZFEUbjNKKwxMCWewz06KkkpNDrayrhxO/ngg93cP0lboEBmli/vwlNPFUlmsyIiIiJJo3CbUczMbv/YKzu8kLTb3l6+HMQzz6xh9+4LtlqrViVZvLgTOXP6JKdLERERkWRRuM0Ibp+CyCD7WhKD7XffnaZ373XcvBl70Zirq4UPP2zCyy/XxcVF4xBERETEXAq3GcEXpe0fv2RN8lNdvHjXFmwLFcrCypVdqVOnUHK6ExEREUkxCrfp3dYh9o+f/T1ZV3oNHFiVHTvOERISxZdfdiB7du9kNigiIiKSchRu07NNPePeWjdfzcd6iqNHb1C+fC7bY4vFwhdfdMDT0xWLpkMQERGRNEbz3KZXv38YN9iODE705pGRMbz00hYqVJjF11+fsFvm5eWmYCsiIiJpks7cpjfWGAhoChd32tefvwjuvol6irNnb/P00wHs2XMFgH791nPs2AvkyZMpZXsVERERSWEKt+mJYcQ/l22/Y5C5YKKeYu3aYzz33Abu3o0AwMPDlffea0Tu3IkLxiIiIiJmUrhNTybHM8rk+UuQuUCCm4aHR/O//33PjBl7bLUSJfxYtaobVavmS8kuRURERFKNwm16MTtP3NrLRqI2PX06kO7dV3PgwFVb7emnKzBvXjuyZPFMqQ5FREREUp3CbXrwaTwXdyUy2H7//d907bqKe/ciAfD0dGXatFYMGlRVF42JiIiI01G4dXYhV+PWXoxM9OZlyuTAzS12OEPp0jlYvboblSrFcxZYRERExAko3Dqz8Nsw54HxsMPvgqt7op+iSJFsfPllBwICjjF7dhsyZfJI4SZFREREHEfz3Dqr6HCYmd2+1nUreGZ55GZr1hzl3r0Iu1qHDmVZsqSTgq2IiIg4PYVbZzX1gdveZi8LRZo+dPXQ0CgGDNhA166rGTx4I4aRuDG5IiIiIs5E4dYZXfnV/nHWYtD/2ENXP3r0BjVrfs4XXxwEYPnyv9i163wqNigiIiJiDoVbZ7S8rv3jgWfiXc0wDL788gDVq8/jyJEbAPj6urN4cUcaNiyayk2KiIiIOJ4uKHMmhhH3Rg3P/hbvqsHBkQwbtoklS/601SpWzM2qVd0oWzZnanYpIiIiYhqFW2cRcRdmZLOvZS4E+WrFWfXPP6/x9NMBHD9+01Z7/vmqfPZZS7y9Ez+TgoiIiIizUbh1BgdmwA8j4tYHxR03++ef16hVaz7h4dEAZMrkweeft6NHjydSu0sRERER02nMbVoXfif+YPuyAfHcQaxixdw0bVocAH//vOzf/7yCrYiIiGQYCrdpmWHA3Adu0lD2mUfeWtdisbBwYQfefPMpfv11AKVK5UjlJkVERETSDoXbtGxZ7dibNdzXciG0WWZ7aBgGs2bt4YcfztptliOHDxMmNMbLS6NOREREJGNR+kmrllSD6/vtaxX62v559244Awd+Q0DAUfLmzcTBg4PJkyeTg5sUERERSVt05jYtCr0RN9iOjrb9c8+ey1SpMpeAgKMAXL0azIYNJxzZoYiIiEiapDO3adHs3PaPX4oBiwuGYTBt2u/8739biYqyApAtmxcLF3agQ4eyJjQqIpK+xcTEEBUVZXYb6VJERITt/y4uOtfmbFL6+Lm7u+Pq6prs5wGF27Rniof946d3g8WFwMAwnntug90Z2tq1C7JiRReKFMnm2B5FRNI5wzC4evUqd+/exTAefhGvJJ3VasXNzY0rV64o3DqhlD5+FouFrFmzkjdvXizxzAb1OBRu05Ibf4L1gTMEBevx22+XePrpAC5cuGsr/+9/dXn//ca4u6fMXzkiIvKvu3fvcufOHXLlyoWvr2+yf9lKXDExMURERODp6ZliZ+zEcVLy+BmGQUhICDdu3MDb25ts2bIl6/kUbtMKwwqLK9vXXrJy714ErVt/xe3bsbMm5MjhzaJFHWnTprQJTYqIpH+GYXD9+nWyZMlCzpy6XXlqiYmJAcDLy0vh1gml9PHz9vYmIiKC69evkzVr1mT9QanPAdKKOQ/MZ/v0j2CxkDmzJ9OntwKgXr3CHDw4RMFWRCQVxcTEEBMTQ5YsWcxuRSRDyZIli+37Lzl05jYtOLUOQq/bHhpuvlgKPmV73LNnJby93Wnfvgxubvp7REQkNUVHx85O4+amX5EijnT/ey46OjpZ339KSmYLPAlfdwbAarXw4fZ6jDi5Ms5qnTuXU7AVEXEgjbMVcayU+p5TWjJTdDh8WQaA6/d8aTW/J29sbsrMWXtZufIvk5sTERGRdevWcfr0abPbcDrXr19n0aJFpuxb4dYshoHP5zkA2Hm6KP5ThvD9yZIAWCxw7twdE5sTERGRn376iQULFpAjRw6zW3E6WbNmZcuWLaxZs8bh+zY13N68eZNhw4ZRpUoVOnfuzPHjxxO1XUxMDB9//DG1atWiUaNGfPvtt6ncacrz/6khMVYL733fgCZz+/BPUGYA8uTxZdu2PowZU8/kDkVExJmtXbuWMmXKUKZMGcqVK0eDBg2YMGEC4eHhtnVWrlxJ48aNqVixIj169Ijze3jp0qU0bNiQ6tWr8+KLLxIYGOjol2Ga4OBg3nvvPWbOnImfn5+tHhERgb+/PzNmzLBbv3fv3rz//vt2tcaNG7Nw4ULbY7O+nn/++SddunShSpUqDB8+nDt37jz2c5w6dQp/f38OHTpkV1+6dCn169enfPnyNG/enJ9++gkAT09PZs2axZw5c7h27VpKvIxEMy3cGobB8OHDCQwMJCAggN69ezNs2DBCQkIS3Pazzz5jw4YNTJ8+ncmTJ/PBBx9w5MgRB3SdMjy29uXGbQvN5/Xmne8bYTViD0PTpsU5dGgIjRsXM7lDERFJDzJlysSePXv46aefeP/99/nuu+/47LPPANi2bRvjx49n6NChfPvttxQoUICBAwcSGhoKxH4cP2nSJF5//XXWrVtHaGgor732momvxrG++uor2rVrR5EiRezqv/32G2FhYezcufOxns+sr+fNmzcZMGAAFStW5OuvvyZ37ty89dZbj/UcUVFRjBkzhqeffprKlf+dtnTr1q3MnTuX9957j59++omnnnqKkSNHEhwcDEC2bNkYMmQIc+bMSdHXlBDTwu3+/fs5cOAAEyZMoESJEnTq1IlixYqxbdu2R24XGRnJ0qVLGT58ODVr1qRKlSr06dOHZcuWOajzZIqJZOe3+6k8eQg/nC4OgIuLhfHjG/Hddz3JkyeTyQ2KiEh6YbFYyJIlCzly5KBevXq0bduWP//8E4g9a9u8eXO6detGoUKFeO+997hz544ttM2ZM4eBAwfSokULChUqxIgRI9i1axc3b9408RU5zubNm+natWuc+o4dO6hXrx5//fUXN27cSPTzJfXreezYMapXrx7vfx9++GGC+129ejXe3t68+eabFCpUiDFjxvDHH39w9erVRPc+e/ZsgoKCePHFF+3qFy9eZMKECTRs2JDs2bPz/PPPExISwqlTp2zrtGnThh9//DHZ03s9DtPmOTl69Cj58+enZMmStlqVKlU4dOgQHTp0eOh2Z8+eJTQ0lKeeespuu6+//jpZ/RiGYftrNTW5Hp7HRzvqcT04NsTmy+fLl1+246mnChEREZ7A1pJWhIWF2f1fnIuOn/NLzWMYERGB1WqNO9/myQBcfh0HUfdSfJ+J4p4Za913oVSXRK1utVqBfyfbv379Oj///DMVK1YkJiaGI0eO0L9/f7vJ+IsUKcKRI0eoWrUq586do3r16rblRYoUoXv37oSGhiYYVAzDYOnSpSxdupRbt25RuXJl3nrrLYoVi/1ksnz58nz55ZdUrlwZwzAICAhg5syZthNcb7zxBgBdu3Zl8uTJhIeHExAQAMDTTz9NmzZt6NOnDwB37tzhqaeeIiAggDJlyhAcHMwnn3zCDz/8gKurKz169GDw4MGPdSV+TEwMwcHB5M6dO85r3blzJy+++CLnz59nx44ddOnSxfaaDcOwW/9+7erVq0n+ehYtWpS1a9fGu8zX1zfBY3HkyBFq166Ni4sLMTExuLm5UbZsWfbv30+LFi0S/FocOXKEuXPn0qtXLzZt2kSVKlUoWrQohmHw3HPP4enpaevhxIkTuLi4UKhQIVvN3d2dfPnycfnyZQoUKPDIfcXExGC1WgkLC7O9f//LMIxEHUfTwu29e/coXLiwXS1r1qwcO3Yswe1cXV0pVKiQrZYlS5Zkj+eIiopKcN8pIc+VcyzusQ7/yUOoUNqDNyfWxc8v2CH7lpR37tw5s1uQZNDxc36pdQzd3NyIiIiwq3nt+QTL7cRdG5Jq9nxCeKE2iVo1KiqKe/fuUbNmTQBCQ0OpV68ew4YNIzw8nLt37+Lj42M3BjdTpkzcunWL8+fPA7G/X+8vd3V1tX2M/t9t4rN69Wpmz57N+PHjKVmyJNOnT2f48OF2FxdFRcXebj4iIoKoqCgMw7A9b0xMDGfOnOH1119n4MCBFC9e3LasZcuWbN26le7duwOxH40XK1aMIkWKEB4ezpgxY7h06RJz5swhKCiIUaNGkT17dtq3b5+orxvEfpSfLVu2OK/z5MmT/PPPP1SsWJEqVarwww8/0KZN7PGwWq1ER0fbbWMYBlFRUcn+ej7qgraEtr179y4lS5a0W8/X15fLly8nuK1hGLzzzjv4+PhgtVo5evQoEydOZODAgfTs2RPA9n1itVqZOnUq7dq1w9vb2+65s2fPzuXLlxO8MC8iIoLo6GjOnDnz0HU8PDwe+RxgYrh1c3PD09PTrubl5ZXg2VM3N7c4L8zb2zvZZ13d3d3tziKnmpJvQBZPVhfxoXz7F/D19Un9fUqKCwsL49y5cxQtWhRvb2+z25HHpOPn/FLzGEZERHDlyhU8PT3x8vL6d0HNVzF+ecfUM7fU+J99T49a3d0dX19f1q1bx759+3jjjTd44403yJfv3ztiurm52T2fxWLBzc3NdnbMx8cn0fv7r5UrV9K3b1+aNGkCwJgxY9izZw/u7u62W7W6u7sDsRceubu7Y7FYbPtydXXlxIkTrF27Ns7v5vbt2zN16lTCw8PJli0bP/74Ix06dMDLy4sbN26wY8cOZs2aRYUKFQBo1aoV27dvt4XhxPDz8yMsLCzOa//1118pWLAgRYsWpXbt2owfPx4XFxc8PDxwcXGJ9+t5/7VB0r+eyeHh4RFnv76+vkRGRibYy759+zh69CizZs2iYcOGANSqVYvRo0fTsWNHfH198fT0xGKxMH36dK5du8acOXPiPG9YWBh+fn6Jeu1ubm4ULlw4TkYEEj0lm2nh1s/PL844k+Dg4AQT+f03XHBwMJkyxX60f+/evUQl+UexWCz4+DgiaPoQWucdMmU7hq+vj4P2KanF29tbx9CJ6fg5v9Q4hi4uLri4uODq6moLYgCU7R77n4lcE17F5v7rKFy4MIUKFWLu3Ll89dVXtouJsmfPTlBQkN1rDAoKIkeOHGTPnt32+P7yW7duUbduXVauXIm/v/8j9/3PP/9QpEgR27Z58+alXbt2duvcD3wWi4WIiAgsFottfYvFQpMmTShTpkyc586dOze1a9dm165dtG7dml9//ZWxY8fi6urK9euxd/v83//+h4tL7GVFkZGRFChQwP5YJiBLliyEhYURExNjly9+/PFHrl69Sq1atYiJiSEsLIw9e/ZQv359LBaL3Wu4/zosFkuyvp7Hjh2jd+/e8S7r0qULr7/++iNfi5+fH4GBgXZ9BQcH4+XlleDX5Nq1a7i5udGwYUPbuv7+/sTExHDx4kXKli2LxWJh586dfPHFFyxcuJBcuXLFeZ6LFy9SsGDBBPfn6uqKi4sL3t7e8QbhxA4tMS3c+vv789577xEUFGS7f/fhw4ft/qKMT6FChciZMyf79u2jQYMGid5OREQko7JYLPTv35+PP/6YF198kUyZMlGhQgUOHz5sW+fevXucP3+e8uXLU7RoUbJkycLBgwepVKkS8O8QkITGTd5f58KFC7bH165dY8CAAcycOdM2+8B/x4o+OL0U8Mg/Wtq3b8/mzZvJmjUrFStWJG/evADkz58fgLlz59pyQVRUVJIuZqpVqxY//vgjTZs2BSAwMJBDhw7x7rvvUrduXQAGDx7Mzp07qV+/PtmyZSMoKMi2vWEY3L17Fz8/v2R9PUuUKMH69evjXebr65vg66hSpYrd9oZhcOTIkUQN08ifPz9Wq5Xw8HDbvi5dugTE/pEBscfulVde4b333qNq1apxnuPYsWPky5fPdkLSEUybLaFUqVIUL16cyZMnY7VaOXLkCN9//z2NGzfGarUSFBQU75vRxcWF1q1bM23aNIKDg7l9+zYLFy6kcePGJrwKERER59CxY0e8vLxYvXo1AM888wzbt29n5cqVXLx4kbfffpucOXPSoEED3Nzc6Nu3LzNnzmT37t2cOXOGSZMmUadOnXjPzD2oZ8+eLFy4kN27d/PPP//w2WefERkZaQufBQoUYM+ePQDs3r2brVu3PtZradq0KQcPHuSbb76xC2k5c+akefPmLFu2jJiYGAzDYMKECYmaVeBBvXr1YsaMGbaxwT/++KMtgxQsWJCCBQvSsGFD2+wS9erVY+vWrezevZtr164xffp0oqKiqFmzZrK+nh4eHrb9Pfjff+fffZgWLVpw7NgxNm3aBMCyZcsICgriySefBGLPbN+7F/9Qm8qVK1OsWDHGjh3LxYsXOXLkCO+//z5169Ylf/78XLhwgeeff56ePXvSrFkzQkJCCAkJITo62vYckyZNom/fvgn2mZJMvYnDhx9+yPbt26lbty7du3enffv21K9fnytXrlCjRg1OnjwZ73YjR47E3d2d+vXr07hxY7y8vBgyZIiDuxcREXEenp7/1969RzV53nEA/3KLUEVAG6w4RURNsWMQJKDWG5fpxE7qbeBalDkvVSQ6taN0aqUTwctALmpBOPXSFTzL0O7YamkVPWvXVcTW9sTZ4xB6AI+CVwjhIiH7w/EeEJQkJEDS7+cczjFv8rzvL/kR/frked8MwG9/+1scO3YMGo0GM2bMwPbt25GVlYU5c+bg1q1bOHTokPBxcExMDKKjoxEfH4+IiAgMHz4cKSkpOh1ryZIliImJQUJCAubOnYuamhpkZWUJ62y3bNmCkydPYv78+VAoFIiNjdXruTg4OGDq1Kk4d+4cfvWrX3W4LykpCY6OjliyZAkWLFgAR0dH7N69W6/9A8BLL72EwMBAIRgXFRXBz8+vwwzkzJkzUVVVhR9++AGLFi3C0qVLsW3bNsyaNQtnz55FRkaGMIPck9ezJ55//nns2LED8fHxmDp1KpKSkrB161Y4OTkBAE6dOoWgoKAux9ra2iI3NxcajQaLFi3C66+/jhdeeAF79+4F8PjEwbq6Ohw6dAh+fn7CT9sVrHJycjBw4EBhvW5vsdJqtdpePeIT1Go1iouL4eLiIkzV66K1tRUlJSVobm5GQECA8IYxRNvHMt7e3gbvQx9qtRr/+c9/4OXlxfV+Zoo9NG/sn/kzZQ8bGxtRVlYGDw+PXj/556dEo9GgsbFRp7WffaWlpQVr1qzB6tWr4e/v39fl9Eh1dTW+++47SCSSDlecMlR3/auoqMCmTZvw/vvv67R8Auj+vadrXuuzNbdtnnvuOWHtrD6sra0hk8lMUBERERHR45nL9957r9+Gb324uroK64d7w8iRI5GXl9cnr12fLksgIiIi6s8sIdj2lb567RhuiYiIiMhiMNwSERERkcVguCUiIupCH59vTfSTY6z3HMMtERFRO21X3+np17oTkX7a3nM9uQIW0A+ulkBERNSf2NjYwNnZWfgq1+eee07nr/0k3Wk0GjQ1NQHgSVvmyJj902q1UKvVqK6uhrOzc4/3x3BLRET0hLavc20LuGR8ra2taGlpga2tLayt+UGyuTFF/5ydnYX3Xk8w3BIRET3BysoKw4cPh6urq/D1q2RcDQ0NuHHjBkaNGgUHB4e+Lof0ZOz+2dnZGW0Gn+GWiIjoKWxsbPiRuYm0trYCePy1wPwmOPPTn/vHzwGIiIiIyGIw3BIRERGRxWC4JSIiIiKLYaXlVapx+fJlaLVaiESiXjmeVqvFo0ePYGdnx8vLmCn20Lyxf+aPPTR/7KF564v+NTc3w8rKCn5+fs98HE8oA3r9TWVlZdVrQZpMgz00b+yf+WMPzR97aN76on9WVlY6ZTbO3BIRERGRxeCaWyIiIiKyGAy3RERERGQxGG6JiIiIyGIw3BIRERGRxWC4JSIiIiKLwXBLRERERBaD4ZaIiIiILAbDLRERERFZDIZbIiIiIrIYDLdEREREZDEYbomIiIjIYjDcEhEREZHFYLglIiIiIovBcGtkd+7cwdq1ayGVSrFgwQJcu3ZNp3EajQa7du1CYGAggoKC8Mknn5i4UnoaQ3v44MEDyOVySKVSeHt744033sD9+/dNXC09ydD+tVdWVgZfX19UVlaaoELqTk972NjYiNmzZyM3N9dEFVJ3DO1haWkpIiIi4Ovri0mTJuGdd95BY2OjiaulrrS2tiI2NhYZGRk6j+kvWYbh1oi0Wi3WrVuHe/fuQaFQICoqCmvXrkV9fX23Y/ft24ePPvoIGRkZSElJwc6dO6FUKnuhamqvJz384x//iMbGRhQUFODkyZMoLy9HcnJyL1RNbXrSvzYajQZxcXFoaGgwYaX0NMbo4d69e+Ho6Ijo6GjTFUpP1ZMexsTEIDQ0FJ999hmysrLw73//G5mZmb1QNbXX1NSEt956C4WFhXqN6y9ZhuHWiC5fvoxvvvkGO3bsgKenJ+bPnw8PDw98/vnnzxzX3NyMDz74AOvWrUNAQACkUimWLl2KDz/8sJcqpzaG9rC2thYDBw5EWloaPDw84OnpiVdffRXffvtt7xROAAzvX3vvvfceg20f6mkPv/rqKxw/fhyJiYmwsbExcbXUFUN7eO/ePZSVleG1116DWCyGj48PgoKCUFpa2kuVU5vt27fDzs4OUqlU5zH9Kcsw3BrR1atX4ebmhrFjxwrbpFIprly58sxxZWVlUKvVmDZtml7jyPgM7eHgwYORmpoKBwcHYdv169cxZswYk9VKnRnavzZKpRI5OTnYs2ePqUqkbvSkhyqVCm+//TYCAwOhVCpx+fJlU5ZKT2FoD52cnPDCCy8gJycHarUa165dw+nTpxEUFGTqkukJb7zxBhITE2FnZ6fzmP6UZRhujaiurg6jRo3qsM3JyQm3b9/udpyNjQ1GjhwpbBs8eHC348j4DO3hk9r+Ul6+fLkxy6Nu9KR/zc3NiIuLg1wux4svvmiqEqkbPenh/v37cevWLQwbNgyVlZV46623sH79erS2tpqqXOqCoT20sbHB7t27cfDgQUilUoSHh2PixIlYvHixKculLri7u+s9pj9lGYZbI7K1tcWAAQM6bLO3t4dare52nEgk6rDNwcGh23FkfIb2sL2GhgZs2rQJCxYsgEwmM3aJ9Aw96V9KSgrEYjHXafYxQ3vY3NyMvLw8REdHIzExEXK5HH/9619x9uxZnD171pQl0xMM7WFDQwPi4+OxZs0alJSUoLCwEDU1NdixY4cpyyUj6U9ZhuHWiFxcXHDnzp0O21QqVadmdzWuoaEBKpVK2FZXV9ftODI+Q3vYRqvVIi4uDvb29ti6daspSqRnMLR/xcXFOHnyJJKTk2FlZWXKEqkbhvbw/v37aGhoQHBwsLBNLBZjxIgRuHHjhklqpa4Z2sOvvvoKKpUKcrkcgwYNgru7OzZv3ozjx4+jpaXFlCWTEfSnLMNwa0S+vr64fv06amtrhW3ff/89hg8f/sxxI0eOxPPPP4+SkhK9xpHxGdrDNrt378aVK1dw4MCBTjMXZHqG9q+goAAqlQpz586Fv78//P39AQDz5s1Ddna2SWumjgzt4ZAhQzrNDjY1NaG6uhrDhg0zWb3UmaE91Gg0UKlUHU7orKmpgUajgVarNVm9ZBz9Kcsw3BrRuHHjMGbMGKSkpKC1tRVKpRKFhYUIDg5Ga2sramtrodFoOo2ztrZGWFgY0tPToVKpcP/+fRw+fLjDDAT1DkN7CAA5OTnIz89HWloaBg0ahPr6er0uX0Q9Z2j/3nzzTZw5cwYnT54UfgAgOzsbkZGRvfwsftoM7aGdnR3mzZuHlJQUKJVKVFRUYNu2bRgwYABCQkL64Jn8dBnawwkTJsDa2hqbN2/GJ598gqNHjyIhIQEhISF6ndhEpmM2WUZLRqVUKrVTp07VBgYGaidMmKCNj4/XarVabUVFhXb8+PHaq1evdjmutrZWGxERoZVKpVpfX19teHi4tq6urjdLp/8ztIcymUw7fvz4Tj/Uuwzt35PGjx+vraioMGWp9BSG9rCxsVGbnJysnT59utbb21u7cOFC7ZUrV3qzdPo/Q3v4xRdfaBcvXiz8WxgTE6O9e/dub5ZO7bz++uva9PR04ba5ZBkrrZZz/camVqtRXFwMFxcX/OIXv9B5XGtrK0pKStDc3IyAgAD+T7UPGdpD6h/YP/PHHpo/9vCnqT9kGYZbIiIiIrIYXHNLRERERBaD4ZaIiIiILAbDLRERERFZDIZbIiIiIrIYDLdEREREZDEYbomoX/v6668hkUi6/MnIyNB7f8HBwSgoKDBBpc/25PMICAjAypUrUVZWZpLjRUVFGfT6tKmsrIREIkFlZaURq9JNRkaG8Dq9+OKLmDlzJv785z93+MYrIqKnse3rAoiIdLF3716MHj26wzZXV9e+KaYHkpKSMHbsWNTU1ODw4cNYunQpTp8+jUGDBhn1OAkJCRg4cGCX99XW1uLIkSNYtmwZBg8e3OVjXF1doVAo+uw1tre3xwcffIBHjx5BqVQiLS0N//3vf3HkyBG99lNQUIARI0YgMDDQRJUSUX/DcEtEZmHs2LHw8vLq6zJ6bMyYMcIF7SdOnIjJkyfjzJkzWLRokdGP8zS1tbXIzMzE/PnznxpuRSIRvL29jVqTPqytrYXj+/n5wdraGu+++y5u3rwJNzc3nfdz4sQJBAQEMNwS/YRwWQIRUR9xdnbGkCFDUFFR0del9HttQffWrVt9XAkR9XcMt0RkEQ4fPoyQkBD4+PggPDwc//rXv/Qar1KpEB8fjylTpsDPzw+rVq3CzZs3Ozzmww8/xC9/+Uv4+vpiyZIluHr1ao9qbm5uxsOHDyEWi4VtJ06cwKxZs+Dt7Y3IyEh89913HcZ8++23iIyMhFQqxbRp05Cent7lvrtac1tQUACJRIKQkBAAQEhICCQSCaKiojqNf9qa24yMDMyePbvDtgsXLuDnP/85Hj58CODx129mZmZixowZkEql+P3vf9/jAF9TUwMAGDJkCABAo9EgJSUF06dPh1QqRWRkJJRKpfD4tjW7Fy9eRGZmpnC7/fN58OAB4uLiIJPJMHnyZGzbtg1qtbpHdRJR32O4JSKz949//AO7du1CdHQ0cnNzIZVKIZfLoVKpdN5HWloazp07h+3btyM1NRW1tbXYunWrcP+JEyewc+dOvPbaa8jOzoaLiwuWLVuG+/fv612vVqvF7du3sWPHDtja2iIoKAjA4/D59ttvY86cOcjKyoJYLMayZctQWloKAGhpacHq1avh6OiI7OxsyOVy5OTk4OOPP9bpuEFBQVAoFDh48CAA4ODBg1AoFEhISNC59rCwMJSXl+PHH38UthUVFeHll1+Gk5MTAODAgQN4//33IZfLceDAAdTX1yM6OhotLS06H6e90tJSpKenY9y4cXB3dwcAZGdn4+jRo9i4cSOys7MhFouxfv16YYxCoYBCocBLL72E3/zmN8Lt9muIY2NjoVQqsWfPHmzfvh3nzp3DO++8Y1CNRNR/cM0tEZmFV199tcNthUIhfFTt5uaGffv2CTOKIpEIeXl5uHHjhrC+tTuVlZXw8PDArFmzADxes9p+li8jIwORkZGIjo4G8HhmcPLkyTh37hwWLlyo8/OIiIgQ/iwWi5GSkoIRI0YAADIzM/HrX/8af/jDHwAAMpkMYWFhyMnJQVJSEurr6/HgwQOEhoZCJpNBJpNh9OjRGDp0qE7HdnFxgYuLi/C8xo8fj5/97Gc61w4Anp6ekEgkKCoqEl6LCxcuCMGyqakJ2dnZ2Lhxo/C6uLi4IDw8HJcuXcKkSZN0Oo5arYZEIhFujxs3DmlpabCysgIATJgwARkZGZg2bRoA4M6dOygsLMTdu3cxdOhQ4Xdj4MCBcHV17bR++OLFi7h48SJOnDiBCRMmAABu376N3bt3IzExESKRSK/XhYj6D4ZbIjILqamp8PDwEG63/7O/vz+KioqwZcsWXL58WZhVbGho0Hn/ixcvhlwux8KFC+Hv74/AwEDMnDkTwOMlC1VVVTh27BiOHTvWYVz7GUxd7Nq1CxKJBIMHD4abm5sQ1u7du4eqqiqsW7dOeKydnR1kMpmwNMHJyQlz585FYmIiLly4AB8fH4SGhj7z5DFTCAsLE8LttWvXcPfuXYSGhgIAysvL0dTUhKSkJCQlJXUY9+OPP+ocbu3t7ZGfn4/q6mqsXr0aUVFR8PT0FO6fNm0aPv74Y7z55pv45ptvUFVVBUD3nv/www8AgPnz53e6r6qqqsPvFxGZF4ZbIjILHh4eT71awq5du5Cfn4/IyEisX78eEydOxMsvv6zX/oODg/Hpp5/iiy++wKVLl7BhwwbMmDGjw7rVDRs2CIG3TdsaUF2NHj26y+eh1WqfOqb9fSkpKSguLkZxcTHOnz+Pffv2ISMjQwiXvSEsLAyZmZlQqVQ4f/48pk+f3ulSZjt37hRmRNsMGzZM52NYW1vDy8sLXl5emDVrFg4dOoSFCxfC1vbxP1sbNmzApUuXEBkZiTlz5mD48OGdZve7Y2NjA4VCIfwHo40+V2Mgov6Ha26JyOwpFAr87ne/Q1xcHGbPni2c2KSPvXv3Qq1WIyIiAnv27MGWLVtQWFiIhw8fYtCgQXBzc8ODBw+EwOXl5YW//e1vKCkpMcpzGDp0KNzc3PD1118L21paWnDp0iXhI/Vr164hNTUVMpkMa9euRV5eHnx8fPD3v/9dr2MNGDAAwOOTsgwxatQoSCQS/POf/8T58+cRFhYm3Ofu7g6RSIT6+nrhdfL09MThw4eF2VJ9xcTEoLKyEqdOnQIA1NXV4dNPP8WmTZsgl8sRHBwsnHD2JJFI1OVa33HjxkGj0XQI0dbW1sjNzTXo94eI+g/O3BKR2XNxccGXX36JyZMno6ysTJht1Se8KZVKXLlyBatWrYJIJMLp06fh7OwMR0dHAI8DVkJCAsRiMXx9ffHZZ5/h+PHjCA8PN9rziI2NxZ/+9Ce4ubkhICAAeXl5qKmpwcqVKwE8Xj+am5sLW1tbTJkyBbdv30ZpaWmXH60/i1gshpubG44ePYpXXnkF5eXl8Pb2xtixY3Xex9y5c6FQKHD9+nXhhDjg8XKCFStWIDMzEyKRCJ6ensjPz0dRURFiY2P1qrONRCJBaGgosrKyMG/ePAwYMAAODg74/PPP4e7uju+//x779+8H0LnnPj4+OHPmDKZMmSJcneKVV17BpEmTIJPJsHnzZqxfvx729vZITU1FS0tLh6tXEJH54cwtEZm95ORkNDc3Y/ny5cjNzcWmTZvg4uKi16xqcnIyxGIx4uLisGrVKqjVahw8eBDW1o//mly0aBHi4uKQn5+P5cuX4+LFi9i/fz98fHyM9jwWLFiAxMREnDp1CitXrkR1dTWOHDkirDUdOXIkMjMzceHCBaxYsQIJCQkICQmBXC7X+1ipqakoKSlBVFQUUlNT8ejRI73Gz5kzB19++SVmzJgBBweHDvetW7cOUVFROHDgAFasWIGbN28iNzdX75PX2ouJicGNGzdw5swZiEQi/OUvf0FZWRmio6Px0UcfISEhAba2tp16vmLFCnh5eWHNmjWQy+UoLy8X7ktPT4eXlxfi4+OxceNGjBo1CocOHeq0TIGIzIuV9lkLvYiIiIiIzAhnbomIiIjIYjDcEhEREZHFYLglIiIiIovBcEtEREREFoPhloiIiIgsBsMtEREREVkMhlsiIiIishgMt0RERERkMRhuiYiIiMhiMNwSERERkcX4H+i5L01PqfDGAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "\n", "for sd in [\n", " # save_df1, \n", " # save_df2, \n", " score_df\n", " ]:\n", " # 计算二分类指标\n", " evaluation_metrics = calculate_binary_classification_metrics(sd, score_col='score', label_col='label', threshold=0.6,\n", " future_return_col='future_return', total_mv_col='total_mv')\n", "\n", " # 打印指标\n", " print(\"二分类评估指标:\")\n", " for metric, value in evaluation_metrics.items():\n", " if isinstance(value, (float, int)):\n", " print(f\"{metric}: {value:.4f}\")\n", " elif isinstance(value, (list, tuple, np.ndarray)):\n", " print(f\"{metric}: (array of length {len(value)})\")\n", " else:\n", " print(f\"{metric}: {value}\")\n", "\n", " # 绘制 ROC 曲线\n", " plot_roc_curve(evaluation_metrics)" ] }, { "cell_type": "code", "execution_count": 112, "id": "7e9023cc", "metadata": {}, "outputs": [], "source": [ "def analyze_factors(\n", " df: pd.DataFrame,\n", " feature_columns: list[str],\n", " target_column: str = 'target', # 假设目标列默认为 'target'\n", " trade_date_col: str = 'trade_date', # 假设日期列默认为 'trade_date'\n", " mcap_col: str = 'total_mv', # 新增: 市值列名称\n", " mcap_bins: int = 5 # 新增: 市值分位数的数量 (例如 5 表示五分位数)\n", ") -> pd.DataFrame:\n", " \"\"\"\n", " 分析DataFrame中指定特征列的各种指标,包括基本统计、相关性、日间IC、ICIR以及在不同市值分位数上的IC。\n", "\n", " Args:\n", " df (pd.DataFrame): 包含日期、目标列、特征列和市值列的DataFrame。\n", " 需要包含 trade_date_col, target_column, feature_columns 和 mcap_col 中的所有列。\n", " feature_columns (list[str]): 需要分析的特征列名称列表。\n", " target_column (str): 目标变量列的名称。\n", " trade_date_col (str): 交易日期列的名称。\n", " mcap_col (str): 市值列的名称。\n", " mcap_bins (int): 市值分位数的数量 (例如 5 表示五分位数)。\n", "\n", " Returns:\n", " pd.DataFrame: 包含各个因子分析指标的汇总DataFrame。\n", " 同时打印因子在不同市值分位数上的平均IC表格。\n", " 如果输入数据或列有问题,可能返回空或包含NaN的DataFrame。\n", " \"\"\"\n", "\n", " # --- 数据校验 ---\n", " required_cols = [trade_date_col, target_column, mcap_col] + feature_columns\n", " if not all(col in df.columns for col in required_cols):\n", " missing = [col for col in required_cols if col not in df.columns]\n", " print(f\"错误: 输入DataFrame缺少必需的列: {missing}\")\n", " return pd.DataFrame() # 返回空DataFrame\n", "\n", " # 确保日期列是 datetime 类型\n", " df = df.copy() # 在副本上操作\n", " df[trade_date_col] = pd.to_datetime(df[trade_date_col], errors='coerce')\n", " df.dropna(subset=[trade_date_col], inplace=True) # 移除日期转换失败的行\n", "\n", " # 过滤掉那些在 feature_columns, target_column, mcap_col 上有 NaN 的行,以确保后续计算是在完整数据上\n", " # 直接在 df 副本上进行清洗\n", " initial_rows_before_clean = len(df)\n", " df.dropna(subset=feature_columns + [target_column, mcap_col], inplace=True)\n", " rows_dropped_clean = initial_rows_before_clean - len(df)\n", " if rows_dropped_clean > 0:\n", " print(f\"警告: 移除了 {rows_dropped_clean} 行,因为其特征、目标或市值列存在空值。\")\n", "\n", " if df.empty:\n", " print(\"错误: 清理缺失值后数据为空,无法进行因子分析。\")\n", " return pd.DataFrame() # 返回空DataFrame\n", "\n", "\n", " print(f\"开始分析 {len(feature_columns)} 个因子指标...\")\n", "\n", " # --- 1. 基本因子统计量 ---\n", " basic_stats = df[feature_columns].describe().T\n", "\n", " print(\"\\n--- 基本因子统计量 ---\")\n", " print(basic_stats)\n", "\n", " # --- 2. 因子与目标变量的整体相关性 ---\n", " overall_correlation = {}\n", " for feature in feature_columns:\n", " # 在清理后的 df 上计算相关性\n", " if df[[feature, target_column]].dropna().shape[0] > 1: # 确保至少有两个有效数据点\n", " overall_correlation[feature] = {\n", " 'Pearson_Correlation_with_Target': df[feature].corr(df[target_column], method='pearson'),\n", " 'Spearman_Correlation_with_Target': df[feature].corr(df[target_column], method='spearman')\n", " }\n", " else:\n", " overall_correlation[feature] = {\n", " 'Pearson_Correlation_with_Target': np.nan,\n", " 'Spearman_Correlation_with_Target': np.nan\n", " }\n", " overall_corr_df = pd.DataFrame.from_dict(overall_correlation, orient='index')\n", "\n", " print(\"\\n--- 因子与目标变量的整体相关性 ---\")\n", " print(overall_corr_df)\n", "\n", " # --- 3. 因子之间的相关性矩阵 ---\n", " # 在清理后的 df 上计算相关性\n", " factor_correlation_matrix = df[feature_columns].corr(method='spearman') # 改回 Spearman\n", "\n", " print(\"\\n--- 因子之间的相关性矩阵 (Spearman) ---\") # 修正打印信息\n", " print(factor_correlation_matrix)\n", "\n", " # --- 4. 日间 IC 和 ICIR ---\n", " print(\"\\n--- 计算日间 IC (Spearman 相关性) 和 ICIR ---\")\n", "\n", " # 直接在清理后的 df 上计算每日 IC\n", " if df.empty: # 理论上上面已经检查过,这里再检查一次更安全\n", " daily_ic_series = pd.Series(dtype=float) # 空 Series\n", " ic_stats = pd.DataFrame({\n", " 'Mean_IC (Spearman)': np.nan, 'Std_Dev_IC': np.nan, 'ICIR': np.nan\n", " }, index=feature_columns)\n", " else:\n", " daily_ic_series = df.groupby(trade_date_col).apply(\n", " lambda day_group: {\n", " feature: day_group[feature].corr(day_group[target_column], method='spearman')\n", " for feature in feature_columns if day_group.shape[0] > 1 # 确保每日数据点多于1才能计算相关性\n", " }\n", " ).apply(pd.Series) # 将字典结果转换为 DataFrame\n", "\n", " # 计算 IC 的统计量\n", " if not daily_ic_series.empty:\n", " ic_mean = daily_ic_series.mean()\n", " ic_std = daily_ic_series.std()\n", " # 避免除以零\n", " ic_ir = ic_mean / ic_std.replace(0, np.nan) # 使用 replace 0 为 NaN\n", "\n", " ic_stats = pd.DataFrame({\n", " 'Mean_IC (Spearman)': ic_mean,\n", " 'Std_Dev_IC': ic_std,\n", " 'ICIR': ic_ir\n", " })\n", " print(\"\\n--- 日间 IC 和 ICIR (Spearman) ---\")\n", " print(ic_stats)\n", " else:\n", " ic_stats = pd.DataFrame({\n", " 'Mean_IC (Spearman)': np.nan, 'Std_Dev_IC': np.nan, 'ICIR': np.nan\n", " }, index=feature_columns)\n", "\n", "\n", " # --- 5. 因子在不同市值分位数上的平均 IC ---\n", " print(f\"\\n--- 计算因子在 {mcap_bins} 个市值分位数上的平均 IC (Spearman) ---\")\n", "\n", " # 在清理后的 df 上计算每日市值分位数,直接添加到 df 中\n", " # 使用 transform() 和 qcut() 在每个日期分组内计算分位数\n", " # labels=False 返回整数 0 to mcap_bins-1\n", " # duplicates='drop' 处理在某些日期股票数量少于 bins 导致分位数边缘重复的情况,会返回 NaN\n", " # 添加一个临时列来存储分位数\n", " mcap_bin_col_name = f'_mcap_bin_{mcap_bins}'\n", " df[mcap_bin_col_name] = df.groupby(trade_date_col)[mcap_col].transform(\n", " lambda x: pd.qcut(x, q=mcap_bins, labels=False, duplicates='drop') if len(x) >= mcap_bins else np.nan # 确保股票数量足够进行分位数划分\n", " )\n", "\n", " # 过滤掉无法划分分位数 (NaN) 的行,进行分位数 IC 计算\n", " # 创建一个临时 DataFrame df_binned_analysis\n", " df_binned_analysis = df.dropna(subset=[mcap_bin_col_name]).copy()\n", "\n", " if df_binned_analysis.empty:\n", " print(\"错误: 划分市值分位数后数据为空,无法计算分位数上的 IC。\")\n", " avg_ic_by_bin = pd.DataFrame(index=range(mcap_bins), columns=feature_columns) # Placeholder\n", " else:\n", " # 按日期和市值分位数分组,计算每个分组内的因子与目标变量的截面相关性 (分位数IC)\n", " binned_ic_by_day = df_binned_analysis.groupby([trade_date_col, mcap_bin_col_name]).apply(\n", " lambda group: {\n", " feature: group[feature].corr(group[target_column], method='spearman')\n", " for feature in feature_columns if group.shape[0] > 1 # 确保分位数组内数据点多于1\n", " }\n", " ).apply(pd.Series) # 将嵌套结果转为 DataFrame\n", "\n", " # 对每个分位数组的每日 IC 求平均\n", " # unstack(level=mcap_bin_col_name) 将 mcap_bin 作为列\n", " # mean(axis=0) 对日期索引求平均\n", " avg_ic_by_bin = binned_ic_by_day.unstack(level=mcap_bin_col_name).mean(axis=0).unstack()\n", "\n", " # 重命名索引和列,使表格更清晰\n", " if not avg_ic_by_bin.empty:\n", " # Index name will be the original column name used for grouping ('_mcap_bin_X')\n", " # Rename the index name explicitly\n", " avg_ic_by_bin.index.name = 'MarketCap_Bin'\n", " avg_ic_by_bin.columns.name = 'Feature'\n", " # 可以根据需要对分位数 bin 索引进行排序 (虽然 pd.qcut labels=False usually sorts)\n", " avg_ic_by_bin = avg_ic_by_bin.sort_index()\n", "\n", " print(avg_ic_by_bin)\n", "\n", "\n", " # --- 6. 汇总所有指标 ---\n", " # 将基本统计、整体相关性、IC/ICIR 合并到一个 DataFrame\n", " # 注意:合并时需要根据索引进行对齐 (因子名称)\n", " summary_df = basic_stats\n", " summary_df = summary_df.merge(overall_corr_df, left_index=True, right_index=True, how='left')\n", " summary_df = summary_df.merge(ic_stats, left_index=True, right_index=True, how='left')\n", "\n", " # print(\"\\n--- 因子分析汇总报告 ---\")\n", " # print(summary_df)\n", "\n", " # --- 清理临时列 'mcap_bin' ---\n", " # 修正:在函数结束时从我们一直在操作的 df 副本中删除临时列\n", " if mcap_bin_col_name in df.columns:\n", " df.drop(columns=[mcap_bin_col_name], inplace=True)\n", "\n", "\n", " return summary_df # 主要返回汇总报告,分位数IC单独打印\n", "\n", "# # 运行分析函数\n", "# factor_analysis_report = analyze_factors(test_data.copy(), feature_columns, 'future_return')\n", "\n", "# print(\"\\n--- 最终汇总报告 DataFrame ---\")\n", "# print(factor_analysis_report)" ] }, { "cell_type": "code", "execution_count": 113, "id": "a0000d75", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "开始分析 'score' 在 'circ_mv' 和 'future_return' 下的表现...\n", "准备数据,处理 NaN 值...\n", "原始数据 17430 行,移除 NaN 后剩余 17140 行用于分析。\n", "对 'circ_mv' 和 'future_return' 进行 100 分位数分箱...\n", "按二维分箱分组计算 Spearman Rank IC...\n", "整理结果用于绘图...\n", "circ_mv_bin 0 1 2 3 4 5 6 7 8 9 ... 90 91 92 \\\n", "future_return_bin ... \n", "0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "... .. .. .. .. .. .. .. .. .. .. ... .. .. .. \n", "95 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "96 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "97 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "98 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "99 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN \n", "\n", "circ_mv_bin 93 94 95 96 97 98 99 \n", "future_return_bin \n", "0 NaN NaN NaN NaN NaN NaN NaN \n", "1 NaN NaN NaN NaN NaN NaN NaN \n", "2 NaN NaN NaN NaN NaN NaN NaN \n", "3 NaN NaN NaN NaN NaN NaN NaN \n", "4 NaN NaN NaN NaN NaN NaN NaN \n", "... .. .. .. .. .. .. .. \n", "95 NaN NaN NaN NaN NaN NaN NaN \n", "96 NaN NaN NaN NaN NaN NaN NaN \n", "97 NaN NaN NaN NaN NaN NaN NaN \n", "98 NaN NaN NaN NaN NaN NaN NaN \n", "99 NaN NaN NaN NaN NaN NaN NaN \n", "\n", "[100 rows x 100 columns]\n", "生成热力图...\n", "分析完成。\n" ] }, { "data": { "image/png": "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VuncuXN6+eWXs5T39/dX9+7d9d1332nHjh05XJ3nmDJliiSpT58+9gV0SXruuec0YcIE/fjjjyyie7j27dtr4sSJWrhwoV566SWzywEAAIDJWEQHAAAeJzIyUn/++aeCg4PT7UR3dJyLYRhKTk5WcnKyS2qw2WwqVqxYlrLz5s3T1KlTVbRoUY0aNSrd2eU3CgsL05QpU9S3b1+tXbtWzz77rF544QV1797dvuh6//33S5IuXbqk2NhYSdd+QZB6PZW/v7/279+vL7/8MsO5rl69qitXrqh///5ZupfMrFmzRg899FC2duYWKVJEknTlypVbnt8bJCYmasOGDfL391f9+vXTvJYnTx6VKFFC//zzjy5cuJDmmCB4lsDAQEVFRWn16tUsogMAAIBFdAAAPMG///6rCRMmaOPGjTpz5oyKFi2qBg0aqHfv3vZFyFQ2m00zZ87UggULdOTIERUpUkTVqlVTnz59VLFixXTvvXTpUs2YMUMHDhxQWFiYHnjgAb366quqUKFCmtyND2VctmyZ5syZo3379mnGjBnpzrPes2ePJk2apC1btighIUHly5dXx44d1bZtW6e/DwcPHlRwcLACAwMdLkTbbDbZbDb9+++/Dl8/efKkkpKS7DXv2bNHc+bMUXBwsCwWixITE/XBBx8oKSlJderUUVRUVLr3SUlJydIi+rx58/T2228rKChIbdu21bvvvquQkBAFBATcdDG9atWqqlSpkqZMmaJp06YpJCREffv2TZNZvHixEhMT7f/+559/VKZMGft7G4ahgwcPasaMGfbMhQsXZLPZlD9/fiUmJiopKckli+gWi+Wmu+xvtGfPHklK9xDVhIQETZs2TT/++KNOnDihggULqkaNGurXr5/Kli1rzy1cuFCDBg3SyJEjVa1aNX388cfatm2bAgMD9eCDD+qtt97K9K8FUlJSNGjQIC1evFjdu3dP9/11tYMHDyo5OVklS5a0P3T1ekOGDNH58+ez/fBXm82mhx9+WH5+flq3bl26ZwU0atRIVqtVv/32m/0vFK5cuaJp06bphx9+0PHjx5UnTx7dc8896tOnjyIiIpy/yWzau3evJk2apB07dig+Pl4lS5ZUq1at1Llz5wy/D+vXr9fo0aP1119/qUiRImrdurV69OiRLpucnKyvv/5aS5cu1ZEjRxQeHq6HHnpI/fv3V6lSpW657lq1amnhwoUyDOOm/y0DAAAgd2MRHQAAk8XHx6tDhw46duyYGjVqpHLlyuno0aOaO3eudu3apQULFtgXzGw2m3r27Km1a9eqXLlyat++vc6ePauVK1fql19+0cyZM9McCzJixAjNmDFDxYsX15NPPqmzZ89qxYoVWrdunaZOnaqaNWtmWNOQIUM0f/58lS5dWmXLllVoaGia19etW6devXopPDxcTZs2VWhoqH755RcNGTJE//77r3r37u3U96Jjx472h2jezMMPP3zTzL59+yRdOx5m4cKFCgoKUnJysq5evaqFCxcqKSlJefLkyXAR/ezZs6pevXqm72+1WjVnzhz5+fnpo48+0oULF/T5559nqX5JeuSRRzR58mTdd999mjdvnl599dU0rxuGoZkzZ9r//dNPP+l///uf3n33XbVt21Y2m02GYahly5Z677337Ll27drpzJkzGT589VacPHkyw4eJZiQpKUk7d+7UkCFD7DWlunr1qrp166bNmzerVq1aatiwoc6cOaOffvpJO3bs0Pfff5/mGBRJOnDggD744ANVrFhRTz31lH7//Xf98MMPSkhIcHjWumEYGjZsmNsW0CXpxIkTkpTul1+psvJzm5GAgAC1aNFC06ZN09atW1W7dm37a3/++aeOHTum559/Ps0RP6+//rp+/vln3XvvvXr22WcVHx+v5cuXq2PHjlq8eLFLFppvZs+ePerQoYNSUlL02GOPqVChQtq5c6fGjBmjU6dOaejQoWnycXFx6tq1q+677z61bdtWGzZs0Oeff669e/fq888/ty9mJycn6+WXX9bGjRtVq1Yt1a9fX0ePHtVPP/2kTZs2adGiRVn+SxJHSpQoIavVqvPnz3MuOgAAgI9jER0AAJNt2rRJR48e1dNPP60PPvjAfv2jjz7S8uXLdfToUd1xxx2Srj3scu3atXr44Yc1ceJE+87MH374Qa+99pomTpxoP4953bp1mjFjhqpUqaKZM2faj4749ddf9corr+j111/XihUr0u3uXLJkieLj4zV16tR0x1FI0uXLlzVw4EDlyZNHixYt0u233y5J6tu3r5544gl9+eWXio6OVsGCBbP9vXjttdfk7++vvHnzKjw83H7darVq6NChOnv2rF5//XWVK1dOSUlJGS5s2Ww2JSYm6ty5c/ZrTZo0kcVikSQNHDhQq1at0tatW9ONS3Xx4kVdvHgx3e5p6drO5tRfaoSHh2v27NnaunWrHnroISUmJqpFixYKDQ3Vli1b1KlTJ3Xu3Dnd+dcxMTF6+umn7QvSjRo1UqNGjdLN9f333+vvv/9WoUKFdO7cOdWtW1eLFi3SJ598ombNmunq1auSrh0P4w6JiYk3PYKkSpUqaf7t5+en559/Xh06dLBf++OPP7R582b7LxFSVa9eXcOHD9e6devUokWLNO/z9ddf6/nnn9dbb70lPz8/Wa1WPfroo1q3bp2uXLmS7hc9kjR8+HDNnz/fbQvo0rWfVUkKCQlx+Xu3atXKvnv/+kX0H3/80f56qosXL+rnn39WuXLlNGfOHPvPbIMGDfThhx9q27ZtbllE/+qrr2S1WjVu3Dg1b97cfv2pp57S/Pnz9dZbb6XZVX/27FlFR0fbf/litVr1/PPPa+3atVq9erWaNGkiSfrmm2+0ceNGvfzyy3r99dft46dPn66RI0dq2rRpevPNN2+p9tSf9ev/EgQAAAC+iUV0AABMlrpb8tChQ4qPj1e+fPkkSW+88YbeeOONNNnFixdLurYQfP3id9OmTTVmzJg0x0d89913kqT+/funWfhs0KCBmjZtqpUrV2rDhg165JFH0swRFxen6dOn64EHHsiw3vXr1+vs2bOKiIjQnDlz0ryWL18+nThxQtu3b89wUfhmMnog6OXLl9WzZ0+dPXtWvXv3VocOHdSwYUNFRUXp7bfftucuXbqkDz74QAMGDMjyw0Cvd/1Z6qlnkN945I10bYf19Qu2oaGheuihhyRdWzhNXTw9evSoJKlcuXLp3uP06dOSlOmu7qSkJI0bN05BQUFq166dJk+erEKFCumFF17QpEmTNGnSJD399NOSpKJFi2bnVp0WGhqq//77L9NMt27dJF07P33//v0aNWpUmsVdSXrooYfsfyWQ6siRI9q1a5eka0fW3KhkyZJ644037DuRw8PDVbNmTS1fvlz//fdfugXhESNGaPbs2apatarbFtAl2eu7/pcyrlK1alVVqVJFK1eu1LBhwxQQEKCUlBQtX75cFSpUULVq1ezZPHnyKDw8XOfOndORI0dUvnx5SdKjjz6qRx991OW1OfLJJ5/ok08+sf87KSlJ27Zt04ULF5SYmKiTJ0/afxEnScHBwWn+IiM8PFydO3fW66+/rjVr1tgX0ZcuXSrp2pE1Y8eOtefj4+MlXfvl5K06e/asJPf9kgoAAACei0V0AABMdu+996ply5ZaunSpHnzwQVWpUkURERGqU6eOmjZtmmax/NChQwoJCbEviKUKCQnR448/nubaX3/9JUkZnn189913a+XKlfrrr7/SLaI3atTI4QK6JB0+fFjStYXm1MXmG508edLxDWdDXFycevbsqX379qlevXrq1auXpGtnFS9atEjdunVTiRIlZLPZ9Nprr+mXX37R3r17NXv27GwvfN1zzz32Hf+//PKLpGu7WmfMmKG5c+fq/vvvl81mU0pKSpbeLyYmRlL6ndlS1hbRJ0yYoGPHjqlDhw5pFhk7duyov//+W23btrX34rbbbstSTbeqVKlSN+1tv379JEk1a9bUyy+/rEWLFqVbRJeufQ/mzp2rLVu2aO/evTp//rx9R3JG3+NmzZql+6uJ1B4bhpHm+vz583Xy5ElVrFhRe/fu1S+//JLu5zynpP4SLHUx90bt27fXnj17tGbNGqeOG2ndurVGjRqljRs36qGHHtLWrVt16tSpNDv9pWvHv7z55psaPny4oqKiVL58eUVERKhGjRqKiopy6hdNztq0aZOWLVumHTt26NChQ0pOTk5zRNX1SpYsme4on0qVKkn6v19MSdLff/8tSWmOO7qeKz6DTp48qXz58vEAWAAAAMj/5hEAAJDTPv74Yy1YsEC9evVS2bJl9fvvv6t///5q2bKlLly4kKX3uHLlihISEtItKGb3gXh33XVXpq+nvv/gwYO1b9++DL+effbZbM2Zke+++05PPfWUDhw4IElpjofp2bOnrly5omHDhikxMVF9+vTRL7/8oqZNm+qbb75xaudov3799M4778hms2nFihWqVq2aAgICdOzYMUnXFnGHDRuW5pgZR1JSUvT7778rLCxMkZGR6V5PXUQvXrx4huPXrVunKVOmqGDBgunOly9UqJDGjh2rihUr2h/ambrImNPuuecebdiwIUvZBg0a6O6779bGjRu1Y8eONK/FxMSoWbNm+vLLL1WoUCG9/PLLmjJlisOzzaWMd/Q7cvLkSb3yyiuaN2+eihUrpvfff99tR3Kk/vXCsWPHMtyNfvr0aSUnJ6dbKM6qFi1aKCAgwH6Ey48//ig/Pz+1bNkyXfaZZ57Rzz//rCFDhuj+++/X4cOH9d5776lJkybauXOn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7LHHVKxYMc2ePVvPPfecYmJi9MYbb6h27dqKiopSr1697At0jz32mOrWrauPP/5YGzZs0IQJE/T000/bF9D//fdfdenSRb/++qsGDBig2rVr2+e/fpfrnj17NGfOHPtDW6935MgRPfvss9q0aZPq1auX4SKeYRjavHmz9u3bl2a39M0kJydL+r8Hbm7btk1z5sxReHi4/diZ69lstkyPRrkVjRs3Vrt27dS/f3/t3bv3pvmWLVuqVKlSWrt2rf0Yob59+6p79+5KSkrSrFmztHv3bj3//POaOXOm/P39tWbNGpeeYX7//ferefPm+u2337Ry5UqXva8j/v7+GjdunIYMGaLg4GB9++23OnLkiJ5//nnNnj37lhdmw8LC7AvKTZo0yfD9goODNWvWLPXq1cv+ENelS5eqRIkSeu+999SjR49bqiErgoODNW3aNDVp0kRbt27VnDlzFBAQoLFjx9r/SmPZsmVpxpQrV06ffvqpveZLly6pS5cu9p3h17/3V199pf79+8tms2nu3Ln6448/1LRpUy1atEj33nuvUzUfP35c3bp1U926dfXcc885f/MAAADIVfyMnPr/YQEAALjAqlWr1LNnT0nSnXfeqWHDhqlWrVrpcgcPHtS3336r77//XhcvXtSTTz6pDz/8UNK1xeeMzjY/e/asZs6cqenTpys5OVnDhg1Tu3bt7K937txZGzdu1NChQxUSEqKPP/5YSUlJ+vXXX+1nr584cUIzZszQt99+q6SkJDVr1izDXf7R0dHatm2bfVH+jTfe0EsvvZSl78HUqVP18ccfa9q0aSpVqpSeffZZnT17Vj169FCfPn3S5Vu0aKFz587p999/z9L7Z1dSUpK6dOmiv/76S8uXL3f6IZmAJ7l69apatmypgIAAzZ49O8vPVwAAAEDux3EuAADAozVo0ED33nuvatWqpT59+jg8gqZixYoaNmyYBg8erD///DPNUSmOHg76yy+/aOrUqapSpYref//9NEeoSFKHDh104MABDR8+XNK1vxro169fmsW1lJQU/frrrwoMDNSbb76p559/PsO5OnTooMOHDysyMlItW7ZUVFRUlr8HqeffJyYmqmzZsho+fLi+/PJL+4M7M8pfvnw5y++fXcHBwfriiy+0YcMGFtCRawQGBuqtt95S1apVWUAHAABAGuxEBwAAHi8pKSnHHp55+PBhlStXzuFDSbPi6NGjCg4OVvHixV1Y2f85e/aszpw5o5IlS9qP7jAM45ZqBgAAAABkDYvoAAAAAAAAAAA4wINFAQAAAAAAAABwgEV0AAAAAAAAAAAcYBEdAAAAALxMYmLiTTOc3AkAAOAaLKIDAHKVn376Sd99953D1xcvXqwffvghS++VkJCgpKQkpaSkZHn+q1evKiEhQefPn8/yGHiu5ORkXblyxewyAOQCP/zwgy5cuGD/95UrVzR8+HC99dZb2X6v8+fP68EHH1Tnzp0VFxeXYcZms+nxxx9Xt27d9N9//zldNwAAAKRAswsAAMCVPv/8c8XFxempp57K8PWRI0eqQIECeuKJJ276XkOGDNGyZcucqqNo0aJav369U2NhjpSUFJ0+fVqHDh3Svn37tHPnTv3666/q06ePOnbsaHZ5ALzYyZMnNXDgQBUpUkSrVq1SUFCQQkNDtX//fm3fvl0vvviiKlasmOX3mzhxouLj42Wz2ZSUlKSDBw9KuvaLv+DgYFWoUEE//vijDh48qHvvvVdFihSxjzUMQ5cvX1ZoaKj8/dlTBQAAkBUsogMAvNq5c+f0999/KyQkRCEhIQoICFBQUJB9QeFGgYGB9tcNw1BiYqIKFiyoUqVKpcvWrl1bhQoVUmBgoAICAtK89vPPPysuLk7t2rVT3rx57devXr2qpKQkhYaGuvZGkSNOnz6t7t276+zZszp16pSSk5Ptr4WGhqpUqVI6cOCAiRUCyA2mTJmi5ORkde7cWUFBQfbrffv2VYcOHTR8+HDNmDEjS++1detWzZ49W5K0adMmRUVFpXm9WbNmGj16tMaNGyfp2l9gLVmyxP66zWaTJK1evVqlS5e+ldsCAADwGX4GB+UBALzYqlWr1LdvX/sCekJCgmw2mwoUKJBhPj4+Xn5+fsqbN68Mw1BSUpI6d+6sPn36ZGverl276pdffvHIRYjo6Ght3rxZ+/btM7sUr/Dqq6/KZrPp9ttvV0hIiKZOnao33nhDL774ovz8/Oy57du3a9euXRm+R0BAgKKjo2+5luTkZH355ZdavHixTpw4oTx58qhu3br69NNPb/m9gRsdPHhQzzzzjAYNGqQ2bdpkmImPj9ekSZO0YsUKnTlzRvfcc48GDRqkyMjIdNmkpCR99dVX9p/fihUr6vXXX9eDDz7ospqHDRum33//XWvWrHGY2bBhgyZOnKi9e/cqPDxcTz31lHr37q3AwPT7hywWi8aNG6ddu3bJz89PzZs314ABA5QnTx6X1fz333/riSeeULFixbRixQoFBweneb1Pnz5avny5Bg0apE6dOmX6XkePHlXbtm118eJFTZkyRdu3b9eECRP0ySefKCIiQklJSQoPD9f8+fM1ZcoUde3aVTVq1NC0adO0e/duffTRR0pOTlZiYqKaNm3q0vsEAADIzdiJDgDwak2aNJHFYrH/Ozo6WocOHXJ4lMoTTzyh8PBwzZs3z10lwsONHz/e/n8fPHhQU6dOVXh4eJoFdOnawtyECRMyfI/w8HCXLKKPGzdOU6dO1QMPPKBHH31U586d059//nnL7wvc6OzZs+rWrZsuXrzoMJOQkKDOnTsrJiZG1apVU7NmzfTrr78qOjpaCxYsSHP8iM1m06uvvqq1a9eqYsWK6tChg7Zt26aXX35Z06ZNU926dW+55unTp2vu3LkZ/uVQqh9++EEDBgxQaGionnjiCSUlJWnq1Kk6ffq0RowYkSa7efNmdenSRTabTVFRUQoLC9PixYv1999/6+uvv073GeCMlJQUDR48WMnJyerfv3+6BXTp2tFhGzdu1OjRo1WpUiU99NBDGb5XTEyMevbsqbNnz6pPnz6qV6+eQkJCNGHCBB08eNB+TNn69es1bdo03X333erbt6/8/f31zz//aPPmzSpQoIBq1ap1y/cFAADga1hEBwDkOmfOnFGVKlUcvn7vvfe6sRr3GzVqlC5fvmx2GblO6hE9P//8s0qWLGm/3qlTJ/31118umeP777/XHXfckWYBLzsPts3I0aNH1bhxYz355JP68MMPXVGmT9i0aZM6duyoXr16qXfv3maX41IHDhxQjx499M8//2SamzRpkmJiYvT4449r9OjR8vf3V+/evdW6dWu99dZbmjNnjj07d+5crV27Vvfff7++/vprBQcHy2azKTo6WoMHD9aKFSvSHGOSHSkpKRo3bpwmT56cae7MmTMaNmyYgoKCNGvWLN19992SpBo1aujtt99WVFSUfYE6MTFRAwcOVFJSkr744gs98sgjkqSmTZuqS5cumjdvntq3b+9UvdebNm2atm3bpho1ajh8FkexYsX04YcfqkePHurdu7fGjx+v+vXrp8kYhqHPP/9cJ0+eVOvWrdW9e3dJ0l133aWAgABt3bpV0rXvVeov+9599137med169aVn5+fLBYLi+gAAABO4EkyAIBcJ2/evBo5cmSGXyVKlDC7vBxXsmTJbD2gzpddunRJycnJcnS6XVJSki5duiSr1Wo/CsLf31+BgYH2Lz8/v3Rn5jvr5MmTuu2229LsgOXBf3ClQ4cOqV27dpKuHUvlyNWrVzV37lwFBQVpyJAh9p/D8PBwvfTSS9qxY4cOHTpkz3/77beSpKFDh9p3WwcEBKhHjx46duyYNm7c6HTNb7/9tiZPnqzu3btnugt98eLFSkhI0LPPPmtfQJekp59+Wrfffru+++47+7U1a9bo2LFjatKkiX0BXZLq16+vGjVqpMk6a8+ePfr0008VHh6ukSNHZrqzvVGjRho6dKisVqu6du2qKVOmpPkFmp+fnyZMmKB3333X/l6tW7fWwIEDNXXqVH311VeSJKvVquHDh+vdd99VeHi4Dh48qIMHDyowMFCfffaZGjRokKZvAAAAyBp2ogMAcp2goCCHu83DwsJuOj4hIUFBQUEKCgpy+s/5DcOQzWZTYmKiQkJCMjyLF+Z78sknM9yN+8477+idd96x//vll19W0aJF3VgZkDPOnDljX7DN7Fzx2NhYxcfHq169eipcuHCa11LPOF+/fr0qVKigs2fP6q+//lLZsmVVtWrVNNnatWsrKChIGzZsUIMGDZyqOSEhQZ999pmaNGmipUuXOsxt2bJFkvToo4+muR4YGKi6detq7dq1N82m3t9nn32mS5cupXlwdHYcP35cXbt2VVJSkoYPH67y5cvfdEyHDh0UFhamoUOHavTo0Vq9erWGDBliP38+ICBAzzzzjD1/9OhR5cmTR/Xq1bNfW7dunfr375/pPOHh4dqxY4dT9wUAAOCr+P/RAwBynXPnzikqKsrh6zc7zqV9+/Y6cOBAluZq3LjxTTPffPON6tSpk6X3u9H27ds1adIk7dixQ/7+/ipfvryio6P1xBNPOFzgz8qDRRs1aiTp2m7MQ4cO6auvvtKGDRv02GOP6c0330yTPX/+vD777DP9/PPPOnv2rEqWLKkGDRqoR48eKliwYLbvaeHChRo0aJAGDx6smJgYrVq1SuXLl9f48eM1ffp0LVy4UEWLFtXIkSN1//33q2/fvvrpp5/0008/qUKFCmnea+DAgVq0aJEWLVqku+66K9u19OzZU8nJyfad5IsWLdLmzZvVunVr1alTRzabTcnJybrzzjvTnL3vSjcePbR58+Y0167/+Um934weaHt9T1OPcLle6vcp1fXvkdqTkSNHpnvAZEZz3njUybJlyzRnzhzt27dPM2bMSLeQumfPHk2aNElbtmxRQkKCypcvr44dO6pt27bZ/n45uueb/RxfunRJX375pZYvX67jx4+rYMGCatSokfr166dChQqlua/rTZw4URMnTrT/+/r/riZMmKCJEydm+N94Rv8dXv99bt26tebNm6dFixbp4MGDWrt2rfLly5emjl69eqldu3YaNWqU1q9fr5SUFNWoUUNDhw5VmTJlnPqe1axZU7Vr175p7tSpU5KU4X9XpUuXVlBQkP0XUJllg4ODVbJkyTS/rJo4caImTJigzp07a+DAgfbrY8eO1RdffKFu3bqpX79+9usff/xxlv7a49SpU/Lz88uwjrJly+r8+fOKj49Xvnz57DVfv2P9+qxhGIqLi1NERMRN571RXFycXnjhBZ06dUrt27fXypUr9dtvvykwMDBLf1kyYsQIffjhh9qxY4dee+01LVmyxH6c1PUCAwPTfV9Sf1E8ZcqUDH9pER0dneX/9w0AAAD/h0V0AECuU7RoUYcPFk1ddMtMkyZNVLt27QwXKFL9/PPPiouLU7t27TLcqZiSkiKbzabLly87vYN56dKlGjhwoIKCgtSsWTPlz59fq1ev1uuvvy6LxaJBgwY59b7X++233/Tqq69KkipXrpzuuJtTp07pueeeU1xcnO6//341a9bMvlC6Zs0aLVy4UPnz53dq7nHjxum+++5T9erVtXHjRj399NMqVqyYHnvsMS1cuFBjx47Vt99+q1atWumnn37SsmXL1KtXL/v4pKQkrVq1SnfeeadTC+iS1Lp1a/v/ffXqVX366aeSpOrVq6tZs2YKCgqyH02RU4vo3bp1s//fX3zxhUqWLKmWLVvar11//npW5c+f3/6+ly5d0qxZs1SlShU1bNgwTcYVhgwZovnz56t06dIqW7ZsusW+devWqVevXgoPD1fTpk0VGhqqX375RUOGDNG///7rkvPGb/ZzHB8fr+eee04HDhxQ/fr11bhxY+3fv19z587Vtm3btGDBAoWFhalkyZL279vx48e1dOlS3X///br//vtvucbrXb16Va+88op+++03VahQQRUqVMhwcfX06dP2z5jWrVtr+/btWrdunU6ePKlFixY5ddRPVo8eunTpkiQ5/PzKly+fTp48maVs/vz57Vnp2s/8mjVrNHPmTLVp00Z33nmnDh8+rK+++kp33nmnevbs6XTN4eHhGf7FUerP+7///qt8+fLZay5SpEi6bIECBSRdO17JmUX0n376yX5UzJtvvqn77rsvW+PfeOMN1a1bV4MGDdIbb7xh/29qyZIl+vjjjxUcHCx/f39dvHhRO3fuVMOGDZWUlKTo6Gj7Z+HFixd1+vTpdO99/S8NAQAAkHUsogMAvNY///yjCxcu2I9ekaTLly/LZrPp4MGDGY5JTk5WYmJimteTk5Nls9nsOxL79u1707kPHTqkuLg4de3aNd2OYFc4deqUhg4dqpCQEC1YsMB+xvmrr76q5s2b65tvvlGXLl1UrFgxp+eIj4/Xa6+9pg4dOqh79+7KkydPusy7776ruLg49enTRz169LBfHzZsmObOnau5c+fq5Zdfdmr+atWq6auvvtLevXvVqlUrSdLs2bPtvyw4ceKEpGtnFBcuXDjdIvqvv/6q+Pj4NIvQt2LBggX6999/JV078uKZZ57R3XffneMP47x+x+0XX3yh0qVLp7nmjPz589vf4+jRo5o1a5buuuuuW37fGy1ZskTx8fGaOnVqugchStf+exw4cKDy5MmjRYsW6fbbb5d07b+xJ554Ql9++aWio6Od+ouGVFn5OR47dqz279+vd955R88++6z9+gcffKBvvvlGCxcuVIcOHVSmTBn792jTpk1aunSp6tat6/IHi3755ZcyDEPz589XtWrVHObmzZunpk2basyYMQoKCpLNZtNTTz2l2NhY/f333+n+MsOVUhdaM9oBLV3bYX7lypUsZy9cuGD/d2BgoD788EO1adNGw4cP18yZM/Xee+/JMAyNGjXK/our7PL393d4ZFfqe6Y+dDm15ozyN2az65VXXlHVqlVVt25dBQcHa/PmzQoLC1NgYKAaN24sq9WqTZs2pRv3xBNP6MiRI/bjc6ZNm5bm9Vq1aumDDz5QcHCwfvzxR82fP18REREaPHiwrl69qhIlSth3/L/22msO6+NoKgAAgOxjER0A4LUmT56sBQsWZPhaZse5nDp1Kt3rme1eN8Py5ct15coVvfLKK2keEpo/f36NHTtWp0+ftv/iwFkXL15Uq1at9Prrr2f4+oULF7RmzRqVKFEi3QMIu3Xrpjp16qhs2bJOz5963EjqTv5atWrZd4tev7s/MDBQjz/+uGbOnKm9e/fajwpZtmyZ/P391aJFC6drSBUfH6/x48crLCxMly9fVuHChVW0aFEtWrRIlStX1ksvvXTLc+RGcXFxmj59uh544IEMX1+/fr3Onj2riIgIzZkzJ81r+fLl04kTJ7R9+/Ys/YWIIzf7OU5JSdEPP/ygwMBAnThxQmPHjrW/du7cOUnSH3/8oQ4dOjhdQ3adPHlSixcvvukDgPPkyaN3333X/t96QECA6tWrp9jYWJ05cyZHF9HDw8MlXfuLj4wkJycrOTk529lUd955p3r16qWxY8eqX79+Wr9+vXr27On0X5VI175fZ8+edVjD9f97fc0hISFpsqn3cWPN2XH9USqpO9uTkpL077//OvzFyenTpzN9+HXJkiXtf5kycuRISdf+O4qNjVXVqlVVrlw5+yL6+PHjM/zvsmvXrhk+BwIAAACZYxEdAOC1Bg4cqAEDBig0NFQhISFKTk5Wnz59tG7dOk2ZMkUPPvigli5dqvj4eD377LMyDEPdunXTn3/+aV9gSD3z2tkdhznl0KFDktKfly0pS+cZZ0VwcLDDhUdJOnLkiFJSUlS5cuV0f/5//WKOs248BiezB/i1bt1aM2fO1LJly1S1alVdvnxZa9eu1QMPPJDpolNWvfvuu/rvv//Ut29fjRs3Tv7+/hozZoxatmypsWPH2h+k6MlsNluOHNNgs9kcvtaoUSOHC+iSdPjwYUnXHlIZGxubYeb6Yz6ccbOf43Pnztl3QU+ePDlHashIZt+39u3b33QBXbr2gMsbH+qZunPaMIxbK/AmbrvtNknXflFyo6tXr+r8+fP2X3pllpWk//77L919SNce2Pvzzz/rp59+UpUqVdS9e/dbqrlEiRLavXu3Lly4YF+4vr4G6f8WtFM/N+Li4tIt3N+YdZXdu3crJSUlw8/1pKQknT9/XpUqVbrp+6xfv1779u2Tn5+fTp8+rUmTJik5OVnffvutPRMWFpbhkU085BoAAMA52T9IEQAAD5EvXz4VLFhQoaGhiomJ0TPPPKN169bp3XfftS96fvXVV/ZF0YCAAI0dO1YVK1ZUly5dNGbMGCUlJSk0NNT+YEFvcPXqVSUkJNzSLklJKlasmIoXL+70+ISEBLf98iEyMlKVKlXSsmXLJElr166V1WpNc3a4s5YtW6bvv/9eTZs21aOPPmq/XqhQIQ0aNEjJyclatWrVLc+Tk5KSkjI8/9gVjh075vC1m+0aTl3oHTx4sPbt25fh1/XHqzjjZj/HqTVEREQ4rGHevHm3VENGbuX7lqpcuXIuqib7KlasqJCQEMXExKR7LSYmRjabzX6cVIECBVSqVClZLJZ0i/unTp3SsWPHMjx6KiEhwb5gffbsWfs55c5K/b7u2rUr3Wt//vmnpP87yiQr2Vs5Lisj69atk3Ttr25ulPrf780+k202mz766CPdddddKlKkiIoVK6YpU6bo6tWr6t69u+Lj4yVd+wVFlSpV0n1t3rzZpfcEAADgK1hEBwB4rStXrmjVqlV66aWX1K5dOx09elTPPPOMChcurPXr12vDhg1KTExUQECANmzYoA0bNmjz5s2Kjo5W48aNNXnyZD388MN6//33tW7dOodHEZgh9ZiGffv2pXvtiy++0H333Zdm12FOKFu2rPz9/XXgwIF0u2qPHz+u++67T+3bt8/RGq7XqlUrxcXFadeuXVq2bJnCw8PTLHo7Y9euXXrrrbdUrFgxDR8+PN3rUVFR+vrrr9Ocxb5q1SotWbLE/nXmzJlbqiE7/Pz8JKXf5bxlyxaHO5+z8vBJR+97/vx57d2715lSJf3fz3FGzyjYs2ePZs2aleEirSsVLlxYBQoU0JEjR9L9N37lyhXNmjVLS5YsSTfuVr5vhw8fdsnu9ls9sulWhISEqH79+vrzzz/TPVR30aJFkqR69erZrzVp0kSnTp3Szz//fNNsqnfffVcnTpxQly5ddPr0ab399tu3VHPTpk0lKd1n47Fjx7Rp0ybdfffd9vP3H374YQUFBWnOnDlKSUmxZy9duqSVK1eqcOHCGe4Yd9bly5c1f/58hYWF6ZFHHkn3euoi+s3+smbSpEnau3dvmmdR3H333Ro8eLCio6Ptf9Hz/vvva9myZem+7rnnHpfdEwAAgC9hER0A4LVefPFF9ezZU+vXr1fLli31008/6bffflOPHj304osvqnPnzjp8+LDOnTunzp07q3PnzurevbtGjBih8ePH66OPPpK/v79mzpyZ4w+PzK7HHntMISEhmjVrVpoFyCtXrmj58uWSpDp16uRoDQUKFFDDhg118uTJdMdgLFy40C01XK9ly5by9/fX7Nmz9euvv+rRRx+1n2vsjMOHD+uVV15RYmKiPvnkkwyPm5DSL/6NHDlSb7zxhv0r9egdd0it8cCBA/ZrSUlJGj16tMMxxYsXV1BQULqjNq5evZrufffv358mM27cOCUkJDhdb+pxJD/88EOa97bZbBo+fLjee+89nT9/3un3zwp/f389/vjjslqt+vLLL9O8tnDhQr333nv2HcLXK1WqlKT0R5Tc7PtmGIY+/PDDHD9uxR1eeukl+fv7q3///vrrr7/sD0P97rvvdPvtt+uhhx6yZ6OjoxUWFqa3335b27ZtkyStWbNGkydPVt68efXYY4+lee8ff/xRP/zwg1q1aqUBAwaobdu2WrFihX3R3RlVq1ZV/fr1tXbtWk2YMEHJyck6efKkXnvtNSUnJ+upp56yZ4sUKWJ/SOvbb78tq9Wqixcv6o033tC5c+f05JNPuvR4pE8++URnzpxRmzZtlC9fvnSvp/4yLrNF9HXr1unzzz9X9erV1bx58zSvtW3bVp06dbL/YqdEiRKqWLFiui9HD14FAABA5jgUDwDgtfr376+lS5eqU6dO9h2vkydPVlBQkAIDA+Xn56fu3bvr7Nmzmjt3rmw2my5cuCCr1Srp2s7mJk2a6Ntvv9Xtt9+u4OBgM28njeLFi2v48OEaPHiwnnrqKT366KPKmzevfvnlFx07dkzR0dGKiIjI8Trefvtt7d27V59++qk2bNigu+++W/v379eGDRtUpkyZWz7DODtuu+021a5d277I1qpVq1t6v+LFi+v+++/X/fffn6VfBqTuNv7555/TnAffqVMn/fXXX7dUS1Y9+uij+uqrr/Thhx8qKChIwcHB+uyzzxQQEGA/puJGgYGBatGihRYuXKiePXuqfPnyOnnypE6cOKFZs2ZJkmrWrKmiRYtq3rx5qly5su68804tXLhQK1asUI0aNbRjxw6n6g0LC9OIESPUu3dvPfXUU2rSpImKFy+uP/74Q3v37lWzZs1Uv359p78fWdWvXz9t3rxZEyZM0G+//aZ7771X//77r1atWqUiRYqoX79+6caULFlSdevW1ffffy/DMFS8eHHFxcUpMDBQY8aMkXRtN3NoaKi++OILFS9eXMWLF9f06dN18OBBVapUyW0/Fznlvvvu08CBAzVq1Cg9/vjj9gfvhoeHa/To0Wk+M8uUKaORI0dq4MCBeu655+zZoKAgffTRR2mORvn333/17rvvqmjRoho8eLAk6c0339Svv/6q999/X7Vq1VLp0qWdqvnDDz9Up06dNHHiRH355Zey2Wyy2Wxq3LixnnvuuTTZgQMH6tChQ5o3b54WLVokwzB09epVVatWTX369HFq/ozMnTtXM2fOVIkSJRy+b+rxP44W0Q3D0JgxYxQUFKT3339ffn5+Gf6iJiu/vLl+5z0AAACyhkV0AIDXSl0AvV7qYnqqoKAgBQQE2B98l7q7NFWePHn0yiuvZGm+/fv3288iT915mpUjH5zVunVrlS5dWp9//rlWrVqlq1evqlKlSurRo4eefvrpHJv3eiVKlNCCBQs0adIkrVy5Ujt27FDRokX1/PPPq1evXm4/S75169b6448/VKJECdWtW/eW3itPnjwaN25cmgftZbYAlXr+u7+/f5oxfn5+SkxMvKVasuree+/V6NGjNWnSJPXu3VuFCxdW8+bN1bt3bz3xxBMOxw0dOlQFCxbUihUrtHbtWuXPnz9NPm/evPryyy/1wQcfaOTIkQoODtYDDzygBQsW6LPPPnN6EV2SGjZsqDlz5mjSpElav369kpKSVL58eQ0fPtxtP8f58+fX3Llz9cUXX2j58uWaPXu2ihcvrrZt26pnz54Oz6EeO3asxowZo3Xr1um///5TkSJFFB0dbX+9VKlSmjRpkkaPHq1BgwYpb968atiwoebNm+fSRVgzvfDCC6pZs6a++eYbxcXFqVKlSurWrVu6z1JJat68ue666y599dVX+uuvv1SyZEn72dypDMPQwIEDdeHCBY0fP95+vEq+fPn07rvvqlu3bnrjjTc0a9Yspz5fixYtqoULF2rmzJn69ddfFRgYqKioKLVp08a+SztVWFiYZsyYoQULFmjFihW6evWqGjZsqOeff94lD+BMSkrSp59+qqlTpypfvnyaPHlyhg8rPXfunL7//ntJcvjLAz8/P40bN067du1S5cqVJcn+C4Lrpf77+uNebpT6PQcAAEDW+Rm54W9NAQA+KykpSRMnTlRISIh99/n1Zs+erYSEhHQLCikpKbLZbEpKStLTTz+tMmXK3HSulStXqnfv3vZ/BwcHa8uWLQoNDXXNzcB0FotFTz31lIYOHarnn38+zWv//POPjh49qpo1ayokJMR+/fz587p69arDneAAfEtSUpK+//57ffHF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cZwLAAAAAAAAAHipxo0bZ/r66tWrM7weHx+vO+64I821AgUKKDY2NtP3i4+PV0BAgMqUKWO/lj9/fp08eTLD/KRJk1SyZEk1bNhQ0rVF+AYNGujFF19UcnKy3nvvPfXr108LFizIdN6s2LVrl5YvX659+/bp1KlTCggIULFixRQZGanHHntMVapUcep9WUQHAAAAAAAA4BM4E/3/BAYGKiQkJM210NBQWa3Wm44LDg5Ocy0sLCzDcRs3btTMmTP19ddfKzDw2lJ06rEvqd577z01adJEhw4dsu9Wz659+/bpvffe0/nz5/XYY4+pS5cuKlasmGw2m06fPq3NmzerZ8+eqly5st566y2VLl06W+/PIjoAAAAAAAAAeClHO81vplChQjpz5kyaa5cuXUq3QJ7RuMuXL+vSpUvKmzevpGu7028cFxcXp/79+6t///6qVauWw/crXry4JOno0aNOLaLPnz9f48aNU79+/fT000+ne71KlSp66KGH1KdPH3399dd65plnNGLECDVo0CDLc3AmOgAAAAAAAAD4mOrVq+vAgQO6ePGi/VpMTIxuv/32TMeVKVNGRYsW1bZt2xyOO3v2rLp27aqGDRvqxRdftF//77//1Lx58zS71lPfp2TJktm+hwMHDujrr7/W7NmzM1xAv15AQIC6dOmiiRMn6oMPPkhz3zfDIjoAAAAAAAAA+JjKlSurQoUKGjNmjFJSUrR7926tXLlSjRo1UkpKii5evCibzZZunL+/v6KiojR+/HhdunRJ586d0/Tp09WoUSNJUmJiorp06aLChQtr4MCBSkhIUEJCgpKSklSkSBHlzZtXb731liwWi9auXashQ4aoXr16aR5wmp17+OGHH1S2bNksj6levbp++ukn5c+fP8tjWEQHAAAAAAAA4BOMXPh1K0aOHKnVq1erXr16ateunVq2bKkGDRro+PHjqlWrlvbv35/huFdffVVBQUFq0KCBGjVqpNDQUHXr1k2S9Pvvv2v37t3asmWLatWqpfvuu0/33Xefhv0/9u49zsZ6////c81gWBhGTskg0bZYI5JDSQoVaqSDDk7ZqcghKeWQQyEiKaWDdnaF7N2mHZ3syuHbrvZWk1MulkNJiSK7jRlrGoz1+6Nf89ljrDHXmmvW+1qzHvfbbW41az3f7/fr6tXt+uPt8r4mTpQkPfPMMzpy5Ih69+6tCRMm6Oqrr9bcuXMjvoaEBPtb3HbHeEKhUHH/W8cUy7JMlwAAAAAAAAA4wu/3my4hpvzh75NNl+C47TdMLNb4YDCojIwMpaSkqHnz5kUed/LkSa1bt07Hjh1TmzZtVLZs2WLV4WZx+WLRU28uwWBQgUBAPp9PXq+3QN6yLFs3JPJm8+HGFNZnt10D+cjG0GP35p1agx5HL2+qJnoced6NNdFj82vQY7N5N9ZEj82vEQv5WOqxG2uKhbzJHkdjjXjLA8Xl9XrVsWNH2+MSEhIKfWFoaRKXm+gAAAAAAAAAgNKhU6dO8ng8Rc6vWrXK1vxsogMAAAAAAACIC6FQ0TdaETuGDx9eovOziQ4AAAAAAAAAiFnXX399ic5v/9WlAAAAAAAAAADECTbRAQAAAAAAAAAII6LjXA4ePKgPPvhA27dv14EDB5SYmKgaNWrI7/fryiuvVJUqVZyuEwAAAAAAAACKJ2S6AETT8ePHtW/fPp199tk6cuSIqlevHtE8tp5EP3jwoB566CGlp6dry5YtSktL02233aYbb7xRPp9P//73v3XVVVfpscceU1ZWVkQFAQAAAAAAAAAQqezsbI0ePVoXXnihunXrpl27dmnGjBnq2bOnDhw4YHu+Im+if/rpp+rRo4fOOeccrVmzRtOmTVOvXr3UsWNHderUSbfccouefPJJffTRRzpx4oTS09O1bds22wUBAAAAAAAAABCpxx9/XP/+97/10EMP6eTJk5Kku+66SwkJCZoxY4bt+Yq0ib5z506NGTNGzz33nEaMGKHy5cuHzSYnJ2vSpEkaN26c7r77bh05csR2UQAAAAAAAAAAROKjjz7ShAkT1K9fv7zPzj//fI0cOVKffvqp7fk8oVCoSCcBZWVlqVKlSrYmj2RMSbMsy3QJAAAAAAAAgCP8fr/pEmJK4yVTTZfguJ29xpsuwXXatGmjWbNm6bLLLlOTJk20bNkyNWnSRCtXrtS4ceP0xRdf2JqvyC8WTUxM1Nq1a9WuXbsiT+62DfTfnXpzCQaDCgQC8vl88nq9BfKWZdm6IZE3mw83prA+u+0ayEc2hh67N+/UGvQ4enlTNdHjyPNurIkem1+DHpvNu7Ememx+jVjIx1KP3VhTLORN9jgaa8RbHkBBV1xxhZ544gnVrFkz77MdO3Zozpw5uuKKK2zPV+RN9H379mnQoEHatGlTge/atm2rChUqKDEx8f8mLlNGPXv21D333GO7KAAAAAAAAAAAIjFu3DgNGzZMPXv2lCTddNNNys3N1UUXXaSxY8fanq/Im+hJSUkqU+b08cOHD2vSpEn5Plu7dq3mz5/PJjoAAAAAAAAAIGqqVKmihQsX6osvvtCOHTskSY0bN1bbtm0jmq/Im+gejyfvSfOnnnpKSUlJOn78uO69915JUvfu3fPl69atq2PHjunYsWMqV65cRMUBAAAAAAAAgFOK9nZIlBZt2rRRmzZtij1PkTfR/9e8efPUuXNnffbZZxo2bFje52+88YbKli2rnJwc3XbbbWrevHmxCwQAAAAAAAAAwI5PP/1Ur732mr777jvl5uaqfv366tu3rzp16mR7roRICvB4PHruuedUpUqVvN8l6fHHH9frr7+uxx57LJJpAQAAAAAAAAAolsWLF+vOO+9UVlaWrrjiCl111VU6fvy4hg4dqr/97W+254voSfTf/b55/rvatWvrzTffVOvWrYszLQAAAAAAAAAAEZk3b57uvPNOjRo1Kt/nTz31lF5++WXdfPPNtuYr0ib6G2+8UaQd+lM31QEAAAAAAADALUIh9i/jQWZmptq3b1/g83bt2mnBggW25yvScS6bN2/W8ePHbU8OAAAAAAAAAEA03XDDDXrllVd09OjRvM+ys7O1aNEidevWzfZ8nlDozO+kzc3N1Y8//qgbbrhBX3zxhXw+nwKBgC6//HKtWrVKfr9fgUBA3bt31/vvv682bdroiy++sF2MJC1fvlxz5szR6tWrJUlvvfWWnn32WR09elQ333yzRo4cqYSEiI5ylyRZlhXxWAAAAAAAAMBN/H6/6RJiynlvlL53OX5zy8OmSzBu8ODB+X4PhUL67LPPVL58eTVp0kQej0c7duxQVlaWLr74Yr388su25i/ScS6JiYlFOqrlu+++k9/vV25urq0ifvfTTz9p6tSpqly5siTpn//8px5++GE9+uijat26tcaMGaNFixapf//+Ec3/u1NvLsFgUIFAQD6fT16vt0DesixbNyTyZvPhxhTWZ7ddA/nIxtBj9+adWoMeRy9vqiZ6HHnejTXRY/Nr0GOzeTfWRI/NrxEL+VjqsRtrioW8yR5HY414ywOQUlJSCnyWnp6e7/e6detGPH9ELxYNhUIaNmyYDh06lPe7JP3jH/9QcnKyDh8+HNGcY8eOVe3atfMes3/llVd01VVXqVevXpKkMWPG6KGHHir2JjoAAAAAAAAAoHSYPn16ic4f0Sb6LbfcogoVKuimm25SQkJC3lPqqampkqQqVarYnnPRokXat2+fxo4dq8mTJ0uStm7dqoceeigvk5aWpn379umXX35RtWrVIikdAAAAAAAAQLzixaJxIxgM6ptvvtGvv/6a99mJEye0bt06DRs2zNZcRd5ED4VCOnnypCTp0UcfLfDdhAkTTjtuypQpZ5z722+/1dNPP61XXnlF2dnZeZ9nZmaqfv36eb8nJiaqYsWKOnDgQLE20YPBYL7ff1/zf9c+0xi7a5CPbv50Y87UZ7ddA3n7Y+ixu/NOrEGPo5uPxhr02Nl8NNagx87mo7EGPTabj8Ya9NjZfDTWcHs+1nocjTVKW950j6OxRjzlT3c0MRDv/v3vf+vee+9VVlaWpP87ScXj8SglJcX2JnqRXiwqSd98842uu+66076Y87777lO5cuXynkoPhULKzc3V8ePH9fTTTxc6b25urm677TZ17NhRQ4cO1eeff66xY8dq9erVSktL0+uvv67mzZvn5Tt27KjZs2erVatWti70d5ZlKScnx9aYpKQkW2PIm827sSbyzubdWBN582uQdzbvxprIm1+DvLN5N9ZE3tm8G2sib34N8s7m3VgTefNrxFs+0j2yeHXeX6eZLsFx39w6znQJrtOzZ09deOGF6tmzp3r37q1//vOf+u9//6s//vGPGjVqlHr06GFrviI/iV6vXj2tWLHitN+daaO8MC+++KISEhIKvEFV+u1A+IMHD+b7LCsrS+XKlYt4PUny+Xz5fs/Oztbu3bvVoEEDVahQoUB+165dBcYUhrzZfLgxhfXZbddAPrIx9Ni9eafWoMfRy5uqiR5HnndjTfTY/Br02GzejTXRY/NrxEI+lnrsxppiIW+yx9FYI97yAAravXu3Jk6cqObNm6t+/fpav369unTpomHDhumFF14ouU30smXL5p157qQ333xT//nPf9S2bVtJvz2Znp2drYsuukg+n0/r1q1Tp06dJP12E8nKytLZZ59drDXD/TWXChUqhP3O7l+NIW82X9iYcH122zWQj3wMPXZn3sk16HF08tFYgx47m4/GGvTY2Xw01qDHZvPRWIMeO5uPxhqxko+VHkdjjdKaN9XjaKwRb3kU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bdmyRf/617+UlJSkiy66SMePH1e1atV02WWX6e6779YVV1yhihUrSpIOHDig++67T3/5y19sFQQAAAAAAAAAQKSef/55DRo0SCNGjMj77LrrrlPt2rU1d+7cktlEr1ixovx+v9q1a6cLL7xQ27Ztk9fr1d///ndlZGSoRo0auvbaa/PyOTk5GjVqlHr16qUlS5aoV69etooCAAAAAAAAAMeFTBeAaPjxxx/Vpk2bAp+3adNG8+fPtz2fJxQKnfF/nWAwqMsvv1zdu3dXlSpV9PXXX+u5557TY489pnPOOUcpKSl6+eWX1b17d/Xp00d///vf9cknn6hz585q0KCBLrnkEtuFlRTLskyXAAAAAAAAADjC7/ebLiGmNHhxlukSHLd78CjTJbjOrbfeqlq1amn27NlKTEyU9Ns7PUeOHKn9+/frr3/9q635irSJLknfffedXn31VZUvX1579+7V2WefrYyMDL3wwguSpM6dO6tly5baunWrkpKSNH78eHXv3t3m5ZU8y7IK3FyCwaACgYB8Pp+8Xm+Rxthdg3z08uHGFNZnt10D+cjG0GP35p1agx5HL2+qJnoced6NNdFj82vQY7N5N9ZEj82vEQv5WOqxG2uKhbzJHkdjjXjLwx420ePDpk2bNGDAANWoUUOtWrWSJK1fv14HDhzQa6+9pubNm9uaL6EooW3btmnt2rVKS0vTf//7X/34448qX768evTooe+//16SVK5cOS1cuFCPPPKIfvnlF23evNnmpQEAAAAAAAAAUDwXXHCBlixZopYtW2rz5s366quv1LJlSy1dutT2BrpUxDPRLcvSa6+9pjJlyigzM1M//fSTfv75Z6WkpGjbtm367rvv8rK5ublq27atVqxYoXbt2qljx462iwIAAAAAAAAAx3Emetxo1KiRZsyY4chcRdpEv+mmm3TTTTcpIyND33zzjZ566in9+uuvmjp1qrxer5566ilt3LhRN998s5KSktSjRw9Vq1ZN06dP12WXXSaPx+NIsQAAAAAAAAAAnMnhw4f1448/qkmTJtq7d6/+3//7f7r66qtVvXp123MV6TiXkydPasKECbr//vv1n//8RxdeeKFGjRqlu+++W+XLl9eDDz6Yt3n+3XffqX379urcubM8Ho9WrlxpuygAAAAAAAAAACKxZcsWde3aNe99nr/88otmzJiha6+9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r18/x+dNiHTgyZMnde+99+q+++5zsBwAAAAAAAAAANwj4k30F154QVu2bNFVV13lZD0AAAAAAAAAALhGRJvoK1as0HPPPadevXrJ4/Fo48aN+umnn3Ty5Emn6wMAAAAAAAAAR3hCpe+nOA4ePKghQ4aoZcuWuuGGG7Rt27YijcvNzdWMGTPUtm1bXXHFFXr//ffzfb97927169dPLVu2VP/+/bVv375837/11lvq1KmT2rZtqyeffNL1+8q2z0RfuHChHn/8cV133XUaMGCArrnmGnk8HklSQkKCatSoodq1a6tFixYaNmyYKlWq5HjRAAAAAAAAAIDIhUIhDRs2TJK0dOlSffXVVxoyZIjeeecdVaxYsdCxTz/9tJYvX65nn31WZcuW1fDhw1W/fn01a9ZMOTk5GjhwoJo0aaLly5drxYoVGjZsmJYuXaqEhAT985//1MMPP6xHH31UrVu31pgxY7Ro0SL1798/GpcdkSI/ib57924NHDhQ06dP15133qnp06fnbZ6vWbNGf/nLX/TUU09p4MCBatq0qd544w3Nnj27xAoHAAAAAAAAAERm/fr12rBhg6ZOnarzzjtP119/vc4991ytXLmy0HHHjh3TokWLNGzYMLVp0ybvafPFixdLkj744AMdPHhQU6dOVb169TRo0CBlZ2dr/fr1kqRXXnlFV111lXr16qUGDRrkbaK7WZGeRP/73/+u8ePHy+/3669//auaN2+e953H41Ht2rVVu3btfGMSEhK0evVqTZw40dmKAQAAAAAAAACSpM6dOxf6/apVq077+datW1WnTh01atQo77OWLVtq06ZNuu6668LO9+233yoYDKpDhw75xr399tt586alpSklJSXv+xYtWmjTpk266KKLtHXrVj300EN536WlpWnfvn365ZdfVK1atcIv1hBPKBQ648k5e/bs0TfffKPLL7883+e7du3SNddco0AgUGDMggULFAwGNXjw4DMWsXDhQk2dOjXfZ2PHjtWAAQP01Vdf6dFHH9WuXbvUvn17TZ06VVWrVj3jnOFYlhXxWAAAAAAAAMBN/H6/6RJiSsM5pe/kjHPffq/Q78Ntoj///PP6/PPP9dprr+V9tnDhQq1du1bPPfdc2Pm+/PJL9e/fX1u3bs37bPv27erbt68yMjL08MMPKxQKadq0aXnfP/744zp58qTGjRun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NeIt36pVqyJnITUbUwrPRH+cM9FP9Z///EcPPvig0tPTdeWVV+q6667Tvn37JEkjRozQ4MGDbc1XrE300/nqq680btw4jRw5Up07dy7SmHfeeUdjxozRV199pcTEREnS/v37ddlllykhIUHjxo3Lt2Gfnp6uoUOHqmvXrrbrsyxLDRs2zPdZdna2du/erQYNGqhChQoFxuzatavAmMKQN5sPN6awPrvtGshHNoYeuzfv1Br0OHp5UzXR48jzbqyJHptfgx6bzbuxJnpsfo1YyMdSj91YUyzkTfY4GmvEW54n0e1pNroUbqLPYBP9TI4ePaq1a9cqJSVFF154oe3xxTrO5XSaN2+uZ555Rvfee2+RN9Hr1KmjkydP6tdff817nP6HH36QJPXs2VPr1q3L20Q/evSovv32W9WpUyfiGsPdXCpUqBD2O7s3JPJm84WNCddnt10D+cjH0GN35p1cgx5HJx+NNeixs/lorEGPnc1HYw16bDYfjTXosbP5aKwRK/lY6XE01iiteVM9jsYa8ZYHULiKFSsWea/6dIr1YtFwzj77bP34449Fzl9wwQU699xzNXHiRO3Zs0dbtmzRY489pksuuUT9+vXTypUrlZGRIem3c9hTUlLk9/tLonQAAAAAAAAAQIx788031a9fP3Xs2FFff/21pkyZomnTpunEiRO25yrWk+jLli0r8Fl2drbeffddnX/++UUvokwZzZ8/XzNmzNBNN92kY8eO6eKLL9aUKVN01llnafjw4RowYICqVq2qYDCoOXPmKCGhRPb/AQAAAAAAAAAx7LXXXtPMmTPVqVMnffnllzpx4oRatWqlyZMnq3z58rr//vttzVesTfRnnnmm4IRlyqhJkyZ68MEHbc119tln6+mnnz7td4MGDVL37t21fft2paWlqVatWpGUCwAAAAAAACCOeRx9OyTcauHChRo7dqz69u2rJk2aSJK6d++ukydP6oknnrC9ie74i0XdzrIs0yUAAAAAAAAAjuDIY3v8D5W+F4taM3mx6KlatGihl19+WRdddJGaNGmiZcuWqUmTJvr3v/+te+65Rxs3brQ1n60n0T/55BMlJyfrggsusLWI25x6cwkGgwoEAvL5fKd9cYNlWbZuSOTN5sONKazPbrsG8pGNocfuzTu1Bj2OXt5UTfQ48rwba6LH5tegx2bzbqyJHptfIxby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OXKkli5dqszMTCUmJio1NTVvbHJyct5YkzyhUKgETs5xL8uyTJcAAAAAAAAAOMLv95suIaZcesMs0yU4LunwikK/P90m+n/+8x9lZ2efNn/HHXdo5MiR6tatW95nvXv31q233qoePXoUutbEiROVmJioSZMm5X329NNP5x3xcqo9e/aoS5cuWrFihY4cOaIBAwZo48aNed9///336tatm/Fz0ePySfRTby7BYFCBQEA+n09er7dA3rIsWzck8mbz4cYU1me3XQP5yMbQY/fmnVqDHkcvb6omehx53o010WPza9Bjs3k31kSPza8RC/lY6rEba4qFvMkeR2ONeMsDp9skP5Ozzjqr0O8OHjyY77PMzEyVK1fujPOmpKRo165d+T7LysoKO7ZmzZqSpB9++EH169dXdna2srKyVKlSJVvrljTORAcAAAAAAAAASPrtDPN169bl/X706FF9++23qlOnzhnHtmzZUhs2bFBubm7eZ5s3b9bZZ5+t//znP+rWrZuCwWDed7+vU6dOHaWmpqp69er51v59rGlsogMAAAAAAAAAJEnp6elauXKlMjIyJElz585VSkpK3t+QyMnJUVZW1mnHtm/fXidOnND8+fMlSWvWrNFXX32lTp066ayzzlKlSpX08MMPy7IsrVmzRuPHj9cll1yiRo0aKSEhQd27d9czzzyjrKws/fe//9Wrr76qTp06RefCCxGXx7kAAAAAAAAAiD+euHo7ZGSaNm2q4cOHa8CAAapataqCwaDmzJmjhITfnsd+6aWXtHLlSi1fvrzA2LJly2rWrFkaOXKkXn31VR06dEhDhw7VeeedJ0l65plnNH78ePXu3VvJyclKT0/XsGHD8sbfe++9uuuuu3TZZZcpFAqpfv36Gjx4cHQuvBBsogMAAAAAAAAA8gwaNEjdu3fX9u3blZaWplq1auV9N3z4cA0fPjzs2EsvvVQfffSR1q1bp3r16qlx48Z535199tl5T6mfTuXKlbV48WKtW7dOx44dU5s2bVS2bFlnLqoY2EQHAAAAAAAAAOSTmpqq1NTUiMZWrVpVnTt3jmhsQkKCWrduHdHYksKZ6AAAAAAAAAAAhMGT6AAAAAAAAADiQ4hD0WEfT6IDAAAAAAAAABCGJxSKrz9+sSzLdAkAAAAAAACAI/x+v+kSYkqHnk+YLsFxnyx70HQJpV5cHudy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Ffvv45M6duyou+66S2vWrLGveX8N92rXEhkZqQEDBuj555/XpEmTFBAQoIiICPXp00ePPPKIFi5cyOsuF5eQkKDBgwfriSee0Jdffqlx48ZpyZIlCgsLU6dOnfTJJ59o48aNOd74pMauJSkpSWPHjlWvXr20ePFiffDBBypdurRq1qypWrVqKTw8XBs3buTnGACcyG0fLHpNYGCgRowYoWXLlumVV17RN998Iw8PD+3bt0/Dhg2Tj4+P0Snib5hMJnl7eys2NlZHjhxRiRIl1Lt3bwUGBmrGjBl68cUXNW/ePPXo0UMhISGSrr6oSExMZKkeF5GZmSlfX1/17t1bktS5c2ft3r1bn332mSIiIpSWlqavv/5aO3bs0MMPP6zU1FRFR0drwIABBmeOvLBarapevbrat28v6WpD5pNPPtG2bdvUtm1bHTt2TJs2bdKOHTvUsmVLWSwW6uxisrKyZDKZVK5cOXXv3l0XLlxQWFiYFi9erIoVK+qNN95QSEiImjRpovPnz+v06dNq1KiR0WnjFpnNZtWrV0/9+vWT1WrVwIEDlZKSosTERLVr105paWk6c+aMJkyYoJCQEGVkZGjXrl2aOHEinwpzQbVr11ZERIQaNmyo7du3a8eOHbLZbIqPj1dCQoJ27NihFi1aKC0tjXu1Czl37pzCwsIUGhqqESNGSJJatmypxYsXS7r6KaH333+f110u7syZMwoKCtLo0aMlXX1WxbJly7R79249+OCDOnTokH766Sdec7mwc+fOqUyZMgoNDZWPj4/atGmj5ORkvf3221q9erVWr16tFStW8HMMAE7k9k10SfYnzbdp00Y//vijkpOT9c4776hFixZGp4ZbYLVa5eHhoWrVqikzM1Pbt2+XyWRS7969VbVqVc2aNUvvvvuuli5dqrlz5yooKEgbNmzQiy++yJskLiIzM1OJiYm6dOmSfVuzZs3syzCNGTNGW7Zs0d69ezV//nyZTCaNHDlSrVu3Nipl5FN6erpOnjypWrVqaenSpfr999+VkpIiSapcubKqVasmPz8/hYeHy9PTkzq7mGuzVu+//34lJSXp+eef19SpUzVkyBANGjRINptNp0+f1urVq+Xh4aGhQ4eqWbNmBmeNW9WsWTOtXbtWixcv1h9//KHSpUvr3Xffldls1t69e2WxWHTvvfcqKChIq1atkslk0ssv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8X/9mw7/+c9/5OnpecufWgAAAABwczTRAQCA26hbt64k6bfffrNv+/PPP/XUU0/p8OHDLjPGNV27dtXWrVu1detWtWjRQv7+/vk+1+XLlzVv3jw9+uij9tnp1yQkJOSriX7+/HnFxcXp999/V82aNRUQEHDD2GXLlunChQsym8369ddfNWvWLA0cOFDe3t6SpAEDBujw4cOKjIz827Xb/86jjz6q1atX59q2c+dORUVFSbpar82bN992w/56p06d0tNPP63Q0FCFhYXl+fjrm+ilS5fW008/re+//97+NXny5L9dHmbNmjV69NFHc50PAAAAQP6wnAsAAHAb9957rzp37qy3335bVqtVd911l+bNm6fq1avroYcecpkxruncubNmzJihtLQ0Pf744/k+z8WLFzV06FClpKRo7NixOfYlJSXp4sWLt7R8iNVqVVZWlooXL66GDRvqxx9/VPXq1bV79+4cs9uzs7Nls9nszd7+/furdu3a2rVrl1544QVZLBaVLl1ahw8fVoMGDXT58mV5e3vLZrMpOTnZYTP+VvTp00ddu3bVoUOH1LBhQ0lSu3bt1KxZMz3zzDMKCQnRzp07Va5cOfXt2zff41wvOjpaw4YN04QJE/L9iYHr13NPTk7WV199pcWLF9u3ZWdnq2TJkg7P8ddff+nLL7/UypUr85UDAAAAgNxoogMAALcyffp0zZw5U9OnT1d2draaNm2qqVOn2mc9u8oY0tW1vBs1aqR9+/Zp7ty5eT7earVq48aNev/993XhwgWFh4fbZ5zHxMTou+++0969e1WsWDHVr1/f4XmysrIkXZ1p3alTpxvG7Nu3T1999ZX9+2nTptkb/8WLF1ft2rW1du1alSlTRu+//75+/vln7du3T+fOndPEiRPVs2dPxcbGKjQ0VAsWLLhpPjdTunRpTZgwQePGjdOyZcsUEBAgk8mk+fPn66OPPtL27dvVvHlzjR07NteM/Nvx4Ycf6q233lKLFi3yfQ6r1Zrj+xkzZjiMPXfunI4ePaqzZ8/al3NJT0/Xyy+/rOeeey7fD2cFAAAAkJvJdv2UFwAAAOSb1WrN1Qz9Xx4eHre81vrt2rt3r5599lk99NBDmjx5sqpVq2bfl56ervbt26t69er65z//qZCQEIfneffdd3Xo0CEtXLhQx48fV4kSJW66VEhmZqYqVKigcuXKadu2bfr222+1bds2de/eXS+//LLKli2rU6dO6a233tKuXbs0duxYhYaGKjU1VUOGDNGBAwc0bdo09ejRI9/XPnPmTJ09e1Zvv/12vs9R0I4fP65u3brd0oNF//jjD3Xt2lU1a9bU3LlzVbVqVc2ePVtxcXGaMWMGS7kAAAAATkQTHQAAwInGjx+vb7/91uH+0NBQvfbaawWWzx9//KG77777ts7x+uuv6+jRo1q+fHmej42JidG6dev05JNPqmrVqvbtmZmZmjNnjp544glVr17dvj07O1vLly9X37595eXlle+cbTabLl26JD8/v3yfo7BLS0tTqVKlcnzv6emp4sWLG5gVAAAA4H5oogMAADjR6dOndfHiRYf7AwICFBgYWIAZAQAAAABuB010AAAAAAAAAAAcKJgFOQEAAAAAAAAAcEE00QEAAAAAAAAAcIAmOgAAAAAAAAAADtBEBwAAAAAAAADAAZroAAAAAAAAAAA4QBMdAAAAAAAAAAAHaKIDAAAAAAAAAOAATXQAAAAAAAAAABz4f+vX7CVnnsudAAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as plt\n", "import seaborn as sns\n", "from scipy.stats import spearmanr\n", "from tqdm import tqdm # 用于显示进度条 (可选)\n", "\n", "# 设置 Matplotlib/Seaborn 样式 (可选)\n", "sns.set_theme(style=\"whitegrid\")\n", "plt.rcParams['font.sans-serif'] = ['SimHei'] # 或者其他支持中文的字体\n", "plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题\n", "\n", "def analyze_score_performance_2d(score_df: pd.DataFrame,\n", " score_col: str = 'score',\n", " label_col: str = 'label',\n", " condition1_col: str = 'circ_mv',\n", " condition2_col: str = 'future_return',\n", " n_bins: int = 100,\n", " min_samples_per_bin: int = 30): # 每个格子最少样本数\n", " \"\"\"\n", " 分析 score 在两个条件下 (如市值、未来收益) 的二维分箱表现。\n", "\n", " Args:\n", " score_df (pd.DataFrame): 包含分数、标签和条件列的 DataFrame。\n", " score_col (str): 预测分数所在的列名。\n", " label_col (str): 目标标签所在的列名 (应为数值或可排序类别)。\n", " condition1_col (str): 第一个条件列名 (例如 'circ_mv')。\n", " condition2_col (str): 第二个条件列名 (例如 'future_return')。\n", " n_bins (int): 每个条件划分的箱数 (分位数数量)。\n", " min_samples_per_bin (int): 计算指标所需的最小样本数,小于此数目的格子结果将被屏蔽。\n", "\n", " Returns:\n", " tuple: 包含 (performance_pivot, count_pivot, fig)\n", " performance_pivot: 以二维分箱为索引/列的 Spearman 相关系数矩阵。\n", " count_pivot: 每个二维分箱的样本数量矩阵。\n", " fig: 生成的热力图 Matplotlib Figure 对象。\n", " \"\"\"\n", " print(f\"开始分析 '{score_col}' 在 '{condition1_col}' 和 '{condition2_col}' 下的表现...\")\n", "\n", " required_cols = [score_col, label_col, condition1_col, condition2_col]\n", " if not all(col in score_df.columns for col in required_cols):\n", " missing = [col for col in required_cols if col not in score_df.columns]\n", " raise ValueError(f\"输入 DataFrame 缺少必需列: {missing}\")\n", "\n", " # --- 1. 数据准备和清洗 ---\n", " print(\"准备数据,处理 NaN 值...\")\n", " # 只保留需要的列,并移除包含 NaN 的行,避免影响分箱和计算\n", " analysis_df = score_df[required_cols].dropna().copy()\n", " n_original = len(score_df)\n", " n_after_drop = len(analysis_df)\n", " print(f\"原始数据 {n_original} 行,移除 NaN 后剩余 {n_after_drop} 行用于分析。\")\n", "\n", " if n_after_drop < min_samples_per_bin * n_bins: # 检查数据量是否过少\n", " print(f\"警告: 清理 NaN 后数据量 ({n_after_drop}) 可能不足以支持 {n_bins}x{n_bins} 的精细分箱分析。\")\n", " if n_after_drop < min_samples_per_bin:\n", " print(\"错误: 有效数据过少,无法进行分析。\")\n", " return None, None, None\n", "\n", " # --- 2. 二维分箱 ---\n", " print(f\"对 '{condition1_col}' 和 '{condition2_col}' 进行 {n_bins} 分位数分箱...\")\n", " bin1_col = f'{condition1_col}_bin'\n", " bin2_col = f'{condition2_col}_bin'\n", "\n", " try:\n", " # 使用 qcut 进行分位数分箱,labels=False 返回 0 到 n_bins-1 的整数标签\n", " # duplicates='drop' 会丢弃导致边界不唯一的重复值所在的箱子,可能导致某些箱号缺失\n", " # 对于可视化,这通常可以接受,但如果需要严格的等分,需先 rank\n", " analysis_df[bin1_col] = pd.qcut(analysis_df[condition1_col], q=n_bins, labels=False, duplicates='drop')\n", " analysis_df[bin2_col] = pd.qcut(analysis_df[condition2_col], q=n_bins, labels=False, duplicates='drop')\n", " except Exception as e:\n", " print(f\"错误: 分箱失败,请检查数据分布或减少 n_bins。错误信息: {e}\")\n", " # 可以尝试先 rank 再 qcut\n", " # analysis_df[bin1_col] = pd.qcut(analysis_df[condition1_col].rank(method='first'), q=n_bins, labels=False, duplicates='raise')\n", " # analysis_df[bin2_col] = pd.qcut(analysis_df[condition2_col].rank(method='first'), q=n_bins, labels=False, duplicates='raise')\n", " return None, None, None\n", "\n", " # --- 3. 分组计算表现指标 (Spearman Rank IC) ---\n", " print(\"按二维分箱分组计算 Spearman Rank IC...\")\n", "\n", " def safe_spearmanr(x, y):\n", " \"\"\"安全计算 Spearman 相关性,处理数据量过少的情况\"\"\"\n", " if len(x) < max(2, min_samples_per_bin): # 要求至少有 min_samples_per_bin 个点才计算\n", " return np.nan\n", " corr, p_value = spearmanr(x, y)\n", " return corr if not np.isnan(corr) else np.nan # 确保返回 NaN 而不是 None 或其他\n", "\n", " # 按两个分箱列分组\n", " grouped = analysis_df.groupby([bin1_col, bin2_col])\n", "\n", " # 计算每个格子的 Spearman 相关系数\n", " # apply 可能较慢,但计算相关性通常需要 apply\n", " performance_series = grouped.apply(lambda sub: safe_spearmanr(sub[score_col], sub[label_col]))\n", "\n", " # 计算每个格子的样本数量\n", " count_series = grouped.size()\n", "\n", " # --- 4. 结果整理成 Pivot Table (用于绘图) ---\n", " print(\"整理结果用于绘图...\")\n", " try:\n", " # 将 performance_series 转换成二维矩阵\n", " # index 为 condition1_bin, columns 为 condition2_bin\n", " performance_pivot = performance_series.unstack(level=0) # level=0 对应第一个 groupby key (bin1_col)\n", " count_pivot = count_series.unstack(level=0)\n", "\n", " # 可选:按列和索引排序,确保顺序正确\n", " performance_pivot = performance_pivot.sort_index(axis=0).sort_index(axis=1)\n", " count_pivot = count_pivot.sort_index(axis=0).sort_index(axis=1)\n", " \n", " print(performance_pivot)\n", "\n", " except Exception as e:\n", " print(f\"错误: 无法将结果转换为二维矩阵,可能因为分箱不均匀或数据问题: {e}\")\n", " return None, None, None\n", "\n", " # --- 5. 可视化:绘制热力图 ---\n", " print(\"生成热力图...\")\n", " fig, ax = plt.subplots(figsize=(16, 12)) # 调整图像大小\n", "\n", " # 使用 count_pivot 创建一个 mask,屏蔽掉样本量过小的格子\n", " mask = count_pivot < min_samples_per_bin\n", "\n", " # 绘制热力图\n", " sns.heatmap(performance_pivot,\n", " annot=False, # 100x100 个格子加注释会太密集\n", " fmt=\".2f\",\n", " cmap=\"viridis\", # 选择颜色映射, 'viridis', 'coolwarm', 'RdYlGn' 等都不错\n", " linewidths=.5,\n", " linecolor='lightgray',\n", " # mask=mask, # 应用 mask\n", " ax=ax,\n", " cbar_kws={'label': f'Spearman Rank IC ({score_col} vs {label_col})'}) # 颜色条标签\n", "\n", " # 设置标题和轴标签\n", " ax.set_title(f'{score_col} 表现分析 (Rank IC vs {label_col})\\n基于 {condition1_col} 和 {condition2_col} {n_bins}x{n_bins} 分箱', fontsize=16)\n", " ax.set_xlabel(f'{condition1_col} 分位数 (0 -> 高)', fontsize=12)\n", " ax.set_ylabel(f'{condition2_col} 分位数 (0 -> 高)', fontsize=12)\n", "\n", " # 可选:调整刻度标签,避免显示所有 100 个刻度\n", " if n_bins > 20:\n", " tick_interval = n_bins // 10 # 大约显示 10 个刻度\n", " ax.set_xticks(np.arange(0, n_bins, tick_interval) + 0.5)\n", " ax.set_yticks(np.arange(0, n_bins, tick_interval) + 0.5)\n", " ax.set_xticklabels(np.arange(0, n_bins, tick_interval))\n", " ax.set_yticklabels(np.arange(0, n_bins, tick_interval))\n", "\n", " plt.xticks(rotation=45, ha='right')\n", " plt.yticks(rotation=0)\n", " plt.tight_layout() # 调整布局\n", "\n", " print(\"分析完成。\")\n", " return performance_pivot, count_pivot, fig\n", "\n", "# --- 如何使用 ---\n", "# 假设你的包含预测结果和所需列的 DataFrame 是 final_predictions_df\n", "# 确保它包含 'score', 'label', 'circ_mv', 'future_return'\n", "\n", "# # 示例调用 (你需要有实际的 score_df)\n", "try:\n", " # 确保数据类型正确\n", " cols_to_numeric = ['score', 'label', 'circ_mv', 'future_return']\n", " for col in cols_to_numeric:\n", " if col in score_df.columns:\n", " score_df[col] = pd.to_numeric(score_df[col], errors='coerce')\n", "\n", " # 调用分析函数\n", " performance_matrix, count_matrix, heatmap_figure = analyze_score_performance_2d(\n", " score_df,\n", " n_bins=100, # 你要求的100分箱\n", " min_samples_per_bin=50 # 每个格子至少需要50个样本才显示IC,可以调整\n", " )\n", "\n", " # 显示图像\n", " if heatmap_figure:\n", " plt.show()\n", "\n", " # 可以查看具体的 performance_matrix 和 count_matrix\n", " # print(\"\\nPerformance Matrix (Spearman IC):\")\n", " # print(performance_matrix)\n", " # print(\"\\nCount Matrix:\")\n", " # print(count_matrix)\n", "\n", "except ValueError as ve:\n", " print(f\"数据错误: {ve}\")\n", "except Exception as e:\n", " print(f\"发生未知错误: {e}\")" ] }, { "cell_type": "code", "execution_count": 114, "id": "a436dba4", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Empty DataFrame\n", "Columns: [ts_code, trade_date, is_st]\n", "Index: []\n" ] } ], "source": [ "print(df[(df['ts_code'] == '600242.SH') & (df['trade_date'] >= '2023-06-01')][['ts_code', 'trade_date', 'is_st']])" ] } ], "metadata": { "kernelspec": { "display_name": "stock", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.2" } }, "nbformat": 4, "nbformat_minor": 5 }