Passed
Push — master ( 684169...29f120 )
by Rafael S.
01:50
created

Exercise.update_portfolio()   A

Complexity

Conditions 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 2
1
"""options: Options plugin for the trade module.
2
3
trade: Financial Application Framework
4
http://trade.readthedocs.org/
5
https://github.com/rochars/trade
6
License: MIT
7
8
This plugin is an example of vanilla call and put operations on the trade module.
9
10
With this plugin you can:
11
- Create calls and puts that references underlying assets
12
- Create exercise operations that change both the option and underlying
13
  asset position on the portfolio
14
15
It provides:
16
- Option, a subclass of Asset
17
- Exercise, a subclass of Operation
18
- the fetch_exercises() task for the OperationContainer
19
- the fetch_exercise_operations() task for the Portfolio
20
21
Copyright (c) 2015 Rafael da Silva Rocha
22
23
Permission is hereby granted, free of charge, to any person obtaining a copy
24
of this software and associated documentation files (the "Software"), to deal
25
in the Software without restriction, including without limitation the rights
26
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
27
copies of the Software, and to permit persons to whom the Software is
28
furnished to do so, subject to the following conditions:
29
30
The above copyright notice and this permission notice shall be included in
31
all copies or substantial portions of the Software.
32
33
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
39
THE SOFTWARE.
40
"""
41
42
from __future__ import absolute_import
43
44
from . trade.occurrence import Occurrence
45
from . trade.subject import Subject
46
from . trade.utils import merge_operations
47
48
49
class Option(Subject):
50
    """Represents a vanilla option.
51
52
    Represents both calls and puts.
53
54
    This class can be used for cal and put operations or as a base
55
    for classes that represent exotic options.
56
57
    Attributes:
58
        name: A string representing the name of the asset.
59
        symbol: A string representing the symbol of the asset.
60
        expiration_date: A string 'YYYY-mm-dd' representing the
61
            expiration date of the asset, if any.
62
        underlying_assets: A dict of Assets representing the
63
            underlying assets of this asset and the ratio to which
64
            the asset relates to the Option. It looks like this:
65
            {Asset: float}
66
    """
67
68
    def exercise(self, quantity, price, premium=0):
69
        """Exercises the option.
70
71
        If a premium is informed, then it will be considered on the
72
        underlying asset cost.
73
74
        Returns a list of operations:
75
            - one operation with zero value representing the option
76
              being consumed by the exercise;
77
            - operations representing the purchase or sale of its
78
              underlying assets
79
        """
80
        operations = []
81
82
        # Create an operation to consume
83
        # the option on the portfolio.
84
        option_consuming = Occurrence(
85
            quantity=abs(quantity)*-1,
86
            price=0,
87
            subject=self
88
        )
89
        # this operation should not create
90
        # any results, just update the
91
        # quantity and price in the accumulator.
92
        option_consuming.update_results = False
93
        operations.append(option_consuming)
94
95
        # Create an operation to represent
96
        # the purchase or sale of the
97
        # underlying asset. If the option has
98
        # no underlying asset, then the only
99
        # operation created will be option
100
        # consuming operation.
101
        for underlying_asset, ratio in self.underlying_assets.items():
102
            operations.append(
103
                Occurrence(
104
                    quantity=quantity * ratio,
105
                    price=price + premium,
106
                    subject=underlying_asset
107
                )
108
            )
109
        return operations
110
111
112
class Exercise(Occurrence):
113
    """An option exercise operation.
114
115
    An exercise will likely change the state of both the derivative and
116
    its underlyings assets.
117
    """
118
119
    update_position = False
120
    update_container = False
121
122
    def update_portfolio(self, portfolio):
123
        """A Portfolio task.
124
125
        Fetch the operations in a exercise operations and  get the premium
126
        of the option that is being exercised.
127
128
        It searches on the Portfolio object for an Accumulator of the option
129
        and then use the accumulator cost as the premium to be included
130
        on the exercise operation price.
131
        """
132
        self.fetch_operations(portfolio)
133
        for operation in self.operations:
134
            portfolio.accumulate(operation)
135
136
    def update_accumulator(self, accumulator):
137
        """Exercise operations should not update the accumulator.
138
139
        Its its underlying operations that should update the
140
        accumulator.
141
        """
142
        pass
143
144
    def fetch_operations(self, portfolio=None):
145
        """Fetch the operations created by the exercise.
146
147
        If a portfolio is informed, then the premium of the option
148
        will be considered.
149
150
        An exercise creates multiple operations:
151
        - one operation to consume the option that it being exercised
152
        - operations to represent the sale or the purchase of each
153
            of its underlying assets, if any.
154
        """
155
        if portfolio:
156
            self.operations = self.subject.exercise(
157
                self.quantity,
158
                self.price,
159
                portfolio.subjects[self.subject.symbol].state['price']
160
            )
161
        else:
162
            self.operations = self.subject.exercise(
163
                self.quantity,
164
                self.price,
165
            )
166
        for operation in self.operations:
167
            operation.date = self.date
168
169
170
def fetch_exercises(container):
171
    """An OperationContainer task.
172
173
    Fetch all exercise operations on the container into a single
174
    exercise (by asset) on the container positions dictionary under
175
    the key 'exercises'.
176
    """
177
    if 'positions' not in container.context:
178
        container.context['positions'] = {}
179
    for operation in container.operations:
180
        if isinstance(operation, Exercise):
181
            if 'exercises' not in container.context['positions']:
182
                container.context['positions']['exercises'] = {}
183
            symbol = operation.subject.symbol
184
            if symbol in container.context['positions']['exercises'].keys():
185
                merge_operations(
186
                    container.context['positions']['exercises'][symbol],
187
                    operation
188
                )
189
            else:
190
                container.context['positions']['exercises'][symbol] = operation
191