t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share

     So far, we have built a good understanding of how algorithmic trading works and how we can build trading signals from market data. We also looked into some basic trading strategies, as well as more sophisticated trading strategies, so it may seem like we are in a good place to start trading, right? Not quite. Another very important requirement to be successful at algorithmic trading is understanding risk management and using good risk management practices.

     Bad risk management practices can turn any good algorithmic trading strategy into a non profitable one. On the other hand, good risk management practices can turn a seemingly inferior [ɪnˈfɪriər]次的,较差的 trading strategy into an actually profitable one. In this chapter, we will examine the different kinds of risk in algorithmic trading, look at how to quantitatively[ˈkwɑːntəteɪtɪvli]  measure and compare these risks, and explore how to build a good risk management system to adhere to循 these risk management practices.

In this chapter, we will cover the following topics:

  • Differentiating between the types of risk and risk factors
  • Quantifying the risk量化风险
  • Differentiating between the measures of risk
  • Making a risk management algorithm制定风险管理算法

Differentiating between the types of risk and risk factors 

     Risks in algorithmic trading strategies can basically be of two things:

  • risks that cause money loss and
  • risks that cause illegal/forbidden behavior in markets that cause regulatory actions[ˈreɡjələtɔːri]监管行动.

Let's take a look at the risks involved before we look at what factors lead to increasing/decreasing these risks in the business of algorithmic trading. 

Risk of trading losses

      This is the most obvious and intuitive one—we want to trade to make money, but we always run through the risk of losing money against other market participants. Trading is a zero-sum game: some participants will make money, while some will lose money. The amount that's lost by the losing participants is the amount that's gained by the winning participants. This simple fact is what also makes trading quite challenging. Generally, less informed participants will lose money to more informed participants. Informed is a loose term here; it can mean participants with access to information that others don't have. This can include access to secretive or expensive or even illegal information sources, the ability to transport and consume such information that other participants don't have, and so on. Information edge信息优势 can also be gained by participants with a superior ability to glean[ɡliːn]收集 information from the available information, that is, some participants will have better signals, better analytics abilities, and better predictive abilities to edge out挤掉,淘汰 less informed participants. Obviously, more sophisticated participants will also beat less sophisticated participants.

     Sophistication can be gained from technology advantages as well, such as faster reacting trading strategies. The use of a low-level language such as C/C++ is harder to develop software in but allows us to build trading software systems that react in single-digit microseconds processing time.

  • An extreme speed advantage is available to participants that use Field Programmable Gate Arrays (FPGAs) to achieve sub-microsecond response times to market data updates.
  • Another avenue of gaining sophistication is by having more complex trading algorithms with more complex logic that's meant to squeeze out as much edge as possible.

It should be clear that algorithmic trading is an extremely complex and competitive business and that all the participants are doing their best to squeeze out every bit of profit possible by being more informed and sophisticated.

https://www.efinancialcareers.com/news/2017/07/xr-trading-2016 discusses an example of trading losses due to decreased profitability, which occurs due to competition among market participants.

Regulation violation risks违规风险

     The other risk that isn't everyone's first thought has to do with making sure that algorithmic trading strategies are not violating any regulatory rules. Failing to do so often results in astronomical[ˌæstrəˈnɑːmɪkl] fines天文罚款, massive legal fees巨额法律费用, and can often get participants banned from trading from certain or all exchanges. Since setting up successful algorithmic trading businesses are multi-year, multi-million dollar ventures[ˈventʃər]风险, getting shut down due to regulatory reasons can be crushing[ˈkrʌʃɪŋ]毁坏(某人的信心或幸福),令人沮丧. The SEC美国证券交易委员会(https://www.sec.gov/), FINRA美国金融业监管局(https://www.finra.org/#/), and CFTC美国商品期货交易委员会(https://www.cftc.gov/) are just some of many regulatory governing bodies watching over监视 algorithmic trading activity in equity股票, currency货币, futures期货, and options期权 markets.

     These regulatory firms enforce执行 global and local regulations法规. In addition, the electronic trading exchanges themselves impose regulations and laws, the violation of which can also incur severe penalties. There are many market participants or algorithmic trading strategy behaviors that are forbidden. Some incur[ɪnˈkɜːr]招致,遭受 a warning or an audit and some incur penalties. Insider trading reports内幕交易报告 are quite well known by people inside and outside of the algorithmic trading business. While insider trading doesn't really apply to algorithmic trading or high-frequency trading, we will introduce some of the common issues in algorithmic trading here.

     This list is nowhere near complete, but these are the top regulatory issues in algorithmic trading or high-frequency trading.

Spoofing欺骗

     Spoofing[ˈspuːfɪŋ]电子欺骗 typically refers to the practice of entering orders into the market that are not considered bonafide[ˌboʊnə ˈfaɪdi]真正的,真实的. A bonafide order is one that is entered with the intent of trading. Spoofing orders are entered into the market with the intent of misleading other market participants, and these orders were never entered with the intent of being executed. The purpose of these orders is to make other participants believe that there are more real market participants who are willing to buy or sell than there actually are. By spoofing on the bid side通过在喊价方(买入方,出价方,投标方)进行欺骗,

  • market participants are misled into thinking there is a lot of interest in buying.
  • This usually leads to other market participants adding more orders to the bid side
    and moving or removing orders on the ask side卖出方 with the expectation that prices will go up
  • When prices go up, the spoofer then sells at a higher price than would've been possible without the spoofing orders. At this point, the spoofer initiates开始实施,发起 a short position and cancels all the spoofing bid orders, causing other market participants to do the same. This drive prices back down from these synthetically raised higher prices.
  • When prices have dropped sufficiently, the spoofer then buys at lower prices to cover the short position and lock in a profit.

     Spoofing algorithms can repeat this over and over in markets that are mostly algorithmically trading and make a lot of money. This, however, is illegal in most markets because it causes market price instability, provides participants with misleading information about available market liquidity, and adversely affects负面影响 non-algorithmic trading investors/strategies. In summary, if such behavior was not made illegal, it would cause cascading instability连锁不稳定性 and make most market participants exit providing liquidity. Spoofing is treated as a serious violation in most electronic exchanges, and exchanges have sophisticated algorithms/monitoring systems to detect such behavior and flag标记 market participants who are spoofing.

The first case of spoofing got a lot of publicity, and those of you who are interested can learn more at https://www.justice.gov/usao-ndil/pr/high-frequency-trader-sentenced-three-years-prison-disrupting-futures-market-first

Quote stuffing塞单或填鸭式报价行为

     Quote stuffing is a manipulation tactic[ˈtæktɪk]策略,手法 that was employed被采用 by high-frequency trading participants. Nowadays, most exchanges have many rules that make quote stuffing infeasible[ɪnˈfizəbəl]不可实行的 as a profitable trading strategy. Quote stuffing is the practice行为 of using very fast trading algorithms and hardware to enter, modify, and cancel large amounts of orders in one or more trading instruments. Since each order action by a market participant causes the generation of public market data, it is possible for very fast participants to generate a massive amount of market data and massively slow down slower participants who can no longer react in time, thereby causing profits for high-frequency trading algorithms.

     This is not as feasible in modern electronic trading markets, mainly because exchanges have put in rules on messaging limits on individual market participants. Exchanges have the ability to analyze and flag short-lived non-bonafide order flow标记短期的非真实订单流, and modern matching engines are able to better synchronize同步 market data feeds市场数据源 with order flow feeds订单流源.

     https://www.businessinsider.com/huge-first-high-frequency-trading-firm-is-charged-with-quote-stuffing-and-manipulation-2010-9 discusses a recent quote stuffing market manipulation incident that caused regulatory actions.

Banging the close冲击收盘

     Banging the close is a disruptive[dɪsˈrʌptɪv]破坏的 and manipulative trading practice that still happens periodically[ˌpɪriˈɑːdɪkli]定期地,周期性地 in electronic trading markets, either intentionally or accidentally有意或无意地, by trading algorithms. This practice has to do with illegally manipulating the closing price of a derivative, also known as the settlement price最后结算价. Since positions in derivatives markets such as futures期货 are marked at the settlement price at the end of the day, this tactic[ˈtæktɪk]策略 uses large orders during the final few minutes or seconds of closing where many market participants are out of the market already to drive less liquid market prices in an illegal and disruptive way

     This is, in some sense, similar to spoofing, but in this case, often, the participants banging the close may not pick up new executions during the closing period, but may simply try to move market prices to make their already existing positions more profitable. For cash- settled derivatives contracts, the more favorable settlement price leads to more profit. This is why trading closes are also monitored quite closely by electronic trading derivative exchanges to detect and flag this disruptive practice.

     https://www.cftc.gov/PressRoom/PressReleases/5815-10 discusses an incident of banging the close for those who are interested.

Sources of risk风险来源

     Now that we have a good understanding of the different kinds of risk in algorithmic trading, let's look at the factors in algorithmic trading strategy development, optimization, maintenance, and operation that causes them.

Software implementation risk

     A modern algorithmic trading business is essentially a technology business, hence giving birth to the new term FinTech to mean the intersection of finance and technology. Computer software is designed, developed, and tested by humans who are error-prone and sometimes, these errors creep[kriːp]蔓延 into trading systems and algorithmic trading strategies. Software implementation bugs are often the most overlooked被忽视的 source of risk in algorithmic trading. While operation risk and market risk are extremely important, software implementation bugs have the potential to cause millions of dollars in losses, and there have been many cases of firms going bankrupt due to software implementation bugs within minutes.

     In recent times, there was the infamous Knight Capital incident, where a software implementation bug combined with an operations risk issue caused them to lose $440 million within 45 minutes and they ended up getting shut down. Software implementation bugs are also very tricky棘手 because software engineering is a very complex process, and when we add the additional complexity of having sophisticated and complex algorithmic trading strategies and logic, it is hard to guarantee that the implementation of trading strategies and systems are safe from bugs. More information can be found athttps://dealbook.nytimes.com/2012/08/02/knight-capital-says-trading-mishap-cost-it-440-million/. 

     Modern algorithmic trading firms have rigorous[ˈrɪɡərəs](规则,制度等)严格的,苛刻的  software development practices to safeguard themselves against software bugs. These include

  • rigorous unit tests, which are small tests on individual software components to verify their behavior doesn't change to an incorrect behavior as software development/maintenance being made to existing components is performed.
  • There are also regression tests, which are tests that test larger components that are composed of smaller components as a whole to ensure the higher-level behavior remains consistent.
  • All electronic trading exchanges also provide a test market environment with test market data feeds and test order entry interfaces where market participants have to build, test, and certify[ˈsɜːrtɪfaɪ]验证 their components with the exchange before they are even allowed to trade in live markets.

     Most sophisticated algorithmic trading participants also have backtesting software that simulates a trading strategy over historically recorded data to ensure strategy behavior is in line with expectations. We will explore backtesting further in Chapter 9 , Creating a Backtester in Python. Finally, other software management practices, such as code reviews and change management, are also performed on a daily basis to verify the integrity完整性 of algorithmic trading systems and strategies on a daily basis. Despite all of these precautions[prɪˈkɔːʃn]预防措施, software implementation bugs do slip into live trading markets, so we should always be aware警惕 and cautious谨慎 because software is never perfect and the cost of mistakes/bugs is very high in the algorithmic trading business, and even higher in the HFT(High-frequency trading) business. 

DevOps risk开发运营风险

     DevOps risk is the term that is used to describe the risk potential when algorithmic trading strategies are deployed to live markets. This involves building and deploying correct trading strategies and configuring the configuration, the signal parameters, the trading parameters, and starting, stopping, and monitoring them. Most modern trading firms trade markets electronically almost 23 hours a day, and they have a large number of staff whose only job is to keep an eye on the automated algorithmic trading strategies that are deployed to live markets to ensure they are behaving as expected and no erroneous[ɪˈroʊniəs]没有错误的  behavior goes uninvestigated不予调查. They are known as the Trading Desk, or TradeOps or DevOps.

     These people have a decent understanding有足够理解 of software development, trading rules, and exchange for provided risk monitoring interfaces. Often, when software implementation bugs end up going to live markets, they are the final line of defense最后一道防线, and it is their job to monitor the systems, detect issues, safely pause or stop the algorithms, and contact and resolve the issues that have emerged. This is the most common understanding of where operation risk can show up. Another source of operation risk is in algorithmic trading strategies that are not 100% black box. Black box trading strategies are trading strategies that do not require any human feedback or interaction. These are started at a certain time and then stopped at a certain time, and the algorithms themselves make all the decisions.

     Gray box trading strategies are trading strategies that are not 100% autonomous. These strategies still have a lot of automated decision-making built into them, but they also have external controls that allow the traders or TradeOps engineers to monitor the strategies, as well as adjust parameters and trading strategy behavior, and even send manual orders. Now, during these manual human interventions, there is another source of risk, which is basically the risk of humans making mistakes in the commands/adjustments that are sent to these strategies. Sending incorrect parameters can cause the algorithm to behave incorrectly and cause losses.

There are also cases of sending bad commands, which can cause an unexpected and unintentional large impact on the market, causing trading losses and market disruptions市场混乱 that add regulatory fines. One of the common errors is the fat finger error, where prices, sizes, and buy/sell instructions are sent incorrectly due to a fat finger. Some examples can be found at https://www.bloomberg.com/news/articles/2019-01-24/oops-a-brief-history-of-some-of-the-market-s-worst-fat-fingers

Market risk

     Finally, we have market risk, which is what is commonly thought of when we think of risk in algorithmic trading. This is the risk of trading against and losing money to more informed participants. Every market participant, at some point or the other, on some trade or the other, will lose money to a more informed participant. We discussed what makes an informed participant superior to a non-informed one in the previous section. Obviously, the only way to avoid market risk is to get access to more information, improve the trading edge, improve sophistication, and improve technology advantages. But since market risk is a truth of all algorithmic trading strategies, a very important aspect is to understand the behavior of the algorithmic trading strategy before deploying it to live markets.

     This involves understanding what to expect normal behavior to look like and, more importantly, understanding when a certain strategy makes and loses money and quantifying loss metrics to set up expectations. Then, risk limits are set up at multiple places in an algorithmic trading pipeline in the trading strategy, then in a central risk monitoring system, then in the order gateway, sometimes at the clearing firm, and finally sometimes even at the exchange level. Each extra layer of risk check can slow down a market participant's ability to react to fast-moving markets, but it is essential to have these to prevent runaway失控的 trading algorithms from causing a lot of damage.

     Once the trading strategy has violated maximum trading risk limits assigned to it, it will be shut down at one or more places where the risk validation is set up. Market risk is very important to understand, implement, and configure correctly because incorrect risk estimates can kill a profitable trading strategy by increasing the frequency and magnitude of losing trades, losing positions, losing days, and even losing weeks or months. This is because the trading strategy could have lost its profitable edge盈利优势 and if you leave it running for too long without adapting it to changing markets, it can erode[ɪˈroʊd] 侵蚀,腐蚀,削弱,降低 all the profits the strategy may have generated in the past. Sometimes, market conditions are very different than what is expected and strategies can go through periods of larger than normal losses, in which cases it is important to have risk limits set up to detect outsized losses and adjust trading parameters or stop trading.

     We will look at研究 what risk measures are common in algorithmic trading, how to quantify and research them from historical data, and how to configure and calibrate algorithmic strategies before deploying them to live markets. For now, the summary is that market risk is a normal part of algorithmic trading, but failing to understand and prepare for it can destroy a lot of good trading strategies.

Quantifying the risk量化风险

     Now, let's get started with understanding what realistic risk constraints look like and how to quantify them. We will list, define, and implement some of the most commonly used risk limits in the modern algorithmic trading industry today. We will use the volatility adjusted mean reversion strategy we built in https://blog.csdn.net/Linli522362242/article/details/121896073, Sophisticated Algorithmic Strategies, as our realistic trading strategy, which we now need to define and quantify risk measures for.

The severity of risk violations风险违规的严重程度

     One thing to understand before diving into all the different risk measures is defining what the severity[sɪˈverəti]  of a risk violation means. So far, we've been discussing risk violations as being maximum risk limit violations. But in practice, there are multiple levels of every risk limit, and each level of risk limit violation is not equally as catastrophic[ˌkætəˈstrɑːfɪk]灾难性的 to algorithmic trading strategies. The lowest severity risk violation would be considered a warning risk violation, which means that this risk violation, while not expected to happen regularly定期发生, can happen normally during a trading strategy operation. Intuitively, it is easy to think of this as, say, on most days, trading strategies do not send more than 5,000 orders a day, but on certain volatile days, it is possible and acceptable that the trading strategy sends 20,000 orders on that day. This would be considered an example of a warning risk violation – this is unlikely, but not a sign of trouble. The purpose of this risk violation is to warn the trader that something unlikely is happening in the market or trading strategy.

     The next level of risk violation is what would be considered as something where the strategy is still functioning correctly正常运行 but has reached the limits of what it is allowed to do, and must safely liquidate[ˈlɪkwɪdeɪt]清算,清理债务 and shut down. Here, the strategy is allowed to send orders and make trades that flatten the position and cancel new entry orders, if there are any. Basically, the strategy is done trading but is allowed to automatically handle the violation and finish trading until a trader checks on what happens and decides to either restart and allocate higher risk limits to the trading strategy.

     The final level of risk violation is what would be considered a maximum possible risk violation, which is a violation that should never, ever happen. If a trading strategy ever triggers this risk violation, it is a sign that something went very wrong. This risk violation means that the strategy is no longer allowed to send any more order flow to the live markets. This risk violation would only be triggered during periods of extremely unexpected events, such as a flash crash market condition闪电崩盘市场条件. This severity of risk violation basically means that the algorithmic trading strategy is not designed to deal with such an event automatically and must freeze trading and then resort to external operators to manage open positions未结头寸 and live orders.

Differentiating the measures of risk

     Let's explore different measures of risk. We will use the trading performance from the volatility adjusted mean reversion strategy we saw in https://blog.csdn.net/Linli522362242/article/details/121896073, Sophisticated Algorithmic Strategies, as an example of a trading strategy in which we wish to understand the risks behind and quantify and calibrate them.

     In t5  https://blog.csdn.net/Linli522362242/article/details/121896073, Sophisticated Algorithmic Trading Strategies, we built the Mean Reversion, Volatility Adjusted Mean Reversion, Trend Following, and Volatility Adjusted Trend Following strategies. These can be found in https://blog.csdn.net/Linli522362242/article/details/121896073, Sophisticated Algorithmic Strategies, in the Mean reversion strategy(+APO+StdDev) that dynamically adjusts for changing volatility section. Let's load up the trading performance . csv file, as shown in the following code block, and quickly look at what fields we have available:

import pandas as pd
import pandas_datareader.data as pdr
 
def load_financial_data( start_date, end_date, output_file='', stock_symbol='GOOG' ):
    if len(output_file) == 0:
        output_file = stock_symbol+'_data_large.pkl'   
        
    try:
        df = pd.read_pickle( output_file )
        print( "File {} data found...reading {} data".format( output_file ,stock_symbol) )
    except FileNotFoundError:
        print( "File {} not found...downloading the {} data".format( output_file, stock_symbol ) )
        df = pdr.DataReader( stock_symbol, "yahoo", start_date, end_date )
        df.to_pickle( output_file )
    return df 
 
goog_data = load_financial_data( stock_symbol='GOOG',
                                 start_date='2014-01-01', 
                                 end_date='2018-01-01',
                                 output_file='goog_data.pkl'
                               )
goog_data.head()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第1张图片

# Variables/constants for EMA Calculation:
NUM_PERIODS_FAST_10 = 10 # Static time period parameter for the fast EMA
K_FAST = 2/(NUM_PERIODS_FAST_10 + 1) # Static smoothing factor parameter for fast EMA
ema_fast = 0 # initial ema
ema_fast_values = [] # we will hold fast EMA values for visualization purpose
 
NUM_PERIODS_SLOW_40 = 40 # Static time period parameter for the slow EMA
K_SLOW = 2/(NUM_PERIODS_SLOW_40 + 1) # Static smoothing factor parameter for slow EMA
ema_slow = 0 # initial ema
ema_slow_values = [] # we will hold slow EMA values for visualization purpose
 
apo_values = [] # track computed absolute price oscillator values
 
# Variables for Trading Strategy trade, position & pnl management:
 
# Container for tracking buy/sell order, 
# +1 for buy order, -1 for sell order, 0 for no-action
orders = []
 
# Container for tracking positions, 
# positive for long positions, negative for short positions, 0 for flat/no position
positions = []
 
# Container for tracking total_pnls, this is the sum of 
# closed_pnl i.e. pnls already locked in 
# and open_pnl i.e. pnls for open-position marked to market price
pnls = []
 
 
last_buy_price = 0  # used to prevent over-trading at/around the same price
last_sell_price = 0 # used to prevent over-trading at/around the same price
position = 0 # Current position of the trading strategy
 
# Summation of products of 
# buy_trade_price and buy_trade_qty for every buy Trade made
# since last time being flat
buy_sum_price_qty = 0
# Summation of buy_trade_qty for every buy Trade made since last time being flat
buy_sum_qty = 0
 
# Summation of products of 
# sell_trade_price and sell_trade_qty for every sell Trade made 
# since last time being flat
sell_sum_price_qty = 0
# Summation of sell_trade_qty for every sell Trade made since last time being flat
sell_sum_qty = 0
 
open_pnl = 0   # Open/Unrealized PnL marked to market
closed_pnl = 0 # Closed/Realized PnL so far
 
# Constants that define strategy behavior/thresholds
 
# APO trading signal value below which(-10) to enter buy-orders/long-position
APO_VALUE_FOR_BUY_ENTRY = -10 # (oversold, expect a bounce back up)
# APO trading signal value above which to enter sell-orders/short-position
APO_VALUE_FOR_SELL_ENTRY = 10 # (overbought, expect a bounce back down)
 
# Minimum price change since last trade before considering trading again,
MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # this is to prevent over-trading at/around same prices
NUM_SHARES_PER_TRADE = 10
 
# positions are closed if currently open positions are profitable above a certain amount, 
# regardless of APO values. 
# This is used to algorithmically lock profits and initiate more positions 
# instead of relying only on the trading signal value. 
 
# Minimum Open/Unrealized profit at which to close positions and lock profits
MIN_PROFIT_TO_CLOSE = 10*NUM_SHARES_PER_TRADE

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第2张图片

import statistics as stats
import math as math
 
data2 = goog_data.copy()
close = data2['Close']
 
# Constants/variables that are used to compute standard deviation as a volatility measure
SMA_NUM_PERIODS_20 = 20 # look back period
price_history = []      # history of prices
 
for close_price in close:
    price_history.append( close_price )
    if len( price_history) > SMA_NUM_PERIODS_20 : # we track at most 'time_period' number of prices
        del ( price_history[0] )
    
    # calculate vairance during the SMA_NUM_PERIODS_20 periods
    sma = stats.mean( price_history )
    variance = 0 # variance is square of standard deviation
    for hist_price in price_history:
        variance = variance + ( (hist_price-sma)**2 )
    
    stddev = math.sqrt( variance/len(price_history) )
    
    # a volatility factor that ranges from 0 to 1
    stddev_factor = stddev/15 # 15 since since the population stddev.mean() = 15.45
    # closer to 0 indicate very low volatility,
    # around 1 indicate normal volatility
    # > 1 indicate above-normal volatility
    if stddev_factor == 0:
        stddev_factor = 1
        
    # This section updates fast and slow EMA and computes APO trading signal
    if (ema_fast==0): # first observation
        ema_fast = close_price # initial ema_fast or ema_slow
        ema_slow = close_price
    else:
        # ema fomula 
        # K_FAST*stddev_factor or K_SLOW*stddev_factor 
        # more reactive to newest observations during periods of higher than normal volatility
        ema_fast = (close_price-ema_fast) * K_FAST*stddev_factor + ema_fast
        ema_slow = (close_price-ema_slow) * K_SLOW*stddev_factor + ema_slow
    
    ema_fast_values.append( ema_fast )
    ema_slow_values.append( ema_slow )
    
    apo = ema_fast - ema_slow
    apo_values.append( apo )
    
    # 6. This section checks trading signal against trading parameters/thresholds and positions, to trade.
    
    # We will perform a sell trade at close_price if the following conditions are met:
    # 1. The APO trading signal value(positive) > Sell-Entry threshold (overbought, expect a bounce back down, sell for profit)
    #     and the difference between current-price and last trade-price is different enough.(>Minimum price change)
    # 2. We are long( +ve position ) and 
    #    either APO trading signal value >= 0 or current position is profitable enough to lock profit.
    # APO_VALUE_FOR_SELL_ENTRY * stdev_factor:
    #              by increasing the threshold for entry by a factor of volatility,
    #              makes us less aggressive in entering positions(here is sell) during periods of higher volatility,            
    # dynamic MIN_PROFIT_TO_CLOSE / stddev_factor: 
    #              to decrease the the expected profit threshold during periods of increased volatility
    #              to be more aggressive in exciting positions
    #              it is riskier to hold on to positions for longer periods of time.
    if ( ( apo > APO_VALUE_FOR_SELL_ENTRY*stddev_factor and \
           abs( close_price-last_sell_price ) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stddev_factor
         )
         or
         ( position>0 and (apo >=0 or open_pnl > MIN_PROFIT_TO_CLOSE/stddev_factor ) )
       ): # long from -ve APO and APO has gone positive or position is profitable, sell to close position
        orders.append(-1) # mark the sell trade
        last_sell_price = close_price
        position -= NUM_SHARES_PER_TRADE
        sell_sum_qty += NUM_SHARES_PER_TRADE
        sell_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update vwap sell-price
        print( "Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position )
        
    # 7. We will perform a buy trade at close_price if the following conditions are met:
    # 1. The APO trading signal value(negative) < below Buy-Entry threshold (oversold, expect a bounce back up, buy for future profit)
    #    and the difference between current-price and last trade-price is different enough.(>Minimum price change)
    # 2. We are short( -ve position ) and
    #    either APO trading signal value is <= 0 or current position is profitable enough to lock profit.
    # APO_VALUE_FOR_BUY_ENTRY * stdev_factor:
    #              by increasing the threshold for entry by a factor of volatility,
    #              makes us less aggressive in entering positions(here is sell) during periods of higher volatility,            
    # dynamic MIN_PROFIT_TO_CLOSE / stddev_factor: 
    #              to decrease the the expected profit threshold during periods of increased volatility
    #              to be more aggressive in exciting positions
    #              it is riskier to hold on to positions for longer periods of time.    
    elif ( ( apo < APO_VALUE_FOR_BUY_ENTRY*stddev_factor and \
             abs( close_price-last_buy_price ) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stddev_factor
           )
           or
           ( position<0 and (apo <=0 or open_pnl > MIN_PROFIT_TO_CLOSE/stddev_factor ) )
         ): # short from +ve APO and APO has gone negative or position is profitable, buy to close position
        orders.append(+1) # mark the buy trade
        last_buy_price = close_price
        position += NUM_SHARES_PER_TRADE
        buy_sum_qty += NUM_SHARES_PER_TRADE
        buy_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update the vwap buy-price
        print( "Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position )
    else:
        # No trade since none of the conditions were met to buy or sell
        orders.append( 0 )
        
    positions.append( position )
    
    # 8. The code of the trading strategy contains logic for position/PnL management. 
    #    It needs to update positions and compute open and closed PnLs when market prices change 
    #    and/or trades are made causing a change in positions
    
    # This section updates Open/Unrealized & Closed/Realized positions
    open_pnl = 0
    if position > 0:
        # long position and some sell trades have been made against it, 
        # close that amount based on how much was sold against this long position
        # PnL_realized = sell_sum_qty * (Average Sell Price - Average Buy Price)
        if sell_sum_qty > 0:           # vwap for sell                   # vwap  for buy
            open_pnl = sell_sum_qty * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty)
        # mark the remaining position to market 
        # i.e. pnl would be what it would be if we closed at current price
        # sell
        # position -= NUM_SHARES_PER_TRADE
        # sell_sum_qty += NUM_SHARES_PER_TRADE
        # PnL_unrealized = remaining position * (Exit Price - Average Buy Price)
        # if now, sell sell_sum_qty @ any price, we should use abs(position-sell_sum_qty) *
        open_pnl += abs(position) * ( close_price - buy_sum_price_qty/buy_sum_qty )
        # print( position, (buy_sum_qty-sell_sum_qty), open_pnl)
    elif position < 0:
        # short position and some buy trades have been made against it, 
        # close that amount based on how much was bought against this short position
        # PnL_realized = buy_sum_qty * (Average Sell Price - Average Buy Price)
        if buy_sum_qty > 0:           # vwap for sell                   # vwap  for buy
            open_pnl = buy_sum_qty * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty)
        # mark the remaining position to market
        # i.e. pnl would be what it would be if we closed at current price
        # buy
        # position += NUM_SHARE_PER_TRADE
        # buy_sum_qty += NUM_SHARE_PER_TRADE
        # PnL_unrealized = remaining position * (Average Sell Price - Exit Price)
        # if now, buy buy_sum_qty @ any price, we should use abs(position-buy_sum_qty) * 
        open_pnl += abs(position) * ( sell_sum_price_qty/sell_sum_qty - close_price )
        # print( position, (buy_sum_qty-sell_sum_qty), open_pnl)
    else:
        # flat, so update closed_pnl and reset tracking variables for positions & pnls
        closed_pnl += (sell_sum_price_qty - buy_sum_price_qty)
        
        buy_sum_price_qty = 0
        buy_sum_qty = 0
        last_buy_price = 0
        
        sell_sum_price_qty = 0
        sell_sum_qty = 0
        last_sell_price = 0
        
    print( "OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl) )
    pnls.append(closed_pnl + open_pnl)

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第3张图片

# This section prepares the dataframe from the trading strategy results and visualizes the results
data2 = data2.assign( ClosePrice = pd.Series(close, index=data2.index) )
data2 = data2.assign( Fast10DayEMA = pd.Series(ema_fast_values, index=data2.index) )
data2 = data2.assign( Slow40DayEMA = pd.Series(ema_slow_values, index=data2.index) )
data2 = data2.assign( APO = pd.Series(apo_values, index=data2.index) )
data2 = data2.assign( Trades = pd.Series(orders, index=data2.index) )
data2 = data2.assign( Position = pd.Series(positions, index=data2.index) )
data2 = data2.assign( Pnl = pd.Series(pnls, index=data2.index) )
import matplotlib.pyplot as plt
 
fig = plt.figure( figsize=(20,10) )
 
data2['ClosePrice'].plot(color='k', lw=3., legend=True)
data2['Fast10DayEMA'].plot(color='y', lw=1., legend=True)
data2['Slow40DayEMA'].plot(color='m', lw=1., legend=True)
plt.plot( data2.loc[ data2.Trades == 1 ].index, data2.ClosePrice[data2.Trades == 1 ],
          color='y', lw=0, marker='^', markersize=7, label='buy'
        )
plt.plot( data2.loc[ data2.Trades == -1 ].index, data2.ClosePrice[data2.Trades == -1 ],
          color='b', lw=0, marker='v', markersize=7, label='sell'
        )
plt.autoscale(enable=True, axis='x', tight=True)
 
plt.legend()
plt.show()

more aggressive in exiting positions during periods of increased volatility(for example, VS), because as we discussed before, during periods of higher than normal volatility, it is riskier to hold on to positions for longer periods of time

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第4张图片

fig = plt.figure( figsize=(20,10) )
 
data2['APO'].plot(color='k', lw=3., legend=True)
plt.plot( data2.loc[ data2.Trades == 1 ].index, data2.APO[data2.Trades == 1 ],
          color='y', lw=0, marker='^', markersize=7, label='buy'
        )
plt.plot( data2.loc[ data2.Trades == -1 ].index, data2.APO[data2.Trades == -1 ],
          color='b', lw=0, marker='v', markersize=7, label='sell'
        )
plt.axhline(y=0, lw=0.5, color='k')
for i in range( APO_VALUE_FOR_BUY_ENTRY, APO_VALUE_FOR_BUY_ENTRY*5, APO_VALUE_FOR_BUY_ENTRY ):
  plt.axhline(y=i, lw=0.5, color='r')
for i in range( APO_VALUE_FOR_SELL_ENTRY, APO_VALUE_FOR_SELL_ENTRY*5, APO_VALUE_FOR_SELL_ENTRY ):
  plt.axhline(y=i, lw=0.5, color='g')
 
plt.autoscale(enable=True, axis='x', tight=True)
plt.legend()
 
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第5张图片

fig = plt.figure( figsize=(20,10))
 
data2['Position'].plot(color='k', lw=1., legend=True)
plt.plot( data2.loc[ data2.Position == 0 ].index, data2.Position[ data2.Position == 0 ], 
          color='r', lw=0, marker='.', label='flat'
        )
plt.plot( data2.loc[ data2.Position > 0 ].index, data2.Position[ data2.Position > 0 ],
          color='y', lw=0, marker='+', label='long'
        )
plt.plot( data2.loc[ data2.Position < 0 ].index, data2.Position[ data2.Position < 0 ],
          color='b', lw=0, marker='_', label='short'
        )
plt.axhline(y=0, lw=0.5, color='k')
for i in range( NUM_SHARES_PER_TRADE, NUM_SHARES_PER_TRADE*25, NUM_SHARES_PER_TRADE*5 ):
  plt.axhline(y=i, lw=0.5, color='r')
for i in range( -NUM_SHARES_PER_TRADE, -NUM_SHARES_PER_TRADE*25, -NUM_SHARES_PER_TRADE*5 ):
  plt.axhline(y=i, lw=0.5, color='g')
 
plt.autoscale(enable=True, axis='x', tight=True)
plt.legend()
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第6张图片

fig = plt.figure( figsize=(20,10))
 
plt.plot( data.index, data['Pnl'], color='g', lw=1.,
          label='BasicMeanReversionPnL'
        )#########################
plt.plot( data.loc[ data.Pnl > 0 ].index, data.Pnl[ data.Pnl > 0 ], 
          color='y', lw=0, marker='.',
          #label='Pnl'
        )
plt.plot( data.loc[ data.Pnl < 0 ].index, data.Pnl[ data.Pnl < 0 ],
          color='r', lw=0, marker='.',
          #label='Pnl'
        )
 
plt.plot( data2.index, data2['Pnl'], color='b', lw=1.,
          label='VolatilityAdjustedMeanReversionPnL'
        )#########################
plt.plot( data2.loc[ data.Pnl > 0 ].index, data2.Pnl[ data.Pnl > 0 ], 
          color='y', lw=0, marker='.',
          #label='Pnl'
        )
plt.plot( data2.loc[ data.Pnl < 0 ].index, data2.Pnl[ data.Pnl < 0 ],
          color='r', lw=0, marker='.',
          #label='Pnl'
        )
plt.axhline(y=15000, ls='--', alpha=0.5)
plt.autoscale(enable=True, axis='x', tight=True)
plt.legend()
plt.show()

    In this case, dynamically adjusts(StdDev(and) plus APO) the trading strategy(here is the Mean Reversion Strategy) for volatility increases the strategy(Mean Reversion Strategy) performance by 200%!t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第7张图片

data2.head(n=5)

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第8张图片

data2.to_csv("volatility_adjusted_mean_reversion_apo_stddev.csv", sep=",")

     Let's load up the trading performance . csv file, as shown in the following code block, and quickly look at what fields we have available:

import pandas as pd
import matplotlib.pyplot as plt

results = pd.read_csv('volatility_adjusted_mean_reversion_apo_stddev.csv')
print( results.head(1) )

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第9张图片

     For the purposes of implementing and quantifying risk measures, the fields we are interested in are Date, High, Low, ClosePrice, Trades, Position, and PnL. We will ignore the other fields since we do not require them for the risk measures we are currently interested in. Now, let's dive into understanding and implementing our risk measures.

Stop-loss(PnL is the cumulative result)

     The first risk limit we will look at is quite intuitive and is called stop-loss, or max-loss. This limit is the maximum amount of money a strategy is allowed to lose, that is, the minimum PnL allowed. This often has a notion of a time frame for that loss, meaning stop-loss can be for a day, for a week, for a month, or for the entire lifetime of the strategy. A stop-loss with a time frame of a day means that if the strategy loses a stop-loss amount of money in a single day, it is not allowed to trade any more on that day, but can resume the next day. Similarly, for a stop-loss amount in a week, it is not allowed to trade anymore for that week, but can resume next week.

     Now, let's compute stop-loss levels on a week and month for the volatility adjusted mean reversion strategy, as shown in the following code:

num_days = len( results.index ) # results.index : RangeIndex(start=0, stop=1007, step=1)

pnl = results['Pnl']

weekly_losses = []
monthly_losses = []

for i in range( 0, num_days ):
    if i>=5 and pnl[i-5] > pnl[i]:
        weekly_losses.append( pnl[i] - pnl[i-5] )
    
    if i>=20 and pnl[i-20] > pnl[i]:
        monthly_losses.append( pnl[i] - pnl[i-20] )

figure = plt.figure(figsize=(10,6))
plt.hist( weekly_losses, 50 )
plt.gca().set( title='Weekly Loss Distribution',
               xlabel='pnl $', ylabel='Frequency'
             )
plt.show()

Let's have a look at the weekly loss distribution plot shown here :

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第10张图片

figure = plt.figure(figsize=(10,6))
plt.hist( monthly_losses, 50 )
plt.gca().set( title='Monthly Loss Distribution',
               xlabel='pnl $', ylabel='Frequency'
             )
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第11张图片

     The plots show the distribution of weekly and monthly losses. From these, we can observe the following:

  • A weekly loss of anything more than $3K and a monthly loss of anything more than $4K is highly unexpected.
  • A weekly loss of more than $7K and a monthly loss of $8K have never happened, so it can be considered an unprecedented[ʌnˈpresɪdentɪd]前所未有的,史无前例的  event, but we will revisit this later.

Max drawdown最大回撤

     Max drawdown is also a PnL metric, but this measures the maximum loss that a strategy can take over a series of days衡量的是策略在一系列天数内可以承受的最大损失. This is defined as the peak to trough[trɔːf]波谷 decline in a trading strategy's account value. This is important as a risk measure so that we can get an idea of what the historical maximum decline历史最大跌幅 in the account value can be. This is important because we can get unlucky during the deployment of a trading strategy and run it in live markets right at the beginning of the drawdown. 

     Having an expectation of what the maximum drawdown is can help us understand whether the strategy loss streak连续亏损 is still within our expectations or whether something unprecedented is happening. Let's look at how to compute it:

results.info()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第12张图片

results['Date']=pd.to_datetime( results['Date'] ) # convert Dtype in the 'Date' column to datetime  
results=results.set_index(['Date'])  # set the 'Date' column as index
results.head()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第13张图片

max_pnl = 0
max_drawdown = 0
drawdown_max_pnl = 0
drawdown_min_pnl = 0

for i in range(0, num_days):
    max_pnl = max( max_pnl, pnl[i] )
    drawdown = max_pnl - pnl[i] # get the decline
    
    if drawdown > max_drawdown:
        max_drawdown = drawdown # maximum decline
        drawdown_max_pnl = max_pnl # peak
        drawdown_min_pnl = pnl[i]  # trough
        
print( 'Max Drawdown:', max_drawdown )
print( 'Max PnL', drawdown_max_pnl)
print( 'Min PnL', drawdown_min_pnl)

figure = plt.figure( figsize=(10,6) )
results['Pnl'].plot( x='Date' )
plt.axhline( y=drawdown_max_pnl, color='g' )
plt.axhline( y=drawdown_min_pnl, color='r' )
plt.gca().set( xlabel='Date', ylabel='Pnl' )

plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第14张图片In the plot, the max drawdown occurs roughly during the period of 2016-07-01 to 2017-07-01 of this PnL series, with the maximum PnL being 42K and the minimum PnL after that high being 29K, causing the maximum drawdown achieved to be roughly 12K:

max_pnl = 0
max_drawdown = 0
drawdown_max_pnl = 0
drawdown_min_pnl = 0

for i in range(0, num_days):
    max_pnl = max( max_pnl, pnl[i] )
    drawdown = max_pnl - pnl[i] # get the decline
    
    if drawdown > max_drawdown:
        max_drawdown = drawdown # maximum decline
        drawdown_max_pnl = max_pnl # peak
        drawdown_min_pnl = pnl[i]  # trough
        
print( 'Max Drawdown:', max_drawdown )
print( 'Max PnL', drawdown_max_pnl)
print( 'Min PnL', drawdown_min_pnl)

figure = plt.figure( figsize=(10,6) )
# .loc['2016-07-01':'2017-07-01']
results.loc['2016-07-01':'2017-07-01']['Pnl'].plot( x='Date' )
plt.axhline( y=drawdown_max_pnl, color='g' )
plt.axhline( y=drawdown_min_pnl, color='r' )
plt.gca().set( xlabel='Date', ylabel='Pnl' )

plt.show()

 The plot is simply the same plot as before but zoomed in to the exact observations where the drawdown occurs. As we mentioned previously, after achieving a high of roughly 42K, PnLs have a large drawdown of 12K and drop down to roughly 29K, before rebounding.

 t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第15张图片

Position limits( from distribution of the positions)

     Position limits are also quite straightforward and intuitive to understand. It is simply the maximum position, long or short, that the strategy should have at any point in its trading lifetime. It is possible to have two different position limits, one for the maximum long position and another for the maximum short position, which can be useful, for instance, where shorting stocks have different rules/risks associated with them than being long on stocks does. Every unit of open position has a risk associated with it. Generally, the larger the position a strategy puts on, the larger the risk associated with it. So, the best strategies are the ones that can make money while getting into as small a position as possible. In either case, before a strategy is deployed to production, it is important to quantify and estimate what the maximum positions the strategy can get into, based on historical performance, so that we can find out when a strategy is within its normal behavior parameters and when it is outside of historical norms.

     Finding the maximum position is straightforward. Let's find a quick distribution of the positions with the help of the following code:

position = results['Position']

figure = plt.figure( figsize=(10,6) )
plt.rcParams.update({'font.size': 12})

plt.hist( position, bins=20 ) # If bins is an integer, it defines the number of equal-width bins in the range.
plt.gca().set( title='Position Distribution',
               xlabel='Shares', ylabel='Frequency',
             )
plt.show()

 t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第16张图片

We can see the following from the preceding chart:

  • For this trading strategy, which has been applied to Google stock data, the strategy is unlikely to have a position exceeding 200 shares(lower frequency) and has never had a position exceeding 250.
  • If it gets into position levels exceeding 250, we should be careful that the trading strategy is still performing as expected.

Position holding time

     While analyzing positions that a trading strategy gets into, it is also important to measure how long a position stays open until it is closed and returned to its flat position or opposition position. The longer a position stays open, the more risk it is taking on, because the more time there is for markets to make massive moves that can potentially go against与...背道而驰 the open position. A long position is initiated开始实施,发起when the position goes from

  • being short
  • or flat
  • to being long

and is closed when the position goes back to

  • flat
  • or short.

Similarly, short positions are initiated when the position goes from

  • being long
  • or flat
  • to being short

and is closed when the position goes back to

  • flat
  • or long.

Now, let's find the distribution of open position durations with the help of the following code: 

position_holding_times = []
current_pos = 0
current_pos_start = 0

for i in range(0, num_days):
    pos = results['Position'].iloc[i]
    
    # flat and starting a new position
    if current_pos ==0 : # current position = 0 after closing the position 
        if pos !=0 :
            current_pos = pos
            current_pos_start = i
        continue
    # else:
    # going from long position to flat or short position or
    # going from short position to flat or long position
    if current_pos * pos <= 0: # +ve*(-ve)<0  and (+ve or -ve) * 0 =0
        position_holding_times.append( i-current_pos_start )
        current_pos = pos
        current_pos_start = i # new pos

print( position_holding_times )
figure = plt.figure( figsize=(10,6) )
plt.hist( position_holding_times, 100 )
plt.gca().set( title='Position Holding Time Distribution',
               xlabel='Holding time days',
               ylabel='Frequency'
             )
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第17张图片

     So, for this strategy, we can see that the holding time is pretty distributed, with the longest one lasting around 102 days and the shortest one lasting around 3 days.

Variance of PnLs

     We need to measure how much the PnLs can vary from day to day or even week to week. This is an important measure of risk because if a trading strategy has large swings in PnLs, the account value is very volatile[ˈvɑːlətl]易变的,动荡不定的 and it is hard to run a trading strategy with such a profile. Often, we compute the Standard Deviation of returns over different days or weeks or whatever timeframe we choose to use as our investment time horizon. Most optimization methods try to find optimal trading performance as a balance between PnLs and the Standard Deviation of returns.

     Computing the standard deviation of returns is easy. Let's compute the standard deviation of weekly returns, as shown in the following code:

last_week = 0
weekly_pnls = []

for i in range(0,num_days):
    
    if i - last_week >= 5:
        pnl_change = pnl[i] - pnl[last_week]
        weekly_pnls.append( pnl_change )
            
        last_week = i

from statistics import stdev, mean

print( 'PnL Standard Deviation:', stdev(weekly_pnls) )

figure = plt.figure( figsize=(10,6) )

plt.hist( weekly_pnls, 50 )
plt.gca().set( title='Weekly PnL Distribution',
               xlabel='$', ylabel='Frequency'
             )
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第18张图片

     We can see that the weekly PnLs are close to being normally distributed around a mean of $0, which intuitively makes sense. The distribution is right skewed, which yields the positive cumulative PnLs for this trading strategy. There are some very large profits and losses for some weeks, but they are very rare, which is also within the expectations of what the distribution should look like.

Sharpe ratio

     Sharpe ratio is a very commonly used performance and risk metric that's used in the industry to measure and compare the performance of algorithmic trading strategies. Sharpe ratio is defined as the ratio of average PnL over a period of time and the PnL standard deviation over the same period. The benefit of the Sharpe ratio is that it captures the profitability of a trading strategy while also accounting for the risk by using the volatility of the returns. Let's have a look at the mathematical representation:

Here, we have the following:

  • : PnL on the  trading day.
  • N : Number of trading days over which this Sharpe is being computed.

     Another performance and risk measure similar to the Sharpe ratio is known as the Sortino ratio, which

  • only uses observations where the trading strategy loses money and
  • ignores the ones where the trading strategy makes money.

The simple idea is that, for a trading strategy, Sharpe upside moves in PnLs are a good thing, so they should not be considered when computing the standard deviation. Another way to say the same thing would be that only downside moves or losses are actual risk observations

     Let's compute the Sharpe and Sortino ratios for our trading strategy. We will use a week as the time horizon for our trading strategy:

last_week_idx = 0
weekly_pnls = []
weekly_losses = []

for i in range(0, num_days):
    
    if i-last_week_idx >=5 :
        pnl_change = pnl[i] - pnl[last_week_idx]
        weekly_pnls.append( pnl_change )
        
        if pnl_change < 0:
            weekly_losses.append( pnl_change )
            
        last_week_idx = i
    
sharpe_ratio = mean( weekly_pnls ) / stdev(weekly_pnls)
sortino_ratio = mean( weekly_pnls ) / stdev(weekly_losses)

print( 'Sharpe ratio:', sharpe_ratio )
print( 'Sortion ratio:', sortino_ratio )

 
     Here, we can see that the Sharpe ratio and the Sortino ratio are close to each other, which is what we expect since both are risk-adjusted return metrics. The Sortino ratio is slightly higher than the Sharpe ratio, which also makes sense since, by definition, the Sortino ratio does not consider large increases in PnLs as being contributions to the drawdown/risk for the trading strategy, indicating that the Sharpe ratio was, in fact, penalizing some large +ve jumps in PnLs.

Maximum executions(or trades) per period

     This risk measure is an interval-based risk check. An interval-based risk is a counter that resets after a fixed amount of time and the risk check is imposed within such a time slice. So, while there is no final limit, it's important that the limit isn't exceeded within the time interval that is meant to detect and avoid over-trading. The interval-based risk measure we will inspect is maximum executions per period. This measures the maximum number of trades allowed in a given timeframe. Then, at the end of the timeframe, the counter is reset and starts over. This would detect and prevent a runaway失控 strategy that buys and sells at a very fast pace.

     Let's look at the distribution of executions per period for our strategy using a week as our timeframe, as shown here:

executions_this_week = 0
executions_per_week = []
last_week = 0

for i in range(0, num_days) :
    
    if results['Trades'].iloc[i] !=0 : # results['Trades']:[...,0,...,-1,...,1....]
        executions_this_week += 1
        
    if i-last_week >=5 :
        executions_per_week.append( executions_this_week )
        executions_this_week = 0
        last_week = i
        
figure = plt.figure( figsize=(10,6) )

plt.hist( executions_per_week, 10 )
plt.gca().set( title='Weekly number of executions Distribution',
               xlabel='Number of executions', ylabel='Frequency'
             )
plt.show()

 t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第19张图片

     As we can see, for this trading strategy, it's never traded more than five times a week in the past, which is when it trades every day of the week, which doesn't help us much.

Now, let's look at the maximum executions per month:

executions_this_month = 0
executions_per_month = []
last_month = 0

for i in range(0, num_days) :
    
    if results['Trades'].iloc[i] !=0 : # results['Trades']:[...,0,...,-1,...,1....]
        executions_this_month += 1
        
    if i-last_month >=20 :
        executions_per_month.append( executions_this_month )
        executions_this_month = 0
        last_month = i
        
figure = plt.figure( figsize=(10,6) )

plt.hist( executions_per_month, 10 )
plt.gca().set( title='Monthly number of executions Distribution',
               xlabel='Number of executions', ylabel='Frequency'
             )
plt.show()

 t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第20张图片
We can observe the following from the preceding plot:

  • It is possible for the strategy to trade every day in a month, so this risk measure can't really be used for this strategy.
  • However, this is still an important risk measure to understand and calibrate, especially for algorithmic trading strategies that trade frequently, and especially for HFT strategies.

Maximum trade size

     This risk metric measures what the maximum possible trade size for a single trade for the trading strategy is. In our previous examples, we use static trade sizes(-1,0 or 1), but it is not very difficult to build a trading strategy that sends a larger order when the trading signal is stronger and a smaller order when the trading signal is weaker. Alternatively, a strategy can choose to liquidate a larger than normal position in one trade if it's profitable, in which case it will send out a pretty large order. This risk measure is also very helpful when the trading strategy is a gray box trading strategy as it prevents fat-finger errors, among other things. We will skip implementing this risk measure here, but all we do is find a distribution of per trade size, which should be straightforward to implement based on our implementation of previous risk measures.

Volume limits(Involving position changes)

     This risk metric measures the traded volume, which can also have an interval-based variant that measures volume per period. This is another risk measure that is meant to detect and prevent overtrading. For example, some of the catastrophic software implementation bugs we discussed in this chapter could've been prevented if they had a tight volume limit严格的交易量限制 in place that warned operators about risk violations and possibly a volume limit that shut down trading strategies.

Let's observe the traded volume for our strategy, which is shown in the following code:

traded_volume = 0
for i in range(0, num_days):
    if results['Trades'].iloc[i] !=0 :
        traded_volume += abs( results['Position'].iloc[i] - results['Position'].iloc[i-1] )
print( 'Total traded volume:', traded_volume )

     In this case, the strategy behavior is as expected, that is, no overtrading is detected. We can use this to calibrate what total traded volume to expect from this strategy when it is deployed to live markets. If it ever trades significantly more than what is expected, we can detect that to be an over-trading condition.

Making a risk management algorithm

     By now, we're aware of the different types of risks and factors, including the risks in a trading strategy and the most common risk metrics for algorithmic trading strategies. Now, let's have a look at incorporating these risk measures into our volatility adjusted mean reversion trading strategy to make it safer before deploying it into live markets. We will set the risk limits to 150% of the maximum achieved historically. We are doing this because it is possible that there is a day in the future that is very different from what we've seen historically. Let's get started:

import pandas as pd
import pandas_datareader.data as pdr
 
def load_financial_data( start_date, end_date, output_file='', stock_symbol='GOOG' ):
    if len(output_file) == 0:
        output_file = stock_symbol+'_data_large.pkl'   
        
    try:
        df = pd.read_pickle( output_file )
        print( "File {} data found...reading {} data".format( output_file ,stock_symbol) )
    except FileNotFoundError:
        print( "File {} not found...downloading the {} data".format( output_file, stock_symbol ) )
        df = pdr.DataReader( stock_symbol, "yahoo", start_date, end_date )
        df.to_pickle( output_file )
    return df 
 
goog_data = load_financial_data( stock_symbol='GOOG',
                                 start_date='2014-01-01', 
                                 end_date='2018-01-01',
                                 output_file='goog_data.pkl'
                               )
goog_data.head()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第21张图片

# Variables/constants for EMA Calculation:
NUM_PERIODS_FAST_10 = 10 # Static time period parameter for the fast EMA
K_FAST = 2/(NUM_PERIODS_FAST_10 + 1) # Static smoothing factor parameter for fast EMA
ema_fast = 0 # initial ema
ema_fast_values = [] # we will hold fast EMA values for visualization purpose
 
NUM_PERIODS_SLOW_40 = 40 # Static time period parameter for the slow EMA
K_SLOW = 2/(NUM_PERIODS_SLOW_40 + 1) # Static smoothing factor parameter for slow EMA
ema_slow = 0 # initial ema
ema_slow_values = [] # we will hold slow EMA values for visualization purpose
 
apo_values = [] # track computed absolute price oscillator values
 
# Variables for Trading Strategy trade, position & pnl management:
 
# Container for tracking buy/sell order, 
# +1 for buy order, -1 for sell order, 0 for no-action
orders = []
 
# Container for tracking positions, 
# positive for long positions, negative for short positions, 0 for flat/no position
positions = []
 
# Container for tracking total_pnls, this is the sum of 
# closed_pnl i.e. pnls already locked in 
# and open_pnl i.e. pnls for open-position marked to market price
pnls = []
 
 
last_buy_price = 0  # used to prevent over-trading at/around the same price
last_sell_price = 0 # used to prevent over-trading at/around the same price
position = 0 # Current position of the trading strategy
 
# Summation of products of 
# buy_trade_price and buy_trade_qty for every buy Trade made
# since last time being flat
buy_sum_price_qty = 0
# Summation of buy_trade_qty for every buy Trade made since last time being flat
buy_sum_qty = 0
 
# Summation of products of 
# sell_trade_price and sell_trade_qty for every sell Trade made 
# since last time being flat
sell_sum_price_qty = 0
# Summation of sell_trade_qty for every sell Trade made since last time being flat
sell_sum_qty = 0
 
open_pnl = 0   # Open/Unrealized PnL marked to market
closed_pnl = 0 # Closed/Realized PnL so far
 
# Constants that define strategy behavior/thresholds
 
# APO trading signal value below which(-10) to enter buy-orders/long-position
APO_VALUE_FOR_BUY_ENTRY = -10 # (oversold, expect a bounce back up)
# APO trading signal value above which to enter sell-orders/short-position
APO_VALUE_FOR_SELL_ENTRY = 10 # (overbought, expect a bounce back down)
 
# Minimum price change since last trade before considering trading again,
MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # this is to prevent over-trading at/around same prices
NUM_SHARES_PER_TRADE = 10
 
# positions are closed if currently open positions are profitable above a certain amount, 
# regardless of APO values. 
# This is used to algorithmically lock profits and initiate more positions 
# instead of relying only on the trading signal value. 
 
# Minimum Open/Unrealized profit at which to close positions and lock profits
MIN_PROFIT_TO_CLOSE = 10*NUM_SHARES_PER_TRADE

import statistics as stats
import math as math
 
data3 = goog_data.copy()
close = data3['Close']
 
# Constants/variables that are used to compute standard deviation as a volatility measure
SMA_NUM_PERIODS_20 = 20 # look back period
price_history = []      # history of prices

1. Let's define our risk limits, which we are not allowed to breach[briːtʃ]违反,破坏. As we discussed previously, it will be set to 150% of the historically observed maximums

# Risk limits
RISK_LIMIT_WEEKLY_STOP_LOSS = -7000*1.5 # -7000 from Weekly Loss Distribution
RISK_LIMIT_MONTHLY_STOP_LOSS = -8000*1.5 # -8000 from Monthly Loss Distribution
RISK_LIMIT_MAX_POSITION = 250*1.5       # 250( max( abs( short or long) ) from Position Distribution
RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS = 120*1.5 # 120 >105 from POSITION_HOLDING_TIME Distribution
RISK_LIMIT_MAX_TRADE_SIZE = 10*1.5 # NUM_SHARES_PER_TRADE = 10
RISK_LIMIT_MAX_TRADED_VOLUME = 4000*1.5 # 4000 from total trade volume limits

2. We will maintain some variables to track and check for risk violations with the help of the following code:

risk_violated = False

traded_volume = 0
current_pos = 0
current_pos_start = 0

3. As we can see, we have some code for computing the Simple Moving Average and Standard Deviation for volatility adjustments. We will compute the fast and slow EMAs and the APO value, which we can use as our mean reversion(prices revert toward the mean) trading signal:

# close = data3['Close']
for close_price in close:
    price_history.append( close_price )
    if len( price_history) > SMA_NUM_PERIODS_20 : # we track at most 'time_period' number of prices
        del ( price_history[0] )
    
    # calculate vairance during the SMA_NUM_PERIODS_20 periods
    sma = stats.mean( price_history )
    variance = 0 # variance is square of standard deviation
    for hist_price in price_history:
        variance = variance + ( (hist_price-sma)**2 )
    
    stddev = math.sqrt( variance/len(price_history) )
    
    # a volatility factor that ranges from 0 to 1
    stddev_factor = stddev/15 # 15 since since the population stddev.mean() = 15.45
    # closer to 0 indicate very low volatility,
    # around 1 indicate normal volatility
    # > 1 indicate above-normal volatility
    if stddev_factor == 0:
        stddev_factor = 1
        
    # This section updates fast and slow EMA and computes APO trading signal
    if (ema_fast==0): # first observation
        ema_fast = close_price # initial ema_fast or ema_slow
        ema_slow = close_price
    else:
        # ema fomula 
        # K_FAST*stddev_factor or K_SLOW*stddev_factor 
        # more reactive to newest observations during periods of higher than normal volatility
        ema_fast = (close_price-ema_fast) * K_FAST*stddev_factor + ema_fast
        ema_slow = (close_price-ema_slow) * K_SLOW*stddev_factor + ema_slow
    
    ema_fast_values.append( ema_fast )
    ema_slow_values.append( ema_slow )
    
    apo = ema_fast - ema_slow
    apo_values.append( apo )
    
    ############################
    # 4.# Now, before we can evaluate our signal and 
    #     check whether we can send an order out,
    #     we need to perform a risk check to ensure that the trade size we may attempt
    #   # is within MAX_TRADE_SIZE limits:
    if NUM_SHARES_PER_TRADE > RISK_LIMIT_MAX_TRADE_SIZE :
        print( 'Risk Violation: NUM_SHARES_PER_TRADE', NUM_SHARES_PER_TRADE,
               ' > RISK_LIMIT_MAX_TRADE_SIZE', RISK_LIMIT_MAX_TRADE_SIZE
             )
        risk_violated=True
    ############################   
        
    # 6. This section checks trading signal against trading parameters/thresholds and positions, to trade.
    
    # We will perform a sell trade at close_price if the following conditions are met:
    # 1. The APO trading signal value(positive) > Sell-Entry threshold (overbought, expect a bounce back down, sell for profit)
    #     and the difference between current-price and last trade-price is different enough.(>Minimum price change)
    # 2. We are long( +ve position ) and 
    #    either APO trading signal value >= 0 or current position is profitable enough to lock profit.
    # APO_VALUE_FOR_SELL_ENTRY * stdev_factor:
    #              by increasing the threshold for entry by a factor of volatility,
    #              makes us less aggressive in entering positions(here is sell) during periods of higher volatility,            
    # dynamic MIN_PROFIT_TO_CLOSE / stddev_factor: 
    #              to decrease the the expected profit threshold during periods of increased volatility
    #              to be more aggressive in exciting positions
    #              it is riskier to hold on to positions for longer periods of time.
    if not risk_violated and ( ( apo > APO_VALUE_FOR_SELL_ENTRY*stddev_factor and \
                                 abs( close_price-last_sell_price ) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stddev_factor
                               )
                               or
                               ( position>0 and (apo >=0 or open_pnl > MIN_PROFIT_TO_CLOSE/stddev_factor ) )
                             ): # long from -ve APO and APO has gone positive or position is profitable, sell to close position
        orders.append(-1) # mark the sell trade
        last_sell_price = close_price
        position -= NUM_SHARES_PER_TRADE
        traded_volume += NUM_SHARES_PER_TRADE ############################
        sell_sum_qty += NUM_SHARES_PER_TRADE
        sell_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update vwap sell-price
        print( "Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position )
        
    # 7. We will perform a buy trade at close_price if the following conditions are met:
    # 1. The APO trading signal value(negative) < below Buy-Entry threshold (oversold, expect a bounce back up, buy for future profit)
    #    and the difference between current-price and last trade-price is different enough.(>Minimum price change)
    # 2. We are short( -ve position ) and
    #    either APO trading signal value is <= 0 or current position is profitable enough to lock profit.
    # APO_VALUE_FOR_BUY_ENTRY * stdev_factor:
    #              by increasing the threshold for entry by a factor of volatility,
    #              makes us less aggressive in entering positions(here is sell) during periods of higher volatility,            
    # dynamic MIN_PROFIT_TO_CLOSE / stddev_factor: 
    #              to decrease the the expected profit threshold during periods of increased volatility
    #              to be more aggressive in exciting positions
    #              it is riskier to hold on to positions for longer periods of time.    
    elif not risk_violated and ( ( apo < APO_VALUE_FOR_BUY_ENTRY*stddev_factor and \
                                   abs( close_price-last_buy_price ) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stddev_factor
                                 )
                                 or
                                 ( position<0 and (apo <=0 or open_pnl > MIN_PROFIT_TO_CLOSE/stddev_factor ) )
                               ): # short from +ve APO and APO has gone negative or position is profitable, buy to close position
        orders.append(+1) # mark the buy trade
        last_buy_price = close_price
        position += NUM_SHARES_PER_TRADE
        traded_volume += NUM_SHARES_PER_TRADE ############################
        buy_sum_qty += NUM_SHARES_PER_TRADE
        buy_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update the vwap buy-price
        print( "Buy ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position )
    else:
        # No trade since none of the conditions were met to buy or sell
        orders.append( 0 )
        
    positions.append( position )
    
    ############################
    # 6.# Now, we will check that, after any potential orders have been sent out and trades
    #     have been made this round, we haven't breached any of our risk limits,
    #   # starting with the Maximum Position Holding Time risk limit.
    # flat and starting a new postion
    if current_pos ==0 : # current position = 0 after closing the position 
        if position != 0:
            current_pos = position
            current_pos_start = len(positions) # new start index
            
    # going from long position to flat or short position or
    # going from short position to flat or long position
    elif current_pos * position <=0 :
        position_holding_time = len(positions) - current_pos_start
        current_pos = position
        current_pos_start = len(positions)
        
        if position_holding_time > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS:
            print( 'Risk Violation: position_holding_time', position_holding_time, 
                   ' > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS', RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS
                 )
            risk_violated = True
    
    # 7. check that the new long/short position is within the Max Position risk limits 
    if abs(position) > RISK_LIMIT_MAX_POSITION:
        print( 'Risk Violation: position', position, 
               ' > RISK_LIMIT_MAX_POSITION', RISK_LIMIT_MAX_POSITION
             )
    # 8. check that the updated traded volume doesn't violate the allocated Maximum Traded Volume risk limit:
    if traded_volume  > RISK_LIMIT_MAX_TRADED_VOLUME:
        print( 'Risk Violation: traded_volume', traded_volume,
               ' > RISK_LIMIT_MAX_TRADED_VOLUME', RISK_LIMIT_MAX_TRADED_VOLUME
             )
        risk_violated = True
    ############################
    
    # 8. The code of the trading strategy contains logic for position/PnL management. 
    #    It needs to update positions and compute open and closed PnLs when market prices change 
    #    and/or trades are made causing a change in positions
    
    # This section updates Open/Unrealized & Closed/Realized positions
    open_pnl = 0
    if position > 0:
        # long position and some sell trades have been made against it, 
        # close that amount based on how much was sold against this long position
        # PnL_realized = sell_sum_qty * (Average Sell Price - Average Buy Price)
        if sell_sum_qty > 0:           # vwap for sell                   # vwap  for buy
            open_pnl = sell_sum_qty * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty)
        # mark the remaining position to market 
        # i.e. pnl would be what it would be if we closed at current price
        # sell
        # position -= NUM_SHARES_PER_TRADE
        # sell_sum_qty += NUM_SHARES_PER_TRADE
        # PnL_unrealized = remaining position * (Exit Price - Average Buy Price)
        # if now, sell sell_sum_qty @ any price, we should use abs(position-sell_sum_qty) *
        open_pnl += abs(position) * ( close_price - buy_sum_price_qty/buy_sum_qty )
        # print( position, (buy_sum_qty-sell_sum_qty), open_pnl)
    elif position < 0:
        # short position and some buy trades have been made against it, 
        # close that amount based on how much was bought against this short position
        # PnL_realized = buy_sum_qty * (Average Sell Price - Average Buy Price)
        if buy_sum_qty > 0:           # vwap for sell                   # vwap  for buy
            open_pnl = buy_sum_qty * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty)
        # mark the remaining position to market
        # i.e. pnl would be what it would be if we closed at current price
        # buy
        # position += NUM_SHARE_PER_TRADE
        # buy_sum_qty += NUM_SHARE_PER_TRADE
        # PnL_unrealized = remaining position * (Average Sell Price - Exit Price)
        # if now, buy buy_sum_qty @ any price, we should use abs(position+buy_sum_qty) * 
        open_pnl += abs(position) * ( sell_sum_price_qty/sell_sum_qty - close_price )
        # print( position, (buy_sum_qty-sell_sum_qty), open_pnl)
    else:
        # flat, so update closed_pnl and reset tracking variables for positions & pnls
        closed_pnl += (sell_sum_price_qty - buy_sum_price_qty)
        
        buy_sum_price_qty = 0
        buy_sum_qty = 0
        last_buy_price = 0
        
        sell_sum_price_qty = 0
        sell_sum_qty = 0
        last_sell_price = 0
        
    print( "OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl) )
    pnls.append(closed_pnl + open_pnl)
    
    ############################
    if len( pnls ) > 5:
        weekly_loss = pnls[-1] - pnls[-6] # negative value
        
        if weekly_loss < RISK_LIMIT_WEEKLY_STOP_LOSS:
            print( 'Risk Violation : weekly_loss', week_loss,
                   ' < RISK_LIMIT_WEEKLY_STOP_LOSS', RISK_LIMIT_WEEKLY_STOP_LOSS
                 )
            risk_violated = True
            
    if len( pnls ) > 20:
        monthly_loss = pnls[-1] - pnls[-21]
        
        if monthly_loss < RISK_LIMIT_MONTHLY_STOP_LOSS:
            print( 'Risk Violation : monthly_loss', monthly_loss,
                   '< RISK_LIMIT_MONTHLY_STOP_LOSS', RISK_LIMIT_MONTHLY_STOP_LOSS
                 )
            risk_violated = True
    
    ############################

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第22张图片

     Here, we have added a robust risk management system to our existing trading strategy that can be extended to any other trading strategies we intend on deploying to live trading markets in the future. This will protect live trading strategies from going rogue[roʊɡ]流氓 in production or behaving outside of our expected parameters, hence providing great risk control over our trading strategies.

# This section prepares the dataframe from the trading strategy results and visualizes the results
data3 = data3.assign( ClosePrice = pd.Series(close, index=data3.index) )
data3 = data3.assign( Fast10DayEMA = pd.Series(ema_fast_values, index=data3.index) )
data3 = data3.assign( Slow40DayEMA = pd.Series(ema_slow_values, index=data3.index) )
data3 = data3.assign( APO = pd.Series(apo_values, index=data3.index) )
data3 = data3.assign( Trades = pd.Series(orders, index=data3.index) )
data3 = data3.assign( Position = pd.Series(positions, index=data3.index) )
data3 = data3.assign( Pnl = pd.Series(pnls, index=data3.index) )
import matplotlib.pyplot as plt
 
fig = plt.figure( figsize=(20,10) )
 
data3['ClosePrice'].plot(color='k', lw=3., legend=True)
data3['Fast10DayEMA'].plot(color='y', lw=1., legend=True)
data3['Slow40DayEMA'].plot(color='m', lw=1., legend=True)
plt.plot( data3.loc[ data3.Trades == 1 ].index, data3.ClosePrice[data3.Trades == 1 ],
          color='y', lw=0, marker='^', markersize=7, label='buy'
        )
plt.plot( data3.loc[ data3.Trades == -1 ].index, data3.ClosePrice[data3.Trades == -1 ],
          color='b', lw=0, marker='v', markersize=7, label='sell'
        )
plt.autoscale(enable=True, axis='x', tight=True)
 
plt.legend()
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第23张图片     This result is the same as before, because the limitation is based on historical data, no new data is added.

fig = plt.figure( figsize=(20,10) )

data3['Pnl'].plot(color='k', lw=1., legend=True)
plt.plot( data3.loc[ data3.Pnl > 0 ].index, data3.Pnl[ data3.Pnl > 0 ],
          color='b', lw=0, marker='.'
        )
plt.plot( data3.loc[ data3.Pnl < 0 ].index, data3.Pnl[ data3.Pnl < 0 ],
         color='r', lw=0, marker='.'
        )

plt.autoscale( enable=True, axis='x', tight=True)
plt.legend()
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第24张图片

Realistically adjusting risk

     In the risk management system we built in the previous section, we used static risk limits that we used for the duration of the strategy's lifetime. In practice, however, this is never the case. When a new algorithmic trading strategy is built and deployed, it is first deployed with very low-risk limits—usually the least amount of risk possible. This is for a variety of reasons,

  • the first one being to make tests and work out software implementation bugs, if there are any. The larger the amount of new code being deployed to live markets, the greater the risk.
  • The other reason is to make sure strategy behavior is consistent with what is expected based on historical performance analysis.
    • It is usually monitored very closely by multiple people to make sure nothing unexpected happens.
    • Then, after a couple of days or weeks, when initial bugs have been worked out and strategy performance is in line with simulation performance, it is slowly scaled up to take more risks in order to generate more profits.

     Conversely, after a strategy goes through a bad patch of losses, it is often reevaluated at reduced risk limits to check whether the trading strategy's performance has degraded from historical expectations and if it is no longer profitable to deploy it in live markets anymore. The obvious objective is to make as much money as possible, but achieving that requires not only a good risk check system but also a good system to adjust risk through different PnL profiles in the lifetime of the strategy

     A simple intuitive approach to adjusting risk in trading can be to start with low risk, increase the risk slightly after a good performance, and reduce the risk slightly after a poor performance. This is generally the approach that's followed by most participants: the challenges are to quantify good/poor performance in order to increase/decrease risk and to quantify the amount by which to increase/decrease risk.

import pandas as pd
import pandas_datareader.data as pdr
 
def load_financial_data( start_date, end_date, output_file='', stock_symbol='GOOG' ):
    if len(output_file) == 0:
        output_file = stock_symbol+'_data_large.pkl'   
        
    try:
        df = pd.read_pickle( output_file )
        print( "File {} data found...reading {} data".format( output_file ,stock_symbol) )
    except FileNotFoundError:
        print( "File {} not found...downloading the {} data".format( output_file, stock_symbol ) )
        df = pdr.DataReader( stock_symbol, "yahoo", start_date, end_date )
        df.to_pickle( output_file )
    return df 
 
goog_data = load_financial_data( stock_symbol='GOOG',
                                 start_date='2014-01-01', 
                                 end_date='2018-01-01',
                                 output_file='goog_data.pkl'
                               )
goog_data.head()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第25张图片 

# Variables/constants for EMA Calculation:
NUM_PERIODS_FAST_10 = 10 # Static time period parameter for the fast EMA
K_FAST = 2/(NUM_PERIODS_FAST_10 + 1) # Static smoothing factor parameter for fast EMA
ema_fast = 0 # initial ema
ema_fast_values = [] # we will hold fast EMA values for visualization purpose
 
NUM_PERIODS_SLOW_40 = 40 # Static time period parameter for the slow EMA
K_SLOW = 2/(NUM_PERIODS_SLOW_40 + 1) # Static smoothing factor parameter for slow EMA
ema_slow = 0 # initial ema
ema_slow_values = [] # we will hold slow EMA values for visualization purpose
 
apo_values = [] # track computed absolute price oscillator values
 
# Variables for Trading Strategy trade, position & pnl management:
 
# Container for tracking buy/sell order, 
# +1 for buy order, -1 for sell order, 0 for no-action
orders = []
 
# Container for tracking positions, 
# positive for long positions, negative for short positions, 0 for flat/no position
positions = []
 
# Container for tracking total_pnls, this is the sum of 
# closed_pnl i.e. pnls already locked in 
# and open_pnl i.e. pnls for open-position marked to market price
pnls = []
 
 
last_buy_price = 0  # used to prevent over-trading at/around the same price
last_sell_price = 0 # used to prevent over-trading at/around the same price
position = 0 # Current position of the trading strategy
 
# Summation of products of 
# buy_trade_price and buy_trade_qty for every buy Trade made
# since last time being flat
buy_sum_price_qty = 0
# Summation of buy_trade_qty for every buy Trade made since last time being flat
buy_sum_qty = 0
 
# Summation of products of 
# sell_trade_price and sell_trade_qty for every sell Trade made 
# since last time being flat
sell_sum_price_qty = 0
# Summation of sell_trade_qty for every sell Trade made since last time being flat
sell_sum_qty = 0
 
open_pnl = 0   # Open/Unrealized PnL marked to market
closed_pnl = 0 # Closed/Realized PnL so far
 
# Constants that define strategy behavior/thresholds
 
# APO trading signal value below which(-10) to enter buy-orders/long-position
APO_VALUE_FOR_BUY_ENTRY = -10 # (oversold, expect a bounce back up)
# APO trading signal value above which to enter sell-orders/short-position
APO_VALUE_FOR_SELL_ENTRY = 10 # (overbought, expect a bounce back down)
 
# Minimum price change since last trade before considering trading again,
MIN_PRICE_MOVE_FROM_LAST_TRADE = 10 # this is to prevent over-trading at/around same prices

     Let's look at a practical implementation using our previous volatility adjusted mean reversion strategy with risk checks. We will increase the trade size and risk after a good month and reduce the trade size and risk after a bad month by a small increment. Let's get started:

1. First, we will define the limits of how small a trade size can be and what the maximum allowed trade size can be over the course of the strategy's lifetime. For this implementation, we allow no less than 5 share per trade and no more than 50 per trade. Every time we have a good/bad month, we will increase/decrease the trade size by 5 shares. We will start very small, as we discussed previously, and increment slowly if we continue to do well. Let's have a look at the code:

MIN_NUM_SHARES_PER_TRADE = 5       ### Influence PnL
MAX_NUM_SHARES_PER_TRADE = 50
INCREMENT_NUM_SHARES_PER_TRADE = 5 ###
num_shares_per_trade = MIN_NUM_SHARES_PER_TRADE # Beginning number of shares to buy/sell on every trade
num_shares_history = []   # history of num-shares
abs_position_history = [] # history of absolute-position
import statistics as stats
import math as math
 
# Constants/variables that are used to compute standard deviation as a volatility measure
SMA_NUM_PERIODS_20 = 20 # look back period
price_history = []      # history of prices

2. Next, we will define similar minimum, maximum, and increment values for the different risk limits. As the strategy trade size evolves over time, the risk limits will also have to be adjusted to accommodate the increased trading size:

# Risk limits and increments to risk limits when we have good/bad months
risk_limit_weekly_stop_loss = -500
INCREMENT_RISK_LIMIT_WEEKLY_STOP_LOSS = -1000 # -7000 from Weekly Loss Distribution
# INCREMENT_RISK_LIMIT_WEEKLY_STOP_LOSS = -12000

risk_limit_monthly_stop_loss = -2000 # 20/5=4*(-500)=-2000
INCREMENT_RISK_LIMIT_MONTHLY_STOP_LOSS = -1000
# risk_limit_monthly_stop_loss = -15000
# INCREMENT_RISK_LIMIT_MONTHLY_STOP_LOSS = -30000

risk_limit_max_position = 100           ######
INCREMENT_RISK_LIMIT_MAX_POSITION = 20  ###### 

max_position_history = [] # history of max-trade-size
RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS = 120

risk_limit_max_trade_size = 20
INCREMENT_RISK_LIMIT_MAX_TRADE_SIZE = 5
max_trade_size_history = [] # history of max-trade-size

risk_violated = False

traded_volume = 0
current_pos = 0
current_pos_start = 0

last_risk_change_index = 0  #######
data4 = goog_data.copy()
close = data4['Close']

for close_price in close:
    price_history.append( close_price )
    if len( price_history) > SMA_NUM_PERIODS_20 : # we track at most 'time_period' number of prices
        del ( price_history[0] )
    
    # calculate vairance during the SMA_NUM_PERIODS_20 periods
    sma = stats.mean( price_history )
    variance = 0 # variance is square of standard deviation
    for hist_price in price_history:
        variance = variance + ( (hist_price-sma)**2 )
    
    stddev = math.sqrt( variance/len(price_history) )
    
    # a volatility factor that ranges from 0 to 1
    stddev_factor = stddev/15 # 15 since since the population stddev.mean() = 15.45
    # closer to 0 indicate very low volatility,
    # around 1 indicate normal volatility
    # > 1 indicate above-normal volatility
    if stddev_factor == 0:
        stddev_factor = 1
        
    # This section updates fast and slow EMA and computes APO trading signal
    if (ema_fast==0): # first observation
        ema_fast = close_price # initial ema_fast or ema_slow
        ema_slow = close_price
    else:
        # ema fomula 
        # K_FAST*stddev_factor or K_SLOW*stddev_factor 
        # more reactive to newest observations during periods of higher than normal volatility
        ema_fast = (close_price-ema_fast) * K_FAST*stddev_factor + ema_fast
        ema_slow = (close_price-ema_slow) * K_SLOW*stddev_factor + ema_slow
    
    ema_fast_values.append( ema_fast )
    ema_slow_values.append( ema_slow )
    
    apo = ema_fast - ema_slow
    apo_values.append( apo )
    
    ############################
    # 4.# Now, before we can evaluate our signal and 
    #     check whether we can send an order out,
    #     we need to perform a risk check to ensure that the trade size we may attempt
    #   # is within MAX_TRADE_SIZE limits:
    if num_shares_per_trade > risk_limit_max_trade_size :
        print( 'Risk Violation: NUM_SHARES_PER_TRADE', num_shares_per_trade,
               ' > RISK_LIMIT_MAX_TRADE_SIZE', risk_limit_max_trade_size
             )
        risk_violated=True
        
    MIN_PROFIT_TO_CLOSE = num_shares_per_trade * 10
    ############################   
        
    # 6. This section checks trading signal against trading parameters/thresholds and positions, to trade.
    
    # We will perform a sell trade at close_price if the following conditions are met:
    # 1. The APO trading signal value(positive) > Sell-Entry threshold (overbought, expect a bounce back down, sell for profit)
    #     and the difference between current-price and last trade-price is different enough.(>Minimum price change)
    # 2. We are long( +ve position ) and 
    #    either APO trading signal value >= 0 or current position is profitable enough to lock profit.
    # APO_VALUE_FOR_SELL_ENTRY * stdev_factor:
    #              by increasing the threshold for entry by a factor of volatility,
    #              makes us less aggressive in entering positions(here is sell) during periods of higher volatility,            
    # dynamic MIN_PROFIT_TO_CLOSE / stddev_factor: 
    #              to decrease the the expected profit threshold during periods of increased volatility
    #              to be more aggressive in exciting positions
    #              it is riskier to hold on to positions for longer periods of time.
    if not risk_violated and ( ( apo > APO_VALUE_FOR_SELL_ENTRY*stddev_factor and \
                                 abs( close_price-last_sell_price ) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stddev_factor
                               )
                               or
                               ( position>0 and (apo >=0 or open_pnl > MIN_PROFIT_TO_CLOSE/stddev_factor ) )
                             ): # long from -ve APO and APO has gone positive or position is profitable, sell to close position
        orders.append(-1) # mark the sell trade
        last_sell_price = close_price
        ############################
        if (-risk_limit_max_position)< position<=0: # opening a new entry position
            position -= num_shares_per_trade # reduce position by the size of this trade
            traded_volume += num_shares_per_trade 
            sell_sum_qty += num_shares_per_trade
            sell_sum_price_qty += (close_price * num_shares_per_trade) # update vwap sell-price
            print( "Sell ", num_shares_per_trade, " @ ", close_price, "Position: ", position )
        elif position >0: # closing an existing position, position < 0?
            traded_volume += abs(position)
            sell_sum_qty += abs(position)
            sell_sum_price_qty += (close_price * abs(position) ) # update vwap sell-price
            print( "Sell ", abs(position), " @ ", close_price, "Position: ", position )
            position = 0
        else:#elif abs(position) > risk_limit_max_position:
            print( 'Risk Violation: position', position, 
                   ' > RISK_LIMIT_MAX_POSITION', risk_limit_max_position
                 )
            traded_volume += abs(position)
            buy_sum_qty += abs(position)
            buy_sum_price_qty += (close_price * abs(position) ) # update vwap sell-price
            print( "Buy ", abs(position), " @ ", close_price, "Position: ", position )
            position = 0            
        ############################
        
    # 7. We will perform a buy trade at close_price if the following conditions are met:
    # 1. The APO trading signal value(negative) < below Buy-Entry threshold (oversold, expect a bounce back up, buy for future profit)
    #    and the difference between current-price and last trade-price is different enough.(>Minimum price change)
    # 2. We are short( -ve position ) and
    #    either APO trading signal value is <= 0 or current position is profitable enough to lock profit.
    # APO_VALUE_FOR_BUY_ENTRY * stdev_factor:
    #              by increasing the threshold for entry by a factor of volatility,
    #              makes us less aggressive in entering positions(here is sell) during periods of higher volatility,            
    # dynamic MIN_PROFIT_TO_CLOSE / stddev_factor: 
    #              to decrease the the expected profit threshold during periods of increased volatility
    #              to be more aggressive in exciting positions
    #              it is riskier to hold on to positions for longer periods of time.    
    elif not risk_violated and ( ( apo < APO_VALUE_FOR_BUY_ENTRY*stddev_factor and \
                                   abs( close_price-last_buy_price ) > MIN_PRICE_MOVE_FROM_LAST_TRADE*stddev_factor
                                 )
                                 or
                                 ( position<0 and (apo <=0 or open_pnl > MIN_PROFIT_TO_CLOSE/stddev_factor ) )
                               ): # short from +ve APO and APO has gone negative or position is profitable, buy to close position
        orders.append(+1) # mark the buy trade
        last_buy_price = close_price
        ############################
        if 0 <= position < risk_limit_max_position: # opening a new entry position
            position += num_shares_per_trade
            traded_volume += num_shares_per_trade
            buy_sum_qty += num_shares_per_trade
            buy_sum_price_qty += (close_price * num_shares_per_trade) # update the vwap buy-price
            print( "Buy ", num_shares_per_trade, " @ ", close_price, "Position: ", position )
        elif position<0:# position<0 : # position <0, closing an existing position
            traded_volume += abs(position)
            buy_sum_qty += abs(position)
            buy_sum_price_qty += (close_price * abs(position)) # update the vwap buy-price
            print( "Buy ", abs(position), " @ ", close_price, "Position: ", position )
            position = 0 # increase position by the size of this trade
        else: #elif abs(position) > risk_limit_max_position:
            print( 'Risk Violation: position', position, 
                   ' > RISK_LIMIT_MAX_POSITION', risk_limit_max_position
                 )
            traded_volume += abs(position)
            sell_sum_qty += abs(position)
            sell_sum_price_qty += (close_price * abs(position)) # update the vwap buy-price
            print( "Sell ", abs(position), " @ ", close_price, "Position: ", position )
            position = 0 # increase position by the size of this trade            
        ############################    
    else:
        # No trade since none of the conditions were met to buy or sell
        orders.append( 0 )
        
    positions.append( position )
    
    ############################
    # 6.# Now, we will check that, after any potential orders have been sent out and trades
    #     have been made this round, we haven't breached any of our risk limits,
    #   # starting with the Maximum Position Holding Time risk limit.
    # flat and starting a new postion
    if current_pos ==0 : # current position = 0 after closing the position 
        if position != 0:
            current_pos = position
            current_pos_start = len(positions) # new start index
            
    # going from long position to flat or short position or
    # going from short position to flat or long position
    elif current_pos * position <=0 :
        position_holding_time = len(positions) - current_pos_start
        current_pos = position
        current_pos_start = len(positions)
        
        if position_holding_time > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS:
            print( 'Risk Violation: position_holding_time', position_holding_time, 
                   ' > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS', RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS
                 )
            risk_violated = True
    
#     # 7. check that the new long/short position is within the Max Position risk limits 
#     if abs(position) > risk_limit_max_position:
#         print( 'Risk Violation: position', position, 
#                ' > RISK_LIMIT_MAX_POSITION', risk_limit_max_position
#              )
#         risk_violated = True
    ############################
    
    # 8. The code of the trading strategy contains logic for position/PnL management. 
    #    It needs to update positions and compute open and closed PnLs when market prices change 
    #    and/or trades are made causing a change in positions
    
    # This section updates Open/Unrealized & Closed/Realized positions
    open_pnl = 0
    if position > 0: # after -sell_sum_qty
        # long position and some sell trades have been made against it, 
        # close that amount based on how much was sold against this long position
        # PnL_realized = sell_sum_qty * (Average Sell Price - Average Buy Price)
        if sell_sum_qty > 0:           # vwap for sell                   # vwap  for buy
            open_pnl = sell_sum_qty * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty)
        # mark the remaining position to market 
        # i.e. pnl would be what it would be if we closed at current price
        # sell
        # position -= NUM_SHARES_PER_TRADE
        # sell_sum_qty += NUM_SHARES_PER_TRADE
        # PnL_unrealized = remaining position * (Exit Price - Average Buy Price)
        # if now, sell sell_sum_qty @ any price, we should use abs(position-sell_sum_qty) *
        open_pnl += abs(position) * ( close_price - buy_sum_price_qty/buy_sum_qty )
        # print( position, (buy_sum_qty-sell_sum_qty), open_pnl)
    elif position < 0: # # after +buy_sum_qty
        # short position and some buy trades have been made against it, 
        # close that amount based on how much was bought against this short position
        # PnL_realized = buy_sum_qty * (Average Sell Price - Average Buy Price)
        if buy_sum_qty > 0:           # vwap for sell                   # vwap  for buy
            open_pnl = buy_sum_qty * (sell_sum_price_qty/sell_sum_qty - buy_sum_price_qty/buy_sum_qty)
        # mark the remaining position to market
        # i.e. pnl would be what it would be if we closed at current price
        # buy
        # position += NUM_SHARE_PER_TRADE
        # buy_sum_qty += NUM_SHARE_PER_TRADE
        # PnL_unrealized = remaining position * (Average Sell Price - Exit Price)
        # if now, buy buy_sum_qty @ any price, we should use abs(position+buy_sum_qty) * 
        open_pnl += abs(position) * ( sell_sum_price_qty/sell_sum_qty - close_price )
        # print( position, (buy_sum_qty-sell_sum_qty), open_pnl)
    else:
        # flat, so update closed_pnl and reset tracking variables for positions & pnls
        closed_pnl += (sell_sum_price_qty - buy_sum_price_qty)
        
        buy_sum_price_qty = 0
        buy_sum_qty = 0
        last_buy_price = 0
        
        sell_sum_price_qty = 0
        sell_sum_qty = 0
        last_sell_price = 0
        
    print( "OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl) )
    pnls.append(closed_pnl + open_pnl)
    
    ############################
    # Analyze monthly performance and adjust risk up/down
            
    if len( pnls ) > 20:
        monthly_pnls = pnls[-1] - pnls[-21]
        
        if len(pnls) - last_risk_change_index > 20:
            # increase trade size and risk limits if we had a good month
            if monthly_pnls > 0:
                num_shares_per_trade += INCREMENT_NUM_SHARES_PER_TRADE
                if num_shares_per_trade <= MAX_NUM_SHARES_PER_TRADE :
                    print( 'Increasing trade-size and risk' )
                    risk_limit_weekly_stop_loss +=  INCREMENT_RISK_LIMIT_WEEKLY_STOP_LOSS
                    risk_limit_monthly_stop_loss += INCREMENT_RISK_LIMIT_MONTHLY_STOP_LOSS
                    risk_limit_max_position += INCREMENT_RISK_LIMIT_MAX_POSITION
                    risk_limit_max_trade_size += INCREMENT_RISK_LIMIT_MAX_TRADE_SIZE
                else:
                    num_shares_per_trade = MAX_NUM_SHARES_PER_TRADE
                    
            # decrease trade size and risk limits if we had a bad month        
            elif monthly_pnls < 0:
                num_shares_per_trade -= INCREMENT_NUM_SHARES_PER_TRADE
                if num_shares_per_trade >= MIN_NUM_SHARES_PER_TRADE :
                    print('Decreasing trade-size and risk')
                    risk_limit_weekly_stop_loss -= INCREMENT_RISK_LIMIT_WEEKLY_STOP_LOSS
                    risk_limit_monthly_stop_loss -= INCREMENT_RISK_LIMIT_MONTHLY_STOP_LOSS
                    risk_limit_max_position -= INCREMENT_RISK_LIMIT_MAX_POSITION
                    risk_limit_max_trade_size -= INCREMENT_RISK_LIMIT_MAX_TRADE_SIZE
                else:
                    num_shares_per_trade = MIN_NUM_SHARES_PER_TRADE
                
            last_risk_change_index = len(pnls)
    # end if len( pnls ) > 20        
    # Track trade-sizes/positions and risk limits as they evolve over time
    num_shares_history.append( num_shares_per_trade )
    abs_position_history.append( abs(position) )
    max_trade_size_history.append( risk_limit_max_trade_size )
    max_position_history.append( risk_limit_max_position )
    
    if len( pnls ) > 5:
        weekly_loss = pnls[-1] - pnls[-6] # negative value
        
        if weekly_loss < risk_limit_weekly_stop_loss:
            print( 'Risk Violation : weekly_loss', weekly_loss,
                   ' < RISK_LIMIT_WEEKLY_STOP_LOSS', risk_limit_weekly_stop_loss
                 )
            risk_violated = True
            
    if len( pnls ) > 20:
        monthly_loss = pnls[-1] - pnls[-21]
        
        if monthly_loss < risk_limit_monthly_stop_loss:
            print( 'Risk Violation : monthly_loss', monthly_loss,
                   '< RISK_LIMIT_MONTHLY_STOP_LOSS', risk_limit_monthly_stop_loss
                 )
            risk_violated = True
    
    ############################

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第26张图片
... ...
t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第27张图片

# This section prepares the dataframe from the trading strategy results and visualizes the results
data4 = data4.assign( ClosePrice=pd.Series(close, index=data4.index) )
data4 = data4.assign( Fast10DayEMA=pd.Series(ema_fast_values, index=data4.index) )
data4 = data4.assign( Slow40DayEMA=pd.Series(ema_slow_values, index=data4.index) )
data4 = data4.assign( APO=pd.Series(apo_values, index=data4.index) )
data4 = data4.assign( Trades=pd.Series(orders, index=data4.index) )
data4 = data4.assign( Position=pd.Series(positions, index=data4.index) )
data4 = data4.assign( Pnl=pd.Series(pnls, index=data4.index) )
data4 = data4.assign( NumShares=pd.Series(num_shares_history, index=data4.index) )
data4 = data4.assign( MaxTradeSize=pd.Series(max_trade_size_history, index=data4.index) )
data4 = data4.assign( AbsPosition=pd.Series(abs_position_history, index=data4.index) )
data4 = data4.assign( MaxPosition=pd.Series(max_position_history, index=data4.index) )
import matplotlib.pyplot as plt

fig = plt.figure( figsize=(20,10) )

data4['ClosePrice'].plot( color='k', lw=3., legend=True )
data4['Fast10DayEMA'].plot( color='y', lw=1., legend=True )
data4['Slow40DayEMA'].plot( color='m', lw=1., legend=True )
plt.plot( data4.loc[ data4.Trades == 1 ].index, data4.ClosePrice[data4.Trades == 1 ],
          color='b', lw=0, marker='^', markersize=7, label='buy'
        )
plt.plot( data4.loc[ data4.Trades == -1 ].index, data4.ClosePrice[data4.Trades == -1 ],
          color='y', lw=0, marker='v', markersize=7, label='sell'
        )
plt.autoscale(enable=True, axis='x', tight=True)
plt.legend()
plt.show()

      The plot that shows buy and sell trades overlaid on Google stock prices, strategy behavior changed as it goes through phases of risk increases and decreases(The higher the risk, the greater the possible return): 

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第28张图片

fig = plt.figure( figsize=(20,10) )

data4['APO'].plot( color='k', lw=3., legend=True )
plt.plot( data4.loc[ data4.Trades == 1 ].index, data4.APO[data4.Trades == 1 ],
          color='b', lw=0, marker='^', markersize=7, label='buy'
        )
plt.plot( data4.loc[ data4.Trades == -1 ].index, data4.APO[data4.Trades == -1 ],
          color='y', lw=0, marker='v', markersize=7, label='sell'
        )
plt.axhline(y=0, lw=0.5, color='k')
for i in range( APO_VALUE_FOR_BUY_ENTRY, APO_VALUE_FOR_BUY_ENTRY*5, APO_VALUE_FOR_BUY_ENTRY ):
  plt.axhline( y=i, lw=0.5, color='r' )
for i in range( APO_VALUE_FOR_SELL_ENTRY, APO_VALUE_FOR_SELL_ENTRY*5, APO_VALUE_FOR_SELL_ENTRY ):
  plt.axhline( y=i, lw=0.5, color='g' )

plt.autoscale(enable=True, axis='x', tight=True)
plt.legend()
plt.show()

The buy and sell trades that are overlaid on APO signal value changes also changed(The higher the risk, the greater the possible return)
t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第29张图片

fig = plt.figure( figsize=(20,10) )

data4['Position'].plot(color='k', lw=1., legend=True)
plt.plot( data4.loc[ data4.Position == 0 ].index, data4.Position[ data4.Position == 0 ],
          color='r', lw=0, marker='.', label='flat'
        )
plt.plot( data4.loc[ data4.Position > 0 ].index, data4.Position[ data4.Position > 0 ],
          color='b', lw=0, marker='+', label='long'
        )
plt.plot( data4.loc[ data4.Position < 0 ].index, data4.Position[ data4.Position < 0 ],
          color='y', lw=0, marker='x', label='short'
        )
plt.axhline( y=0, lw=0.5, color='k')
plt.autoscale(enable=True, axis='x', tight=True)
plt.legend()
plt.show()

     the position plot is especially interesting because it shows how the magnitude of the positions increases over time. Initially, they are very small (less than 50 shares) and increase or decrease or keep constant over time as strategy performance change :t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第30张图片

fig = plt.figure( figsize=(20,10) )

data4['Pnl'].plot(color='k', lw=1., legend=True)
plt.plot( data4.loc[ data4.Pnl > 0 ].index, data4.Pnl[ data4.Pnl > 0 ],
          color='b', lw=0, marker='.',
          label='Pnl'
        )
plt.plot( data4.loc[ data4.Pnl < 0 ].index, data4.Pnl[ data4.Pnl < 0 ],
          color='y', lw=0, marker='.',
          label='Pnl'
        )

plt.autoscale( enable=True, axis='x', tight=True)
plt.legend()
plt.show()

     the PnL plot is also quite interesting and reflects what we would expect it to show. It slowly increases initially when we are trading small sizes and over time, the trading sizes increase/decrease and the PnLs also increase/decrease with the larger change on trade size and risk limits(check the following NumShares and MaxTradeSize plot):t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第31张图片

fig = plt.figure( figsize=(20,10) )

data4['NumShares'].plot( color='b', lw=3., legend=True )
data4['MaxTradeSize'].plot( color='y', lw=1., legend=True )
plt.autoscale( enable=True, axis='x', tight=True)
plt.legend()
plt.show()

     the trade size and max trade size risk limit evolution plot shows that, initially, we start with 5 share per trade, then increase it slowly when we have a positive month, then decrease it slowly when we have a negative month. In some periods(for example, from 2017-01 to 2017-04), the  strategy gets into a streak of consecutively profitable months and causes the trade size to increase every month:t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第32张图片

fig = plt.figure( figsize=(20,10) )

data4['AbsPosition'].plot( color='b', lw=1., legend=True )
data4['MaxPosition'].plot( color='g', lw=1., legend=True )
plt.autoscale( enable=True, axis='x', tight=True )
plt.legend()
plt.show()

t6_Managing the Risk_limit_metrics of Algorithmic Strategies_Sharpe_adjust_trade side_position_share_第33张图片

     As shown in the preceding plot, the absolute positions that the strategy puts on, as well as
the max position risk limit evolution plot, stay consistent with expectations, that is, starting low and then increase/decrease and even keep constant.

Summary

     In this chapter, you learned about the different types of risks and risk factors. Then, we went through the sources of risk and learned about quantifying the risks. Moving ahead, we also learned about how to measure and manage the risks (market risk, operational risk, and software implementation bugs) of algorithmic strategies. We incorporated a full production-ready risk management system into our previously built trading strategy, thus making them safe for deployment to live trading markets. Finally, we discussed and built a practical risk scaling system that starts with very low-risk exposure and dynamically manages the risk exposure over time as the strategy performance evolves.

     In the next chapter, we will look at how the algorithm's trading interacts with the different factors in the trading arena. You will learn how to build a trading bot from scratch. Using the algorithm that we will build in the prior sections, you will know how to implement it, where to connect it, and how to handle it.

你可能感兴趣的:(大数据,人工智能)