Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented May 24, 2025

📄 7% (0.07x) speedup for make_decreasing_ohlc in plotly/figure_factory/_ohlc.py

⏱️ Runtime : 1.74 milliseconds 1.63 milliseconds (best of 224 runs)

📝 Explanation and details

Here’s an optimized version of your code. The main bottleneck is utils.flatten(self.decrease_x) and utils.flatten(self.decrease_y), which uses Python list comprehension for flattening and is repeatedly called for every render. This can be replaced with a local, more efficient flatten method using itertools.chain.from_iterable, which is both faster and has lower memory overhead for a sequence of lists. Additionally, the string multiplication for hover text can be replaced with a more efficient allocation.

We also avoid re-initialization of temporary lists inside the class __init__, reduce attribute lookups, and use __slots__ for memory improvement.

Key optimized changes:

  • Use itertools.chain.from_iterable for flattening lists.
  • Avoid unnecessary variable allocations.
  • Use __slots__ in the class to improve attribute access speed.
  • Preallocate "text_decrease" as a list and only tuple at the end (less string overhead).
  • Avoid repeated lookups and minimize append calls inside loops.

Summary of optimizations:

  • Bulk flatten with itertools.chain.from_iterable instead of nested list comprehensions.
  • Only use min with a generator.
  • Use local lookups for hot loops.
  • Use __slots__ to speed up instance attribute management.
  • Reuse intermediate variables.
  • Branch prediction hints (short-circuit None first).

Let me know if you need Cython-level or further memory tuning!

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 28 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests Details
from datetime import datetime, timedelta

# imports
import pytest
from plotly.figure_factory._ohlc import make_decreasing_ohlc

# function to test (as provided)
# [Code omitted for brevity; assume make_decreasing_ohlc and dependencies are defined above]

# -------------------- UNIT TESTS BEGIN HERE --------------------

# 1. BASIC TEST CASES

def test_single_decreasing_ohlc():
    # Single bar, close < open (decreasing)
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_single_increasing_ohlc():
    # Single bar, close > open (should NOT be in decreasing trace)
    open_ = [10]
    high = [12]
    low = [9]
    close = [13]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_mixed_bars_decreasing_and_increasing():
    # Multiple bars, some increasing, some decreasing
    open_ = [10, 10, 10]
    high = [12, 13, 11]
    low = [9, 8, 9]
    close = [8, 11, 10]  # 8<10 (decr), 11>10 (incr), 10==10 (decr)
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_dates_input_decreasing():
    # Test with datetime x-axis
    dt0 = datetime(2020, 1, 1)
    dates = [dt0, dt0 + timedelta(days=1), dt0 + timedelta(days=2)]
    open_ = [10, 10, 10]
    high = [12, 11, 13]
    low = [8, 9, 9]
    close = [8, 11, 9]  # decr, incr, decr
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_kwargs_override_defaults():
    # Test that kwargs override defaults
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    custom_line = {"color": "blue", "width": 3}
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates, line=custom_line, name="Down", showlegend=True); result = codeflash_output

# 2. EDGE TEST CASES

def test_all_increasing_bars():
    # All bars increasing: output should have empty x/y
    open_ = [1, 2, 3]
    high = [2, 3, 4]
    low = [0, 1, 2]
    close = [3, 4, 5]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_decreasing_bars():
    # All bars decreasing: output should have all bars
    open_ = [5, 5, 5]
    high = [6, 7, 8]
    low = [4, 3, 2]
    close = [4, 3, 2]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_equal_open_close():
    # All bars with open == close (should be decreasing)
    open_ = [5, 7, 9]
    high = [6, 8, 10]
    low = [4, 6, 8]
    close = [5, 7, 9]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_empty_input_lists():
    # All lists empty: should return empty x/y/text
    open_ = []
    high = []
    low = []
    close = []
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_none_in_close_list():
    # Some close values are None: should skip those bars
    open_ = [10, 11, 12]
    high = [12, 13, 14]
    low = [9, 10, 11]
    close = [8, None, 10]  # Only bars 0 and 2 are valid
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output


def test_dates_with_irregular_intervals():
    # Dates with irregular intervals should still work
    dt0 = datetime(2021, 1, 1)
    dates = [dt0, dt0 + timedelta(days=2), dt0 + timedelta(days=5)]
    open_ = [10, 9, 12]
    high = [11, 10, 13]
    low = [9, 8, 11]
    close = [8, 7, 12]  # All decreasing
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output


def test_large_number_of_bars_mixed():
    # 1000 bars, half decreasing, half increasing
    n = 1000
    open_ = [i for i in range(n)]
    high = [i + 2 for i in range(n)]
    low = [i - 2 for i in range(n)]
    close = [i - 1 if i % 2 == 0 else i + 1 for i in range(n)]  # even: decr, odd: incr
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_large_number_of_bars_all_decreasing_with_dates():
    # 1000 bars, all decreasing, with datetime x-axis
    n = 1000
    dt0 = datetime(2022, 1, 1)
    dates = [dt0 + timedelta(days=i) for i in range(n)]
    open_ = [1000 - i for i in range(n)]
    high = [1001 - i for i in range(n)]
    low = [999 - i for i in range(n)]
    close = [999 - i for i in range(n)]
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_large_number_of_bars_all_increasing():
    # 1000 bars, all increasing
    n = 1000
    open_ = [i for i in range(n)]
    high = [i + 3 for i in range(n)]
    low = [i - 3 for i in range(n)]
    close = [i + 1 for i in range(n)]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

import datetime

# imports
import pytest
from plotly import exceptions
# function to test (as provided)
from plotly.figure_factory import utils
from plotly.figure_factory._ohlc import make_decreasing_ohlc

_DEFAULT_DECREASING_COLOR = "#FF4136"
from plotly.figure_factory._ohlc import make_decreasing_ohlc

# ---------------------
# Unit tests start here
# ---------------------

# 1. Basic Test Cases

def test_decreasing_basic_no_dates():
    # One decreasing bar, no dates
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output


def test_decreasing_and_increasing_mixed():
    # Two bars: one decreasing, one increasing
    open_ = [10, 5]
    high = [12, 8]
    low = [9, 4]
    close = [8, 7]  # first is decreasing, second is increasing
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_decreasing_equal_open_close():
    # Bar where open == close (should be decreasing)
    open_ = [10]
    high = [12]
    low = [9]
    close = [10]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_decreasing_multiple_bars():
    # Three bars: two decreasing, one increasing
    open_ = [10, 5, 8]
    high = [12, 8, 10]
    low = [9, 4, 7]
    close = [8, 7, 10]  # first and third: decreasing, increasing, increasing
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

# 2. Edge Test Cases

def test_empty_lists():
    # All lists empty
    open_ = []
    high = []
    low = []
    close = []
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_none_in_close():
    # close contains None, should be skipped
    open_ = [10, 5]
    high = [12, 8]
    low = [9, 4]
    close = [None, 4]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_increasing():
    # All bars are increasing, so decreasing trace should be empty
    open_ = [1, 2, 3]
    high = [2, 3, 4]
    low = [0, 1, 2]
    close = [2, 3, 4]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_all_decreasing():
    # All bars are decreasing
    open_ = [5, 6, 7]
    high = [6, 7, 8]
    low = [4, 5, 6]
    close = [4, 5, 6]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_dates_with_irregular_intervals():
    # Dates with irregular intervals
    dt1 = datetime.datetime(2024, 1, 1)
    dt2 = datetime.datetime(2024, 1, 3)
    dt3 = datetime.datetime(2024, 1, 10)
    open_ = [10, 8, 7]
    high = [12, 10, 9]
    low = [9, 7, 6]
    close = [8, 7, 6]  # all decreasing
    dates = [dt1, dt2, dt3]
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_invalid_input_length():
    # Lists of different lengths should raise an error
    open_ = [10, 8]
    high = [12]
    low = [9, 7]
    close = [8, 7]
    dates = None
    with pytest.raises(IndexError):
        make_decreasing_ohlc(open_, high, low, close, dates)


def test_kwargs_override():
    # Test that kwargs override defaults
    open_ = [10]
    high = [12]
    low = [9]
    close = [8]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates, line=dict(color="blue", width=3), name="Down"); result = codeflash_output

# 3. Large Scale Test Cases

def test_large_input_all_decreasing():
    # Large input, all decreasing
    N = 1000
    open_ = list(range(1000, 0, -1))
    high = [o + 2 for o in open_]
    low = [o - 2 for o in open_]
    close = [o - 1 for o in open_]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output

def test_large_input_mixed():
    # Large input, half increasing, half decreasing
    N = 1000
    open_ = [i for i in range(N)]
    high = [o + 2 for o in open_]
    low = [o - 2 for o in open_]
    # First half decreasing, second half increasing
    close = [o - 1 if i < N//2 else o + 1 for i, o in enumerate(open_)]
    dates = None
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output
    # Check a sample bar
    idx = 10
    y_start = 7 * idx

def test_large_input_with_dates():
    # Large input with dates
    N = 500
    base = datetime.datetime(2024, 1, 1)
    dates = [base + datetime.timedelta(days=i) for i in range(N)]
    open_ = [i for i in range(N)]
    high = [o + 2 for o in open_]
    low = [o - 2 for o in open_]
    close = [o - 1 for o in open_]
    codeflash_output = make_decreasing_ohlc(open_, high, low, close, dates); result = codeflash_output
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-make_decreasing_ohlc-mb2brzz7 and push.

Codeflash

Here’s an optimized version of your code. The **main bottleneck** is `utils.flatten(self.decrease_x)` and `utils.flatten(self.decrease_y)`, which uses Python list comprehension for flattening and is repeatedly called for every render. This can be replaced with a local, more efficient flatten method using `itertools.chain.from_iterable`, which is both faster and has lower memory overhead for a sequence of lists. Additionally, the string multiplication for hover text can be replaced with a more efficient allocation.

We also avoid re-initialization of temporary lists inside the class `__init__`, reduce attribute lookups, and use `__slots__` for memory improvement.

**Key optimized changes:**

- Use `itertools.chain.from_iterable` for flattening lists.
- Avoid unnecessary variable allocations.
- Use `__slots__` in the class to improve attribute access speed.
- Preallocate `"text_decrease"` as a list and only tuple at the end (less string overhead).
- Avoid repeated lookups and minimize append calls inside loops.




**Summary of optimizations:**
- Bulk flatten with `itertools.chain.from_iterable` instead of nested list comprehensions.
- Only use `min` with a generator.
- Use local lookups for hot loops.
- Use `__slots__` to speed up instance attribute management.
- Reuse intermediate variables.
- Branch prediction hints (short-circuit None first).

Let me know if you need Cython-level or further memory tuning!
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label May 24, 2025
@codeflash-ai codeflash-ai bot requested a review from misrasaurabh1 May 24, 2025 14:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants