Files
strategy-lab/research/strats/WVAP_DIVERGENCE/from_signal_func_nb.py
David Brazda 4d3bff4c86 daily update
2024-10-22 16:34:18 +02:00

2584 lines
146 KiB
Python

import vectorbtpro as vbt
from vectorbtpro.portfolio.nb.from_signals import *
signal_func_nb_path = r"/Users/davidbrazda/Documents/Development/python/strategy-lab1/research/strats/WVAP_DIVERGENCE/signal_func_nb.py"
globals().update(vbt.import_module_from_path(signal_func_nb_path).__dict__, reload=False)
# %? blocks[signal_func_nb_block]
# %? blocks[post_signal_func_nb_block]
@register_jitted
def post_signal_func_nb(
c: PostSignalContext,
) -> None:
"""Custom post-signal function."""
return None
# %? blocks[post_segment_func_nb_block]
@register_jitted
def post_segment_func_nb(
c: SignalSegmentContext,
) -> None:
"""Custom post-segment function."""
return None
@register_chunkable(
size=ch.ArraySizer(arg_query="group_lens", axis=0),
arg_take_spec=dict(
target_shape=base_ch.shape_gl_slicer,
group_lens=ch.ArraySlicer(axis=0),
cash_sharing=None,
index=None,
freq=None,
open=base_ch.flex_array_gl_slicer,
high=base_ch.flex_array_gl_slicer,
low=base_ch.flex_array_gl_slicer,
close=base_ch.flex_array_gl_slicer,
init_cash=RepFunc(portfolio_ch.get_init_cash_slicer),
init_position=base_ch.flex_1d_array_gl_slicer,
init_price=base_ch.flex_1d_array_gl_slicer,
cash_deposits=RepFunc(portfolio_ch.get_cash_deposits_slicer),
cash_earnings=base_ch.flex_array_gl_slicer,
cash_dividends=base_ch.flex_array_gl_slicer,
signal_args=ch.ArgsTaker(),
post_signal_args=ch.ArgsTaker(),
post_segment_args=ch.ArgsTaker(),
size=base_ch.flex_array_gl_slicer,
price=base_ch.flex_array_gl_slicer,
size_type=base_ch.flex_array_gl_slicer,
fees=base_ch.flex_array_gl_slicer,
fixed_fees=base_ch.flex_array_gl_slicer,
slippage=base_ch.flex_array_gl_slicer,
min_size=base_ch.flex_array_gl_slicer,
max_size=base_ch.flex_array_gl_slicer,
size_granularity=base_ch.flex_array_gl_slicer,
leverage=base_ch.flex_array_gl_slicer,
leverage_mode=base_ch.flex_array_gl_slicer,
reject_prob=base_ch.flex_array_gl_slicer,
price_area_vio_mode=base_ch.flex_array_gl_slicer,
allow_partial=base_ch.flex_array_gl_slicer,
raise_reject=base_ch.flex_array_gl_slicer,
log=base_ch.flex_array_gl_slicer,
val_price=base_ch.flex_array_gl_slicer,
accumulate=base_ch.flex_array_gl_slicer,
upon_long_conflict=base_ch.flex_array_gl_slicer,
upon_short_conflict=base_ch.flex_array_gl_slicer,
upon_dir_conflict=base_ch.flex_array_gl_slicer,
upon_opposite_entry=base_ch.flex_array_gl_slicer,
order_type=base_ch.flex_array_gl_slicer,
limit_delta=base_ch.flex_array_gl_slicer,
limit_tif=base_ch.flex_array_gl_slicer,
limit_expiry=base_ch.flex_array_gl_slicer,
limit_reverse=base_ch.flex_array_gl_slicer,
limit_order_price=base_ch.flex_array_gl_slicer,
upon_adj_limit_conflict=base_ch.flex_array_gl_slicer,
upon_opp_limit_conflict=base_ch.flex_array_gl_slicer,
use_stops=None,
stop_ladder=None,
sl_stop=base_ch.flex_array_gl_slicer,
tsl_stop=base_ch.flex_array_gl_slicer,
tsl_th=base_ch.flex_array_gl_slicer,
tp_stop=base_ch.flex_array_gl_slicer,
td_stop=base_ch.flex_array_gl_slicer,
dt_stop=base_ch.flex_array_gl_slicer,
stop_entry_price=base_ch.flex_array_gl_slicer,
stop_exit_price=base_ch.flex_array_gl_slicer,
stop_exit_type=base_ch.flex_array_gl_slicer,
stop_order_type=base_ch.flex_array_gl_slicer,
stop_limit_delta=base_ch.flex_array_gl_slicer,
upon_stop_update=base_ch.flex_array_gl_slicer,
upon_adj_stop_conflict=base_ch.flex_array_gl_slicer,
upon_opp_stop_conflict=base_ch.flex_array_gl_slicer,
delta_format=base_ch.flex_array_gl_slicer,
time_delta_format=base_ch.flex_array_gl_slicer,
from_ago=base_ch.flex_array_gl_slicer,
sim_start=base_ch.FlexArraySlicer(),
sim_end=base_ch.FlexArraySlicer(),
call_seq=base_ch.array_gl_slicer,
auto_call_seq=None,
ffill_val_price=None,
update_value=None,
fill_pos_info=None,
max_order_records=None,
max_log_records=None,
in_outputs=ch.ArgsTaker(),
),
**portfolio_ch.merge_sim_outs_config,
setup_id=None, # %? line.replace("None", task_id)
)
@register_jitted(
tags={"can_parallel"},
cache=True,
task_id_or_func=None, # %? line.replace("None", task_id)
)
def from_signal_func_nb(
target_shape: tp.Shape,
group_lens: tp.GroupLens,
cash_sharing: bool,
index: tp.Optional[tp.Array1d] = None,
freq: tp.Optional[int] = None,
open: tp.FlexArray2dLike = np.nan,
high: tp.FlexArray2dLike = np.nan,
low: tp.FlexArray2dLike = np.nan,
close: tp.FlexArray2dLike = np.nan,
init_cash: tp.FlexArray1dLike = 100.0,
init_position: tp.FlexArray1dLike = 0.0,
init_price: tp.FlexArray1dLike = np.nan,
cash_deposits: tp.FlexArray2dLike = 0.0,
cash_earnings: tp.FlexArray2dLike = 0.0,
cash_dividends: tp.FlexArray2dLike = 0.0,
signal_args: tp.ArgsLike = (),
post_signal_args: tp.ArgsLike = (),
post_segment_args: tp.ArgsLike = (),
size: tp.FlexArray2dLike = np.inf,
price: tp.FlexArray2dLike = np.inf,
size_type: tp.FlexArray2dLike = SizeType.Amount,
fees: tp.FlexArray2dLike = 0.0,
fixed_fees: tp.FlexArray2dLike = 0.0,
slippage: tp.FlexArray2dLike = 0.0,
min_size: tp.FlexArray2dLike = np.nan,
max_size: tp.FlexArray2dLike = np.nan,
size_granularity: tp.FlexArray2dLike = np.nan,
leverage: tp.FlexArray2dLike = 1.0,
leverage_mode: tp.FlexArray2dLike = LeverageMode.Lazy,
reject_prob: tp.FlexArray2dLike = 0.0,
price_area_vio_mode: tp.FlexArray2dLike = PriceAreaVioMode.Ignore,
allow_partial: tp.FlexArray2dLike = True,
raise_reject: tp.FlexArray2dLike = False,
log: tp.FlexArray2dLike = False,
val_price: tp.FlexArray2dLike = np.inf,
accumulate: tp.FlexArray2dLike = AccumulationMode.Disabled,
upon_long_conflict: tp.FlexArray2dLike = ConflictMode.Ignore,
upon_short_conflict: tp.FlexArray2dLike = ConflictMode.Ignore,
upon_dir_conflict: tp.FlexArray2dLike = DirectionConflictMode.Ignore,
upon_opposite_entry: tp.FlexArray2dLike = OppositeEntryMode.ReverseReduce,
order_type: tp.FlexArray2dLike = OrderType.Market,
limit_delta: tp.FlexArray2dLike = np.nan,
limit_tif: tp.FlexArray2dLike = -1,
limit_expiry: tp.FlexArray2dLike = -1,
limit_reverse: tp.FlexArray2dLike = False,
limit_order_price: tp.FlexArray2dLike = LimitOrderPrice.Limit,
upon_adj_limit_conflict: tp.FlexArray2dLike = PendingConflictMode.KeepIgnore,
upon_opp_limit_conflict: tp.FlexArray2dLike = PendingConflictMode.CancelExecute,
use_stops: bool = True,
stop_ladder: int = StopLadderMode.Disabled,
sl_stop: tp.FlexArray2dLike = np.nan,
tsl_stop: tp.FlexArray2dLike = np.nan,
tsl_th: tp.FlexArray2dLike = np.nan,
tp_stop: tp.FlexArray2dLike = np.nan,
td_stop: tp.FlexArray2dLike = -1,
dt_stop: tp.FlexArray2dLike = -1,
stop_entry_price: tp.FlexArray2dLike = StopEntryPrice.Close,
stop_exit_price: tp.FlexArray2dLike = StopExitPrice.Stop,
stop_exit_type: tp.FlexArray2dLike = StopExitType.Close,
stop_order_type: tp.FlexArray2dLike = OrderType.Market,
stop_limit_delta: tp.FlexArray2dLike = np.nan,
upon_stop_update: tp.FlexArray2dLike = StopUpdateMode.Keep,
upon_adj_stop_conflict: tp.FlexArray2dLike = PendingConflictMode.KeepExecute,
upon_opp_stop_conflict: tp.FlexArray2dLike = PendingConflictMode.KeepExecute,
delta_format: tp.FlexArray2dLike = DeltaFormat.Percent,
time_delta_format: tp.FlexArray2dLike = TimeDeltaFormat.Index,
from_ago: tp.FlexArray2dLike = 0,
sim_start: tp.Optional[tp.FlexArray1dLike] = None,
sim_end: tp.Optional[tp.FlexArray1dLike] = None,
call_seq: tp.Optional[tp.Array2d] = None,
auto_call_seq: bool = False,
ffill_val_price: bool = True,
update_value: bool = False,
fill_pos_info: bool = True,
max_order_records: tp.Optional[int] = None,
max_log_records: tp.Optional[int] = 0,
in_outputs: tp.Optional[tp.NamedTuple] = None,
) -> SimulationOutput:
"""Simulate given a signal function.
Iterates in the column-major order. Utilizes flexible broadcasting.
`signal_func_nb` is a user-defined signal generation function that is called at each row and column
(= element). It must accept the context of the type `vectorbtpro.portfolio.enums.SignalContext`
and return 4 signals: long entry, long exit, short entry, and short exit.
`post_signal_func_nb` is a user-defined post-signal function that is called after an order has been processed.
It must accept the context of the type `vectorbtpro.portfolio.enums.PostSignalContext` and return nothing.
`post_segment_func_nb` is a user-defined post-segment function that is called after each row and group
(= segment). It must accept the context of the type `vectorbtpro.portfolio.enums.SignalSegmentContext`
and return nothing.
"""
check_group_lens_nb(group_lens, target_shape[1])
open_ = to_2d_array_nb(np.asarray(open))
high_ = to_2d_array_nb(np.asarray(high))
low_ = to_2d_array_nb(np.asarray(low))
close_ = to_2d_array_nb(np.asarray(close))
init_cash_ = to_1d_array_nb(np.asarray(init_cash))
init_position_ = to_1d_array_nb(np.asarray(init_position))
init_price_ = to_1d_array_nb(np.asarray(init_price))
cash_deposits_ = to_2d_array_nb(np.asarray(cash_deposits))
cash_earnings_ = to_2d_array_nb(np.asarray(cash_earnings))
cash_dividends_ = to_2d_array_nb(np.asarray(cash_dividends))
size_ = to_2d_array_nb(np.asarray(size))
price_ = to_2d_array_nb(np.asarray(price))
size_type_ = to_2d_array_nb(np.asarray(size_type))
fees_ = to_2d_array_nb(np.asarray(fees))
fixed_fees_ = to_2d_array_nb(np.asarray(fixed_fees))
slippage_ = to_2d_array_nb(np.asarray(slippage))
min_size_ = to_2d_array_nb(np.asarray(min_size))
max_size_ = to_2d_array_nb(np.asarray(max_size))
size_granularity_ = to_2d_array_nb(np.asarray(size_granularity))
leverage_ = to_2d_array_nb(np.asarray(leverage))
leverage_mode_ = to_2d_array_nb(np.asarray(leverage_mode))
reject_prob_ = to_2d_array_nb(np.asarray(reject_prob))
price_area_vio_mode_ = to_2d_array_nb(np.asarray(price_area_vio_mode))
allow_partial_ = to_2d_array_nb(np.asarray(allow_partial))
raise_reject_ = to_2d_array_nb(np.asarray(raise_reject))
log_ = to_2d_array_nb(np.asarray(log))
val_price_ = to_2d_array_nb(np.asarray(val_price))
accumulate_ = to_2d_array_nb(np.asarray(accumulate))
upon_long_conflict_ = to_2d_array_nb(np.asarray(upon_long_conflict))
upon_short_conflict_ = to_2d_array_nb(np.asarray(upon_short_conflict))
upon_dir_conflict_ = to_2d_array_nb(np.asarray(upon_dir_conflict))
upon_opposite_entry_ = to_2d_array_nb(np.asarray(upon_opposite_entry))
order_type_ = to_2d_array_nb(np.asarray(order_type))
limit_delta_ = to_2d_array_nb(np.asarray(limit_delta))
limit_tif_ = to_2d_array_nb(np.asarray(limit_tif))
limit_expiry_ = to_2d_array_nb(np.asarray(limit_expiry))
limit_reverse_ = to_2d_array_nb(np.asarray(limit_reverse))
limit_order_price_ = to_2d_array_nb(np.asarray(limit_order_price))
upon_adj_limit_conflict_ = to_2d_array_nb(np.asarray(upon_adj_limit_conflict))
upon_opp_limit_conflict_ = to_2d_array_nb(np.asarray(upon_opp_limit_conflict))
sl_stop_ = to_2d_array_nb(np.asarray(sl_stop))
tsl_stop_ = to_2d_array_nb(np.asarray(tsl_stop))
tsl_th_ = to_2d_array_nb(np.asarray(tsl_th))
tp_stop_ = to_2d_array_nb(np.asarray(tp_stop))
td_stop_ = to_2d_array_nb(np.asarray(td_stop))
dt_stop_ = to_2d_array_nb(np.asarray(dt_stop))
stop_entry_price_ = to_2d_array_nb(np.asarray(stop_entry_price))
stop_exit_price_ = to_2d_array_nb(np.asarray(stop_exit_price))
stop_exit_type_ = to_2d_array_nb(np.asarray(stop_exit_type))
stop_order_type_ = to_2d_array_nb(np.asarray(stop_order_type))
stop_limit_delta_ = to_2d_array_nb(np.asarray(stop_limit_delta))
upon_stop_update_ = to_2d_array_nb(np.asarray(upon_stop_update))
upon_adj_stop_conflict_ = to_2d_array_nb(np.asarray(upon_adj_stop_conflict))
upon_opp_stop_conflict_ = to_2d_array_nb(np.asarray(upon_opp_stop_conflict))
delta_format_ = to_2d_array_nb(np.asarray(delta_format))
time_delta_format_ = to_2d_array_nb(np.asarray(time_delta_format))
from_ago_ = to_2d_array_nb(np.asarray(from_ago))
n_sl_steps = sl_stop_.shape[0]
n_tsl_steps = tsl_stop_.shape[0]
n_tp_steps = tp_stop_.shape[0]
n_td_steps = td_stop_.shape[0]
n_dt_steps = dt_stop_.shape[0]
order_records, log_records = prepare_fs_records_nb(
target_shape=target_shape,
max_order_records=max_order_records,
max_log_records=max_log_records,
)
order_counts = np.full(target_shape[1], 0, dtype=np.int_)
log_counts = np.full(target_shape[1], 0, dtype=np.int_)
last_cash = prepare_last_cash_nb(
target_shape=target_shape,
group_lens=group_lens,
cash_sharing=cash_sharing,
init_cash=init_cash_,
)
last_position = prepare_last_position_nb(
target_shape=target_shape,
init_position=init_position_,
)
last_value = prepare_last_value_nb(
target_shape=target_shape,
group_lens=group_lens,
cash_sharing=cash_sharing,
init_cash=init_cash_,
init_position=init_position_,
init_price=init_price_,
)
last_pos_info = prepare_last_pos_info_nb(
target_shape,
init_position=init_position_,
init_price=init_price_,
fill_pos_info=fill_pos_info,
)
last_cash_deposits = np.full_like(last_cash, 0.0)
last_val_price = np.full_like(last_position, np.nan)
last_debt = np.full(target_shape[1], 0.0, dtype=np.float_)
last_locked_cash = np.full(target_shape[1], 0.0, dtype=np.float_)
last_free_cash = last_cash.copy()
prev_close_value = last_value.copy()
last_return = np.full_like(last_cash, np.nan)
track_cash_deposits = cash_deposits_.size > 1
if track_cash_deposits:
cash_deposits_out = np.full((target_shape[0], len(group_lens)), 0.0, dtype=np.float_)
else:
cash_deposits_out = np.full((1, 1), 0.0, dtype=np.float_)
track_cash_earnings = cash_earnings_.size > 1 or cash_dividends_.size > 1
if track_cash_earnings:
cash_earnings_out = np.full(target_shape, 0.0, dtype=np.float_)
else:
cash_earnings_out = np.full((1, 1), 0.0, dtype=np.float_)
last_limit_info = np.empty(target_shape[1], dtype=limit_info_dt)
last_limit_info["signal_idx"][:] = -1
last_limit_info["creation_idx"][:] = -1
last_limit_info["init_idx"][:] = -1
last_limit_info["init_price"][:] = np.nan
last_limit_info["init_size"][:] = np.nan
last_limit_info["init_size_type"][:] = -1
last_limit_info["init_direction"][:] = -1
last_limit_info["init_stop_type"][:] = -1
last_limit_info["delta"][:] = np.nan
last_limit_info["delta_format"][:] = -1
last_limit_info["tif"][:] = -1
last_limit_info["expiry"][:] = -1
last_limit_info["time_delta_format"][:] = -1
last_limit_info["reverse"][:] = False
last_limit_info["order_price"][:] = np.nan
if use_stops:
last_sl_info = np.empty(target_shape[1], dtype=sl_info_dt)
last_sl_info["init_idx"][:] = -1
last_sl_info["init_price"][:] = np.nan
last_sl_info["init_position"][:] = np.nan
last_sl_info["stop"][:] = np.nan
last_sl_info["exit_price"][:] = -1
last_sl_info["exit_size"][:] = np.nan
last_sl_info["exit_size_type"][:] = -1
last_sl_info["exit_type"][:] = -1
last_sl_info["order_type"][:] = -1
last_sl_info["limit_delta"][:] = np.nan
last_sl_info["delta_format"][:] = -1
last_sl_info["ladder"][:] = -1
last_sl_info["step"][:] = -1
last_sl_info["step_idx"][:] = -1
last_tsl_info = np.empty(target_shape[1], dtype=tsl_info_dt)
last_tsl_info["init_idx"][:] = -1
last_tsl_info["init_price"][:] = np.nan
last_tsl_info["init_position"][:] = np.nan
last_tsl_info["peak_idx"][:] = -1
last_tsl_info["peak_price"][:] = np.nan
last_tsl_info["stop"][:] = np.nan
last_tsl_info["th"][:] = np.nan
last_tsl_info["exit_price"][:] = -1
last_tsl_info["exit_size"][:] = np.nan
last_tsl_info["exit_size_type"][:] = -1
last_tsl_info["exit_type"][:] = -1
last_tsl_info["order_type"][:] = -1
last_tsl_info["limit_delta"][:] = np.nan
last_tsl_info["delta_format"][:] = -1
last_tsl_info["ladder"][:] = -1
last_tsl_info["step"][:] = -1
last_tsl_info["step_idx"][:] = -1
last_tp_info = np.empty(target_shape[1], dtype=tp_info_dt)
last_tp_info["init_idx"][:] = -1
last_tp_info["init_price"][:] = np.nan
last_tp_info["init_position"][:] = np.nan
last_tp_info["stop"][:] = np.nan
last_tp_info["exit_price"][:] = -1
last_tp_info["exit_size"][:] = np.nan
last_tp_info["exit_size_type"][:] = -1
last_tp_info["exit_type"][:] = -1
last_tp_info["order_type"][:] = -1
last_tp_info["limit_delta"][:] = np.nan
last_tp_info["delta_format"][:] = -1
last_tp_info["ladder"][:] = -1
last_tp_info["step"][:] = -1
last_tp_info["step_idx"][:] = -1
last_td_info = np.empty(target_shape[1], dtype=time_info_dt)
last_td_info["init_idx"][:] = -1
last_td_info["init_position"][:] = np.nan
last_td_info["stop"][:] = -1
last_td_info["exit_price"][:] = -1
last_td_info["exit_size"][:] = np.nan
last_td_info["exit_size_type"][:] = -1
last_td_info["exit_type"][:] = -1
last_td_info["order_type"][:] = -1
last_td_info["limit_delta"][:] = np.nan
last_td_info["delta_format"][:] = -1
last_td_info["time_delta_format"][:] = -1
last_td_info["ladder"][:] = -1
last_td_info["step"][:] = -1
last_td_info["step_idx"][:] = -1
last_dt_info = np.empty(target_shape[1], dtype=time_info_dt)
last_dt_info["init_idx"][:] = -1
last_dt_info["init_position"][:] = np.nan
last_dt_info["stop"][:] = -1
last_dt_info["exit_price"][:] = -1
last_dt_info["exit_size"][:] = np.nan
last_dt_info["exit_size_type"][:] = -1
last_dt_info["exit_type"][:] = -1
last_dt_info["order_type"][:] = -1
last_dt_info["limit_delta"][:] = np.nan
last_dt_info["delta_format"][:] = -1
last_dt_info["time_delta_format"][:] = -1
last_dt_info["ladder"][:] = -1
last_dt_info["step"][:] = -1
last_dt_info["step_idx"][:] = -1
else:
last_sl_info = np.empty(0, dtype=sl_info_dt)
last_tsl_info = np.empty(0, dtype=tsl_info_dt)
last_tp_info = np.empty(0, dtype=tp_info_dt)
last_td_info = np.empty(0, dtype=time_info_dt)
last_dt_info = np.empty(0, dtype=time_info_dt)
last_signal = np.empty(target_shape[1], dtype=np.int_)
main_info = np.empty(target_shape[1], dtype=main_info_dt)
temp_call_seq = np.empty(target_shape[1], dtype=np.int_)
temp_sort_by = np.empty(target_shape[1], dtype=np.float_)
group_end_idxs = np.cumsum(group_lens)
group_start_idxs = group_end_idxs - group_lens
sim_start_, sim_end_ = generic_nb.prepare_sim_range_nb(
sim_shape=(target_shape[0], len(group_lens)),
sim_start=sim_start,
sim_end=sim_end,
)
for group in prange(len(group_lens)):
from_col = group_start_idxs[group]
to_col = group_end_idxs[group]
group_len = to_col - from_col
_sim_start = sim_start_[group]
_sim_end = sim_end_[group]
for i in range(_sim_start, _sim_end):
# Add cash
if cash_sharing:
_cash_deposits = flex_select_nb(cash_deposits_, i, group)
if _cash_deposits < 0:
_cash_deposits = max(_cash_deposits, -last_cash[group])
last_cash[group] += _cash_deposits
last_free_cash[group] += _cash_deposits
last_cash_deposits[group] = _cash_deposits
if track_cash_deposits:
cash_deposits_out[i, group] += _cash_deposits
else:
for col in range(from_col, to_col):
_cash_deposits = flex_select_nb(cash_deposits_, i, col)
if _cash_deposits < 0:
_cash_deposits = max(_cash_deposits, -last_cash[col])
last_cash[col] += _cash_deposits
last_free_cash[col] += _cash_deposits
last_cash_deposits[col] = _cash_deposits
if track_cash_deposits:
cash_deposits_out[i, col] += _cash_deposits
# Update valuation price using current open
for c in range(group_len):
col = from_col + c
_open = flex_select_nb(open_, i, col)
if not np.isnan(_open) or not ffill_val_price:
last_val_price[col] = _open
# Update value and return
if cash_sharing:
group_value = last_cash[group]
for col in range(from_col, to_col):
if last_position[col] != 0:
group_value += last_position[col] * last_val_price[col]
last_value[group] = group_value
last_return[group] = get_return_nb(
input_value=prev_close_value[group],
output_value=last_value[group] - last_cash_deposits[group],
)
else:
for col in range(from_col, to_col):
group_value = last_cash[col]
if last_position[col] != 0:
group_value += last_position[col] * last_val_price[col]
last_value[col] = group_value
last_return[col] = get_return_nb(
input_value=prev_close_value[col],
output_value=last_value[col] - last_cash_deposits[col],
)
# Update open position stats
if fill_pos_info:
for col in range(from_col, to_col):
update_open_pos_info_stats_nb(last_pos_info[col], last_position[col], last_val_price[col])
# Get signals
skip = True
for c in range(group_len):
col = from_col + c
signal_ctx = SignalContext(
target_shape=target_shape,
group_lens=group_lens,
cash_sharing=cash_sharing,
index=index,
freq=freq,
open=open_,
high=high_,
low=low_,
close=close_,
init_cash=init_cash_,
init_position=init_position_,
init_price=init_price_,
order_records=order_records,
order_counts=order_counts,
log_records=log_records,
log_counts=log_counts,
track_cash_deposits=track_cash_deposits,
cash_deposits_out=cash_deposits_out,
track_cash_earnings=track_cash_earnings,
cash_earnings_out=cash_earnings_out,
in_outputs=in_outputs,
last_cash=last_cash,
last_position=last_position,
last_debt=last_debt,
last_locked_cash=last_locked_cash,
last_free_cash=last_free_cash,
last_val_price=last_val_price,
last_value=last_value,
last_return=last_return,
last_pos_info=last_pos_info,
last_limit_info=last_limit_info,
last_sl_info=last_sl_info,
last_tsl_info=last_tsl_info,
last_tp_info=last_tp_info,
last_td_info=last_td_info,
last_dt_info=last_dt_info,
sim_start=sim_start_,
sim_end=sim_end_,
group=group,
group_len=group_len,
from_col=from_col,
to_col=to_col,
i=i,
col=col,
)
is_long_entry, is_long_exit, is_short_entry, is_short_exit = signal_func_nb(signal_ctx, *signal_args)
# Update limit and stop prices
_i = i - abs(flex_select_nb(from_ago_, i, col))
if _i < 0:
_price = np.nan
else:
_price = flex_select_nb(price_, _i, col)
last_limit_info["init_price"][col] = resolve_dyn_limit_price_nb(
val_price=last_val_price[col],
price=_price,
limit_price=last_limit_info["init_price"][col],
)
last_sl_info["init_price"][col] = resolve_dyn_stop_entry_price_nb(
val_price=last_val_price[col],
price=_price,
stop_entry_price=last_sl_info["init_price"][col],
)
last_tsl_info["init_price"][col] = resolve_dyn_stop_entry_price_nb(
val_price=last_val_price[col],
price=_price,
stop_entry_price=last_tsl_info["init_price"][col],
)
last_tsl_info["peak_price"][col] = resolve_dyn_stop_entry_price_nb(
val_price=last_val_price[col],
price=_price,
stop_entry_price=last_tsl_info["peak_price"][col],
)
last_tp_info["init_price"][col] = resolve_dyn_stop_entry_price_nb(
val_price=last_val_price[col],
price=_price,
stop_entry_price=last_tp_info["init_price"][col],
)
limit_signal = is_limit_active_nb(
init_idx=last_limit_info["init_idx"][col],
init_price=last_limit_info["init_price"][col],
)
if not use_stops:
sl_stop_signal = False
tsl_stop_signal = False
tp_stop_signal = False
td_stop_signal = False
dt_stop_signal = False
else:
sl_stop_signal = is_stop_active_nb(
init_idx=last_sl_info["init_idx"][col],
stop=last_sl_info["stop"][col],
)
tsl_stop_signal = is_stop_active_nb(
init_idx=last_tsl_info["init_idx"][col],
stop=last_tsl_info["stop"][col],
)
tp_stop_signal = is_stop_active_nb(
init_idx=last_tp_info["init_idx"][col],
stop=last_tp_info["stop"][col],
)
td_stop_signal = is_time_stop_active_nb(
init_idx=last_td_info["init_idx"][col],
stop=last_td_info["stop"][col],
)
dt_stop_signal = is_time_stop_active_nb(
init_idx=last_dt_info["init_idx"][col],
stop=last_dt_info["stop"][col],
)
# Pack signals into a single integer
last_signal[col] = (
(is_long_entry << 10)
| (is_long_exit << 9)
| (is_short_entry << 8)
| (is_short_exit << 7)
| (limit_signal << 6)
| (sl_stop_signal << 5)
| (tsl_stop_signal << 4)
| (tp_stop_signal << 3)
| (td_stop_signal << 2)
| (dt_stop_signal << 1)
)
if last_signal[col] > 0:
skip = False
if not skip:
# Update value and return
if cash_sharing:
group_value = last_cash[group]
for col in range(from_col, to_col):
if last_position[col] != 0:
group_value += last_position[col] * last_val_price[col]
last_value[group] = group_value
last_return[group] = get_return_nb(
input_value=prev_close_value[group],
output_value=last_value[group] - last_cash_deposits[group],
)
else:
for col in range(from_col, to_col):
group_value = last_cash[col]
if last_position[col] != 0:
group_value += last_position[col] * last_val_price[col]
last_value[col] = group_value
last_return[col] = get_return_nb(
input_value=prev_close_value[col],
output_value=last_value[col] - last_cash_deposits[col],
)
# Get size and value of each order
for c in range(group_len):
col = from_col + c
# Set defaults
main_info["bar_zone"][col] = -1
main_info["signal_idx"][col] = -1
main_info["creation_idx"][col] = -1
main_info["idx"][col] = i
main_info["val_price"][col] = np.nan
main_info["price"][col] = np.nan
main_info["size"][col] = np.nan
main_info["size_type"][col] = -1
main_info["direction"][col] = -1
main_info["type"][col] = -1
main_info["stop_type"][col] = -1
temp_sort_by[col] = 0.0
# Unpack a single integer into signals
is_long_entry = (last_signal[col] >> 10) & 1
is_long_exit = (last_signal[col] >> 9) & 1
is_short_entry = (last_signal[col] >> 8) & 1
is_short_exit = (last_signal[col] >> 7) & 1
limit_signal = (last_signal[col] >> 6) & 1
sl_stop_signal = (last_signal[col] >> 5) & 1
tsl_stop_signal = (last_signal[col] >> 4) & 1
tp_stop_signal = (last_signal[col] >> 3) & 1
td_stop_signal = (last_signal[col] >> 2) & 1
dt_stop_signal = (last_signal[col] >> 1) & 1
any_user_signal = is_long_entry or is_long_exit or is_short_entry or is_short_exit
any_limit_signal = limit_signal
any_stop_signal = (
sl_stop_signal or tsl_stop_signal or tp_stop_signal or td_stop_signal or dt_stop_signal
)
# Set initial info
exec_limit_set = False
exec_limit_set_on_open = False
exec_limit_set_on_close = False
exec_limit_signal_i = -1
exec_limit_creation_i = -1
exec_limit_init_i = -1
exec_limit_val_price = np.nan
exec_limit_price = np.nan
exec_limit_size = np.nan
exec_limit_size_type = -1
exec_limit_direction = -1
exec_limit_stop_type = -1
exec_limit_bar_zone = -1
exec_stop_set = False
exec_stop_set_on_open = False
exec_stop_set_on_close = False
exec_stop_init_i = -1
exec_stop_val_price = np.nan
exec_stop_price = np.nan
exec_stop_size = np.nan
exec_stop_size_type = -1
exec_stop_direction = -1
exec_stop_type = -1
exec_stop_stop_type = -1
exec_stop_delta = np.nan
exec_stop_delta_format = -1
exec_stop_make_limit = False
exec_stop_bar_zone = -1
user_on_open = False
user_on_close = False
exec_user_set = False
exec_user_val_price = np.nan
exec_user_price = np.nan
exec_user_size = np.nan
exec_user_size_type = -1
exec_user_direction = -1
exec_user_type = -1
exec_user_stop_type = -1
exec_user_make_limit = False
exec_user_bar_zone = -1
# Resolve the current bar
_i = i - abs(flex_select_nb(from_ago_, i, col))
_open = flex_select_nb(open_, i, col)
_high = flex_select_nb(high_, i, col)
_low = flex_select_nb(low_, i, col)
_close = flex_select_nb(close_, i, col)
_high, _low = resolve_hl_nb(
open=_open,
high=_high,
low=_low,
close=_close,
)
# Process the limit signal
if any_limit_signal:
# Check whether the limit price was hit
_signal_i = last_limit_info["signal_idx"][col]
_creation_i = last_limit_info["creation_idx"][col]
_init_i = last_limit_info["init_idx"][col]
_price = last_limit_info["init_price"][col]
_size = last_limit_info["init_size"][col]
_size_type = last_limit_info["init_size_type"][col]
_direction = last_limit_info["init_direction"][col]
_stop_type = last_limit_info["init_stop_type"][col]
_delta = last_limit_info["delta"][col]
_delta_format = last_limit_info["delta_format"][col]
_tif = last_limit_info["tif"][col]
_expiry = last_limit_info["expiry"][col]
_time_delta_format = last_limit_info["time_delta_format"][col]
_reverse = last_limit_info["reverse"][col]
_order_price = last_limit_info["order_price"][col]
limit_expired_on_open, limit_expired = check_limit_expired_nb(
creation_idx=_creation_i,
i=i,
tif=_tif,
expiry=_expiry,
time_delta_format=_time_delta_format,
index=index,
freq=freq,
)
limit_price, limit_hit_on_open, limit_hit = check_limit_hit_nb(
open=_open,
high=_high,
low=_low,
close=_close,
price=_price,
size=_size,
direction=_direction,
limit_delta=_delta,
delta_format=_delta_format,
limit_reverse=_reverse,
can_use_ohlc=True,
check_open=True,
hard_limit=_order_price == LimitOrderPrice.HardLimit,
)
# Resolve the price
limit_price = resolve_limit_order_price_nb(
limit_price=limit_price,
close=_close,
limit_order_price=_order_price,
)
if limit_expired_on_open or (not limit_hit_on_open and limit_expired):
# Expired limit signal
any_limit_signal = False
last_limit_info["signal_idx"][col] = -1
last_limit_info["creation_idx"][col] = -1
last_limit_info["init_idx"][col] = -1
last_limit_info["init_price"][col] = np.nan
last_limit_info["init_size"][col] = np.nan
last_limit_info["init_size_type"][col] = -1
last_limit_info["init_direction"][col] = -1
last_limit_info["delta"][col] = np.nan
last_limit_info["delta_format"][col] = -1
last_limit_info["tif"][col] = -1
last_limit_info["expiry"][col] = -1
last_limit_info["time_delta_format"][col] = -1
last_limit_info["reverse"][col] = False
last_limit_info["order_price"][col] = np.nan
else:
# Save info
if limit_hit:
# Executable limit signal
exec_limit_set = True
exec_limit_set_on_open = limit_hit_on_open
exec_limit_set_on_close = _order_price == LimitOrderPrice.Close
exec_limit_signal_i = _signal_i
exec_limit_creation_i = _creation_i
exec_limit_init_i = _init_i
if np.isinf(limit_price) and limit_price > 0:
exec_limit_val_price = _close
elif np.isinf(limit_price) and limit_price < 0:
exec_limit_val_price = _open
else:
exec_limit_val_price = limit_price
exec_limit_price = limit_price
exec_limit_size = _size
exec_limit_size_type = _size_type
exec_limit_direction = _direction
exec_limit_stop_type = _stop_type
# Process the stop signal
if any_stop_signal:
# Check SL
sl_stop_price, sl_stop_hit_on_open, sl_stop_hit = np.nan, False, False
if sl_stop_signal:
# Check against high and low
sl_stop_price, sl_stop_hit_on_open, sl_stop_hit = check_stop_hit_nb(
open=_open,
high=_high,
low=_low,
close=_close,
is_position_long=last_position[col] > 0,
init_price=last_sl_info["init_price"][col],
stop=last_sl_info["stop"][col],
delta_format=last_sl_info["delta_format"][col],
hit_below=True,
hard_stop=last_sl_info["exit_price"][col] == StopExitPrice.HardStop,
)
# Check TSL and TTP
tsl_stop_price, tsl_stop_hit_on_open, tsl_stop_hit = np.nan, False, False
if tsl_stop_signal:
# Update peak price using open
if last_position[col] > 0:
if _open > last_tsl_info["peak_price"][col]:
if last_tsl_info["delta_format"][col] == DeltaFormat.Target:
last_tsl_info["stop"][col] = (
last_tsl_info["stop"][col] + _open - last_tsl_info["peak_price"][col]
)
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = _open
else:
if _open < last_tsl_info["peak_price"][col]:
if last_tsl_info["delta_format"][col] == DeltaFormat.Target:
last_tsl_info["stop"][col] = (
last_tsl_info["stop"][col] + _open - last_tsl_info["peak_price"][col]
)
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = _open
# Check threshold against previous bars and open
if np.isnan(last_tsl_info["th"][col]):
th_hit = True
else:
th_hit = check_tsl_th_hit_nb(
is_position_long=last_position[col] > 0,
init_price=last_tsl_info["init_price"][col],
peak_price=last_tsl_info["peak_price"][col],
threshold=last_tsl_info["th"][col],
delta_format=last_tsl_info["delta_format"][col],
)
if th_hit:
tsl_stop_price, tsl_stop_hit_on_open, tsl_stop_hit = check_stop_hit_nb(
open=_open,
high=_high,
low=_low,
close=_close,
is_position_long=last_position[col] > 0,
init_price=last_tsl_info["peak_price"][col],
stop=last_tsl_info["stop"][col],
delta_format=last_tsl_info["delta_format"][col],
hit_below=True,
hard_stop=last_tsl_info["exit_price"][col] == StopExitPrice.HardStop,
)
# Update peak price using full bar
if last_position[col] > 0:
if _high > last_tsl_info["peak_price"][col]:
if last_tsl_info["delta_format"][col] == DeltaFormat.Target:
last_tsl_info["stop"][col] = (
last_tsl_info["stop"][col] + _high - last_tsl_info["peak_price"][col]
)
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = _high
else:
if _low < last_tsl_info["peak_price"][col]:
if last_tsl_info["delta_format"][col] == DeltaFormat.Target:
last_tsl_info["stop"][col] = (
last_tsl_info["stop"][col] + _low - last_tsl_info["peak_price"][col]
)
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = _low
if not tsl_stop_hit:
# Check threshold against full bar
if not th_hit:
if np.isnan(last_tsl_info["th"][col]):
th_hit = True
else:
th_hit = check_tsl_th_hit_nb(
is_position_long=last_position[col] > 0,
init_price=last_tsl_info["init_price"][col],
peak_price=last_tsl_info["peak_price"][col],
threshold=last_tsl_info["th"][col],
delta_format=last_tsl_info["delta_format"][col],
)
if th_hit:
# Check threshold against close
tsl_stop_price, tsl_stop_hit_on_open, tsl_stop_hit = check_stop_hit_nb(
open=_open,
high=_high,
low=_low,
close=_close,
is_position_long=last_position[col] > 0,
init_price=last_tsl_info["peak_price"][col],
stop=last_tsl_info["stop"][col],
delta_format=last_tsl_info["delta_format"][col],
hit_below=True,
can_use_ohlc=False,
hard_stop=last_tsl_info["exit_price"][col] == StopExitPrice.HardStop,
)
# Check TP
tp_stop_price, tp_stop_hit_on_open, tp_stop_hit = np.nan, False, False
if tp_stop_signal:
tp_stop_price, tp_stop_hit_on_open, tp_stop_hit = check_stop_hit_nb(
open=_open,
high=_high,
low=_low,
close=_close,
is_position_long=last_position[col] > 0,
init_price=last_tp_info["init_price"][col],
stop=last_tp_info["stop"][col],
delta_format=last_tp_info["delta_format"][col],
hit_below=False,
hard_stop=last_tp_info["exit_price"][col] == StopExitPrice.HardStop,
)
# Check TD
td_stop_price, td_stop_hit_on_open, td_stop_hit = np.nan, False, False
if td_stop_signal:
td_stop_hit_on_open, td_stop_hit = check_td_stop_hit_nb(
init_idx=last_td_info["init_idx"][col],
i=i,
stop=last_td_info["stop"][col],
time_delta_format=last_td_info["time_delta_format"][col],
index=index,
freq=freq,
)
if np.isnan(_open):
td_stop_hit_on_open = False
if td_stop_hit_on_open:
td_stop_price = _open
else:
td_stop_price = _close
# Check DT
dt_stop_price, dt_stop_hit_on_open, dt_stop_hit = np.nan, False, False
if dt_stop_signal:
dt_stop_hit_on_open, dt_stop_hit = check_dt_stop_hit_nb(
i=i,
stop=last_dt_info["stop"][col],
time_delta_format=last_dt_info["time_delta_format"][col],
index=index,
freq=freq,
)
if np.isnan(_open):
dt_stop_hit_on_open = False
if dt_stop_hit_on_open:
dt_stop_price = _open
else:
dt_stop_price = _close
# Resolve the stop signal
sl_hit = False
tsl_hit = False
tp_hit = False
td_hit = False
dt_hit = False
if sl_stop_hit_on_open:
sl_hit = True
elif tsl_stop_hit_on_open:
tsl_hit = True
elif tp_stop_hit_on_open:
tp_hit = True
elif td_stop_hit_on_open:
td_hit = True
elif dt_stop_hit_on_open:
dt_hit = True
elif sl_stop_hit:
sl_hit = True
elif tsl_stop_hit:
tsl_hit = True
elif tp_stop_hit:
tp_hit = True
elif td_stop_hit:
td_hit = True
elif dt_stop_hit:
dt_hit = True
if sl_hit:
stop_price, stop_hit_on_open, stop_hit = sl_stop_price, sl_stop_hit_on_open, sl_stop_hit
_stop_type = StopType.SL
_init_i = last_sl_info["init_idx"][col]
_stop_exit_price = last_sl_info["exit_price"][col]
_stop_exit_size = last_sl_info["exit_size"][col]
_stop_exit_size_type = last_sl_info["exit_size_type"][col]
_stop_exit_type = last_sl_info["exit_type"][col]
_stop_order_type = last_sl_info["order_type"][col]
_limit_delta = last_sl_info["limit_delta"][col]
_delta_format = last_sl_info["delta_format"][col]
_ladder = last_sl_info["ladder"][col]
if np.isnan(_stop_exit_size):
if stop_ladder and _ladder and _ladder != StopLadderMode.Dynamic:
step = last_sl_info["step"][col]
if step < n_sl_steps:
_stop_exit_size = get_stop_ladder_exit_size_nb(
stop_=sl_stop_,
step=step,
col=col,
init_price=last_sl_info["init_price"][col],
init_position=last_sl_info["init_position"][col],
position_now=last_position[col],
ladder=_ladder,
delta_format=last_sl_info["delta_format"][col],
hit_below=True,
)
_stop_exit_size_type = SizeType.Amount
elif tsl_hit:
stop_price, stop_hit_on_open, stop_hit = (
tsl_stop_price,
tsl_stop_hit_on_open,
tsl_stop_hit,
)
if np.isnan(last_tsl_info["th"][col]):
_stop_type = StopType.TSL
else:
_stop_type = StopType.TTP
_init_i = last_tsl_info["init_idx"][col]
_stop_exit_price = last_tsl_info["exit_price"][col]
_stop_exit_size = last_tsl_info["exit_size"][col]
_stop_exit_size_type = last_tsl_info["exit_size_type"][col]
_stop_exit_type = last_tsl_info["exit_type"][col]
_stop_order_type = last_tsl_info["order_type"][col]
_limit_delta = last_tsl_info["limit_delta"][col]
_delta_format = last_tsl_info["delta_format"][col]
_ladder = last_tsl_info["ladder"][col]
if np.isnan(_stop_exit_size):
if stop_ladder and _ladder and _ladder != StopLadderMode.Dynamic:
step = last_tsl_info["step"][col]
if step < n_tsl_steps:
_stop_exit_size = get_stop_ladder_exit_size_nb(
stop_=tsl_stop_,
step=step,
col=col,
init_price=last_tsl_info["init_price"][col],
init_position=last_tsl_info["init_position"][col],
position_now=last_position[col],
ladder=_ladder,
delta_format=last_tsl_info["delta_format"][col],
hit_below=True,
)
_stop_exit_size_type = SizeType.Amount
elif tp_hit:
stop_price, stop_hit_on_open, stop_hit = tp_stop_price, tp_stop_hit_on_open, tp_stop_hit
_stop_type = StopType.TP
_init_i = last_tp_info["init_idx"][col]
_stop_exit_price = last_tp_info["exit_price"][col]
_stop_exit_size = last_tp_info["exit_size"][col]
_stop_exit_size_type = last_tp_info["exit_size_type"][col]
_stop_exit_type = last_tp_info["exit_type"][col]
_stop_order_type = last_tp_info["order_type"][col]
_limit_delta = last_tp_info["limit_delta"][col]
_delta_format = last_tp_info["delta_format"][col]
_ladder = last_tp_info["ladder"][col]
if np.isnan(_stop_exit_size):
if stop_ladder and _ladder and _ladder != StopLadderMode.Dynamic:
step = last_tp_info["step"][col]
if step < n_tp_steps:
_stop_exit_size = get_stop_ladder_exit_size_nb(
stop_=tp_stop_,
step=step,
col=col,
init_price=last_tp_info["init_price"][col],
init_position=last_tp_info["init_position"][col],
position_now=last_position[col],
ladder=_ladder,
delta_format=last_tp_info["delta_format"][col],
hit_below=True,
)
_stop_exit_size_type = SizeType.Amount
elif td_hit:
stop_price, stop_hit_on_open, stop_hit = td_stop_price, td_stop_hit_on_open, td_stop_hit
_stop_type = StopType.TD
_init_i = last_td_info["init_idx"][col]
_stop_exit_price = last_td_info["exit_price"][col]
_stop_exit_size = last_td_info["exit_size"][col]
_stop_exit_size_type = last_td_info["exit_size_type"][col]
_stop_exit_type = last_td_info["exit_type"][col]
_stop_order_type = last_td_info["order_type"][col]
_limit_delta = last_td_info["limit_delta"][col]
_delta_format = last_td_info["delta_format"][col]
_ladder = last_td_info["ladder"][col]
if np.isnan(_stop_exit_size):
if stop_ladder and _ladder and _ladder != StopLadderMode.Dynamic:
step = last_td_info["step"][col]
if step < n_td_steps:
_stop_exit_size = get_time_stop_ladder_exit_size_nb(
stop_=td_stop_,
step=step,
col=col,
init_idx=last_td_info["init_idx"][col],
init_position=last_td_info["init_position"][col],
position_now=last_position[col],
ladder=_ladder,
time_delta_format=last_td_info["time_delta_format"][col],
index=index,
)
_stop_exit_size_type = SizeType.Amount
elif dt_hit:
stop_price, stop_hit_on_open, stop_hit = dt_stop_price, dt_stop_hit_on_open, dt_stop_hit
_stop_type = StopType.DT
_init_i = last_dt_info["init_idx"][col]
_stop_exit_price = last_dt_info["exit_price"][col]
_stop_exit_size = last_dt_info["exit_size"][col]
_stop_exit_size_type = last_dt_info["exit_size_type"][col]
_stop_exit_type = last_dt_info["exit_type"][col]
_stop_order_type = last_dt_info["order_type"][col]
_limit_delta = last_dt_info["limit_delta"][col]
_delta_format = last_dt_info["delta_format"][col]
_ladder = last_dt_info["ladder"][col]
if np.isnan(_stop_exit_size):
if stop_ladder and _ladder and _ladder != StopLadderMode.Dynamic:
step = last_dt_info["step"][col]
if step < n_dt_steps:
_stop_exit_size = get_time_stop_ladder_exit_size_nb(
stop_=dt_stop_,
step=step,
col=col,
init_idx=last_dt_info["init_idx"][col],
init_position=last_dt_info["init_position"][col],
position_now=last_position[col],
ladder=_ladder,
time_delta_format=last_dt_info["time_delta_format"][col],
index=index,
)
_stop_exit_size_type = SizeType.Amount
else:
stop_price, stop_hit_on_open, stop_hit = np.nan, False, False
if stop_hit:
# Stop price was hit
# Resolve the final stop signal
_accumulate = flex_select_nb(accumulate_, i, col)
_size = flex_select_nb(size_, i, col)
_size_type = flex_select_nb(size_type_, i, col)
if not np.isnan(_stop_exit_size):
_accumulate = True
if _stop_exit_type == StopExitType.Close:
_stop_exit_type = StopExitType.CloseReduce
_size = _stop_exit_size
if _stop_exit_size_type != -1:
_size_type = _stop_exit_size_type
(
stop_is_long_entry,
stop_is_long_exit,
stop_is_short_entry,
stop_is_short_exit,
_accumulate,
) = generate_stop_signal_nb(
position_now=last_position[col],
stop_exit_type=_stop_exit_type,
accumulate=_accumulate,
)
# Resolve the price
_price = resolve_stop_exit_price_nb(
stop_price=stop_price,
close=_close,
stop_exit_price=_stop_exit_price,
)
# Convert both signals to size (direction-aware), size type, and direction
_size, _size_type, _direction = signal_to_size_nb(
position_now=last_position[col],
val_price_now=_price,
value_now=last_value[group],
is_long_entry=stop_is_long_entry,
is_long_exit=stop_is_long_exit,
is_short_entry=stop_is_short_entry,
is_short_exit=stop_is_short_exit,
size=_size,
size_type=_size_type,
accumulate=_accumulate,
)
if not np.isnan(_size):
# Executable stop signal
can_execute = True
if _stop_order_type == OrderType.Limit:
# Use close to check whether the limit price was hit
if _stop_exit_price == StopExitPrice.Close:
# Cannot place a limit order at the close price and execute right away
can_execute = False
if can_execute:
limit_price, _, can_execute = check_limit_hit_nb(
open=_open,
high=_high,
low=_low,
close=_close,
price=_price,
size=_size,
direction=_direction,
limit_delta=_limit_delta,
delta_format=_delta_format,
limit_reverse=False,
can_use_ohlc=stop_hit_on_open,
check_open=False,
hard_limit=False,
)
if can_execute:
_price = limit_price
# Save info
exec_stop_set = True
exec_stop_set_on_open = stop_hit_on_open
exec_stop_set_on_close = _stop_exit_price == StopExitPrice.Close
exec_stop_init_i = _init_i
if np.isinf(_price) and _price > 0:
exec_stop_val_price = _close
elif np.isinf(_price) and _price < 0:
exec_stop_val_price = _open
else:
exec_stop_val_price = _price
exec_stop_price = _price
exec_stop_size = _size
exec_stop_size_type = _size_type
exec_stop_direction = _direction
exec_stop_type = _stop_order_type
exec_stop_stop_type = _stop_type
exec_stop_delta = _limit_delta
exec_stop_delta_format = _delta_format
exec_stop_make_limit = not can_execute
# Process user signal
if any_user_signal:
if _i < 0:
_price = np.nan
_size = np.nan
_size_type = -1
_direction = -1
else:
_accumulate = flex_select_nb(accumulate_, _i, col)
if is_long_entry or is_short_entry:
# Resolve any single-direction conflicts
_upon_long_conflict = flex_select_nb(upon_long_conflict_, _i, col)
is_long_entry, is_long_exit = resolve_signal_conflict_nb(
position_now=last_position[col],
is_entry=is_long_entry,
is_exit=is_long_exit,
direction=Direction.LongOnly,
conflict_mode=_upon_long_conflict,
)
_upon_short_conflict = flex_select_nb(upon_short_conflict_, _i, col)
is_short_entry, is_short_exit = resolve_signal_conflict_nb(
position_now=last_position[col],
is_entry=is_short_entry,
is_exit=is_short_exit,
direction=Direction.ShortOnly,
conflict_mode=_upon_short_conflict,
)
# Resolve any multi-direction conflicts
_upon_dir_conflict = flex_select_nb(upon_dir_conflict_, _i, col)
is_long_entry, is_short_entry = resolve_dir_conflict_nb(
position_now=last_position[col],
is_long_entry=is_long_entry,
is_short_entry=is_short_entry,
upon_dir_conflict=_upon_dir_conflict,
)
# Resolve an opposite entry
_upon_opposite_entry = flex_select_nb(upon_opposite_entry_, _i, col)
(
is_long_entry,
is_long_exit,
is_short_entry,
is_short_exit,
_accumulate,
) = resolve_opposite_entry_nb(
position_now=last_position[col],
is_long_entry=is_long_entry,
is_long_exit=is_long_exit,
is_short_entry=is_short_entry,
is_short_exit=is_short_exit,
upon_opposite_entry=_upon_opposite_entry,
accumulate=_accumulate,
)
# Resolve the price
_price = flex_select_nb(price_, _i, col)
# Convert both signals to size (direction-aware), size type, and direction
_val_price = flex_select_nb(val_price_, i, col)
if np.isinf(_val_price) and _val_price > 0:
if np.isinf(_price) and _price > 0:
_val_price = _close
elif np.isinf(_price) and _price < 0:
_val_price = _open
else:
_val_price = _price
elif np.isnan(_val_price) or (np.isinf(_val_price) and _val_price < 0):
_val_price = last_val_price[col]
_size, _size_type, _direction = signal_to_size_nb(
position_now=last_position[col],
val_price_now=_val_price,
value_now=last_value[group],
is_long_entry=is_long_entry,
is_long_exit=is_long_exit,
is_short_entry=is_short_entry,
is_short_exit=is_short_exit,
size=flex_select_nb(size_, _i, col),
size_type=flex_select_nb(size_type_, _i, col),
accumulate=_accumulate,
)
if np.isinf(_price):
if _price > 0:
user_on_close = True
else:
user_on_open = True
if not np.isnan(_size):
# Executable user signal
can_execute = True
_order_type = flex_select_nb(order_type_, _i, col)
if _order_type == OrderType.Limit:
# Use close to check whether the limit price was hit
can_use_ohlc = False
if np.isinf(_price):
if _price > 0:
# Cannot place a limit order at the close price and execute right away
_price = _close
can_execute = False
else:
can_use_ohlc = True
_price = _open
if can_execute:
_limit_delta = flex_select_nb(limit_delta_, _i, col)
_delta_format = flex_select_nb(delta_format_, _i, col)
_limit_reverse = flex_select_nb(limit_reverse_, _i, col)
limit_price, _, can_execute = check_limit_hit_nb(
open=_open,
high=_high,
low=_low,
close=_close,
price=_price,
size=_size,
direction=_direction,
limit_delta=_limit_delta,
delta_format=_delta_format,
limit_reverse=_limit_reverse,
can_use_ohlc=can_use_ohlc,
check_open=False,
hard_limit=False,
)
if can_execute:
_price = limit_price
# Save info
exec_user_set = True
exec_user_val_price = _val_price
exec_user_price = _price
exec_user_size = _size
exec_user_size_type = _size_type
exec_user_direction = _direction
exec_user_type = _order_type
exec_user_stop_type = -1
exec_user_make_limit = not can_execute
if (
exec_limit_set
or exec_stop_set
or exec_user_set
or ((any_limit_signal or any_stop_signal) and any_user_signal)
):
# Choose the main executable signal
# Priority: limit -> stop -> user
# Check whether the main signal comes on open
keep_limit = True
keep_stop = True
execute_limit = False
execute_stop = False
execute_user = False
if exec_limit_set_on_open:
keep_limit = False
keep_stop = False
execute_limit = True
if exec_limit_set_on_close:
exec_limit_bar_zone = BarZone.Close
else:
exec_limit_bar_zone = BarZone.Open
elif exec_stop_set_on_open:
keep_limit = False
keep_stop = _ladder
execute_stop = True
if exec_stop_set_on_close:
exec_stop_bar_zone = BarZone.Close
else:
exec_stop_bar_zone = BarZone.Open
elif any_user_signal and user_on_open:
execute_user = True
if any_limit_signal and (execute_user or not exec_user_set):
stop_size = get_diraware_size_nb(
size=last_limit_info["init_size"][col],
direction=last_limit_info["init_direction"][col],
)
keep_limit, execute_user = resolve_pending_conflict_nb(
is_pending_long=stop_size >= 0,
is_user_long=is_long_entry or is_short_exit,
upon_adj_conflict=flex_select_nb(upon_adj_limit_conflict_, i, col),
upon_opp_conflict=flex_select_nb(upon_opp_limit_conflict_, i, col),
)
if any_stop_signal and (execute_user or not exec_user_set):
keep_stop, execute_user = resolve_pending_conflict_nb(
is_pending_long=last_position[col] < 0,
is_user_long=is_long_entry or is_short_exit,
upon_adj_conflict=flex_select_nb(upon_adj_stop_conflict_, i, col),
upon_opp_conflict=flex_select_nb(upon_opp_stop_conflict_, i, col),
)
if not exec_user_set:
execute_user = False
if execute_user:
exec_user_bar_zone = BarZone.Open
if not execute_limit and not execute_stop and not execute_user:
# Check whether the main signal comes in the middle of the bar
if exec_limit_set and not exec_limit_set_on_open and keep_limit:
keep_limit = False
keep_stop = False
execute_limit = True
exec_limit_bar_zone = BarZone.Middle
elif (
exec_stop_set and not exec_stop_set_on_open and not exec_stop_set_on_close and keep_stop
):
keep_limit = False
keep_stop = _ladder
execute_stop = True
exec_stop_bar_zone = BarZone.Middle
elif any_user_signal and not user_on_open and not user_on_close:
execute_user = True
if any_limit_signal and keep_limit and (execute_user or not exec_user_set):
stop_size = get_diraware_size_nb(
size=last_limit_info["init_size"][col],
direction=last_limit_info["init_direction"][col],
)
keep_limit, execute_user = resolve_pending_conflict_nb(
is_pending_long=stop_size >= 0,
is_user_long=is_long_entry or is_short_exit,
upon_adj_conflict=flex_select_nb(upon_adj_limit_conflict_, i, col),
upon_opp_conflict=flex_select_nb(upon_opp_limit_conflict_, i, col),
)
if any_stop_signal and keep_stop and (execute_user or not exec_user_set):
keep_stop, execute_user = resolve_pending_conflict_nb(
is_pending_long=last_position[col] < 0,
is_user_long=is_long_entry or is_short_exit,
upon_adj_conflict=flex_select_nb(upon_adj_stop_conflict_, i, col),
upon_opp_conflict=flex_select_nb(upon_opp_stop_conflict_, i, col),
)
if not exec_user_set:
execute_user = False
if execute_user:
exec_user_bar_zone = BarZone.Middle
if not execute_limit and not execute_stop and not execute_user:
# Check whether the main signal comes on close
if exec_stop_set_on_close and keep_stop:
keep_limit = False
keep_stop = _ladder
execute_stop = True
exec_stop_bar_zone = BarZone.Close
elif any_user_signal and user_on_close:
execute_user = True
if any_limit_signal and keep_limit and (execute_user or not exec_user_set):
stop_size = get_diraware_size_nb(
size=last_limit_info["init_size"][col],
direction=last_limit_info["init_direction"][col],
)
keep_limit, execute_user = resolve_pending_conflict_nb(
is_pending_long=stop_size >= 0,
is_user_long=is_long_entry or is_short_exit,
upon_adj_conflict=flex_select_nb(upon_adj_limit_conflict_, i, col),
upon_opp_conflict=flex_select_nb(upon_opp_limit_conflict_, i, col),
)
if any_stop_signal and keep_stop and (execute_user or not exec_user_set):
keep_stop, execute_user = resolve_pending_conflict_nb(
is_pending_long=last_position[col] < 0,
is_user_long=is_long_entry or is_short_exit,
upon_adj_conflict=flex_select_nb(upon_adj_stop_conflict_, i, col),
upon_opp_conflict=flex_select_nb(upon_opp_stop_conflict_, i, col),
)
if not exec_user_set:
execute_user = False
if execute_user:
exec_user_bar_zone = BarZone.Close
# Process the limit signal
if execute_limit:
# Execute the signal
main_info["bar_zone"][col] = exec_limit_bar_zone
main_info["signal_idx"][col] = exec_limit_signal_i
main_info["creation_idx"][col] = exec_limit_creation_i
main_info["idx"][col] = exec_limit_init_i
main_info["val_price"][col] = exec_limit_val_price
main_info["price"][col] = exec_limit_price
main_info["size"][col] = exec_limit_size
main_info["size_type"][col] = exec_limit_size_type
main_info["direction"][col] = exec_limit_direction
main_info["type"][col] = OrderType.Limit
main_info["stop_type"][col] = exec_limit_stop_type
if execute_limit or (any_limit_signal and not keep_limit):
# Clear the pending info
any_limit_signal = False
last_limit_info["signal_idx"][col] = -1
last_limit_info["creation_idx"][col] = -1
last_limit_info["init_idx"][col] = -1
last_limit_info["init_price"][col] = np.nan
last_limit_info["init_size"][col] = np.nan
last_limit_info["init_size_type"][col] = -1
last_limit_info["init_direction"][col] = -1
last_limit_info["init_stop_type"][col] = -1
last_limit_info["delta"][col] = np.nan
last_limit_info["delta_format"][col] = -1
last_limit_info["tif"][col] = -1
last_limit_info["expiry"][col] = -1
last_limit_info["time_delta_format"][col] = -1
last_limit_info["reverse"][col] = False
last_limit_info["order_price"][col] = np.nan
# Process the stop signal
if execute_stop:
# Execute the signal
if exec_stop_make_limit:
if any_limit_signal:
raise ValueError("Only one active limit signal is allowed at a time")
_limit_tif = flex_select_nb(limit_tif_, i, col)
_limit_expiry = flex_select_nb(limit_expiry_, i, col)
_time_delta_format = flex_select_nb(time_delta_format_, i, col)
_limit_order_price = flex_select_nb(limit_order_price_, i, col)
last_limit_info["signal_idx"][col] = exec_stop_init_i
last_limit_info["creation_idx"][col] = i
last_limit_info["init_idx"][col] = i
last_limit_info["init_price"][col] = exec_stop_price
last_limit_info["init_size"][col] = exec_stop_size
last_limit_info["init_size_type"][col] = exec_stop_size_type
last_limit_info["init_direction"][col] = exec_stop_direction
last_limit_info["init_stop_type"][col] = exec_stop_stop_type
last_limit_info["delta"][col] = exec_stop_delta
last_limit_info["delta_format"][col] = exec_stop_delta_format
last_limit_info["tif"][col] = _limit_tif
last_limit_info["expiry"][col] = _limit_expiry
last_limit_info["time_delta_format"][col] = _time_delta_format
last_limit_info["reverse"][col] = False
last_limit_info["order_price"][col] = _limit_order_price
else:
main_info["bar_zone"][col] = exec_stop_bar_zone
main_info["signal_idx"][col] = exec_stop_init_i
main_info["creation_idx"][col] = i
main_info["idx"][col] = i
main_info["val_price"][col] = exec_stop_val_price
main_info["price"][col] = exec_stop_price
main_info["size"][col] = exec_stop_size
main_info["size_type"][col] = exec_stop_size_type
main_info["direction"][col] = exec_stop_direction
main_info["type"][col] = exec_stop_type
main_info["stop_type"][col] = exec_stop_stop_type
if any_stop_signal and not keep_stop:
# Clear the pending info
any_stop_signal = False
last_sl_info["init_idx"][col] = -1
last_sl_info["init_price"][col] = np.nan
last_sl_info["init_position"][col] = np.nan
last_sl_info["stop"][col] = np.nan
last_sl_info["exit_price"][col] = -1
last_sl_info["exit_size"][col] = np.nan
last_sl_info["exit_size_type"][col] = -1
last_sl_info["exit_type"][col] = -1
last_sl_info["order_type"][col] = -1
last_sl_info["limit_delta"][col] = np.nan
last_sl_info["delta_format"][col] = -1
last_sl_info["ladder"][col] = -1
last_sl_info["step"][col] = -1
last_sl_info["step_idx"][col] = -1
last_tsl_info["init_idx"][col] = -1
last_tsl_info["init_price"][col] = np.nan
last_tsl_info["init_position"][col] = np.nan
last_tsl_info["peak_idx"][col] = -1
last_tsl_info["peak_price"][col] = np.nan
last_tsl_info["stop"][col] = np.nan
last_tsl_info["th"][col] = np.nan
last_tsl_info["exit_price"][col] = -1
last_tsl_info["exit_size"][col] = np.nan
last_tsl_info["exit_size_type"][col] = -1
last_tsl_info["exit_type"][col] = -1
last_tsl_info["order_type"][col] = -1
last_tsl_info["limit_delta"][col] = np.nan
last_tsl_info["delta_format"][col] = -1
last_tsl_info["ladder"][col] = -1
last_tsl_info["step"][col] = -1
last_tsl_info["step_idx"][col] = -1
last_tp_info["init_idx"][col] = -1
last_tp_info["init_price"][col] = np.nan
last_tp_info["init_position"][col] = np.nan
last_tp_info["stop"][col] = np.nan
last_tp_info["exit_price"][col] = -1
last_tp_info["exit_size"][col] = np.nan
last_tp_info["exit_size_type"][col] = -1
last_tp_info["exit_type"][col] = -1
last_tp_info["order_type"][col] = -1
last_tp_info["limit_delta"][col] = np.nan
last_tp_info["delta_format"][col] = -1
last_tp_info["ladder"][col] = -1
last_tp_info["step"][col] = -1
last_tp_info["step_idx"][col] = -1
last_td_info["init_idx"][col] = -1
last_td_info["init_position"][col] = np.nan
last_td_info["stop"][col] = -1
last_td_info["exit_price"][col] = -1
last_td_info["exit_size"][col] = np.nan
last_td_info["exit_size_type"][col] = -1
last_td_info["exit_type"][col] = -1
last_td_info["order_type"][col] = -1
last_td_info["limit_delta"][col] = np.nan
last_td_info["delta_format"][col] = -1
last_td_info["time_delta_format"][col] = -1
last_td_info["ladder"][col] = -1
last_td_info["step"][col] = -1
last_td_info["step_idx"][col] = -1
last_dt_info["init_idx"][col] = -1
last_dt_info["init_position"][col] = np.nan
last_dt_info["stop"][col] = -1
last_dt_info["exit_price"][col] = -1
last_dt_info["exit_size"][col] = np.nan
last_dt_info["exit_size_type"][col] = -1
last_dt_info["exit_type"][col] = -1
last_dt_info["order_type"][col] = -1
last_dt_info["limit_delta"][col] = np.nan
last_dt_info["delta_format"][col] = -1
last_dt_info["time_delta_format"][col] = -1
last_dt_info["ladder"][col] = -1
last_dt_info["step"][col] = -1
last_dt_info["step_idx"][col] = -1
# Process the user signal
if execute_user:
# Execute the signal
if _i >= 0:
if exec_user_make_limit:
if any_limit_signal:
raise ValueError("Only one active limit signal is allowed at a time")
_limit_delta = flex_select_nb(limit_delta_, _i, col)
_delta_format = flex_select_nb(delta_format_, _i, col)
_limit_tif = flex_select_nb(limit_tif_, _i, col)
_limit_expiry = flex_select_nb(limit_expiry_, _i, col)
_time_delta_format = flex_select_nb(time_delta_format_, _i, col)
_limit_reverse = flex_select_nb(limit_reverse_, _i, col)
_limit_order_price = flex_select_nb(limit_order_price_, _i, col)
last_limit_info["signal_idx"][col] = _i
last_limit_info["creation_idx"][col] = i
last_limit_info["init_idx"][col] = _i
last_limit_info["init_price"][col] = exec_user_price
last_limit_info["init_size"][col] = exec_user_size
last_limit_info["init_size_type"][col] = exec_user_size_type
last_limit_info["init_direction"][col] = exec_user_direction
last_limit_info["init_stop_type"][col] = -1
last_limit_info["delta"][col] = _limit_delta
last_limit_info["delta_format"][col] = _delta_format
last_limit_info["tif"][col] = _limit_tif
last_limit_info["expiry"][col] = _limit_expiry
last_limit_info["time_delta_format"][col] = _time_delta_format
last_limit_info["reverse"][col] = _limit_reverse
last_limit_info["order_price"][col] = _limit_order_price
else:
main_info["bar_zone"][col] = exec_user_bar_zone
main_info["signal_idx"][col] = _i
main_info["creation_idx"][col] = i
main_info["idx"][col] = _i
main_info["val_price"][col] = exec_user_val_price
main_info["price"][col] = exec_user_price
main_info["size"][col] = exec_user_size
main_info["size_type"][col] = exec_user_size_type
main_info["direction"][col] = exec_user_direction
main_info["type"][col] = exec_user_type
main_info["stop_type"][col] = exec_user_stop_type
skip = True
for col in range(from_col, to_col):
if flex_select_nb(log_, i, col):
skip = False
break
if not np.isnan(main_info["size"][col]):
skip = False
break
if not skip:
# Check bar zone and update valuation price
bar_zone = -1
same_bar_zone = True
same_timing = True
for c in range(group_len):
col = from_col + c
if np.isnan(main_info["size"][col]):
continue
if bar_zone == -1:
bar_zone = main_info["bar_zone"][col]
if main_info["bar_zone"][col] != bar_zone:
same_bar_zone = False
same_timing = False
if main_info["bar_zone"][col] == BarZone.Middle:
same_timing = False
_val_price = main_info["val_price"][col]
if not np.isnan(_val_price) or not ffill_val_price:
last_val_price[col] = _val_price
if cash_sharing:
# Dynamically sort by order value -> selling comes first to release funds early
if call_seq is None:
for c in range(group_len):
temp_call_seq[c] = c
call_seq_now = temp_call_seq[:group_len]
else:
call_seq_now = call_seq[i, from_col:to_col]
if auto_call_seq:
# Sort by order value
if not same_timing:
raise ValueError("Cannot sort orders by value if they are executed at different times")
for c in range(group_len):
if call_seq_now[c] != c:
raise ValueError("Call sequence must follow CallSeqType.Default")
col = from_col + c
if np.isnan(main_info["size"][col]):
continue
# Approximate order value
exec_state = ExecState(
cash=last_cash[group] if cash_sharing else last_cash[col],
position=last_position[col],
debt=last_debt[col],
locked_cash=last_locked_cash[col],
free_cash=last_free_cash[group] if cash_sharing else last_free_cash[col],
val_price=last_val_price[col],
value=last_value[group] if cash_sharing else last_value[col],
)
temp_sort_by[c] = approx_order_value_nb(
exec_state=exec_state,
size=main_info["size"][col],
size_type=main_info["size_type"][col],
direction=main_info["direction"][col],
)
insert_argsort_nb(temp_sort_by[:group_len], call_seq_now)
else:
if not same_bar_zone:
# Sort by bar zone
for c in range(group_len):
if call_seq_now[c] != c:
raise ValueError("Call sequence must follow CallSeqType.Default")
col = from_col + c
if np.isnan(main_info["size"][col]):
continue
temp_sort_by[c] = main_info["bar_zone"][col]
insert_argsort_nb(temp_sort_by[:group_len], call_seq_now)
for k in range(group_len):
if cash_sharing:
c = call_seq_now[k]
if c >= group_len:
raise ValueError("Call index out of bounds of the group")
else:
c = k
col = from_col + c
if np.isnan(main_info["size"][col]): # shortcut
continue
# Get current values per column
position_before = position_now = last_position[col]
debt_before = debt_now = last_debt[col]
locked_cash_before = locked_cash_now = last_locked_cash[col]
val_price_before = val_price_now = last_val_price[col]
cash_before = cash_now = last_cash[group] if cash_sharing else last_cash[col]
free_cash_before = free_cash_now = (
last_free_cash[group] if cash_sharing else last_free_cash[col]
)
value_before = value_now = last_value[group] if cash_sharing else last_value[col]
return_before = return_now = last_return[group] if cash_sharing else last_return[col]
# Generate the next order
_i = main_info["idx"][col]
if main_info["type"][col] == OrderType.Limit:
_slippage = 0.0
else:
_slippage = float(flex_select_nb(slippage_, _i, col))
_min_size = flex_select_nb(min_size_, _i, col)
_max_size = flex_select_nb(max_size_, _i, col)
_size_type = flex_select_nb(size_type_, _i, col)
if _size_type != main_info["size_type"][col]:
if not np.isnan(_min_size):
_min_size, _ = resolve_size_nb(
size=_min_size,
size_type=_size_type,
position=position_now,
val_price=val_price_now,
value=value_now,
target_size_type=main_info["size_type"][col],
as_requirement=True,
)
if not np.isnan(_max_size):
_max_size, _ = resolve_size_nb(
size=_max_size,
size_type=_size_type,
position=position_now,
val_price=val_price_now,
value=value_now,
target_size_type=main_info["size_type"][col],
as_requirement=True,
)
order = order_nb(
size=main_info["size"][col],
price=main_info["price"][col],
size_type=main_info["size_type"][col],
direction=main_info["direction"][col],
fees=flex_select_nb(fees_, _i, col),
fixed_fees=flex_select_nb(fixed_fees_, _i, col),
slippage=_slippage,
min_size=_min_size,
max_size=_max_size,
size_granularity=flex_select_nb(size_granularity_, _i, col),
leverage=flex_select_nb(leverage_, _i, col),
leverage_mode=flex_select_nb(leverage_mode_, _i, col),
reject_prob=flex_select_nb(reject_prob_, _i, col),
price_area_vio_mode=flex_select_nb(price_area_vio_mode_, _i, col),
allow_partial=flex_select_nb(allow_partial_, _i, col),
raise_reject=flex_select_nb(raise_reject_, _i, col),
log=flex_select_nb(log_, _i, col),
)
# Process the order
price_area = PriceArea(
open=flex_select_nb(open_, i, col),
high=flex_select_nb(high_, i, col),
low=flex_select_nb(low_, i, col),
close=flex_select_nb(close_, i, col),
)
exec_state = ExecState(
cash=cash_now,
position=position_now,
debt=debt_now,
locked_cash=locked_cash_now,
free_cash=free_cash_now,
val_price=val_price_now,
value=value_now,
)
order_result, new_exec_state = process_order_nb(
group=group,
col=col,
i=i,
exec_state=exec_state,
order=order,
price_area=price_area,
update_value=update_value,
order_records=order_records,
order_counts=order_counts,
log_records=log_records,
log_counts=log_counts,
)
# Append more order information
if order_result.status == OrderStatus.Filled and order_counts[col] >= 1:
order_records["signal_idx"][order_counts[col] - 1, col] = main_info["signal_idx"][col]
order_records["creation_idx"][order_counts[col] - 1, col] = main_info["creation_idx"][col]
order_records["type"][order_counts[col] - 1, col] = main_info["type"][col]
order_records["stop_type"][order_counts[col] - 1, col] = main_info["stop_type"][col]
# Update execution state
cash_now = new_exec_state.cash
position_now = new_exec_state.position
debt_now = new_exec_state.debt
locked_cash_now = new_exec_state.locked_cash
free_cash_now = new_exec_state.free_cash
val_price_now = new_exec_state.val_price
value_now = new_exec_state.value
# Update position record
if fill_pos_info:
if order_result.status == OrderStatus.Filled:
if order_counts[col] > 0:
order_id = order_records["id"][order_counts[col] - 1, col]
else:
order_id = -1
update_pos_info_nb(
last_pos_info[col],
i,
col,
exec_state.position,
position_now,
order_result,
order_id,
)
if use_stops:
# Update stop price
if position_now == 0:
# Not in position anymore -> clear stops (irrespective of order success)
last_sl_info["init_idx"][col] = -1
last_sl_info["init_price"][col] = np.nan
last_sl_info["init_position"][col] = np.nan
last_sl_info["stop"][col] = np.nan
last_sl_info["exit_price"][col] = -1
last_sl_info["exit_size"][col] = np.nan
last_sl_info["exit_size_type"][col] = -1
last_sl_info["exit_type"][col] = -1
last_sl_info["order_type"][col] = -1
last_sl_info["limit_delta"][col] = np.nan
last_sl_info["delta_format"][col] = -1
last_sl_info["ladder"][col] = -1
last_sl_info["step"][col] = -1
last_sl_info["step_idx"][col] = -1
last_tsl_info["init_idx"][col] = -1
last_tsl_info["init_price"][col] = np.nan
last_tsl_info["init_position"][col] = np.nan
last_tsl_info["peak_idx"][col] = -1
last_tsl_info["peak_price"][col] = np.nan
last_tsl_info["stop"][col] = np.nan
last_tsl_info["th"][col] = np.nan
last_tsl_info["exit_price"][col] = -1
last_tsl_info["exit_size"][col] = np.nan
last_tsl_info["exit_size_type"][col] = -1
last_tsl_info["exit_type"][col] = -1
last_tsl_info["order_type"][col] = -1
last_tsl_info["limit_delta"][col] = np.nan
last_tsl_info["delta_format"][col] = -1
last_tsl_info["ladder"][col] = -1
last_tsl_info["step"][col] = -1
last_tsl_info["step_idx"][col] = -1
last_tp_info["init_idx"][col] = -1
last_tp_info["init_price"][col] = np.nan
last_tp_info["init_position"][col] = np.nan
last_tp_info["stop"][col] = np.nan
last_tp_info["exit_price"][col] = -1
last_tp_info["exit_size"][col] = np.nan
last_tp_info["exit_size_type"][col] = -1
last_tp_info["exit_type"][col] = -1
last_tp_info["order_type"][col] = -1
last_tp_info["limit_delta"][col] = np.nan
last_tp_info["delta_format"][col] = -1
last_tp_info["ladder"][col] = -1
last_tp_info["step"][col] = -1
last_tp_info["step_idx"][col] = -1
last_td_info["init_idx"][col] = -1
last_td_info["init_position"][col] = np.nan
last_td_info["stop"][col] = -1
last_td_info["exit_price"][col] = -1
last_td_info["exit_size"][col] = np.nan
last_td_info["exit_size_type"][col] = -1
last_td_info["exit_type"][col] = -1
last_td_info["order_type"][col] = -1
last_td_info["limit_delta"][col] = np.nan
last_td_info["delta_format"][col] = -1
last_td_info["time_delta_format"][col] = -1
last_td_info["ladder"][col] = -1
last_td_info["step"][col] = -1
last_td_info["step_idx"][col] = -1
last_dt_info["init_idx"][col] = -1
last_dt_info["init_position"][col] = np.nan
last_dt_info["stop"][col] = -1
last_dt_info["exit_price"][col] = -1
last_dt_info["exit_size"][col] = np.nan
last_dt_info["exit_size_type"][col] = -1
last_dt_info["exit_type"][col] = -1
last_dt_info["order_type"][col] = -1
last_dt_info["limit_delta"][col] = np.nan
last_dt_info["delta_format"][col] = -1
last_dt_info["time_delta_format"][col] = -1
last_dt_info["ladder"][col] = -1
last_dt_info["step"][col] = -1
last_dt_info["step_idx"][col] = -1
else:
if main_info["stop_type"][col] == StopType.SL:
if last_sl_info["ladder"][col]:
step = last_sl_info["step"][col] + 1
last_sl_info["exit_size"][col] = np.nan
last_sl_info["exit_size_type"][col] = -1
if stop_ladder and last_sl_info["ladder"][col] != StopLadderMode.Dynamic:
if step < n_sl_steps:
last_sl_info["stop"][col] = flex_select_nb(sl_stop_, step, col)
last_sl_info["step"][col] = step
last_sl_info["step_idx"][col] = i
else:
last_sl_info["stop"][col] = np.nan
last_sl_info["step"][col] = -1
last_sl_info["step_idx"][col] = -1
else:
last_sl_info["stop"][col] = np.nan
last_sl_info["step"][col] = step
last_sl_info["step_idx"][col] = i
elif (
main_info["stop_type"][col] == StopType.TSL
or main_info["stop_type"][col] == StopType.TTP
):
if last_tsl_info["ladder"][col]:
step = last_tsl_info["step"][col] + 1
last_tsl_info["step"][col] = step
last_tsl_info["step_idx"][col] = i
last_tsl_info["exit_size"][col] = np.nan
last_tsl_info["exit_size_type"][col] = -1
if stop_ladder and last_tsl_info["ladder"][col] != StopLadderMode.Dynamic:
if step < n_tsl_steps:
last_tsl_info["stop"][col] = flex_select_nb(tsl_stop_, step, col)
last_tsl_info["step"][col] = step
last_tsl_info["step_idx"][col] = i
else:
last_tsl_info["stop"][col] = np.nan
last_tsl_info["step"][col] = -1
last_tsl_info["step_idx"][col] = -1
else:
last_tsl_info["stop"][col] = np.nan
last_tsl_info["step"][col] = step
last_tsl_info["step_idx"][col] = i
elif main_info["stop_type"][col] == StopType.TP:
if last_tp_info["ladder"][col]:
step = last_tp_info["step"][col] + 1
last_tp_info["step"][col] = step
last_tp_info["step_idx"][col] = i
last_tp_info["exit_size"][col] = np.nan
last_tp_info["exit_size_type"][col] = -1
if stop_ladder and last_tp_info["ladder"][col] != StopLadderMode.Dynamic:
if step < n_tp_steps:
last_tp_info["stop"][col] = flex_select_nb(tp_stop_, step, col)
last_tp_info["step"][col] = step
last_tp_info["step_idx"][col] = i
else:
last_tp_info["stop"][col] = np.nan
last_tp_info["step"][col] = -1
last_tp_info["step_idx"][col] = -1
else:
last_tp_info["stop"][col] = np.nan
last_tp_info["step"][col] = step
last_tp_info["step_idx"][col] = i
elif main_info["stop_type"][col] == StopType.TD:
if last_td_info["ladder"][col]:
step = last_td_info["step"][col] + 1
last_td_info["step"][col] = step
last_td_info["step_idx"][col] = i
last_td_info["exit_size"][col] = np.nan
last_td_info["exit_size_type"][col] = -1
if stop_ladder and last_td_info["ladder"][col] != StopLadderMode.Dynamic:
if step < n_td_steps:
last_td_info["stop"][col] = flex_select_nb(td_stop_, step, col)
last_td_info["step"][col] = step
last_td_info["step_idx"][col] = i
else:
last_td_info["stop"][col] = -1
last_td_info["step"][col] = -1
last_td_info["step_idx"][col] = -1
else:
last_td_info["stop"][col] = -1
last_td_info["step"][col] = step
last_td_info["step_idx"][col] = i
elif main_info["stop_type"][col] == StopType.DT:
if last_dt_info["ladder"][col]:
step = last_dt_info["step"][col] + 1
last_dt_info["step"][col] = step
last_dt_info["step_idx"][col] = i
last_dt_info["exit_size"][col] = np.nan
last_dt_info["exit_size_type"][col] = -1
if stop_ladder and last_dt_info["ladder"][col] != StopLadderMode.Dynamic:
if step < n_dt_steps:
last_dt_info["stop"][col] = flex_select_nb(dt_stop_, step, col)
last_dt_info["step"][col] = step
last_dt_info["step_idx"][col] = i
else:
last_dt_info["stop"][col] = -1
last_dt_info["step"][col] = -1
last_dt_info["step_idx"][col] = -1
else:
last_dt_info["stop"][col] = -1
last_dt_info["step"][col] = step
last_dt_info["step_idx"][col] = i
if order_result.status == OrderStatus.Filled and position_now != 0:
# Order filled and in position -> possibly set stops
_price = main_info["price"][col]
_stop_entry_price = flex_select_nb(stop_entry_price_, i, col)
if _stop_entry_price < 0:
if _stop_entry_price == StopEntryPrice.ValPrice:
new_init_price = val_price_now
can_use_ohlc = False
elif _stop_entry_price == StopEntryPrice.Price:
new_init_price = order.price
can_use_ohlc = np.isinf(_price) and _price < 0
if np.isinf(new_init_price):
if new_init_price > 0:
new_init_price = flex_select_nb(close_, i, col)
else:
new_init_price = flex_select_nb(open_, i, col)
elif _stop_entry_price == StopEntryPrice.FillPrice:
new_init_price = order_result.price
can_use_ohlc = np.isinf(_price) and _price < 0
elif _stop_entry_price == StopEntryPrice.Open:
new_init_price = flex_select_nb(open_, i, col)
can_use_ohlc = True
elif _stop_entry_price == StopEntryPrice.Close:
new_init_price = flex_select_nb(close_, i, col)
can_use_ohlc = False
else:
raise ValueError("Invalid StopEntryPrice option")
else:
new_init_price = _stop_entry_price
can_use_ohlc = False
if stop_ladder:
_sl_stop = flex_select_nb(sl_stop_, 0, col)
_tsl_stop = flex_select_nb(tsl_stop_, 0, col)
_tp_stop = flex_select_nb(tp_stop_, 0, col)
_td_stop = flex_select_nb(td_stop_, 0, col)
_dt_stop = flex_select_nb(dt_stop_, 0, col)
else:
_sl_stop = flex_select_nb(sl_stop_, i, col)
_tsl_stop = flex_select_nb(tsl_stop_, i, col)
_tp_stop = flex_select_nb(tp_stop_, i, col)
_td_stop = flex_select_nb(td_stop_, i, col)
_dt_stop = flex_select_nb(dt_stop_, i, col)
_tsl_th = flex_select_nb(tsl_th_, i, col)
_stop_exit_price = flex_select_nb(stop_exit_price_, i, col)
_stop_exit_type = flex_select_nb(stop_exit_type_, i, col)
_stop_order_type = flex_select_nb(stop_order_type_, i, col)
_stop_limit_delta = flex_select_nb(stop_limit_delta_, i, col)
_delta_format = flex_select_nb(delta_format_, i, col)
_time_delta_format = flex_select_nb(time_delta_format_, i, col)
tsl_updated = False
if exec_state.position == 0 or np.sign(position_now) != np.sign(exec_state.position):
# Position opened/reversed -> set stops
last_sl_info["init_idx"][col] = i
last_sl_info["init_price"][col] = new_init_price
last_sl_info["init_position"][col] = position_now
last_sl_info["stop"][col] = _sl_stop
last_sl_info["exit_price"][col] = _stop_exit_price
last_sl_info["exit_size"][col] = np.nan
last_sl_info["exit_size_type"][col] = -1
last_sl_info["exit_type"][col] = _stop_exit_type
last_sl_info["order_type"][col] = _stop_order_type
last_sl_info["limit_delta"][col] = _stop_limit_delta
last_sl_info["delta_format"][col] = _delta_format
last_sl_info["ladder"][col] = stop_ladder
last_sl_info["step"][col] = 0
last_sl_info["step_idx"][col] = i
tsl_updated = True
last_tsl_info["init_idx"][col] = i
last_tsl_info["init_price"][col] = new_init_price
last_tsl_info["init_position"][col] = position_now
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = new_init_price
last_tsl_info["stop"][col] = _tsl_stop
last_tsl_info["th"][col] = _tsl_th
last_tsl_info["exit_price"][col] = _stop_exit_price
last_tsl_info["exit_size"][col] = np.nan
last_tsl_info["exit_size_type"][col] = -1
last_tsl_info["exit_type"][col] = _stop_exit_type
last_tsl_info["order_type"][col] = _stop_order_type
last_tsl_info["limit_delta"][col] = _stop_limit_delta
last_tsl_info["delta_format"][col] = _delta_format
last_tsl_info["ladder"][col] = stop_ladder
last_tsl_info["step"][col] = 0
last_tsl_info["step_idx"][col] = i
last_tp_info["init_idx"][col] = i
last_tp_info["init_price"][col] = new_init_price
last_tp_info["init_position"][col] = position_now
last_tp_info["stop"][col] = _tp_stop
last_tp_info["exit_price"][col] = _stop_exit_price
last_tp_info["exit_size"][col] = np.nan
last_tp_info["exit_size_type"][col] = -1
last_tp_info["exit_type"][col] = _stop_exit_type
last_tp_info["order_type"][col] = _stop_order_type
last_tp_info["limit_delta"][col] = _stop_limit_delta
last_tp_info["delta_format"][col] = _delta_format
last_tp_info["ladder"][col] = stop_ladder
last_tp_info["step"][col] = 0
last_tp_info["step_idx"][col] = i
last_td_info["init_idx"][col] = i
last_td_info["init_position"][col] = position_now
last_td_info["stop"][col] = _td_stop
last_td_info["exit_price"][col] = _stop_exit_price
last_td_info["exit_size"][col] = np.nan
last_td_info["exit_size_type"][col] = -1
last_td_info["exit_type"][col] = _stop_exit_type
last_td_info["order_type"][col] = _stop_order_type
last_td_info["limit_delta"][col] = _stop_limit_delta
last_td_info["delta_format"][col] = _delta_format
last_td_info["time_delta_format"][col] = _time_delta_format
last_td_info["ladder"][col] = stop_ladder
last_td_info["step"][col] = 0
last_td_info["step_idx"][col] = i
last_dt_info["init_idx"][col] = i
last_dt_info["init_position"][col] = position_now
last_dt_info["stop"][col] = _dt_stop
last_dt_info["exit_price"][col] = _stop_exit_price
last_dt_info["exit_size"][col] = np.nan
last_dt_info["exit_size_type"][col] = -1
last_dt_info["exit_type"][col] = _stop_exit_type
last_dt_info["order_type"][col] = _stop_order_type
last_dt_info["limit_delta"][col] = _stop_limit_delta
last_dt_info["delta_format"][col] = _delta_format
last_dt_info["time_delta_format"][col] = _time_delta_format
last_dt_info["ladder"][col] = stop_ladder
last_dt_info["step"][col] = 0
last_dt_info["step_idx"][col] = i
elif abs(position_now) > abs(exec_state.position):
# Position increased -> keep/override stops
_upon_stop_update = flex_select_nb(upon_stop_update_, i, col)
if should_update_stop_nb(new_stop=_sl_stop, upon_stop_update=_upon_stop_update):
last_sl_info["init_idx"][col] = i
last_sl_info["init_price"][col] = new_init_price
last_sl_info["init_position"][col] = position_now
last_sl_info["stop"][col] = _sl_stop
last_sl_info["exit_price"][col] = _stop_exit_price
last_sl_info["exit_size"][col] = np.nan
last_sl_info["exit_size_type"][col] = -1
last_sl_info["exit_type"][col] = _stop_exit_type
last_sl_info["order_type"][col] = _stop_order_type
last_sl_info["limit_delta"][col] = _stop_limit_delta
last_sl_info["delta_format"][col] = _delta_format
last_sl_info["ladder"][col] = stop_ladder
last_sl_info["step"][col] = 0
last_sl_info["step_idx"][col] = i
if should_update_stop_nb(new_stop=_tsl_stop, upon_stop_update=_upon_stop_update):
tsl_updated = True
last_tsl_info["init_idx"][col] = i
last_tsl_info["init_price"][col] = new_init_price
last_tsl_info["init_position"][col] = position_now
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = new_init_price
last_tsl_info["stop"][col] = _tsl_stop
last_tsl_info["th"][col] = _tsl_th
last_tsl_info["exit_price"][col] = _stop_exit_price
last_tsl_info["exit_size"][col] = np.nan
last_tsl_info["exit_size_type"][col] = -1
last_tsl_info["exit_type"][col] = _stop_exit_type
last_tsl_info["order_type"][col] = _stop_order_type
last_tsl_info["limit_delta"][col] = _stop_limit_delta
last_tsl_info["delta_format"][col] = _delta_format
last_tsl_info["ladder"][col] = stop_ladder
last_tsl_info["step"][col] = 0
last_tsl_info["step_idx"][col] = i
if should_update_stop_nb(new_stop=_tp_stop, upon_stop_update=_upon_stop_update):
last_tp_info["init_idx"][col] = i
last_tp_info["init_price"][col] = new_init_price
last_tp_info["init_position"][col] = position_now
last_tp_info["stop"][col] = _tp_stop
last_tp_info["exit_price"][col] = _stop_exit_price
last_tp_info["exit_size"][col] = np.nan
last_tp_info["exit_size_type"][col] = -1
last_tp_info["exit_type"][col] = _stop_exit_type
last_tp_info["order_type"][col] = _stop_order_type
last_tp_info["limit_delta"][col] = _stop_limit_delta
last_tp_info["delta_format"][col] = _delta_format
last_tp_info["ladder"][col] = stop_ladder
last_tp_info["step"][col] = 0
last_tp_info["step_idx"][col] = i
if should_update_time_stop_nb(
new_stop=_td_stop, upon_stop_update=_upon_stop_update
):
last_td_info["init_idx"][col] = i
last_td_info["init_position"][col] = position_now
last_td_info["stop"][col] = _td_stop
last_td_info["exit_price"][col] = _stop_exit_price
last_td_info["exit_size"][col] = np.nan
last_td_info["exit_size_type"][col] = -1
last_td_info["exit_type"][col] = _stop_exit_type
last_td_info["order_type"][col] = _stop_order_type
last_td_info["limit_delta"][col] = _stop_limit_delta
last_td_info["delta_format"][col] = _delta_format
last_td_info["time_delta_format"][col] = _time_delta_format
last_td_info["ladder"][col] = stop_ladder
last_td_info["step"][col] = 0
last_td_info["step_idx"][col] = i
if should_update_time_stop_nb(
new_stop=_dt_stop, upon_stop_update=_upon_stop_update
):
last_dt_info["init_idx"][col] = i
last_dt_info["init_position"][col] = position_now
last_dt_info["stop"][col] = _dt_stop
last_dt_info["exit_price"][col] = _stop_exit_price
last_dt_info["exit_size"][col] = np.nan
last_dt_info["exit_size_type"][col] = -1
last_dt_info["exit_type"][col] = _stop_exit_type
last_dt_info["order_type"][col] = _stop_order_type
last_dt_info["limit_delta"][col] = _stop_limit_delta
last_dt_info["delta_format"][col] = _delta_format
last_dt_info["time_delta_format"][col] = _time_delta_format
last_dt_info["ladder"][col] = stop_ladder
last_dt_info["step"][col] = 0
last_dt_info["step_idx"][col] = i
if tsl_updated:
# Update highest/lowest price
if can_use_ohlc:
_open = flex_select_nb(open_, i, col)
_high = flex_select_nb(high_, i, col)
_low = flex_select_nb(low_, i, col)
_close = flex_select_nb(close_, i, col)
_high, _low = resolve_hl_nb(
open=_open,
high=_high,
low=_low,
close=_close,
)
else:
_open = np.nan
_high = _low = _close = flex_select_nb(close_, i, col)
if tsl_updated:
if position_now > 0:
if _high > last_tsl_info["peak_price"][col]:
if last_tsl_info["delta_format"][col] == DeltaFormat.Target:
last_tsl_info["stop"][col] = (
last_tsl_info["stop"][col]
+ _high
- last_tsl_info["peak_price"][col]
)
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = _high
elif position_now < 0:
if _low < last_tsl_info["peak_price"][col]:
if last_tsl_info["delta_format"][col] == DeltaFormat.Target:
last_tsl_info["stop"][col] = (
last_tsl_info["stop"][col]
+ _low
- last_tsl_info["peak_price"][col]
)
last_tsl_info["peak_idx"][col] = i
last_tsl_info["peak_price"][col] = _low
# Now becomes last
last_position[col] = position_now
last_debt[col] = debt_now
last_locked_cash[col] = locked_cash_now
if not np.isnan(val_price_now) or not ffill_val_price:
last_val_price[col] = val_price_now
if cash_sharing:
last_cash[group] = cash_now
last_free_cash[group] = free_cash_now
last_value[group] = value_now
last_return[group] = return_now
else:
last_cash[col] = cash_now
last_free_cash[col] = free_cash_now
last_value[col] = value_now
last_return[col] = return_now
# Call post-signal function
post_signal_ctx = PostSignalContext(
target_shape=target_shape,
group_lens=group_lens,
cash_sharing=cash_sharing,
index=index,
freq=freq,
open=open_,
high=high_,
low=low_,
close=close_,
init_cash=init_cash_,
init_position=init_position_,
init_price=init_price_,
order_records=order_records,
order_counts=order_counts,
log_records=log_records,
log_counts=log_counts,
track_cash_deposits=track_cash_deposits,
cash_deposits_out=cash_deposits_out,
track_cash_earnings=track_cash_earnings,
cash_earnings_out=cash_earnings_out,
in_outputs=in_outputs,
last_cash=last_cash,
last_position=last_position,
last_debt=last_debt,
last_locked_cash=last_locked_cash,
last_free_cash=last_free_cash,
last_val_price=last_val_price,
last_value=last_value,
last_return=last_return,
last_pos_info=last_pos_info,
last_limit_info=last_limit_info,
last_sl_info=last_sl_info,
last_tsl_info=last_tsl_info,
last_tp_info=last_tp_info,
last_td_info=last_td_info,
last_dt_info=last_dt_info,
sim_start=sim_start_,
sim_end=sim_end_,
group=group,
group_len=group_len,
from_col=from_col,
to_col=to_col,
i=i,
col=col,
cash_before=cash_before,
position_before=position_before,
debt_before=debt_before,
locked_cash_before=locked_cash_before,
free_cash_before=free_cash_before,
val_price_before=val_price_before,
value_before=value_before,
order_result=order_result,
)
post_signal_func_nb(post_signal_ctx, *post_signal_args)
for col in range(from_col, to_col):
# Update valuation price using current close
_close = flex_select_nb(close_, i, col)
if not np.isnan(_close) or not ffill_val_price:
last_val_price[col] = _close
_cash_earnings = flex_select_nb(cash_earnings_, i, col)
_cash_dividends = flex_select_nb(cash_dividends_, i, col)
_cash_earnings += _cash_dividends * last_position[col]
if cash_sharing:
last_cash[group] += _cash_earnings
last_free_cash[group] += _cash_earnings
else:
last_cash[col] += _cash_earnings
last_free_cash[col] += _cash_earnings
if track_cash_earnings:
cash_earnings_out[i, col] += _cash_earnings
# Update value and return
if cash_sharing:
group_value = last_cash[group]
for col in range(from_col, to_col):
if last_position[col] != 0:
group_value += last_position[col] * last_val_price[col]
last_value[group] = group_value
last_return[group] = get_return_nb(
input_value=prev_close_value[group],
output_value=last_value[group] - last_cash_deposits[group],
)
prev_close_value[group] = last_value[group]
else:
for col in range(from_col, to_col):
group_value = last_cash[col]
if last_position[col] != 0:
group_value += last_position[col] * last_val_price[col]
last_value[col] = group_value
last_return[col] = get_return_nb(
input_value=prev_close_value[col],
output_value=last_value[col] - last_cash_deposits[col],
)
prev_close_value[col] = last_value[col]
# Update open position stats
if fill_pos_info:
for col in range(from_col, to_col):
update_open_pos_info_stats_nb(last_pos_info[col], last_position[col], last_val_price[col])
# Call post-segment function
post_segment_ctx = SignalSegmentContext(
target_shape=target_shape,
group_lens=group_lens,
cash_sharing=cash_sharing,
index=index,
freq=freq,
open=open_,
high=high_,
low=low_,
close=close_,
init_cash=init_cash_,
init_position=init_position_,
init_price=init_price_,
order_records=order_records,
order_counts=order_counts,
log_records=log_records,
log_counts=log_counts,
track_cash_deposits=track_cash_deposits,
cash_deposits_out=cash_deposits_out,
track_cash_earnings=track_cash_earnings,
cash_earnings_out=cash_earnings_out,
in_outputs=in_outputs,
last_cash=last_cash,
last_position=last_position,
last_debt=last_debt,
last_locked_cash=last_locked_cash,
last_free_cash=last_free_cash,
last_val_price=last_val_price,
last_value=last_value,
last_return=last_return,
last_pos_info=last_pos_info,
last_limit_info=last_limit_info,
last_sl_info=last_sl_info,
last_tsl_info=last_tsl_info,
last_tp_info=last_tp_info,
last_td_info=last_td_info,
last_dt_info=last_dt_info,
sim_start=sim_start_,
sim_end=sim_end_,
group=group,
group_len=group_len,
from_col=from_col,
to_col=to_col,
i=i,
)
post_segment_func_nb(post_segment_ctx, *post_segment_args)
if i >= sim_end_[group] - 1:
break
sim_start_out, sim_end_out = generic_nb.resolve_ungrouped_sim_range_nb(
target_shape=target_shape,
group_lens=group_lens,
sim_start=sim_start_,
sim_end=sim_end_,
allow_none=True,
)
return prepare_sim_out_nb(
order_records=order_records,
order_counts=order_counts,
log_records=log_records,
log_counts=log_counts,
cash_deposits=cash_deposits_out,
cash_earnings=cash_earnings_out,
call_seq=call_seq,
in_outputs=in_outputs,
sim_start=sim_start_out,
sim_end=sim_end_out,
)