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:

  1. Let's define our risk limits, which we are not allowed to breach. As we discussed previously, it will be set to 150% of the historically observed maximums:
# Risk limits
RISK_LIMIT_WEEKLY_STOP_LOSS = -12000 * 1.5
RISK_LIMIT_MONTHLY_STOP_LOSS = -14000 * 1.5
RISK_LIMIT_MAX_POSITION = 250 * 1.5
RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS = 120 * 1.5
RISK_LIMIT_MAX_TRADE_SIZE = 10 * 1.5
RISK_LIMIT_MAX_TRADED_VOLUME = 4000 * 1.5
  1. 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
  1. 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 trading signal:
close = data['Close']
for close_price in close:
price_history.append(close_price)
if len(price_history) > SMA_NUM_PERIODS: # we track at most 'time_period' number of prices
del (price_history[0])

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)

stdev = math.sqrt(variance / len(price_history))
stdev_factor = stdev / 15
if stdev_factor == 0:
stdev_factor = 1

# This section updates fast and slow EMA and computes APO trading signal
if (ema_fast == 0): # first observation
ema_fast = close_price
ema_slow = close_price
else:
ema_fast = (close_price - ema_fast) * K_FAST * stdev_factor + ema_fast
ema_slow = (close_price - ema_slow) * K_SLOW * stdev_factor + ema_slow

ema_fast_values.append(ema_fast)
ema_slow_values.append(ema_slow)

apo = ema_fast - ema_slow
apo_values.append(apo)
  1. 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('RiskViolation NUM_SHARES_PER_TRADE', NUM_SHARES_PER_TRADE, ' > RISK_LIMIT_MAX_TRADE_SIZE', RISK_LIMIT_MAX_TRADE_SIZE )
risk_violated = True
  1. Now, the next section checks the trading signal to see if we should send orders as usual. However, with an additional check, that would prevent the orders from going out if risk limits have been violated. Let's look at the changes that we need to make to the sell trades:
  # We will perform a sell trade at close_price if the following conditions are met:
# 1. The APO trading signal value is above Sell-Entry threshold and the difference between last trade-price and current-price is different enough.
# 2. We are long( +ve position ) and either APO trading signal value is at or above 0 or current position is profitable enough to lock profit.
if (not risk_violated and
((apo > APO_VALUE_FOR_SELL_ENTRY * stdev_factor and abs(close_price - last_sell_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE * stdev_factor) # APO above sell entry threshold, we should sell
or
(position > 0 and (apo >= 0 or open_pnl > MIN_PROFIT_TO_CLOSE / stdev_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 # reduce position by the size of this trade
sell_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update vwap sell-price
sell_sum_qty += NUM_SHARES_PER_TRADE
traded_volume += NUM_SHARES_PER_TRADE
print("Sell ", NUM_SHARES_PER_TRADE, " @ ", close_price, "Position: ", position)

Similarly, let's look at the buy trade logic:

  # We will perform a buy trade at close_price if the following conditions are met:
# 1. The APO trading signal value is below Buy-Entry threshold and the difference between last trade-price and current-price is different enough.
# 2. We are short( -ve position ) and either APO trading signal value is at or below 0 or current position is profitable enough to lock profit.
elif (not risk_violated and
((apo < APO_VALUE_FOR_BUY_ENTRY * stdev_factor and abs(close_price - last_buy_price) > MIN_PRICE_MOVE_FROM_LAST_TRADE * stdev_factor) # APO below buy entry threshold, we should buy
or
(position < 0 and (apo <= 0 or open_pnl > MIN_PROFIT_TO_CLOSE / stdev_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 # increase position by the size of this trade
buy_sum_price_qty += (close_price * NUM_SHARES_PER_TRADE) # update the vwap buy-price
buy_sum_qty += NUM_SHARES_PER_TRADE
traded_volume += NUM_SHARES_PER_TRADE
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)
  1. 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. Let's have a look at the following code:
  # flat and starting a new position
if current_pos == 0:
if position != 0:
current_pos = position
current_pos_start = len(positions)
continue

# going from long position to flat or short position or
# going from short position to flat or long position
if current_pos * position <= 0:
current_pos = position
position_holding_time = len(positions) - current_pos_start
current_pos_start = len(positions)

if position_holding_time > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS:
print('RiskViolation position_holding_time', position_holding_time, ' > RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS', RISK_LIMIT_MAX_POSITION_HOLDING_TIME_DAYS)
risk_violated = True
  1. We will check that the new long/short position is within the Max Position risk limits, as shown in the following code:
  if abs(position) > RISK_LIMIT_MAX_POSITION:
print('RiskViolation position', position, ' > RISK_LIMIT_MAX_POSITION', RISK_LIMIT_MAX_POSITION)
risk_violated = True
  1. Next, we also 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('RiskViolation traded_volume', traded_volume, ' > RISK_LIMIT_MAX_TRADED_VOLUME', RISK_LIMIT_MAX_TRADED_VOLUME)
risk_violated = True
  1. Next, we will write some code that updates the PnLs, unchanged from before:
  open_pnl = 0
if position > 0:
if sell_sum_qty > 0:
open_pnl = abs(sell_sum_qty) * (sell_sum_price_qty / sell_sum_qty - buy_sum_price_qty / buy_sum_qty)
open_pnl += abs(sell_sum_qty - position) * (close_price - buy_sum_price_qty / buy_sum_qty)
elif position < 0:
if buy_sum_qty > 0:
open_pnl = abs(buy_sum_qty) * (sell_sum_price_qty / sell_sum_qty - buy_sum_price_qty / buy_sum_qty)
open_pnl += abs(buy_sum_qty - position) * (sell_sum_price_qty / sell_sum_qty - close_price)
else:
closed_pnl += (sell_sum_price_qty - buy_sum_price_qty)
buy_sum_price_qty = 0
buy_sum_qty = 0
sell_sum_price_qty = 0
sell_sum_qty = 0
last_buy_price = 0
last_sell_price = 0

print("OpenPnL: ", open_pnl, " ClosedPnL: ", closed_pnl, " TotalPnL: ", (open_pnl + closed_pnl))
pnls.append(closed_pnl + open_pnl)
  1. Now, we need to write the following code, which checks that the new Total PnL, which is the sum of realized and un-realized PnLs, is not in violation of either the Maximum allowed Weekly Stop Loss limit or the Maximum allowed Monthly Stop Loss limit:
  if len(pnls) > 5:
weekly_loss = pnls[-1] - pnls[-6]

if weekly_loss < RISK_LIMIT_WEEKLY_STOP_LOSS:
print('RiskViolation 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('RiskViolation monthly_loss', monthly_loss, ' < RISK_LIMIT_MONTHLY_STOP_LOSS', RISK_LIMIT_MONTHLY_STOP_LOSS)
risk_violated = True

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 in production or behaving outside of our expected parameters, hence providing great risk control over our trading strategies.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.19.31.73