Skip to content

Instantly share code, notes, and snippets.

@zacharyvoase
Created March 2, 2009 13:12
Show Gist options
  • Save zacharyvoase/72742 to your computer and use it in GitHub Desktop.
Save zacharyvoase/72742 to your computer and use it in GitHub Desktop.
#! /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