{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from matplotlib import pyplot as plt\n",
    "from alphamind.api import *\n",
    "from PyFin.api import *\n",
    "from PyFin.Math.Accumulators import MovingMaxDrawdown\n",
    "plt.style.use('fivethirtyeight')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 0. Parameter Settings\n",
    "---------------------"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "engine = SqlEngine('postgres+psycopg2://postgres:A12345678!@10.63.6.220/alpha')\n",
    "universe = Universe('custom', ['zz500'])\n",
    "benchmark_code = 905\n",
    "neutralize_risk = ['SIZE'] + industry_styles\n",
    "constraint_risk = ['SIZE'] + industry_styles\n",
    "start_date = '2015-01-01'\n",
    "end_date = '2017-10-17'\n",
    "industry_lower = 0.75\n",
    "industry_upper = 1.25\n",
    "\n",
    "freq = '1d'\n",
    "horizon = map_freq(freq)\n",
    "dates = makeSchedule(start_date, end_date, tenor=freq, calendar='china.sse', dateGenerationRule=DateGeneration.Backward)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Current Production Strategy\n",
    "-----------------------\n",
    "\n",
    "* **Factor Group #1**\n",
    "  * RVOL: 0.05\n",
    "  * EPS: 0.3\n",
    "  * DROEAfterNonRecurring: 0.35\n",
    "  * DivP: 0.075\n",
    "  * CFinc1: 0.15\n",
    "  * BDTO: 0.05\n",
    "    \n",
    "    \n",
    "* **Factor Group #2**\n",
    "  * VAL: 0.034129344\n",
    "  * RVOL: 0.015881607\n",
    "  * ROEDiluted: 0.048765746\n",
    "  * GREV: 0.042747382\n",
    "  * EPS: -0.015900173\n",
    "  * CHV: 0.019044573\n",
    "  * CFinc1: -0.001792638\n",
    "  * BDTO: 0.014277867"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "factor_group1 = ['RVOL', 'EPS', 'DROEAfterNonRecurring', 'DivP', 'CFinc1', 'BDTO']\n",
    "factor_weight1 = [0.05, 0.3, 0.35, 0.075, 0.15, 0.05]\n",
    "\n",
    "factor_group2 = ['VAL', 'RVOL', 'ROEDiluted', 'GREV', 'EPS', 'CHV', 'CFinc1', 'BDTO']\n",
    "factor_weight2 = [0.034129344, 0.015881607, 0.048765746, 0.042747382, -0.015900173, 0.019044573, -0.001792638, 0.014277867]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "factors = list(set(factor_group1 + factor_group2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "all_data = engine.fetch_data_range(universe, factors, dates=dates, benchmark=905)\n",
    "factor_all_data = all_data['factor']\n",
    "factor_groups = factor_all_data.groupby('trade_date')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "rets = []\n",
    "turn_overs = []\n",
    "turn_over_threshold = 0.70\n",
    "executor = ThresholdExecutor(turn_over_threshold=turn_over_threshold)\n",
    "execution_pipeline = ExecutionPipeline(executors=[executor])\n",
    "leverags = []\n",
    "\n",
    "for i, value in enumerate(factor_groups):\n",
    "    date = value[0]\n",
    "    data = value[1]\n",
    "    codes = data.code.tolist()\n",
    "    ref_date = date.strftime('%Y-%m-%d')\n",
    "    total_data = data.dropna()\n",
    "    dx_return = None\n",
    "    risk_exp = total_data[neutralize_risk].values.astype(float)\n",
    "    industry = total_data.industry.values\n",
    "    benchmark_w = total_data.weight.values\n",
    "    \n",
    "    constraint_exp = total_data[constraint_risk].values\n",
    "    risk_exp_expand = np.concatenate((constraint_exp, np.ones((len(risk_exp), 1))), axis=1).astype(float)\n",
    "    risk_names = constraint_risk + ['total']\n",
    "    risk_target = risk_exp_expand.T @ benchmark_w\n",
    "    lbound = np.zeros(len(total_data))\n",
    "    ubound = 0.01 + benchmark_w\n",
    "\n",
    "    constraint = Constraints(risk_exp_expand, risk_names)\n",
    "    for i, name in enumerate(risk_names):\n",
    "        if name == 'total' or name == 'SIZE':\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i], upper_bound=risk_target[i])\n",
    "        else:\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i]*industry_lower, upper_bound=risk_target[i]*industry_upper)\n",
    "        \n",
    "    er1 = factor_processing(total_data[factor_group1].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight1\n",
    "    \n",
    "    er2 = factor_processing(total_data[factor_group2].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight2\n",
    "    \n",
    "    er = (er1 / er1.std() + er2 / er2.std())\n",
    "    \n",
    "    target_pos, _ = er_portfolio_analysis(er,\n",
    "                                          industry,\n",
    "                                          dx_return,\n",
    "                                          constraint,\n",
    "                                          False,\n",
    "                                          benchmark_w)\n",
    "    target_pos['code'] = total_data['code'].values\n",
    "    \n",
    "    turn_over, executed_pos = execution_pipeline.execute(target_pos=target_pos)\n",
    "    \n",
    "    executed_codes = executed_pos.code.tolist()\n",
    "    dx_retuns = engine.fetch_dx_return(advanceDateByCalendar('china.sse', date, '1b').strftime('%Y-%m-%d'),\n",
    "                                       executed_codes,\n",
    "                                       horizon=horizon)\n",
    "    \n",
    "    result = pd.merge(executed_pos, total_data, on=['code'], how='inner')\n",
    "    result = pd.merge(result, dx_retuns, on=['code'])\n",
    "    leverage = result.weight_x.abs().sum()\n",
    "    \n",
    "    ret = (result.weight_x - result.weight_y).values @ result.dx.values\n",
    "    rets.append(ret)\n",
    "    leverags.append(executed_pos.weight.abs().sum())\n",
    "    turn_overs.append(turn_over)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "ret_df1 = pd.DataFrame({'returns': rets, 'turn_over': turn_overs}, index=dates)\n",
    "ret_df1.loc[advanceDateByCalendar('china.sse', dates[-1], freq)] = 0.\n",
    "ret_df1 = ret_df1.shift(1)\n",
    "ret_df1.iloc[0] = 0.\n",
    "ret_df1['tc_cost'] = ret_df1.turn_over * 0.002"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_df1[['returns', 'tc_cost']].cumsum().plot(figsize=(12, 6),\n",
    "                                             title='Threshold tc rebalanced: Monitored freq {0}, {1} tc'.format(freq,\n",
    "                                                                                                                turn_over_threshold),\n",
    "                                             secondary_y='tc_cost')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_atfer_tc = ret_df1.returns - ret_df1.tc_cost\n",
    "mmd = MovingMaxDrawdown(window=len(ret_atfer_tc))\n",
    "\n",
    "for ret in ret_atfer_tc:\n",
    "    mmd.push({'ret': ret})\n",
    "\n",
    "print(\"total return: \", ret_atfer_tc.mean() * 252 * 100)\n",
    "print(\"sharp: \", ret_atfer_tc.mean() / ret_atfer_tc.std() * np.sqrt(252))\n",
    "print(\"mmd: \", -mmd.result()[0]*100)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Current Production Strategy with Target Turn Over\n",
    "--------------------------------------------"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rets = []\n",
    "turn_overs = []\n",
    "turn_over_target_base = 0.04\n",
    "executor = NaiveExecutor()\n",
    "execution_pipeline = ExecutionPipeline(executors=[executor])\n",
    "leverags = []\n",
    "previous_pos = pd.DataFrame()\n",
    "\n",
    "for i, value in enumerate(factor_groups):\n",
    "    date = value[0]\n",
    "    data = value[1]\n",
    "    codes = data.code.tolist()\n",
    "    ref_date = date.strftime('%Y-%m-%d')\n",
    "    total_data = data.dropna()\n",
    "    dx_return = None\n",
    "    risk_exp = total_data[neutralize_risk].values.astype(float)\n",
    "    industry = total_data.industry.values\n",
    "    benchmark_w = total_data.weight.values\n",
    "    \n",
    "    constraint_exp = total_data[constraint_risk].values\n",
    "    risk_exp_expand = np.concatenate((constraint_exp, np.ones((len(risk_exp), 1))), axis=1).astype(float)\n",
    "    risk_names = constraint_risk + ['total']\n",
    "    risk_target = risk_exp_expand.T @ benchmark_w\n",
    "    lbound = np.zeros(len(total_data))\n",
    "    ubound = 0.01 + benchmark_w\n",
    "\n",
    "    constraint = Constraints(risk_exp_expand, risk_names)\n",
    "    for i, name in enumerate(risk_names):\n",
    "        if name == 'total' or name == 'SIZE':\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i], upper_bound=risk_target[i])\n",
    "        else:\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i]*industry_lower, upper_bound=risk_target[i]*industry_upper)\n",
    "        \n",
    "    er1 = factor_processing(total_data[factor_group1].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight1\n",
    "    \n",
    "    er2 = factor_processing(total_data[factor_group2].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight2\n",
    "    \n",
    "    er = (er1 / er1.std() + er2 / er2.std())\n",
    "    \n",
    "    codes = total_data['code'].values\n",
    "    \n",
    "    if previous_pos.empty:\n",
    "        current_position = None\n",
    "        turn_over_target = None\n",
    "    else:\n",
    "        previous_pos.set_index('code', inplace=True)\n",
    "        remained_pos = previous_pos.loc[codes]\n",
    "        \n",
    "        remained_pos.fillna(0., inplace=True)\n",
    "        turn_over_target = turn_over_target_base\n",
    "        \n",
    "        current_position = remained_pos.weight.values\n",
    "    \n",
    "    try:\n",
    "        target_pos, _ = er_portfolio_analysis(er,\n",
    "                                              industry,\n",
    "                                              dx_return,\n",
    "                                              constraint,\n",
    "                                              False,\n",
    "                                              benchmark_w,\n",
    "                                              current_position=current_position,\n",
    "                                              turn_over_target=turn_over_target)\n",
    "    except ValueError:\n",
    "        print('{0} full rebalance'.format(date))\n",
    "        target_pos, _ = er_portfolio_analysis(er,\n",
    "                                              industry,\n",
    "                                              dx_return,\n",
    "                                              constraint,\n",
    "                                              False,\n",
    "                                              benchmark_w)\n",
    "    \n",
    "    target_pos['code'] = codes\n",
    "    \n",
    "    turn_over, executed_pos = execution_pipeline.execute(target_pos=target_pos)\n",
    "    \n",
    "    executed_codes = executed_pos.code.tolist()\n",
    "    dx_retuns = engine.fetch_dx_return(advanceDateByCalendar('china.sse', date, '1b').strftime('%Y-%m-%d'),\n",
    "                                       executed_codes,\n",
    "                                       horizon=horizon)\n",
    "    \n",
    "    result = pd.merge(executed_pos, total_data, on=['code'], how='inner')\n",
    "    result = pd.merge(result, dx_retuns, on=['code'])\n",
    "    \n",
    "    ret = (result.weight_x - result.weight_y ).values @ result.dx.values\n",
    "    rets.append(ret)\n",
    "    turn_overs.append(turn_over)\n",
    "    previous_pos = executed_pos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "ret_df2 = pd.DataFrame({'returns': rets, 'turn_over': turn_overs}, index=dates)\n",
    "ret_df2.loc[advanceDateByCalendar('china.sse', dates[-1], freq)] = 0.\n",
    "ret_df2 = ret_df2.shift(1)\n",
    "ret_df2.iloc[0] = 0.\n",
    "ret_df2['tc_cost'] = ret_df2.turn_over * 0.002"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_df2[['returns', 'tc_cost']].cumsum().plot(figsize=(12, 6),\n",
    "                                              title='Target turn over rebalanced: Rebalance freq {0}, {1} turnover_target'.format(freq, turn_over_target_base),\n",
    "                                              secondary_y='tc_cost')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_atfer_tc = ret_df2.returns - ret_df2.tc_cost\n",
    "mmd = MovingMaxDrawdown(window=len(ret_atfer_tc))\n",
    "\n",
    "for ret in ret_atfer_tc:\n",
    "    mmd.push({'ret': ret})\n",
    "\n",
    "print(\"total return: \", ret_atfer_tc.mean() * 252 * 100)\n",
    "print(\"sharp: \", ret_atfer_tc.mean() / ret_atfer_tc.std() * np.sqrt(252))\n",
    "print(\"mmd: \", -mmd.result()[0]*100)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Candidate Strategy\n",
    "---------------------\n",
    "\n",
    "* **Factor Group #1**\n",
    "  * RVOL: 0.05\n",
    "  * EPS: 0.3\n",
    "  * DROEAfterNonRecurring: 0.35\n",
    "  * DivP: 0.075\n",
    "  * CFinc1: 0.15\n",
    "  * BDTO: 0.05\n",
    "    \n",
    "    \n",
    "* **Factor Group #2**\n",
    "  * VAL: 0.034129344\n",
    "  * IVR: 0.015881607\n",
    "  * ROEDiluted: 0.048765746\n",
    "  * GREV: 0.042747382\n",
    "  * EPS: -0.015900173\n",
    "  * CHV: 0.019044573\n",
    "  * CFinc1: -0.001792638\n",
    "  * BDTO: 0.014277867\n",
    "\n",
    "\n",
    "* **Factor Group #3**\n",
    "  * con_pe_rolling: -0.6\n",
    "  * con_pb_rolling: -0.6\n",
    "  * con_eps: 0.6\n",
    "  * con_target_price: 1.2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "factor_group1 = ['RVOL', 'EPS', 'DROEAfterNonRecurring', 'DivP', 'CFinc1', 'BDTO']\n",
    "factor_weight1 = [0.05, 0.3, 0.35, 0.075, 0.15, 0.05]\n",
    "group_weight1 = 1.\n",
    "\n",
    "factor_group2 = ['VAL', 'IVR', 'ROEDiluted', 'GREV', 'EPS', 'CHV', 'CFinc1', 'BDTO']\n",
    "factor_weight2 = [0.034129344, 0.02, 0.048765746, 0.042747382, -0.015900173, 0.019044573, -0.001792638, 0.014277867]\n",
    "group_weight2 = 1.\n",
    "\n",
    "factor_group3 = ['cs_pe', 'cs_pb', 'cs_eps']\n",
    "factor_weight3 = [-0.6, -0.6, 0.6]\n",
    "group_weight3 = 0.25"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "factors = {'RVOL': LAST('RVOL'),\n",
    "           'EPS': LAST('EPS'),\n",
    "           'DROEAfterNonRecurring': LAST('DROEAfterNonRecurring'),\n",
    "           'DivP': LAST('DivP'),\n",
    "           'CFinc1': LAST('CFinc1'),\n",
    "           'BDTO': LAST('BDTO'),\n",
    "           'VAL': LAST('VAL'),\n",
    "           'ROEDiluted': LAST('ROEDiluted'),\n",
    "           'GREV': LAST('GREV'),\n",
    "           'CHV': LAST('CHV'),\n",
    "           'cs_pe': LAST('con_pe_rolling'),\n",
    "           'cs_pb': LAST('con_pb_rolling'),\n",
    "           'cs_eps': LAST('con_eps_rolling'),\n",
    "           'IVR': LAST('IVR')}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "all_data = engine.fetch_data_range(universe, factors, dates=dates, benchmark=905)\n",
    "factor_all_data = all_data['factor']\n",
    "factor_groups = factor_all_data.groupby('trade_date')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rets = []\n",
    "turn_overs = []\n",
    "turn_over_threshold = 0.70\n",
    "executor = ThresholdExecutor(turn_over_threshold=turn_over_threshold)\n",
    "execution_pipeline = ExecutionPipeline(executors=[executor])\n",
    "leverags = []\n",
    "\n",
    "for i, value in enumerate(factor_groups):\n",
    "    date = value[0]\n",
    "    data = value[1]\n",
    "    codes = data.code.tolist()\n",
    "    ref_date = date.strftime('%Y-%m-%d')\n",
    "    total_data = data.dropna()\n",
    "    dx_return = None\n",
    "    risk_exp = total_data[neutralize_risk].values.astype(float)\n",
    "    industry = total_data.industry.values\n",
    "    benchmark_w = total_data.weight.values\n",
    "    \n",
    "    constraint_exp = total_data[constraint_risk].values\n",
    "    risk_exp_expand = np.concatenate((constraint_exp, np.ones((len(risk_exp), 1))), axis=1).astype(float)\n",
    "    risk_names = constraint_risk + ['total']\n",
    "    risk_target = risk_exp_expand.T @ benchmark_w\n",
    "    lbound = np.zeros(len(total_data))\n",
    "    ubound = 0.01 + benchmark_w\n",
    "\n",
    "    constraint = Constraints(risk_exp_expand, risk_names)\n",
    "    for i, name in enumerate(risk_names):\n",
    "        if name == 'total' or name == 'SIZE':\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i], upper_bound=risk_target[i])\n",
    "        else:\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i]*industry_lower, upper_bound=risk_target[i]*industry_upper)\n",
    "        \n",
    "    er1 = factor_processing(total_data[factor_group1].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight1\n",
    "    \n",
    "    er2 = factor_processing(total_data[factor_group2].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight2\n",
    "    \n",
    "    er3 = factor_processing((total_data[factor_group3].values @ factor_weight3).reshape((-1,1)),\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]).flatten()\n",
    "    \n",
    "    er = (group_weight1* er1 / er1.std() + \n",
    "          group_weight2 * er2 / er2.std() + \n",
    "          group_weight3 * er3 / er3.std())\n",
    "    \n",
    "    target_pos, _ = er_portfolio_analysis(er,\n",
    "                                          industry,\n",
    "                                          dx_return,\n",
    "                                          constraint,\n",
    "                                          False,\n",
    "                                          benchmark_w)\n",
    "    target_pos['code'] = total_data['code'].values\n",
    "    \n",
    "    turn_over, executed_pos = execution_pipeline.execute(target_pos=target_pos)\n",
    "    \n",
    "    executed_codes = executed_pos.code.tolist()\n",
    "    dx_retuns = engine.fetch_dx_return(advanceDateByCalendar('china.sse', date, '1b').strftime('%Y-%m-%d'),\n",
    "                                       executed_codes,\n",
    "                                       horizon=horizon)\n",
    "    \n",
    "    result = pd.merge(executed_pos, total_data, on=['code'], how='inner')\n",
    "    result = pd.merge(result, dx_retuns, on=['code'])\n",
    "    \n",
    "    ret = (result.weight_x - result.weight_y).values @ result.dx.values\n",
    "    rets.append(ret)\n",
    "    leverags.append(executed_pos.weight.abs().sum())\n",
    "    turn_overs.append(turn_over)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "ret_df3 = pd.DataFrame({'returns': rets, 'turn_over': turn_overs}, index=dates)\n",
    "ret_df3.loc[advanceDateByCalendar('china.sse', dates[-1], freq)] = 0.\n",
    "ret_df3 = ret_df3.shift(1)\n",
    "ret_df3.iloc[0] = 0.\n",
    "ret_df3['tc_cost'] = ret_df3.turn_over * 0.002"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_df3[['returns', 'tc_cost']].cumsum().plot(figsize=(12, 6),\n",
    "                                             title='Threshold tc rebalanced: Monitored freq {0}, {1} tc'.format(freq,\n",
    "                                                                                                                turn_over_threshold),\n",
    "                                             secondary_y='tc_cost')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_atfer_tc = ret_df3.returns - ret_df3.tc_cost\n",
    "\n",
    "mmd = MovingMaxDrawdown(window=len(ret_atfer_tc))\n",
    "\n",
    "for ret in ret_atfer_tc:\n",
    "    mmd.push({'ret': ret})\n",
    "\n",
    "print(\"total return: \", ret_atfer_tc.mean() * 252 * 100)\n",
    "print(\"sharp: \", ret_atfer_tc.mean() / ret_atfer_tc.std() * np.sqrt(252))\n",
    "print(\"mmd: \", -mmd.result()[0]*100)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 4. Candidate Strategy with Target Turn Over\n",
    "-----------------"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rets = []\n",
    "turn_overs = []\n",
    "turn_over_target_base = 0.04\n",
    "executor = NaiveExecutor()\n",
    "execution_pipeline = ExecutionPipeline(executors=[executor])\n",
    "leverags = []\n",
    "previous_pos = pd.DataFrame()\n",
    "\n",
    "for i, value in enumerate(factor_groups):\n",
    "    date = value[0]\n",
    "    data = value[1]\n",
    "    codes = data.code.tolist()\n",
    "    ref_date = date.strftime('%Y-%m-%d')\n",
    "    total_data = data.dropna()\n",
    "    dx_return = None\n",
    "    risk_exp = total_data[neutralize_risk].values.astype(float)\n",
    "    industry = total_data.industry.values\n",
    "    benchmark_w = total_data.weight.values\n",
    "    \n",
    "    constraint_exp = total_data[constraint_risk].values\n",
    "    risk_exp_expand = np.concatenate((constraint_exp, np.ones((len(risk_exp), 1))), axis=1).astype(float)\n",
    "    risk_names = constraint_risk + ['total']\n",
    "    risk_target = risk_exp_expand.T @ benchmark_w\n",
    "    lbound = np.zeros(len(total_data))\n",
    "    ubound = 0.01 + benchmark_w\n",
    "\n",
    "    constraint = Constraints(risk_exp_expand, risk_names)\n",
    "    for i, name in enumerate(risk_names):\n",
    "        if name == 'total' or name == 'SIZE':\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i], upper_bound=risk_target[i])\n",
    "        else:\n",
    "            constraint.set_constraints(name, lower_bound=risk_target[i]*industry_lower, upper_bound=risk_target[i]*industry_upper)\n",
    "        \n",
    "    er1 = factor_processing(total_data[factor_group1].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight1\n",
    "    \n",
    "    er2 = factor_processing(total_data[factor_group2].values,\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]) @ factor_weight2\n",
    "    \n",
    "    er3 = factor_processing((total_data[factor_group3].values @ factor_weight3).reshape((-1,1)),\n",
    "                            pre_process=[winsorize_normal, standardize],\n",
    "                            risk_factors=risk_exp,\n",
    "                            post_process=[winsorize_normal, standardize]).flatten()\n",
    "    \n",
    "    er = (group_weight1* er1 / er1.std() + \n",
    "          group_weight2 * er2 / er2.std() + \n",
    "          group_weight3 * er3 / er3.std())\n",
    "    \n",
    "    codes = total_data['code'].values\n",
    "    \n",
    "    if previous_pos.empty:\n",
    "        current_position = None\n",
    "        turn_over_target = None\n",
    "    else:\n",
    "        previous_pos.set_index('code', inplace=True)\n",
    "        remained_pos = previous_pos.loc[codes]\n",
    "        \n",
    "        remained_pos.fillna(0., inplace=True)\n",
    "        turn_over_target = turn_over_target_base\n",
    "        \n",
    "        current_position = remained_pos.weight.values\n",
    "    \n",
    "    try:\n",
    "        target_pos, _ = er_portfolio_analysis(er,\n",
    "                                              industry,\n",
    "                                              dx_return,\n",
    "                                              constraint,\n",
    "                                              False,\n",
    "                                              benchmark_w,\n",
    "                                              current_position=current_position,\n",
    "                                              turn_over_target=turn_over_target)\n",
    "    except ValueError:\n",
    "        print('{0} full rebalance'.format(date))\n",
    "        target_pos, _ = er_portfolio_analysis(er,\n",
    "                                              industry,\n",
    "                                              dx_return,\n",
    "                                              constraint,\n",
    "                                              False,\n",
    "                                              benchmark_w)\n",
    "    \n",
    "    target_pos['code'] = codes\n",
    "    \n",
    "    turn_over, executed_pos = execution_pipeline.execute(target_pos=target_pos)\n",
    "    \n",
    "    executed_codes = executed_pos.code.tolist()\n",
    "    dx_retuns = engine.fetch_dx_return(advanceDateByCalendar('china.sse', date, '1b').strftime('%Y-%m-%d'),\n",
    "                                       executed_codes,\n",
    "                                       horizon=horizon)\n",
    "    \n",
    "    result = pd.merge(executed_pos, total_data, on=['code'], how='inner')\n",
    "    result = pd.merge(result, dx_retuns, on=['code'])\n",
    "    ret = (result.weight_x - result.weight_y).values @ result.dx.values\n",
    "    rets.append(ret)\n",
    "    turn_overs.append(turn_over)\n",
    "    previous_pos = executed_pos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "ret_df4 = pd.DataFrame({'returns': rets, 'turn_over': turn_overs}, index=dates)\n",
    "ret_df4.loc[advanceDateByCalendar('china.sse', dates[-1], freq)] = 0.\n",
    "ret_df4 = ret_df4.shift(1)\n",
    "ret_df4.iloc[0] = 0.\n",
    "ret_df4['tc_cost'] = ret_df4.turn_over * 0.002"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_df4[['returns', 'tc_cost']].cumsum().plot(figsize=(12, 6),\n",
    "                                              title='Target turn over rebalanced: Rebalance freq {0}, {1} turnover_target'.format(freq, turn_over_target_base),\n",
    "                                              secondary_y='tc_cost')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret_atfer_tc = ret_df4.returns - ret_df4.tc_cost\n",
    "mmd = MovingMaxDrawdown(window=len(ret_atfer_tc))\n",
    "\n",
    "for ret in ret_atfer_tc:\n",
    "    mmd.push({'ret': ret})\n",
    "\n",
    "print(\"total return: \", ret_atfer_tc.mean() * 252 * 100)\n",
    "print(\"sharp: \", ret_atfer_tc.mean() / ret_atfer_tc.std() * np.sqrt(252))\n",
    "print(\"mmd: \", -mmd.result()[0]*100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
