Test Failed
Branch master (3cd48f)
by Rafael S.
02:13
created

Occurrence.volume()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 4
rs 10
1
"""Occurrence
2
3
Copyright (c) 2015-2017 Rafael da Silva Rocha
4
5
Permission is hereby granted, free of charge, to any person obtaining a copy
6
of this software and associated documentation files (the "Software"), to deal
7
in the Software without restriction, including without limitation the rights
8
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
copies of the Software, and to permit persons to whom the Software is
10
furnished to do so, subject to the following conditions:
11
12
The above copyright notice and this permission notice shall be included in
13
all copies or substantial portions of the Software.
14
15
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
THE SOFTWARE.
22
"""
23
24
from __future__ import absolute_import
25
from __future__ import division
26
27
import math
28
29
from . utils import (
0 ignored issues
show
Unused Code introduced by
Unused merge_operations imported from utils
Loading history...
Unused Code introduced by
Unused find_purchase_and_sale imported from utils
Loading history...
30
    average_price,
31
    same_sign,
32
    merge_operations,
33
    find_purchase_and_sale
34
)
35
36
class Occurrence(object):
0 ignored issues
show
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
37
    """An occurrence with a subject in a date.
38
39
    Class Attributes:
40
        update_position: A boolean indication if the operation should
41
            update the position of the accumulator or not.
42
        update_results: A boolean indication if the operation should
43
            update the results of the accumulator or not.
44
        update_container: A boolean indication if the operation should
45
            update the context in a OperationContainer or not.
46
47
    Attributes:
48
        date: A string 'YYYY-mm-dd', the date the operation occurred.
49
        subject: An Asset instance, the asset that is being traded.
50
        quantity: A number representing the quantity being traded.
51
            Positive quantities represent a purchase.
52
            Negative quantities represent a sale.
53
        price: The raw unitary price of the asset being traded.
54
        commissions: A dict of discounts. String keys and float values
55
            representing the name of the discounts and the values
56
            to be deducted added to the the operation value.
57
        operations: A list of underlying occurrences that the
58
            might may have.
59
    """
60
61
    # By default every operation
62
    # updates the accumulator position
63
    update_position = True
64
65
    # By default every operation
66
    # updates the accumulator results
67
    update_results = True
68
69
    # By default every operation updates
70
    # the OperationContainer positions
71
    update_container = True
72
73
    def __init__(self, subject=None, date=None, **kwargs):
74
        #super(Operation, self).__init__(subject, date)
75
        self.subject = subject
76
        self.date = date
77
        self.quantity = kwargs.get('quantity', 0)
78
        self.price = kwargs.get('price', 0)
79
        self.commissions = kwargs.get('commissions', {})
80
        self.raw_results = kwargs.get('raw_results', {})
81
        self.operations = kwargs.get('operations', [])
82
83
    def update_portfolio(self, portfolio):
84
        """Should udpate the portfolio state.
85
86
        This is method is called by the Portfolio object when it accumulates
87
        the Occurrence. It is called before the accumulation occurs, so the
88
        Occurrence is free to manipulate the Portfolio data before it is passed
89
        to its subject corresponding Accumulator.
90
        """
91
        pass
92
93
    @property
94
    def results(self):
95
        """Returns the results associated with the operation."""
96
        return self.raw_results
97
98
    @property
99
    def real_value(self):
100
        """Returns the quantity * the real price of the operation."""
101
        return self.quantity * self.real_price
102
103
    @property
104
    def real_price(self):
105
        """Returns the real price of the operation.
106
107
        The real price is the price with all commission and costs
108
        already deducted or added.
109
        """
110
        return self.price + math.copysign(
111
            self.total_commissions / self.quantity,
112
            self.quantity
113
        )
114
115
    @property
116
    def total_commissions(self):
117
        """Returns the sum of all commissions of this operation."""
118
        return sum(self.commissions.values())
119
120
    @property
121
    def volume(self):
122
        """Returns the quantity of the operation * its raw price."""
123
        return abs(self.quantity) * self.price
124
125
    def update_accumulator(self, accumulator):
126
        """Updates the accumulator with the operation data."""
127
        if self.need_position_update(accumulator):
128
            self.update_positions(accumulator)
129
        if self.update_results:
130
            self.update_accumulator_results(accumulator)
131
132
    def update_accumulator_results(self, accumulator):
133
        """Updates the results stored in the accumulator."""
134
        for key, value in self.results.items():
135
            if key not in accumulator.state['results']:
136
                accumulator.state['results'][key] = 0
137
            accumulator.state['results'][key] += value
138
139
    def need_position_update(self, accumulator):
140
        """Check if there is a need to update the position."""
141
        return (
142
            self.subject.symbol == accumulator.subject.symbol and
143
            self.quantity
144
        )
145
146
    def update_position_different_sign(self, accumulator, new_quantity):
147
        """Update when the operation and position have opposing signs."""
148
        # if we are trading more than the amount in the portfolio
149
        # the result will be calculated based only on what was traded
150
        # (the rest creates a new position)
151
        if abs(self.quantity) > abs(accumulator.state['quantity']):
152
            result_quantity = accumulator.state['quantity'] * -1
153
        else:
154
            result_quantity = self.quantity
155
        results = \
156
            result_quantity * accumulator.state['price'] - \
157
            result_quantity * self.real_price
158
        if results:
159
            self.results['trades'] = results
160
        if not same_sign(accumulator.state['quantity'], new_quantity):
161
            accumulator.state['price'] = self.real_price
162
163
    def update_position_same_sign(self, accumulator):
164
        """Update position when operation and position have the same sign."""
165
        accumulator.state['price'] = average_price(
166
            accumulator.state['quantity'],
167
            accumulator.state['price'],
168
            self.quantity,
169
            self.real_price
170
        )
171
172
    def update_positions(self, accumulator):
173
        """Updates the state of the asset with the operation data."""
174
        new_quantity = accumulator.state['quantity'] + self.quantity
175
        # same sign, udpate the cost
176
        if same_sign(accumulator.state['quantity'], self.quantity):
177
            self.update_position_same_sign(accumulator)
178
        # different signs, update the results
179
        elif accumulator.state['quantity'] != 0:
180
            self.update_position_different_sign(accumulator, new_quantity)
181
        else:
182
            accumulator.state['price'] = self.real_price
183
        accumulator.state['quantity'] = new_quantity
184
        if not accumulator.state['quantity']:
185
            accumulator.state['price'] = 0
186
187
    '''
188
    def update_accumulator(self, accumulator):
189
        """Should udpate the accumulator state.
190
191
        This method is called before the Accumulator log its current state.
192
        """
193
        pass
194
    '''
0 ignored issues
show
Unused Code introduced by
This string statement has no effect and could be removed.
Loading history...