mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-03 15:42:39 +08:00
BUG: DAY_END action not emitted during minute emission
Refactor AlgorithmSimulator so that DAY_END is emitted for both minute and daily emission, and that handling of end-of-minute and end-of-day are separated
This commit is contained in:
committed by
Jean Bredeche
parent
a068eb374a
commit
7641247b41
+13
-6
@@ -2571,7 +2571,7 @@ class TestOrderCancelation(WithDataPortal,
|
||||
)
|
||||
|
||||
def prep_algo(self, cancelation_string, data_frequency="minute",
|
||||
amount=1000):
|
||||
amount=1000, minute_emission=False):
|
||||
code = self.code.format(cancelation_string, amount)
|
||||
algo = TradingAlgorithm(
|
||||
script=code,
|
||||
@@ -2580,21 +2580,28 @@ class TestOrderCancelation(WithDataPortal,
|
||||
period_start=self.sim_params.period_start,
|
||||
period_end=self.sim_params.period_end,
|
||||
env=self.env,
|
||||
data_frequency=data_frequency
|
||||
data_frequency=data_frequency,
|
||||
emission_rate='minute' if minute_emission else 'daily'
|
||||
)
|
||||
)
|
||||
|
||||
return algo
|
||||
|
||||
@parameterized.expand([
|
||||
(1,), (-1,),
|
||||
])
|
||||
def test_eod_order_cancel_minute(self, direction):
|
||||
@parameter_space(
|
||||
direction=[1, -1],
|
||||
minute_emission=[True, False]
|
||||
)
|
||||
def test_eod_order_cancel_minute(self, direction, minute_emission):
|
||||
"""
|
||||
Test that EOD order cancel works in minute mode for both shorts and
|
||||
longs, and both daily emission and minute emission
|
||||
"""
|
||||
# order 1000 shares of asset1. the volume is only 1 share per bar,
|
||||
# so the order should be cancelled at the end of the day.
|
||||
algo = self.prep_algo(
|
||||
"set_cancel_policy(cancel_policy.EODCancel())",
|
||||
amount=np.copysign(1000, direction),
|
||||
minute_emission=minute_emission
|
||||
)
|
||||
|
||||
log_catcher = TestHandler()
|
||||
|
||||
@@ -198,7 +198,7 @@ def calculate_results(sim_params,
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
msg = perf_tracker.handle_market_close_daily(date, data_portal)
|
||||
msg = perf_tracker.handle_market_close(date, data_portal)
|
||||
perf_tracker.position_tracker.sync_last_sale_prices(
|
||||
date, False, data_portal,
|
||||
)
|
||||
|
||||
@@ -303,9 +303,7 @@ class PerformanceTracker(object):
|
||||
|
||||
def handle_minute_close(self, dt, data_portal):
|
||||
"""
|
||||
Handles the close of the given minute. This includes handling
|
||||
market-close functions if the given minute is the end of the market
|
||||
day.
|
||||
Handles the close of the given minute in minute emission.
|
||||
|
||||
Parameters
|
||||
__________
|
||||
@@ -314,9 +312,7 @@ class PerformanceTracker(object):
|
||||
|
||||
Returns
|
||||
_______
|
||||
(dict, dict/None)
|
||||
A tuple of the minute perf packet and daily perf packet.
|
||||
If the market day has not ended, the daily perf packet is None.
|
||||
A minute perf packet.
|
||||
"""
|
||||
self.position_tracker.sync_last_sale_prices(dt, False, data_portal)
|
||||
self.update_performance()
|
||||
@@ -333,40 +329,40 @@ class PerformanceTracker(object):
|
||||
account.leverage)
|
||||
|
||||
minute_packet = self.to_dict(emission_type='minute')
|
||||
return minute_packet
|
||||
|
||||
# if this is the close, update dividends for the next day.
|
||||
# Return the performance tuple
|
||||
if dt == self.market_close:
|
||||
return minute_packet, self._handle_market_close(
|
||||
todays_date, data_portal._adjustment_reader,
|
||||
)
|
||||
else:
|
||||
return minute_packet, None
|
||||
def handle_market_close(self, dt, data_portal):
|
||||
"""
|
||||
Handles the close of the given day, in both minute and daily emission.
|
||||
In daily emission, also updates performance, benchmark and risk metrics
|
||||
as it would in handle_minute_close if it were minute emission.
|
||||
|
||||
def handle_market_close_daily(self, dt, data_portal):
|
||||
Parameters
|
||||
__________
|
||||
dt : Timestamp
|
||||
The minute that is ending
|
||||
|
||||
Returns
|
||||
_______
|
||||
A daily perf packet.
|
||||
"""
|
||||
Function called after handle_data when running with daily emission
|
||||
rate.
|
||||
"""
|
||||
self.position_tracker.sync_last_sale_prices(dt, False, data_portal)
|
||||
self.update_performance()
|
||||
completed_date = self.day
|
||||
account = self.get_account(False)
|
||||
|
||||
benchmark_value = self.all_benchmark_returns[completed_date]
|
||||
if self.emission_rate == 'daily':
|
||||
# this method is called for both minutely and daily emissions, but
|
||||
# this chunk of code here only applies for daily emissions. (since
|
||||
# it's done every minute, elsewhere, for minutely emission).
|
||||
self.position_tracker.sync_last_sale_prices(dt, False, data_portal)
|
||||
self.update_performance()
|
||||
account = self.get_account(False)
|
||||
|
||||
self.cumulative_risk_metrics.update(
|
||||
completed_date,
|
||||
self.todays_performance.returns,
|
||||
benchmark_value,
|
||||
account.leverage)
|
||||
benchmark_value = self.all_benchmark_returns[completed_date]
|
||||
|
||||
daily_packet = self._handle_market_close(
|
||||
completed_date, data_portal._adjustment_reader,
|
||||
)
|
||||
return daily_packet
|
||||
|
||||
def _handle_market_close(self, completed_date, adjustment_reader):
|
||||
self.cumulative_risk_metrics.update(
|
||||
completed_date,
|
||||
self.todays_performance.returns,
|
||||
benchmark_value,
|
||||
account.leverage)
|
||||
|
||||
# increment the day counter before we move markers forward.
|
||||
self.day_count += 1.0
|
||||
@@ -400,8 +396,11 @@ class PerformanceTracker(object):
|
||||
return daily_update
|
||||
|
||||
# Check for any dividends, then return the daily perf packet
|
||||
self.check_upcoming_dividends(next_trading_day=next_trading_day,
|
||||
adjustment_reader=adjustment_reader)
|
||||
self.check_upcoming_dividends(
|
||||
next_trading_day=next_trading_day,
|
||||
adjustment_reader=data_portal._adjustment_reader
|
||||
)
|
||||
|
||||
return daily_update
|
||||
|
||||
def handle_simulation_end(self):
|
||||
|
||||
@@ -81,8 +81,8 @@ cdef class MinuteSimulationClock:
|
||||
if minute_emission:
|
||||
yield minute, MINUTE_END
|
||||
|
||||
if not minute_emission:
|
||||
yield minutes[-1], DAY_END
|
||||
yield minutes[-1], DAY_END
|
||||
|
||||
|
||||
|
||||
cdef class DailySimulationClock:
|
||||
|
||||
@@ -199,20 +199,18 @@ class AlgorithmSimulator(object):
|
||||
once_a_day(dt)
|
||||
elif action == DAY_END:
|
||||
# End of the day.
|
||||
if algo.perf_tracker.emission_rate == 'daily':
|
||||
handle_benchmark(normalize_date(dt))
|
||||
execute_order_cancellation_policy()
|
||||
handle_benchmark(normalize_date(dt))
|
||||
|
||||
yield self._get_daily_message(dt, algo, algo.perf_tracker)
|
||||
elif action == MINUTE_END:
|
||||
handle_benchmark(dt)
|
||||
minute_msg, daily_msg = \
|
||||
minute_msg = \
|
||||
self._get_minute_message(dt, algo, algo.perf_tracker)
|
||||
|
||||
yield minute_msg
|
||||
|
||||
if daily_msg:
|
||||
yield daily_msg
|
||||
|
||||
risk_message = algo.perf_tracker.handle_simulation_end()
|
||||
yield risk_message
|
||||
|
||||
@@ -256,7 +254,7 @@ class AlgorithmSimulator(object):
|
||||
"""
|
||||
Get a perf message for the given datetime.
|
||||
"""
|
||||
perf_message = perf_tracker.handle_market_close_daily(
|
||||
perf_message = perf_tracker.handle_market_close(
|
||||
dt, self.data_portal,
|
||||
)
|
||||
perf_message['daily_perf']['recorded_vars'] = algo.recorded_vars
|
||||
@@ -268,12 +266,9 @@ class AlgorithmSimulator(object):
|
||||
"""
|
||||
rvars = algo.recorded_vars
|
||||
|
||||
minute_message, daily_message = perf_tracker.handle_minute_close(
|
||||
minute_message = perf_tracker.handle_minute_close(
|
||||
dt, self.data_portal,
|
||||
)
|
||||
|
||||
minute_message['minute_perf']['recorded_vars'] = rvars
|
||||
|
||||
if daily_message:
|
||||
daily_message["daily_perf"]["recorded_vars"] = rvars
|
||||
|
||||
return minute_message, daily_message
|
||||
return minute_message
|
||||
|
||||
Reference in New Issue
Block a user