5
\$\begingroup\$

The function below is part of a trading system. Its purpose is to assign a volume (order size) to a bid (buy) quote and and ask (sell) quote, depending on the value of the position_size argument.

This trading system strictly cannot exceed a position limit of 100 in either direction, so our order volumes need to never exceed 100 when combined with the absolute value of current position.

However there is one caveat. Occasionally, for uncontrollable external reasons, a bid or ask (buy or sell order) will be executed twice before its price can be re-adjusted.

So any bid_volume or ask_volume returned by this function needs to be at most, half the value of 100 - position_size, in order to account for the occasional double execution.

Thats why at any step of the if-else blocks below, doubling the ask_volume or bid_volume plus current_position will not exceed 100.

What is the fastest way to achieve this with Python, other than the below code?

def adjust_volume(position_size):

    # Long.
    if position_size > 0:

        if position_size < 45:
            bid_volume = 45 - position_size
            ask_volume = 50

        elif position_size >= 45 and position_size < 65:
            bid_volume = 65 - position_size
            ask_volume = 50

        elif position_size >= 65 and position_size < 82:
            bid_volume = 82 - position_size
            ask_volume = 50

        elif position_size >= 82 and position_size < 100:
            bid_volume = 91 - position_size
            ask_volume = 85

    # Short.
    elif position_size < 0:

        abs_position_size = abs(position_size)

        if position_size > -45:
            bid_volume = 10
            ask_volume = 45 - abs_position_size

        elif position_size <= -45 and position_size > -65:
            bid_volume = 50
            ask_volume = 65 - abs_position_size

        elif position_size <= -65 and position_size > -82:
            bid_volume = 50
            ask_volume = 82 - abs_position_size

        elif position_size <= -82 and position_size > -100:
            bid_volume = 75
            ask_volume = 85 - abs_position_size

        elif position_size == 0:

            bid_volume = 30
            ask_volume = 30

    return position_size, bid_volume, ask_volume


position_size, bid_volume, ask_volume = adjust_volume(-76)

print("bid_volume:", bid_volume)
print("ask_volume:", ask_volume)
\$\endgroup\$
1
  • \$\begingroup\$ This is fun. I'll whip up an example in a minute. \$\endgroup\$
    – Reinderien
    Mar 16, 2020 at 15:56

1 Answer 1

5
\$\begingroup\$

Bug

adjust_volume(0) presently results in:

UnboundLocalError: local variable 'bid_volume' referenced before assignment

since if position_size == 0: is currently indented inside the elif position_size < 0: statement, so is unreachable. (This could be a result of poor formatting when copying the code into the question post.)

Possible Bug

Some volume calculations result in negative volumes. (adjust_volume(99) gives bid_volume == -8, and adjust_volume(-99) gives ask_volume == -14, which seem like invalid results.)

Possible Bug

If adjust_volume() is given an argument >= 100 or <= -100, it also fails with an UnboundLocalError. Limits should be checked and a ValueError be raised instead.

This trading system strictly cannot exceed a position limit of 100 in either direction, so our order volumes need to never exceed 100 when combined with the absolute value of current position.

100 does not exceed 100, so it is likely the original implementation should have allowed +100 and -100 as well. But these currently result in UnboundLocalError.

Chained comparisons

I have to say, this condition makes my brain hurt:

    elif position_size <= -65 and position_size > -82:

It took me several moments to unwind what the range was, and I had to triple check that I got it right. It would be much clearer to express as:

    elif -82 < position_size and position_size <= -65:

or using the chaining comparison operations:

    elif -82 < position_size <= -65:

Bisect

You have 9 ranges:

  • -100 < position_size <= -82
  • -82 < position_size <= -65
  • -65 < position_size <= -45
  • -45 < position_size < 0
  • 0 <= position_size <= 0
  • 0 < position_size < 45
  • 45 <= position_size < 65
  • 65 <= position_size < 82
  • 82 <= position_size < 100

Instead of using a tree of if-elif-else statements to locate the correct range, you could bisect the list -81, -64, -44, 0, 1, 45, 65, 82 to determine the correct range bucket.

Note: Negative bucket limits have been increased by 1 to account for the difference in whether the lower limit or the upper limit is included as position_size goes from negative values to positive values.

from bisect import bisect

POSITION = (-81, -64, -44, 0, 1, 45, 65, 82,)
BID_ASK = (
    (75, 85), # -100 < pos <= -82
    (50, 82), # -82 < pos <= -65
    (50, 65), # -65 < pos <= -45
    (10, 45), # -45 < pos < 0
    (30, 30), # 0 <= pos <= 0
    (45, 50), # 0 < pos < 45
    (65, 50), # 45 <= pos < 65
    (82, 50), # 65 <= pos < 82
    (91, 85), # 82 <= pos < 100
)

def adjust_volume(position_size):

    if not(-100 < position_size < 100):
        raise ValueError("position size out of range")

    bucket = bisect(POSITION, position_size)

    bid_volume, ask_volume = BID_ASK[bucket]
    if position >= 0:
        bid_volume -= position_size
    else:
        ask_volume -= abs(position_size)

    return position_size, bid_volume, ask_volume
\$\endgroup\$
4
  • 2
    \$\begingroup\$ Yeah, roughly what I was going to propose. position_size <= -100 or position_size >= 100 should be not (-100 < position_size < 100) \$\endgroup\$
    – Reinderien
    Mar 16, 2020 at 19:30
  • \$\begingroup\$ @Reinderien “should be” may be a little strong; “could be” would be accurate. It would be slightly shorter which is a plus. Negative logic statements are often less clear, but here, “if not (valid range): raise Error” does read fairly clearly. Ok, you’ve convinced me; I’ll change it. \$\endgroup\$
    – AJNeufeld
    Mar 16, 2020 at 21:22
  • 1
    \$\begingroup\$ Big fan of the chained comparisons. I find using only one operator, <, and ordering the values lowest to highest increases readability a lot. \$\endgroup\$
    – Peilonrayz
    Mar 16, 2020 at 23:27
  • \$\begingroup\$ Sorry, yes, your guess was correct, the if position_size == 0 indentation is incorrect after copying from the script. It should be outside the elif position_size < 0: statement \$\endgroup\$
    – s_dbq
    Mar 16, 2020 at 23:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.