Skip to content

Commit 0f4fc38

Browse files
committed
Algo desc + small fixes + size limit alteration
1 parent 295f0ec commit 0f4fc38

File tree

2 files changed

+83
-11
lines changed

2 files changed

+83
-11
lines changed

examples/price-movement_plots.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"cell_type": "markdown",
1616
"metadata": {},
1717
"source": [
18-
"Price-movement, or price-based, plots focus on plotting price movements that are above some minimal movement threshold or size. As such, price-movement plots \"ignore\" time or, really, more correctly, they allow the time axis to be compressed or exanded as defined by the price movements. In other words, the x-axis is not linear with time, and each unit along the x-axis can represent a different amount of time compared with other units along the same x-axis. This is because each unit is defined by a specific size of price movement, and regardless of the amount of time it took to make that price movement.\n",
18+
"Price-movement, or price-based, plots focus on plotting price movements that are above some minimal movement threshold or size. As such, price-movement plots \"ignore\" time or, really, more correctly, they allow the time axis to be compressed or expanded as defined by the price movements. In other words, the x-axis is not linear with time, and each unit along the x-axis can represent a different amount of time compared with other units along the same x-axis. This is because each unit is defined by a specific size of price movement, and regardless of the amount of time it took to make that price movement.\n",
1919
"\n",
2020
"There are several common types of price-movement based charts, including 'Renko', 'Point and Figure', 'Heiken-Ashi', 'Kagi', and 'Line Break'. At present, mplfinance support 'Renko' and 'Point and Figure' (see below)."
2121
]

src/mplfinance/_utils.py

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,37 @@ def _construct_candlestick_collections(dates, opens, highs, lows, closes, market
378378
def _construct_renko_collections(dates, highs, lows, volumes, config_renko_params, closes, marketcolors=None):
379379
"""Represent the price change with bricks
380380
381+
NOTE: this code assumes if any value open, low, high, close is
382+
missing they all are missing
383+
384+
Algorithm Explanation
385+
---------------------
386+
In the first part of the algorithm, we populate the cdiff array
387+
along with adjusting the dates and volumes arrays into the new_dates and
388+
new_volumes arrays. A single date includes a range from no bricks to many
389+
bricks, if a date has no bricks it shall not be included in new_dates,
390+
and if it has n bricks then it will be included n times. Volumes use a
391+
volume cache to save volume amounts for dates that do not have any bricks
392+
before adding the cache to the next date that has at least one brick.
393+
We populate the cdiff array with each close values difference from the
394+
previously created brick divided by the brick size.
395+
396+
In the second part of the algorithm, we iterate through the values in cdiff
397+
and add 1s or -1s to the bricks array depending on whether the value is
398+
positive or negative. Every time there is a trend change (ex. previous brick is
399+
an upbrick, current brick is a down brick) we draw one less brick to account
400+
for the price having to move the previous bricks amount before creating a
401+
brick in the opposite direction.
402+
403+
In the final part of the algorithm, we enumerate through the bricks array and
404+
assign up-colors or down-colors to the associated index in the color array and
405+
populate the verts list with each bricks vertice to be used to create the matplotlib
406+
PolyCollection.
407+
408+
Useful sources:
409+
https://avilpage.com/2018/01/how-to-plot-renko-charts-with-python.html
410+
https://school.stockcharts.com/doku.php?id=chart_analysis:renko
411+
381412
Parameters
382413
----------
383414
dates : sequence
@@ -413,12 +444,12 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
413444
else:
414445
brick_size = _calculate_atr(atr_length, highs, lows, closes)
415446
else: # is an integer or float
416-
upper_limit = (max(closes) - min(closes)) / 5
417-
lower_limit = (max(closes) - min(closes)) / 32
447+
upper_limit = (max(closes) - min(closes)) / 2
448+
lower_limit = 0.01 * _calculate_atr(len(closes)-1, highs, lows, closes)
418449
if brick_size > upper_limit:
419-
raise ValueError("Specified brick_size may not be larger than (20% of the close price range of the dataset) which has value: "+ str(upper_limit))
450+
raise ValueError("Specified brick_size may not be larger than (50% of the close price range of the dataset) which has value: "+ str(upper_limit))
420451
elif brick_size < lower_limit:
421-
raise ValueError("Specified brick_size may not be smaller than (3.125% of the close price range of the dataset) which has value: "+ str(lower_limit))
452+
raise ValueError("Specified brick_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: "+ str(lower_limit))
422453

423454
alpha = marketcolors['alpha']
424455

@@ -427,7 +458,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
427458
euc = mcolors.to_rgba(marketcolors['edge'][ 'up' ], 1.0)
428459
edc = mcolors.to_rgba(marketcolors['edge']['down'], 1.0)
429460

430-
cdiff = []
461+
cdiff = [] # holds the differences between each close and the previously created brick / the brick size
431462
prev_close_brick = closes[0]
432463
volume_cache = 0 # holds the volumes for the dates that were skipped
433464
new_dates = [] # holds the dates corresponding with the index
@@ -447,7 +478,7 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
447478
new_dates.extend([dates[i]] * abs(brick_diff))
448479
prev_close_brick += brick_diff *brick_size
449480

450-
bricks = [] # holds bricks, 1 for down bricks, -1 for up bricks
481+
bricks = [] # holds bricks, -1 for down bricks, 1 for up bricks
451482
curr_price = closes[0]
452483

453484
last_diff_sign = 0 # direction the bricks were last going in -1 -> down, 1 -> up
@@ -511,6 +542,47 @@ def _construct_renko_collections(dates, highs, lows, volumes, config_renko_param
511542
def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnfig_params, closes, marketcolors=None):
512543
"""Represent the price change with Xs and Os
513544
545+
NOTE: this code assumes if any value open, low, high, close is
546+
missing they all are missing
547+
548+
Algorithm Explanation
549+
---------------------
550+
In the first part of the algorithm, we populate the boxes array
551+
along with adjusting the dates and volumes arrays into the new_dates and
552+
new_volumes arrays. A single date includes a range from no boxes to many
553+
boxes, if a date has no boxes it shall not be included in new_dates,
554+
and if it has n boxes then it will be included n times. Volumes use a
555+
volume cache to save volume amounts for dates that do not have any boxes
556+
before adding the cache to the next date that has at least one box.
557+
We populate the boxes array with each close values difference from the
558+
previously created brick divided by the box size.
559+
560+
The second part of the algorithm has a series of step. First we combine the
561+
adjacent like signed values in the boxes array (ex. [-1, -2, 3, -4] -> [-3, 3, -4]).
562+
Next we subtract 1 from the absolute value of each element in boxes except the
563+
first to ensure every time there is a trend change (ex. previous box is
564+
an X, current brick is a O) we draw one less box to account for the price
565+
having to move the previous box's amount before creating a box in the
566+
opposite direction. Next we adjust volume and dates to combine volume into
567+
non 0 box indexes and to only use dates from non 0 box indexes. We then
568+
remove all 0s from the boxes array and once again combine adjacent similarly
569+
signed differences in boxes.
570+
571+
Lastly, we enumerate through the boxes to populate the line_seg and circle_patches
572+
arrays. line_seg holds the / and \ line segments that make up an X and
573+
circle_patches holds matplotlib.patches Ellipse objects for each O. We start
574+
by filling an x and y array each iteration which contain the x and y
575+
coordinates for each box in the column. Then for each coordinate pair in
576+
x, y we add to either the line_seg array or the circle_patches array
577+
depending on the value of sign for the current column (1 indicates
578+
line_seg, -1 indicates circle_patches). The height of the boxes take
579+
into account padding which separates each box by a small margin in
580+
order to increase readability.
581+
582+
Useful sources:
583+
https://stackoverflow.com/questions/8750648/point-and-figure-chart-with-matplotlib
584+
https://www.investopedia.com/articles/technical/03/081303.asp
585+
514586
Parameters
515587
----------
516588
dates : sequence
@@ -546,12 +618,12 @@ def _construct_pointnfig_collections(dates, highs, lows, volumes, config_pointnf
546618
else:
547619
box_size = _calculate_atr(atr_length, highs, lows, closes)
548620
else: # is an integer or float
549-
upper_limit = (max(closes) - min(closes)) / 5
550-
lower_limit = (max(closes) - min(closes)) / 32
621+
upper_limit = (max(closes) - min(closes)) / 2
622+
lower_limit = 0.01 * _calculate_atr(len(closes)-1, highs, lows, closes)
551623
if box_size > upper_limit:
552-
raise ValueError("Specified box_size may not be larger than (20% of the close price range of the dataset) which has value: "+ str(upper_limit))
624+
raise ValueError("Specified box_size may not be larger than (50% of the close price range of the dataset) which has value: "+ str(upper_limit))
553625
elif box_size < lower_limit:
554-
raise ValueError("Specified box_size may not be smaller than (3.125% of the close price range of the dataset) which has value: "+ str(lower_limit))
626+
raise ValueError("Specified box_size may not be smaller than (0.01* the Average True Value of the dataset) which has value: "+ str(lower_limit))
555627

556628
alpha = marketcolors['alpha']
557629

0 commit comments

Comments
 (0)