Passed
Branch master (bc72f6)
by Rafael S.
01:23
created

Exercise.fetch_operations()   B

Complexity

Conditions 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 0 Features 0
Metric Value
c 6
b 0
f 0
dl 0
loc 24
rs 8.9713
cc 3
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.occurrences import Asset, Operation
45
from . trade.utils import merge_operations
46
47
48
class Option(Asset):
49
    """Represents a vanilla option.
50
51
    Represents both calls and puts.
52
53
    This class can be used for cal and put operations or as a base
54
    for classes that represent exotic options.
55
56
    Attributes:
57
        name: A string representing the name of the asset.
58
        symbol: A string representing the symbol of the asset.
59
        expiration_date: A string 'YYYY-mm-dd' representing the
60
            expiration date of the asset, if any.
61
        underlying_assets: A dict of Assets representing the
62
            underlying assets of this asset and the ratio to which
63
            the asset relates to the Option. It looks like this:
64
            {Asset: float}
65
    """
66
67
    def exercise(self, quantity, price, premium=0):
68
        """Exercises the option.
69
70
        If a premium is informed, then it will be considered on the
71
        underlying asset cost.
72
73
        Returns a list of operations:
74
            - one operation with zero value representing the option
75
              being consumed by the exercise;
76
            - operations representing the purchase or sale of its
77
              underlying assets
78
        """
79
        operations = []
80
81
        # Create an operation to consume
82
        # the option on the portfolio.
83
        option_consuming = Operation(
84
            quantity=abs(quantity)*-1,
85
            price=0,
86
            subject=self
87
        )
88
        # this operation should not create
89
        # any results, just update the
90
        # quantity and price in the accumulator.
91
        option_consuming.update_results = False
92
        operations.append(option_consuming)
93
94
        # Create an operation to represent
95
        # the purchase or sale of the
96
        # underlying asset. If the option has
97
        # no underlying asset, then the only
98
        # operation created will be option
99
        # consuming operation.
100
        for underlying_asset, ratio in self.underlying_assets.items():
101
            operations.append(
102
                Operation(
103
                    quantity=quantity * ratio,
104
                    price=price + premium,
105
                    subject=underlying_asset
106
                )
107
            )
108
        return operations
109
110
111
class Exercise(Operation):
112
    """An option exercise operation.
113
114
    An exercise will likely change the state of both the derivative and
115
    its underlyings assets.
116
    """
117
118
    update_position = False
119
    update_container = False
120
121
    def update_portfolio(self, portfolio):
122
        """A Portfolio task.
123
124
        Fetch the operations in a exercise operations and  get the premium
125
        of the option that is being exercised.
126
127
        It searches on the Portfolio object for an Accumulator of the option
128
        and then use the accumulator cost as the premium to be included
129
        on the exercise operation price.
130
        """
131
        self.fetch_operations(portfolio)
132
        for operation in self.operations:
133
            portfolio.accumulate(operation)
134
135
    def update_accumulator(self, accumulator):
136
        """Exercise operations should not update the accumulator.
137
138
        Its its underlying operations that should update the
139
        accumulator.
140
        """
141
        pass
142
143
    def fetch_operations(self, portfolio=None):
144
        """Fetch the operations created by the exercise.
145
146
        If a portfolio is informed, then the premium of the option
147
        will be considered.
148
149
        An exercise creates multiple operations:
150
        - one operation to consume the option that it being exercised
151
        - operations to represent the sale or the purchase of each
152
            of its underlying assets, if any.
153
        """
154
        if portfolio:
155
            self.operations = self.subject.exercise(
156
                self.quantity,
157
                self.price,
158
                portfolio.subjects[self.subject.symbol].state['price']
159
            )
160
        else:
161
            self.operations = self.subject.exercise(
162
                self.quantity,
163
                self.price,
164
            )
165
        for operation in self.operations:
166
            operation.date = self.date
167
168
169
def fetch_exercises(container):
170
    """An OperationContainer task.
171
172
    Fetch all exercise operations on the container into a single
173
    exercise (by asset) on the container positions dictionary under
174
    the key 'exercises'.
175
    """
176
    if 'positions' not in container.context:
177
        container.context['positions'] = {}
178
    for operation in container.operations:
179
        if isinstance(operation, Exercise):
180
            if 'exercises' not in container.context['positions']:
181
                container.context['positions']['exercises'] = {}
182
            symbol = operation.subject.symbol
183
            if symbol in container.context['positions']['exercises'].keys():
184
                merge_operations(
185
                    container.context['positions']['exercises'][symbol],
186
                    operation
187
                )
188
            else:
189
                container.context['positions']['exercises'][symbol] = operation
190