-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathscalefuncs.py
More file actions
233 lines (197 loc) · 9.13 KB
/
Copy pathscalefuncs.py
File metadata and controls
233 lines (197 loc) · 9.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
"""
Functions used for setting chart scales and
getting the box a value falls within.
"""
import math # type: ignore
from typing import List
from pandas import DataFrame # type: ignore
import datadefs
def chart_scale_one_hundred() -> List[float]:
'''Returns the scale for a chart that ranges from 0 to 100%.
These charts are typically used for market indicators such as
the percent of stocks with buy signals, or the percent in a downtrend.
'''
return [float(x) for x in range(0, 101, 2)]
def chart_scale_percent(min_value: float=1.0, max_value: float=100.0, percent: float=6.5, ndigits: int=2) -> List[float]:
'''Returns the scale for a chart where each box is x% larger than the box
right below it. e.g. if the chart starts at 100 and given percent size is 5%,
then the box above 100 is 105, and the next one above that is 110.25, etc.
This is useful for comparing price movements of differently priced stocks,
or to see that each move up is a similar price gain.
'''
if min_value <= 0.0 or max_value <= 0.0:
return []
percentInc = percent/100.0 + 1.0
nextPoint = math.floor(min_value * 100) / 100
chart_scale = [nextPoint]
while nextPoint <= max_value:
nextPoint *= percentInc
chart_scale.append(math.ceil(nextPoint * 100) / 100)
return chart_scale
def chart_scale_traditional(min_value: float=1.0, max_value: float=100.0) -> List[float]:
'''Returns the scale for the traditional chart.'''
nextPoint = _getBoxAnchorValue(min_value)
chart_scale = [nextPoint]
while nextPoint < max_value:
nextPoint = _getBoxAnchorValue(nextPoint + _tradGetBoxSize(nextPoint))
chart_scale.append(nextPoint)
return chart_scale
def chart_scale_fixed_boxsize(min_value: float = 1.0, max_value: float = 100.0, box_size=1) -> List[float]:
'''Returns the scale with a fixed box size'''
nextPoint = _getBoxAnchorValue(min_value, box_size)
chart_scale = [nextPoint]
while nextPoint < max_value:
nextPoint += box_size
chart_scale.append(nextPoint)
return chart_scale
def __chart_scale__repr_(chart_scale):
if len(chart_scale) < 10:
return str(chart_scale)
else:
return '%s ... %s' % (str(chart_scale[:3]), str(chart_scale[-3:]) )
def getFirstColumnDirection(df: DataFrame, column_name: str, chart_scale: List[float]):
'''Scans a dataframe and returns the first direction (X or O) to trigger.
This is the first column of the Point and Figure chart.
If prices do not move enough then it returns the Undefined direction.
'''
start_value = df[column_name][0]
next_box_up = getNextBoxUp(start_value, chart_scale)
next_box_dn = getNextBoxDn(start_value, chart_scale)
rev_box_up = getReversalUp(start_value, chart_scale)
rev_box_dn = getReversalDn(start_value, chart_scale)
next_box_dir = datadefs.Direction.Undefined
rev_box_dir = datadefs.Direction.Undefined
for date, price in df[column_name].iteritems():
#TODO this will need to be fixed to handle X and O for getIdxBoxForValue and getting box values for each (X O)
_, box_value = getIdxBoxForValue(price, datadefs.Direction.Undefined, chart_scale)
if box_value >= rev_box_up:
rev_box_dir = datadefs.Direction.X
break # a reversal direction is stronger
elif box_value <= rev_box_dn:
rev_box_dir = datadefs.Direction.O
break
elif box_value >= next_box_up:
if next_box_dir == datadefs.Direction.Undefined:
#only set this for the first trigger
next_box_dir= datadefs.Direction.X
elif box_value <= next_box_dn:
if next_box_dir == datadefs.Direction.Undefined:
#only set this for the first trigger
next_box_dir = datadefs.Direction.O
else:
pass
# no defined Direction
if rev_box_dir != datadefs.Direction.Undefined:
return rev_box_dir
else:
return next_box_dir
def _tradGetBoxSize(value: float) -> float:
'''Returns the box size of a given value when using
a traditional Point and Figure chart.
'''
if value < 0:
return 0.0
if value < 5: # 0 < 5 is 0.25
return 0.25
if value < 20: # 5 < 20 is 0.50
return 0.5
if value < 100: # 20 < 100 is 1
return 1.0
if value < 200: # 100 < 200 is 2
return 2.0
if value < 400: # 200 < 400 is 4
return 4.0
if value < 800: # 400 < 800 is 8
return 8.0
if value < 1600: # 800 < 1600 is 16
return 16.0
return 32.0 # 1600 and above is 32
def _getBoxAnchorValue(value: float, box_size: float = None) -> float:
if value <= 0.0:
return 0.0
if not box_size:
box_size = _tradGetBoxSize(value)
if box_size <= 0.0:
raise ValueError("box_size cannot be zero or negative.")
return math.floor(value/box_size) * box_size
def myround(x, prec=2, base=0.05):
return round(base * round(float(x)/base), prec)
def getIdxBoxForValue(value: float, direction, chart_scale: List[float]):
'''Returns the chart_scale index and box value for a given price'''
if value < min(chart_scale):
raise LookupError("Value less than lowest scale value")
if value > max(chart_scale):
raise LookupError("Value greater than highest scale value")
if direction == datadefs.Direction.X or direction == datadefs.Direction.Undefined:
for index, item in enumerate(chart_scale):
if index == len(chart_scale) - 1 and value == item:
return index, item
if value >= item and value < chart_scale[index + 1]:
return index, item
elif direction == datadefs.Direction.O:
for index, item in enumerate(chart_scale):
if value <= item:
return index, item
return index, item #Is this correct?
else:
raise ValueError("The column direction {1} isn't handled in scalefuncs.getIdxBoxForValue".format(direction))
raise LookupError("The value wasn't placed in the scale.")
def getBoxForValue(value: float, direction, chart_scale: List[float]) -> float:
_, box = getIdxBoxForValue(value, direction, chart_scale)
return box
def getBoxesBetweenValues(start_value: float, end_value: float, direction, chart_scale: List[float]) -> List[float]:
'''Returns a list of the box values between two prices.
This is useful if price jumps by multiple boxes.
'''
lo_val = start_value if start_value <= end_value else end_value
hi_val = end_value if start_value <= end_value else start_value
lo_idx, lo_box = getIdxBoxForValue(lo_val, direction, chart_scale)
hi_idx, hi_box = getIdxBoxForValue(hi_val, direction, chart_scale)
boxes = []
for idx in range(lo_idx, hi_idx + 1):
boxes.append(chart_scale[idx])
return boxes
def getBoxesInReversals(price: float, num_reversal_boxes: int, direction, chart_scale: List[float]) -> List[float]:
idx, box = getIdxBoxForValue(price, direction, chart_scale)
if direction == datadefs.Direction.X:
idx = min(idx + 1, len(chart_scale))
idx_rev = (idx - (num_reversal_boxes))# direction reversed up; fill in lower boxes
return chart_scale[idx_rev:idx]
elif direction == datadefs.Direction.O:
idx_rev = idx + (num_reversal_boxes) # direction reversed down; fill in higher boxes
return chart_scale[idx:idx_rev]
else:
raise ValueError('Cannot fill in reversal boxes for %s.') % direction
def getNextBoxUp(value: float, chart_scale: List[float]):
index, item = getIdxBoxForValue(value, datadefs.Direction.X, chart_scale)
num_boxes = len(chart_scale)
if index < num_boxes:
return chart_scale[index + 1]
else:
return chart_scale[num_boxes] # does this correctly handle when value is in highest box?
def getNextBoxDn(value: float, chart_scale: List[float]):
index, item = getIdxBoxForValue(value, datadefs.Direction.O, chart_scale)
if index > 0:
return chart_scale[index - 1]
else:
return chart_scale[0] # does this correctly handle when value is in lowest box?
def getReversalUp(value: float, chart_scale: List[float], num_reversal_boxes: int = 3):
'''For a given price, returns the box that would trigger a chart reversal
up to a column of Xs when in a column of Os.
'''
index, item = getIdxBoxForValue(value, datadefs.Direction.O, chart_scale)
if index <= len(chart_scale) - num_reversal_boxes:
return chart_scale[index + num_reversal_boxes]
else:
return chart_scale[len(chart_scale)] # does this correctly handle when reversal box is off scale?
def getReversalDn(value: float, chart_scale: List[float], num_reversal_boxes: int = 3):
'''For a given price, returns the box that would trigger a chart reversal
down to a column of Os when in a column of Xs.
'''
index, item = getIdxBoxForValue(value, datadefs.Direction.X, chart_scale)
if index >= num_reversal_boxes:
return chart_scale[index - num_reversal_boxes]
else:
return chart_scale[0] # does this correctly handle when reversal box is off scale?
if __name__ == '__main__':
pass