1.
로봇투자자문과 알고리즘트레이딩사이에 만리장성을 쌓는 분들이 계십니다. 질적으로 다른 서비스라고 우깁니다. 알고리즘매매를 위한 기계적인 논리와 투자자문을 위한 알고리즘을 달리 보기때문입니다. 아마도 아주 단순한 알고리즘만 본 듯 합니다.
로봇투자자문이라고 할 때 가장 중요한 것이 포트폴리오 이론과 이에 기초한 자산 분배(Asset Allocation)입니다. Robo-Advisors Will Dominate The Investment Management Industry은 다음과 같이 정보기술에 기반한 포트폴리오 관리의 중요성을 강조합니다.
Portfolio management because of increasingly sophisticated technology platforms will become the norm in the investment management industry. The benefits will accrue to self-directed investors and savvy financial advisors who leverage the technology.
그러면 알고리즘트레이딩과 로봇투자자문이 다르지 않음을 보여주는 사례를 하나 소개합니다. 미국 알고리즘트레이딩서비스를 제공하는 Quantopian이 있습니다. 퀀토피안이 핀테크로 각광을 받고 있는 로빈훗(Robinhood)를 통하여 실거래서비스를 제공하면서 소개한 사례입니다.
Zero Commission Algorithmic Trading – Robinhood & Quantopian
위의 글을 보면 Cambria Investment Management의 Faber가 2007년도에 발표한 A Quantitative Approach to Tactical Asset Allocation을 기초로 한 알고리즘모델을 소개하고 있습니다. 자산분배모형은 아래와 같습니다. 미국주식, 미국외 주식, 채권 및 부동산과 원자재로 자산분배모형을 만들었습니다.
It is based off of Mebane Faber’s “Global Tactical Asset Allocation” (GTAA). GTAA consists of five global asset classes: US stocks, foreign stocks, bonds, real estate and commodities…it is either long the asset class or in cash with its allocation of the funds.
The basics of the strategy go like this:
(1) Look at a 200 day trailing window (SA – Slow Average) versus a 20 day trailing window (FA – Fast Average) – We do this incalculate_exposure
(2) If the FA is greater than the SA, go long about 20% of your portfolio in that security – We do this in ‘open_new_positionsclose_positions`
(3) If the FA is less than the SA, have 0% of your portfolio in that security - We do this in
위의 논문을 근거로 만든 Python Source입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
The initialize function is the place to set your tradable # universe and define any parameters. def initialize(context): # Robinhood only allows long positions, use this trading # guard in case set_long_only() # Since we are trading with Robinhood we can set this to $0! set_commission(commission.PerTrade(cost=0)) # Define our five global assets. context.assets = [sid(8554), # S&P 500 (SPY) sid(22972), # Foreign developed (EFA) sid(23870), # 7-10 year Treasury bond (IEF) sid(28054), # Commodities (DBC) sid(26669)] # Real Estate Invesment Trusts (VNQ) # We will weight each asset equally and leave a 5% cash # reserve. context.weight = 0.95 / len(context.assets) # Define our lookback period for the moving average rule. context.lookback = 200 context.fast_lookback = 20 # Initialize two collections of stocks that we want to buy # and sell. context.reduce_exposure = [] context.increase_exposure = [] # This code is here so we enter into trades on the very # first day that this live algorithm gets deployed context.first_trade = True # Schedule function allows us to set when we want to # execute our functions. In this case, we want it to # check every day if it's the first trade date (when # you first launch the algo) and do it 60 minutes # after market open. at 10:31 AM. schedule_function(first_trade, date_rules.every_day(), time_rules.market_open(minutes=60)) # Schedule our functions so that we sell our old stocks, # settle the cash, and then buy our new stocks. schedule_function(calculate_exposure, date_rules.month_end(days_offset=4), time_rules.market_open()) schedule_function(close_positions, date_rules.month_end(days_offset=4), time_rules.market_open(minutes=60)) schedule_function(open_new_positions, date_rules.month_end(), time_rules.market_open(minutes=60)) def do_unsettled_funds_exist(context): """ For Robinhood users. In order to prevent you from attempting to trade on unsettled cash (settlement dates are T+3) from sale of proceeds. You can use this snippet of code which checks for whether or not you currently have unsettled funds """ if context.portfolio.cash != context.account.settled_cash: return True def get_percent_held(context, security, portfolio_value): """ This calculates the percentage of each security that we currently hold in the portfolio. """ if security in context.portfolio.positions: position = context.portfolio.positions[security] value_held = position.last_sale_price * position.amount percent_held = value_held/float(portfolio_value) return percent_held else: # If we don't hold any positions, return 0% return 0.0 def order_for_robinhood(context, security, weight): """ This is a custom order method for this particular algorithm and places orders based on: (1) How much of each position in context.assets we currently hold (2) How much cash we currently hold This means that if you have existing positions (e.g. AAPL), your positions in that security will not be taken into account when calculating order amounts. The portfolio value that we'll be ordering on is labeled `valid_portfolio_value`. """ valid_portfolio_value = context.portfolio.cash for s in context.assets: # Calculate dollar amount of each position in context.assets # that we currently hold if s in context.portfolio.positions: position = context.portfolio.positions[s] valid_portfolio_value += position.last_sale_price * \ position.amount # Calculate the percent of each security that we want to hold percent_to_order = weight - get_percent_held(context, security, valid_portfolio_value) # If within 1% of target weight, ignore. if abs(percent_to_order) < .01: return # Calculate the dollar value to order for this security value_to_order = percent_to_order * valid_portfolio_value return order_value(security, value_to_order) def check_if_etf_positions_are_held(context): # If you currently hold positions in any of the securites # found in context.assets, we will not be trading on # the first day for stock in context.assets: if stock in context.portfolio.positions: return True return False def first_trade(context, data): # If it's the very first day of this algorithm, equally # weight our portfolio in our ETFs. If unsettled cash # currently if context.first_trade and not \ do_unsettled_funds_exist(context): # If we already hold assets from context.assets # on the first trade date, assume that we've already # traded and don't try and make a trade if check_if_etf_positions_are_held(context): log.info("Already hold context.asset positions. Skipping" " first trade.") else: log.info("First day of trading, going long on our assets") for security in context.assets: if security in data: o_id = order_for_robinhood(context, security, context.weight) if o_id: log.info("Ordering %s shares of %s" % (get_order(o_id).amount, security.symbol)) context.first_trade = False # Calculate the new exposures for each asset. def calculate_exposure(context, data): # Let's create two collections of assets: stocks that we # need to sell, # and stocks that we need to buy. context.reduce_exposure = [] context.increase_exposure = [] # If cash is not settled. Set the reduce and increase # exposure lists to empty lists so we don't trade. if do_unsettled_funds_exist(context): log.info("Cash not settled") return # Get price history for calculating the moving average. prices = history(context.lookback, "1d", "price") # Loop through our assets and compute each ones new # exposure. log.info("Calculating exposure for assets") for security in context.assets: # Here we check whether or not the latest month's # price is greater or less than the entire lookback # period latest_months_price = prices[security][ -context.fast_lookback:].mean() moving_average_rule = prices[security].mean() # If latest month's price is greater than the # lookback, add the security to the increase_exposure # list if latest_months_price > moving_average_rule and \ context.portfolio.positions[security].amount == 0: log.info("Adding security %s to the " "increase_exposure list with latest month's" " price at %s and lookback at %s" % (security.symbol, latest_months_price, moving_average_rule)) context.increase_exposure.append(security) # If latest month's price is less than the lookback, # add the security to the reduce_exposure list elif latest_months_price < moving_average_rule and \ context.portfolio.positions[security].amount > 0: context.reduce_exposure.append(security) log.info("Adding security %s to the " "reduce_exposure list with latest month's" " price at %s and lookback at %s" % (security.symbol, latest_months_price, moving_average_rule)) # Closes current positions. We need to have a separate # function for this so that cash has time to settle before # we add new positions. def close_positions(context, data): log.info("Attempting to reduce exposure in %s positions" % len(context.reduce_exposure)) for security in context.reduce_exposure: if security in data: o_id = order_for_robinhood(context, security, 0.0) log.info("Ordering %s shares of %s" % (get_order(o_id).amount, security.symbol)) # Opens new positions. Again this is a separate function # so that we only buy when we have the available cash. def open_new_positions(context, data): if len(context.increase_exposure) == 0: log.info("Attempting to increase exposure in %s " "positions" % len(context.increase_exposure)) else: log.info("No securities matched our exposure positions," "not trading for this time.") for security in context.increase_exposure: if security in data: o_id = order_for_robinhood(context, security, context.weight) log.info("Ordering %s shares of %s" % (get_order(o_id).amount, security.symbol)) # The handle_data function is run every bar. In this case we'll # use this function to plot our leverage over time. def handle_data(context, data): record(leverage=context.account.leverage) |
2.
‘포트폴리오 관리, 자산 배분’과 관련한 알고리즘이 로봇투자자문의 배경입니다. 알고리즘으로 가능한 서비스가 로봇투자자문만일까요? 조금은 다른 발상을 한 회사가 있습니다. 블로그에서 가끔 소개하는 Alpha Architect입니다. 이 회사는 자신들의 서비스를 DIY Financial Advisor 라고 하고 DIY 투자자문을 서비스화하였습니다.
THE ALPHA ARCHITECT FINANCIAL TOOLKIT
아래는 DIY 투자자문솔류션을 소개한 자료입니다.
나아가 철학을 책으로 발간하기도 하였습니다.
DIY Financial Advisor: A Simple Solution to Build and Protect Your Wealth
굳이 이런 사례를 소개하는 이유는 로봇투자자문을 자산관리서비스로 바라볼 수 있지만 매매서비스로도 접근할 수 있음을 강조하기 위함입니다. HTS(MTS)와 자산관리서비스를 결합하면 그것이 DIY 자산관리입니다. DIY자산관리서비스를 제공할 때 핵심은 역시나 알고리즘입니다. 여기에 ZeroAOS플랫폼을 더하면 좀더 유연한 서비스가 가능하지 않을까요?(^^)
종목추천 서비스를 벗어나서 HTS를 기반으로 한 자산관리서비스를 제공합시다.