Created
March 2, 2009 13:12
-
-
Save zacharyvoase/72742 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env ruby | |
# -*- coding: utf-8 -*- | |
# | |
# exposure.rb => Break a position down into multiple spreads. | |
# Copyright (C) 2009 Zachary Voase | |
# | |
# This program is free software; you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation; either version 2 of the License, or | |
# (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License along | |
# with this program; if not, write to the Free Software Foundation, Inc., | |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
class Array # :nodoc: | |
def sum | |
inject(0) {|s, v| s += v} | |
end | |
end | |
module Exposure | |
=begin rdoc | |
The Contract class represents an exposure at a single expiry. Expiries are | |
indexed, so that instead of storing Dec09 as the expiry, one might use the | |
number 1 for Dec09 and then add one to this number for each three months | |
after it. For example: | |
1. December 2009 | |
2. March 2010 | |
3. June 2010 | |
4. September 2010 | |
And so on. All that is _really_ required is that the object given as the expiry | |
has three abilities: | |
* *Comparison*: the ability to compare one expiry with another and determine if | |
one is less than, equal to or greater than the other. | |
* *Subtraction*: the ability to subtract one expiry from another and yield an | |
object representing the 'distance' between the two. This object itself is | |
subject to some constraints; consult the documentation for the Spread class | |
for more information on the requirements. | |
* *Printing*: the object should have a +to_s+ method which allows it to be | |
printed as part of a string. | |
The exposure should be an number representing the amount of contracts bought or | |
sold at that particular expiry, or perhaps the total amount of money invested in | |
the commodity at that expiry. | |
Example 1: | |
>> contract = Contract.new(400, 1) | |
>> contract.pretty_print | |
=> "CONTRACT LONG 400 AT 1" | |
>> contract.long? | |
=> true | |
>> contract.zero? | |
=> false | |
>> contract.net_exposure | |
=> 400 | |
Example 2: | |
>> contract2 = Contract.new(-200, 3) | |
>> contract2.pretty_print | |
=> "CONTRACT SHORT 200 AT 3" | |
>> contract.short? | |
=> true | |
>> contract.net_exposure | |
=> -200 | |
=end | |
class Contract | |
attr_accessor :exposure | |
attr_reader :expiry | |
# Initializes a Contract instance, simply by storing the exposure and expiry | |
# as instance variables. | |
# | |
# Before storing the exposure, it is checked for negativity. If the exposure | |
# _is_ negative, then it is considered a short contract; a zero or positive | |
# value for exposure is considered long. This is stored in the instance, | |
# and then the value for the exposure is made absolute before being stored | |
# also. | |
def initialize(exposure, expiry) | |
@long = (exposure >= 0) | |
@exposure, @expiry = exposure.abs, expiry | |
end | |
def inspect # :nodoc: | |
if long? | |
"#<Contract(+#{exposure}, #{expiry})>" | |
else | |
"#<Contract(-#{exposure}, #{expiry})>" | |
end | |
end | |
# Return a string with the contract displayed in a human-friendly format. | |
# | |
# The format by which a Contract instance is pretty printed is as follows: | |
# CONTRACT LONG 200 AT 1 | |
# This means that the contract is long (i.e. representing a security that | |
# has been bought), with an exposure of 200 at expiry date 1 (whatever that | |
# may represent). | |
def pretty_print | |
if long? | |
"CONTRACT LONG #{exposure} AT #{expiry}" | |
elsif short? | |
"CONTRACT SHORT #{exposure} AT #{expiry}" | |
end | |
end | |
# A contract's net exposure is its exposure taken at a positive value if the | |
# contract is long, and negative if short; for example, +200 is a 200 long | |
# contract, -50 is a 50 short contract. | |
def net_exposure | |
exposure * (long? ? 1 : -1) | |
end | |
# Return a boolean indicating whether this contract represents a buy or a | |
# sell. | |
def long? | |
(@long or zero?) | |
end | |
# The opposite of long?. | |
def short? | |
!long? | |
end | |
# Return +true+ if the contract has an exposure of zero. | |
def zero? | |
exposure.zero? | |
end | |
end | |
=begin rdoc | |
A Spread is a financial instrument involving the purchase of a certain quantity | |
of one commodity and the sale of another related commodity. In the case of this | |
program, the Spread class represents a specific type of spread: a calendar | |
futures spread. This is the act of purchasing a specific futures contract for | |
one commodity at one expiry date and the simultaneous sale of a contract for the | |
same (or another strongly-related) commodity with another expiry date. | |
Aside from data encapsulation, this class exports several pieces of | |
functionality which are useful, or save time and code. | |
Example 1: | |
>> spread = Spread.new(Contract.new(400, 1), Contract.new(-400, 3)) | |
=> #<Spread...> | |
>> [spread.long?, spread.short?] | |
=> [true, false] | |
>> spread.pretty_print | |
=> "SPREAD LONG 400 ACROSS 1 <=> 3" | |
>> spread.exposure | |
=> 400 | |
>> spread.expiry_distance | |
=> 2 | |
>> spread.net_exposure | |
=> 800 | |
Example 2: | |
>> spread = Spread.new(Contract.new(-300, 1), Contract.new(300, 2)) | |
=> #<Spread...> | |
>> [spread.long?, spread.short?] | |
=> [false, true] | |
>> spread.pretty_print | |
=> "SPREAD SHORT 300 ACROSS 1 <=> 2" | |
>> spread.net_exposure | |
=> -300 | |
Example 3: | |
>> contract1, contract2 = Contract.new(100, 1), Contract.new(-75, 2) | |
>> spread, overlap1, overlap2 = Spread.with_overlap(contract1, contract2) | |
>> puts spread.pretty_print | |
SPREAD LONG 75 ACROSS 1 <=> 2 | |
=> nil | |
>> puts overlap1.pretty_print | |
CONTRACT LONG 25 AT 1 | |
=> nil | |
>> puts overlap2.pretty_print | |
CONTRACT LONG 0 AT 2 | |
=> nil | |
=end | |
class Spread | |
attr_reader :contract1, :contract2 | |
# Initialize a Spread instance. | |
# | |
# A Spread instance is initialized with two contracts with different expiry | |
# dates. If the contracts' exposures are unequal, an error is raised. | |
# Similarly, if the contracts are both long or both short, an error is | |
# raised. | |
def initialize(contract1, contract2) | |
@contract1, @contract2 = [contract1, contract2].sort_by(&:expiry) | |
unless contract1.exposure == contract2.exposure | |
raise "Unequal exposures for spread" | |
end | |
if contract1.long? == contract2.long? | |
raise "Contracts for spread are both %s" % (contract1.long? ? "long" : "short") | |
end | |
end | |
# Return a string with a human-friendly display | |
# A spread is displayed like so: | |
# SPREAD LONG 200 ACROSS 1 <=> 3 | |
# This represents a spread wherein the first contract is long and the second | |
# short, with an exposure of 200 between the expiry dates 1 and 3. | |
def pretty_print | |
"SPREAD %s #{exposure} ACROSS #{contract1.expiry} <=> #{contract2.expiry}" % (long? ? "LONG" : "SHORT") | |
end | |
# The net exposure of a spread is its exposure weighted by its expiry | |
# distance. If the spread long, the net exposure is positive; if the spread is | |
# short then its net exposure is negative. | |
def net_exposure | |
(exposure * expiry_distance) * (long? ? 1 : -1) | |
end | |
def inspect # :nodoc: | |
"#<Spread(#{contract1.inspect}, #{contract2.inspect})>" | |
end | |
# The expiry distance of a spread is the difference between the expiry indices | |
# of its two component contracts. | |
def expiry_distance | |
(contract1.expiry - contract2.expiry).abs | |
end | |
# A spread's exposure is going to be the same as its first contract's. | |
def exposure | |
contract1.exposure | |
end | |
# A spread is defined as _long_ if the earliest of its two contracts is | |
# long. | |
def long? | |
contract1.long? | |
end | |
# The opposite of long?. | |
def short? | |
!long? | |
end | |
# A spread is zero if its exposure is zero. Returns a boolean. | |
def zero? | |
exposure.zero? | |
end | |
def self.with_leftovers(contract1, contract2) | |
# Get the common exposure between the two expiries. For example, the | |
# following two contracts | |
# 1) +100 | |
# 2) -75 | |
# Will be interpreted as a long spread of 75 between 1) and 2), and | |
# leftovers of +25 at 1) and 0 at 2). | |
# This method returns a 3-array containing the generated spread, the | |
# leftover from the first contract passed in, and the leftover from the | |
# second contract passed in (in that order.) | |
spread_exposure = [contract1.exposure, contract2.exposure].min | |
if contract1.long? and contract2.short? | |
spread_contract1 = Contract.new(+spread_exposure, contract1.expiry) # long | |
spread_contract2 = Contract.new(-spread_exposure, contract2.expiry) # short | |
elsif contract1.short? and contract2.long? | |
spread_contract1 = Contract.new(-spread_exposure, contract1.expiry) # short | |
spread_contract2 = Contract.new(+spread_exposure, contract2.expiry) # long | |
end | |
spread = Spread.new(spread_contract1, spread_contract2) | |
if contract1.exposure > contract2.exposure | |
contract1.exposure -= contract2.exposure | |
contract2.exposure = 0 | |
else | |
contract2.exposure -= contract1.exposure | |
contract1.exposure = 0 | |
end | |
[spread, contract1, contract2] | |
end | |
end | |
=begin rdoc | |
The Position class represents a series of spreads and contracts across a range | |
of expiry dates. It provides a thin layer of functionality on top of the native | |
Array class, including the ability to decompose a position made of many | |
individual contracts into several spreads and some leftover contracts, and also | |
a shortcut to calculate the net exposure of the whole position. | |
=end | |
class Position < Array | |
attr_accessor :contracts | |
def pretty_print | |
self.each.inject("") {|s, v| s += v.pretty_print + "\n"} | |
end | |
def inspect() "#<Position(#{super})>" end | |
def net_exposure() (map(&:net_exposure)).sum end | |
# Destructively build a position from a series of individual contracts. This | |
# will aggreggate together any spreads it finds, and in the future may even | |
# recognise butterflies. | |
def self.from_contracts(contracts) | |
# Initialize the required variables for the iteration. | |
match_queue = [] | |
position = Position.new | |
while !contracts.empty? | |
# Get the l | |
contract = contracts.shift | |
next if contract.zero? | |
case | |
when (match_queue.empty? or (contract.long? == match_queue[0].long?)) | |
match_queue.push contract | |
when contract.long? != match_queue[0].long? | |
spread, contract, pushback = Spread.with_leftovers(contract, match_queue.shift) | |
match_queue.unshift pushback unless pushback.zero? | |
contracts.unshift contract unless contract.zero? | |
position << spread | |
end # case | |
end # while !contracts.empty? | |
position += match_queue unless match_queue.empty? | |
position.sort_by do |item| | |
case item | |
when Contract then item.expiry | |
when Spread then item.contract1.expiry | |
# TODO: implement Butterfly. --when Butterfly then item.spread1.contract1.expiry | |
end | |
end # spreads.sort_by | |
# Finally... | |
return position | |
end # def self.from_contracts | |
end # class Position < Array | |
end | |
def main() | |
while (line = STDIN.gets) | |
yield Exposure::Contract.new(*line.split.map(&:to_i).reverse) | |
end | |
end | |
if __FILE__ == $0 | |
contracts = [] | |
main {|c| contracts << c} | |
position = Exposure::Position.from_contracts(contracts) | |
puts position.pretty_print | |
puts | |
puts "NET EXPOSURE " + position.net_exposure.to_s | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment