Deux chemins vers le même endroit
Construisez deux portefeuilles. A : achetez un call et placez à la banque (ou en obligation sans risque) la valeur actuelle du prix d'exercice K * exp(-rT). B : achetez un put et achetez l'action. À l'échéance, si l'action termine au-dessus de K, A vaut le S - K du call plus le K de la banque, soit S ; B vaut l'action S et un put sans valeur, soit S. Si l'action termine sous K, A ne vaut que le K de la banque puisque le call expire sans valeur, et B vaut le K - S du put plus l'action S, soit K. Dans les deux cas les deux portefeuilles valent autant à l'échéance : ils doivent donc coûter autant aujourd'hui.
import math
spot, strike, rate, t = 100, 100, 0.05, 1
pv_strike = strike * math.exp(-rate * t) # what must be put in the bank today to own K at expiry
print(round(pv_strike, 4)) # 95.1229
call = 10.4506 # a call price we observe
put = call - spot + pv_strike # the parity relation
print(round(put, 4)) # 5.5735
print(round(put + spot - pv_strike, 4)) # 10.4506 and back to the callQuand la parité est rompue : l'arbitrage
Si le marché cote des prix qui ne satisfont pas la relation, un profit sans risque existe. Définissez l'écart (C - P) - (S - K e^(-rT)). Un écart positif signifie que le call est trop cher par rapport au put : vendez le call, achetez le put, achetez l'action et empruntez la valeur actuelle du prix d'exercice. Le portefeuille ne coûte rien au départ, ne paie rien à l'échéance quoi que fasse l'action, et empoche l'écart aujourd'hui. Un écart négatif inverse chaque jambe. Les vrais marchés ont des coûts et des fourchettes achat-vente, donc de petits écarts ne sont pas négociables : c'est ce que représente la tolérance.
def parity_gap(call, put, spot, strike, rate, t):
return (call - put) - (spot - strike * math.exp(-rate * t))
print(round(parity_gap(11.0, put, 100, 100, 0.05, 1), 4)) # 0.5494 the call at 11.00 is too expensive: sell it
print(round(parity_gap(call, put, 100, 100, 0.05, 1), 4)) # 0.0 consistent prices: nothing to do
def arbitrage_action(call, put, spot, strike, rate, t, tolerance=0.01):
gap = parity_gap(call, put, spot, strike, rate, t)
if gap > tolerance:
return "sell call, buy put, buy stock"
if gap < -tolerance:
return "buy call, sell put, sell stock"
return "none"
print(arbitrage_action(11.0, put, 100, 100, 0.05, 1)) # sell call, buy put, buy stock# The package of the arbitrage, at today's prices: sell the call at 11, buy the put, buy the stock, borrow the strike's present value
cash_today = 11.0 - put - 100 + 100 * math.exp(-0.05 * 1)
print(round(cash_today, 4)) # 0.5494 you are paid the gap now and owe nothing at expiry# A call minus a put is a synthetic forward: it behaves like the stock bought with borrowed money
print(round(10.4506 - put, 4), round(100 - pv_strike, 4)) # 4.8771 4.8771C + K * exp(-rT) = P + S: un call plus l'obligation égale un put plus l'action, doncP = C - S + K * exp(-rT).- Écart
(C - P) - (S - K e^(-rT))positif : vendez le call, achetez le put et l'action ; négatif : l'inverse. - La parité ne dépend d'aucun modèle : utilisez-la pour contrôler tout modèle de valorisation ; avec dividendes ou exercice anticipé, elle devient une inégalité.
À suivre : la formule de Black–Scholes, un prix en forme fermée pour le call et le put.
Lab : ce qui est noté
- Déduire le put du call avec
parity_put_price25 pts - Déduire le call du put avec
parity_call_price25 pts - Mesurer l'écart des prix à la parité avec
parity_gap25 pts - Nommer l'opération sans risque avec
arbitrage_action25 pts