У меня есть цикл, который циклически повторяет дни и дискретные временные интервалы финансового временного ряда, и внутри него находится некоторый код для моей стратегии. Я извлек этот внутренний код в отдельную функцию, чтобы иметь возможность использовать его в определенных ситуациях для тестирования, и только чуть позже обнаружил, что эта операция сильно увеличила общее время вычислений этого цикла. Это имеет смысл, длинная сигнатура функции, вызов (память стека?) Все это должно приводить к некоторым накладным расходам.
# Fast, but cannot use my strategy for specific instances:
for d in data:
# my strategy code
# Flexible but really slow
def my_strategy(**params):
# my strategy code
f = partial(my_strategy, **params)
for d in data:
f(d)
Теперь мой вопрос: есть ли способ сохранить обе настройки, автономную функцию и функцию с циклами и фактическим кодом внутри без явной избыточности? Мне нужно было бы иметь оба.
Я экспериментировал с inspect.getsource(my_function)
и обернул копию внешней функции, чтобы затем использовать eval
, но через 5 минут я почувствовал себя идиотом, это совсем не так.
Какова наилучшая практика для этого? Если я дублирую код, я должен убедиться, что если я изменю одну версию, другая всегда будет синхронизирована. Мне это не нравится. Я хотел бы отразить это эффективно.
Как вы могли подумать, одним из вариантов может быть: оставить код там, где он есть, внутри цикла, и обернуть ваш конкретный случай массивом и передать его в цикл, когда вам это нужно. Ну, я не могу этого сделать, главным образом потому, что мне нужно иметь временной контроль над выполнением внутреннего кода (я переношу проверенную стратегию в режим реального времени). Здесь я просто ищу какой-нибудь быстрый трюк для элегантного отражения кода, если он есть.
--- РЕДАКТИРОВАТЬ ---
Итак, чтобы проверить это дальше, я применил тот ужасный хак, о котором я упоминал, и заставил его работать с exec. Благодаря этому теперь я могу гарантировать, что используемый код точно такой же. Однако наблюдаемая разница во времени одинакова. Цикл, содержащий код, занимает примерно ПОЛОВИНУ времени, затрачиваемого циклом с вызовом функции.
# horrible hack, please forgive me
def extract_strategy_function():
# Start
source_code = inspect.getsource(fastest_compute_trades)
# Cutting the code snippet
source_code = source_code.split('[safe_start - 1:]:', 1)[1].rsplit("if s['pos_open']:", 1)[0]
# Needs re-indentation so the parser doesn't complain
source_code = reindent(source_code, -2)
function_name = 'single_apply_strategy' # internal, no-one will see this anyway
function_string = f"""
def {function_name}(
# INDICATORS (non mutable)
i_time, close_price, r2, ma1, ma2, rsi, upper, lower,
# STATE VARIABLES, TX HISTORY (mutable, 2 dictionaries and 1 list of dicts)
s, gs, transactions,
# CONSTANT PARAMETERS (non mutable)
min_r2, macd_threshold, rsi_bound, waiting_slots, stop_loss,
take_profit, trail_profit, double_down, dd_qty, commission_fee,
# Day ID, useful when back-testing/grid-searching, left at the end so it can default to 0
i_day=0
):
{source_code}
""".strip()
# Evaluate it
exec(function_string) # Now in locals
return locals()[function_name]
def slow(...):
...
apply_strategy = partial(extract_strategy_function(),
**params, commission_fee=commission_fee, rsi_bound=rsi_bound)
for i_day, day in enumerate(data):
for i_time, close_price, r2, ma1, ma2, rsi, upper, lower, in list(enumerate(day))[safe_start - 1:]:
apply_strategy(i_time, close_price, r2, ma1, ma2, rsi, upper, lower,
day_state, global_state, transactions, i_day=i_day)
if day_state['pos_open']:
...
...
def fast(...,
#some extra parameters are now unpacked here,
#since all code is here they are now needed here
):
...
for i_day, day in enumerate(data):
for i_time, close_price, r2, ma1, ma2, rsi, upper, lower, in list(enumerate(day))[safe_start - 1:]:
# actual code contained in single_apply_strategy written here, the whole strategy (one timestep)
if day_state['pos_open']:
...
...
Какие-либо предложения?
p.s. Излишне говорить, что выход двух установок точно такой же.