def cache_result(self, event): assert isinstance(event, CallResult.Created) # Remember the call result entity. this_result_id = event.entity_id call_result = CallResult.mutator(event=event) self.result[this_result_id] = call_result if isinstance(self.call_result_repo, dict): self.call_result_repo[this_result_id] = call_result # Decrement outstanding dependents for each dependency of this result. for dependency_id in self.dependencies.get(event.call_id, ()): try: self.outstanding_dependents[dependency_id].pop() except IndexError: # Discard the result when there are no longer any outstanding dependents. dependent_result_id = make_call_result_id(event.contract_valuation_id, dependency_id) self.result[dependent_result_id].discard() # Remove one outstanding dependency for each dependent of this result. if self.call_evaluation_queue: for dependent_id in self.dependents.get(event.call_id, ()): try: self.outstanding_dependencies[dependent_id].pop() except IndexError: # Queue the call if there are no more outstanding results. job = (event.contract_specification_id, event.contract_valuation_id, dependent_id) self.call_evaluation_queue.put(job)
def cache_result(self, event): assert isinstance(event, CallResult.Created) # Remember the call result entity. this_result_id = event.entity_id call_result = CallResult.mutator(event=event) self.result[this_result_id] = call_result if isinstance(self.call_result_repo, dict): self.call_result_repo[this_result_id] = call_result # Decrement outstanding dependents for each dependency of this result. for dependency_id in self.dependencies.get(event.call_id, ()): try: self.outstanding_dependents[dependency_id].pop() except IndexError: # Discard the result when there are no longer any outstanding dependents. dependent_result_id = make_call_result_id( event.contract_valuation_id, dependency_id) self.result[dependent_result_id].discard() # Remove one outstanding dependency for each dependent of this result. if self.call_evaluation_queue: for dependent_id in self.dependents.get(event.call_id, ()): try: self.outstanding_dependencies[dependent_id].pop() except IndexError: # Queue the call if there are no more outstanding results. job = (event.contract_specification_id, event.contract_valuation_id, dependent_id) self.call_evaluation_queue.put(job)
def evaluate(self, contract_specification, market_simulation): contract_valuation_id = create_contract_valuation_id() call_result_id = make_call_result_id(contract_valuation_id, contract_specification.id) self.start_contract_valuation(contract_valuation_id, contract_specification.id, market_simulation) return self.call_result_repo[call_result_id]
def get_dependency_results(contract_valuation_id, call_id, dependencies_repo, result_repo): assert isinstance(result_repo, (CallResultRepository, dict)), result_repo dependency_results = {} stub_dependencies = dependencies_repo[call_id] assert isinstance(stub_dependencies, CallDependencies), stub_dependencies for stub_id in stub_dependencies.dependencies: call_result_id = make_call_result_id(contract_valuation_id, stub_id) stub_result = result_repo[call_result_id] assert isinstance(stub_result, CallResult) dependency_results[stub_id] = stub_result return dependency_results
def get_dependency_results(contract_valuation_id, call_id, dependencies_repo, result_repo): assert isinstance(result_repo, (CallResultRepository, dict)), result_repo dependency_results = {} stub_dependencies = dependencies_repo[call_id] assert isinstance(stub_dependencies, CallDependencies), stub_dependencies for stub_id in stub_dependencies.dependencies: call_result_id = make_call_result_id(contract_valuation_id, stub_id) stub_result = result_repo[call_result_id] assert isinstance(stub_result, CallResult) dependency_results[stub_id] = stub_result return dependency_results
def test_register_call_result(self): contract_specification_id = create_uuid4() contract_valuation_id = create_uuid4() call_id = create_uuid4() call_result_id = make_call_result_id(contract_valuation_id, call_id) self.assertRaises(KeyError, self.app.call_result_repo.__getitem__, call_result_id) register_call_result(call_id=call_id, result_value=123, perturbed_values={}, contract_valuation_id=contract_valuation_id, contract_specification_id=contract_specification_id, involved_market_names=[]) call_result = self.app.call_result_repo[call_result_id] assert isinstance(call_result, CallResult) self.assertEqual(call_result.result_value, 123)
def find_dependents_ready_to_be_evaluated(contract_valuation_id, call_id, call_dependents_repo, call_dependencies_repo, call_result_repo, result_counters): # assert isinstance(contract_valuation_id, six.string_types), contract_valuation_id # assert isinstance(call_id, six.string_types), call_id # assert isinstance(call_dependents_repo, CallDependentsRepository) # assert isinstance(call_dependencies_repo, CallDependenciesRepository) # assert isinstance(call_result_repo, CallResultRepository) # Get dependents (if any exist). try: call_dependents = call_dependents_repo[call_id] # Don't worry if there are none. except KeyError: pass else: # Check if any dependents are ready to be evaluated. ready_dependents = [] if result_counters is not None: for dependent_id in call_dependents.dependents: dependent_result_id = make_call_result_id( contract_valuation_id, dependent_id) try: result_counters[dependent_result_id].pop( ) # Pop one off the array (atomic decrement). except (KeyError, IndexError): ready_dependents.append(dependent_id) return ready_dependents # assert isinstance(call_dependents, CallDependents) else: # Todo: Maybe speed this up by prepreparing the dependent-requirements (so it's just one query). dependent_threads = [] for dependent_id in call_dependents.dependents: # Single-threaded identification of dependents ready to be evaluated. add_dependent_if_ready(call_dependencies_repo, call_id, call_result_repo, contract_valuation_id, dependent_id, ready_dependents) [t.join() for t in dependent_threads] return ready_dependents
def test_register_call_result(self): dependency_graph_id = create_uuid4() contract_valuation_id = create_uuid4() call_id = create_uuid4() call_result_id = make_call_result_id(contract_valuation_id, call_id) self.assertRaises(KeyError, self.app.call_result_repo.__getitem__, call_result_id) register_call_result(call_id=call_id, result_value=123, perturbed_values={}, contract_valuation_id=contract_valuation_id, dependency_graph_id=dependency_graph_id) call_result = self.app.call_result_repo[call_result_id] assert isinstance(call_result, CallResult) self.assertEqual(call_result.result_value, 123)
def test_register_call_result(self): contract_specification_id = create_uuid4() contract_valuation_id = create_uuid4() call_id = create_uuid4() call_result_id = make_call_result_id(contract_valuation_id, call_id) self.assertRaises(KeyError, self.app.call_result_repo.__getitem__, call_result_id) register_call_result( call_id=call_id, result_value=123, perturbed_values={}, contract_valuation_id=contract_valuation_id, contract_specification_id=contract_specification_id, involved_market_names=[]) call_result = self.app.call_result_repo[call_result_id] assert isinstance(call_result, CallResult) self.assertEqual(call_result.result_value, 123)
def evaluate_contract_in_parallel( contract_valuation_id, contract_valuation_repo, call_leafs_repo, call_link_repo, call_evaluation_queue, result_counters, usage_counters, call_dependencies_repo, call_dependents_repo, perturbation_dependencies_repo, simulated_price_dependencies_repo): """ Computes value of contract by putting the dependency graph leaves on an evaluation queue and expecting there is at least one worker loop evaluating the queued calls and putting satisfied dependents on the queue. """ contract_valuation = contract_valuation_repo[contract_valuation_id] # assert isinstance(contract_valuation, ContractValuation), contract_valuation dependency_graph_id = contract_valuation.dependency_graph_id if result_counters is not None: assert usage_counters is not None for call_id in regenerate_execution_order(dependency_graph_id, call_link_repo): call_dependencies = call_dependencies_repo[call_id] call_dependents = call_dependents_repo[call_id] assert isinstance(call_dependencies, CallDependencies) assert isinstance(call_dependents, CallDependents) count_dependencies = len(call_dependencies.dependencies) count_dependents = len(call_dependents.dependents) # Crude attempt to count down using atomic operations, so we get an exception when we can't pop off the last one. call_result_id = make_call_result_id(contract_valuation_id, call_id) result_counters[call_result_id] = [None] * (count_dependencies - 1) usage_counters[call_result_id] = [None] * (count_dependents - 1) call_leafs = call_leafs_repo[dependency_graph_id] # assert isinstance(call_leafs, CallLeafs) for call_id in call_leafs.leaf_ids: call_evaluation_queue.put( (dependency_graph_id, contract_valuation_id, call_id)) gevent.sleep(0)
def calculate(self): self.result_cost = 0 self.result_count = 0 self.root_result_id = None self.is_timed_out = Event() self.is_interrupted = Event() self.timeout_msg = '' self.is_finished = Event() self.started = datetime.datetime.now() self.started_evaluating = None self.times = collections.deque() self.call_result_count = 0 self.call_requirement_count = 1 # self.memory_usage_max = 0 self.last_printed_progress = None self.duration_evaluating = None if self.timeout: timeout_thread = Thread(target=self.wait_then_set_is_timed_out) timeout_thread.setDaemon(True) timeout_thread.start() with QuantDslApplicationWithMultithreadingAndPythonObjects( max_dependency_graph_size=self.max_dependency_graph_size, dsl_classes=self.dsl_classes, ) as app: # Subscribe after the application, so events are received after the application. # - this means the final result is persisted before this interface is notified # the result is available and tries to get it, avoiding a race condition self.subscribe() try: # Compile. start_compile = datetime.datetime.now() contract_specification = app.compile(self.source_code, self.observation_date) end_compile = datetime.datetime.now() if self.verbose: # Todo: Separate this, not all users want print statements. print("") # Get a new line after the compilation progress. print("Compilation in {:.3f}s".format((end_compile - start_compile).total_seconds())) # Get simulation args. if self.price_process is not None: price_process_name = self.price_process['name'] calibration_params = {k: v for k, v in self.price_process.items() if k != 'name'} else: price_process_name = 'quantdsl.priceprocess.blackscholes.BlackScholesPriceProcess' calibration_params = {} # Simulate the market prices. start_simulate = datetime.datetime.now() market_simulation = app.simulate( contract_specification, price_process_name=price_process_name, calibration_params=calibration_params, path_count=self.path_count, observation_date=self.observation_date, interest_rate=self.interest_rate, perturbation_factor=self.perturbation_factor, periodisation=self.periodisation, ) end_simulate = datetime.datetime.now() if self.verbose: # Todo: Print number of simulated prices (subscribe to and count SimulatedPrice.Created events). # Todo: Separate this, not all users want print statements? print("Simulation in {:.3f}s".format((end_simulate - start_simulate).total_seconds())) # Estimate the cost of the evaluation (to show progress). # Todo: Improve the call cost estimation, perhaps by running over the depenendency graph and coding # each DSL class to know how long it will take relative to others. call_counts, call_costs = app.calc_counts_and_costs(contract_specification.id, self.is_double_sided_deltas) self.result_count_expected = sum(call_counts.values()) self.result_cost_expected = sum(call_costs.values()) if self.verbose: print("Starting {} node evaluations, please wait...".format(self.result_count_expected)) # Flush, because otherwise on Jupyter hub sometimes this message disrupts the progress display. sys.stdout.flush() # self.expected_num_call_requirements = len(call_costs) # Evaluate the contract specification. start_calc = datetime.datetime.now() self.started_evaluating = datetime.datetime.now() contract_valuation = app.evaluate( contract_specification_id=contract_specification.id, market_simulation_id=market_simulation.id, periodisation=self.periodisation, is_double_sided_deltas=self.is_double_sided_deltas ) # Wait for the result. self.root_result_id = make_call_result_id(contract_valuation.id, contract_valuation.contract_specification_id) if not self.root_result_id in app.call_result_repo: while not self.is_finished.wait(timeout=1): self.check_has_app_thread_errored(app) self.check_is_timed_out() self.check_is_interrupted() # Todo: Separate this, not all users want print statements. if self.verbose and self.duration_evaluating: sys.stdout.flush() print("") print("Evaluation in {:.3f}s".format(self.duration_evaluating.total_seconds())) # Read the results. valuation_result = app.get_result(contract_valuation) periods = app.get_periods(contract_valuation) results = Results( valuation_result=valuation_result, periods=periods, contract_valuation=contract_valuation, market_simulation=market_simulation, ) finally: self.unsubscribe() return results
def get_result(self, contract_valuation): call_result_id = make_call_result_id(contract_valuation.id, contract_valuation.contract_specification_id) return self.call_result_repo[call_result_id]
def read_results(self, evaluation): assert isinstance(evaluation, ContractValuation) market_simulation = self.market_simulation_repo[ evaluation.market_simulation_id] call_result_id = make_call_result_id( evaluation.id, evaluation.contract_specification_id) call_result = self.call_result_repo[call_result_id] fair_value = call_result.result_value perturbation_names = call_result.perturbed_values.keys() perturbation_names = [ i for i in perturbation_names if not i.startswith('-') ] perturbation_names = sorted( perturbation_names, key=lambda x: [int(i) for i in x.split('-')[1:]]) periods = [] for perturbation_name in perturbation_names: perturbed_value = call_result.perturbed_values[perturbation_name] if evaluation.is_double_sided_deltas: perturbed_value_negative = call_result.perturbed_values[ '-' + perturbation_name] else: perturbed_value_negative = None # Assumes format: NAME-YEAR-MONTH perturbed_name_split = perturbation_name.split('-') market_name = perturbed_name_split[0] if market_name == perturbation_name: simulated_price_id = make_simulated_price_id( market_simulation.id, market_name, market_simulation.observation_date, market_simulation.observation_date) simulated_price = self.simulated_price_repo[simulated_price_id] price = simulated_price.value if evaluation.is_double_sided_deltas: dy = perturbed_value - perturbed_value_negative else: dy = perturbed_value - fair_value dx = market_simulation.perturbation_factor * price if evaluation.is_double_sided_deltas: dx *= 2 delta = dy / dx hedge_units = -delta hedge_cost = hedge_units * price periods.append({ 'market_name': market_name, 'delivery_date': None, 'delta': delta, 'perturbation_name': perturbation_name, 'hedge_units': hedge_units, 'price_simulated': price, 'price_discounted': price, 'hedge_cost': hedge_cost, }) elif len(perturbed_name_split) > 2: year = int(perturbed_name_split[1]) month = int(perturbed_name_split[2]) if len(perturbed_name_split) > 3: day = int(perturbed_name_split[3]) delivery_date = datetime.date(year, month, day) simulated_price_id = make_simulated_price_id( market_simulation.id, market_name, delivery_date, delivery_date) simulated_price = self.simulated_price_repo[ simulated_price_id] simulated_price_value = simulated_price.value else: delivery_date = datetime.date(year, month, 1) sum_simulated_prices = 0 count_simulated_prices = 0 for i in range(1, 32): try: _delivery_date = datetime.date(year, month, i) except ValueError: continue else: simulated_price_id = make_simulated_price_id( market_simulation.id, market_name, _delivery_date, _delivery_date) try: simulated_price = self.simulated_price_repo[ simulated_price_id] except KeyError: pass else: sum_simulated_prices += simulated_price.value count_simulated_prices += 1 assert count_simulated_prices, "Can't find any simulated prices for {}-{}".format( year, month) simulated_price_value = sum_simulated_prices / count_simulated_prices # Assume present time of perturbed values is observation date. if evaluation.is_double_sided_deltas: dy = perturbed_value - perturbed_value_negative else: dy = perturbed_value - fair_value discount_rate = discount( value=1, present_date=market_simulation.observation_date, value_date=delivery_date, interest_rate=market_simulation.interest_rate) discounted_simulated_price_value = simulated_price_value * discount_rate dx = market_simulation.perturbation_factor * simulated_price_value if evaluation.is_double_sided_deltas: dx *= 2 delta = dy / dx # The delta of a forward contract at the observation date # is the discount factor at the delivery date. forward_contract_delta = discount_rate # Flatten the book with delta hedging in forward markets. # delta + hedge-units * hedge-delta = 0 # hence: hedge-units = -delta / hedge-delta hedge_units = -delta / forward_contract_delta # Present value of cost of hedge. hedge_cost = hedge_units * discounted_simulated_price_value periods.append({ 'market_name': market_name, 'delivery_date': delivery_date, 'delta': delta, 'perturbation_name': perturbation_name, 'hedge_units': hedge_units, 'price_simulated': simulated_price_value, 'price_discounted': discounted_simulated_price_value, 'hedge_cost': hedge_cost, }) return Results(fair_value, periods)
def is_result_missing(contract_valuation_id, dependent_dependency_id, call_result_repo): call_result_id = make_call_result_id(contract_valuation_id, dependent_dependency_id) return call_result_id not in call_result_repo
def evaluate_call_and_queue_next_calls(contract_valuation_id, dependency_graph_id, call_id, call_evaluation_queue, contract_valuation_repo, call_requirement_repo, market_simulation_repo, call_dependencies_repo, call_result_repo, simulated_price_repo, call_dependents_repo, perturbation_dependencies_repo, simulated_price_requirements_repo, call_result_lock, compute_pool=None, result_counters=None, usage_counters=None): # Get the contract valuation. contract_valuation = contract_valuation_repo[contract_valuation_id] assert isinstance(contract_valuation, ContractValuation) # Get the market simulation. market_simulation = market_simulation_repo[ contract_valuation.market_simulation_id] call_requirement = call_requirement_repo[call_id] perturbation_dependencies = perturbation_dependencies_repo[call_id] assert isinstance(perturbation_dependencies, PerturbationDependencies) # Get the simulated price requirements for this call. simulation_requirements = simulated_price_requirements_repo[ call_id].requirements result_value, perturbed_values = compute_call_result( contract_valuation=contract_valuation, call_requirement=call_requirement, market_simulation=market_simulation, perturbation_dependencies=perturbation_dependencies, call_dependencies_repo=call_dependencies_repo, call_result_repo=call_result_repo, simulated_price_repo=simulated_price_repo, perturbation_dependencies_repo=perturbation_dependencies_repo, simulation_requirements=simulation_requirements, compute_pool=compute_pool, ) # Lock the results. # - avoids race conditions when checking, after a result # has been written, if all results are now available, whilst # others are writing results. # - could perhaps do this with optimistic concurrency control, so # that result events can be collected by dependents # and then evaluated when all are received - to be robust against # concurrent operations causing concurrency exceptions, an accumulating # operation would require to be retried only as many times as there are # remaining dependents. if call_result_lock is not None: call_result_lock.acquire() try: # Register this result. # Todo: Retries on concurrency errors. register_call_result( call_id=call_id, result_value=result_value, perturbed_values=perturbed_values, contract_valuation_id=contract_valuation_id, dependency_graph_id=dependency_graph_id, ) # Find next calls. ready_generator = find_dependents_ready_to_be_evaluated( contract_valuation_id=contract_valuation_id, call_id=call_id, call_dependencies_repo=call_dependencies_repo, call_dependents_repo=call_dependents_repo, call_result_repo=call_result_repo, result_counters=result_counters, ) # Check requirements, and discard result when dependency has been fully used. if usage_counters is not None: call_dependencies = call_dependencies_repo[call_id] assert isinstance(call_dependencies, CallDependencies) for dependency_id in call_dependencies.dependencies: dependency_result_id = make_call_result_id( contract_valuation_id, dependency_id) try: usage_counters[dependency_result_id].pop( ) # Pop one off the array (atomic decrement). except (KeyError, IndexError): call_result = call_result_repo[dependency_result_id] # Todo: Maybe do discard operations after lock has been released? call_result.discard() # Need to remove from the cache if we are to save memory. try: del (call_result_repo._cache[dependency_result_id]) except: pass if call_result_lock is not None: # Make a list from the generator, if we are locking results. next_call_ids = list(ready_generator) else: # Otherwise put things directly on the queue. next_call_ids = [] for next_call_id in ready_generator: call_evaluation_queue.put( (dependency_graph_id, contract_valuation_id, next_call_id)) gevent.sleep(0) finally: # Unlock the results. if call_result_lock is not None: call_result_lock.release() # Queue the next calls (if there are any - see above). for next_call_id in next_call_ids: call_evaluation_queue.put( (dependency_graph_id, contract_valuation_id, next_call_id))
def get_periods(self, contract_valuation): assert isinstance(contract_valuation, ContractValuation) market_simulation = self.market_simulation_repo[contract_valuation.market_simulation_id] call_result_id = make_call_result_id(contract_valuation.id, contract_valuation.contract_specification_id) call_result = self.call_result_repo[call_result_id] fair_value = call_result.result_value perturbation_names = call_result.perturbed_values.keys() perturbation_names = [i for i in perturbation_names if not i.startswith('-')] perturbation_names = sorted(perturbation_names, key=lambda x: [int(i) for i in x.split('-')[1:]]) periods = [] for perturbation_name in perturbation_names: perturbed_value = call_result.perturbed_values[perturbation_name] if contract_valuation.is_double_sided_deltas: perturbed_value_negative = call_result.perturbed_values['-' + perturbation_name] else: perturbed_value_negative = None # Assumes format: NAME-YEAR-MONTH perturbed_name_split = perturbation_name.split('-') market_name = perturbed_name_split[0] if market_name == perturbation_name: simulated_price_id = make_simulated_price_id(market_simulation.id, market_name, market_simulation.observation_date, market_simulation.observation_date) simulated_price = self.simulated_price_repo[simulated_price_id] price = simulated_price.value if contract_valuation.is_double_sided_deltas: dy = perturbed_value - perturbed_value_negative else: dy = perturbed_value - fair_value dx = market_simulation.perturbation_factor * price if contract_valuation.is_double_sided_deltas: dx *= 2 delta = dy / dx hedge_units = - delta hedge_cost = hedge_units * price periods.append({ 'market_name': market_name, 'delivery_date': None, 'delta': delta, 'perturbation_name': perturbation_name, 'hedge_units': hedge_units, 'price_simulated': price, 'price_discounted': price, 'hedge_cost': hedge_cost, 'cash': -hedge_cost, }) elif len(perturbed_name_split) > 2: year = int(perturbed_name_split[1]) month = int(perturbed_name_split[2]) if len(perturbed_name_split) > 3: day = int(perturbed_name_split[3]) delivery_date = datetime.date(year, month, day) simulated_price_id = make_simulated_price_id( market_simulation.id, market_name, delivery_date, delivery_date ) simulated_price = self.simulated_price_repo[simulated_price_id] simulated_price_value = simulated_price.value else: delivery_date = datetime.date(year, month, 1) sum_simulated_prices = 0 count_simulated_prices = 0 for i in range(1, 32): try: _delivery_date = datetime.date(year, month, i) except ValueError: continue else: simulated_price_id = make_simulated_price_id( market_simulation.id, market_name, _delivery_date, _delivery_date ) try: simulated_price = self.simulated_price_repo[simulated_price_id] except KeyError: pass else: sum_simulated_prices += simulated_price.value count_simulated_prices += 1 assert count_simulated_prices, "Can't find any simulated prices for {}-{}".format(year, month) simulated_price_value = sum_simulated_prices / count_simulated_prices # Assume present time of perturbed values is observation date. if contract_valuation.is_double_sided_deltas: dy = perturbed_value - perturbed_value_negative else: dy = perturbed_value - fair_value discount_rate = discount( value=1, present_date=market_simulation.observation_date, value_date=delivery_date, interest_rate=market_simulation.interest_rate ) discounted_simulated_price_value = simulated_price_value * discount_rate dx = market_simulation.perturbation_factor * simulated_price_value if contract_valuation.is_double_sided_deltas: dx *= 2 delta = dy / dx # The delta of a forward contract at the observation date # is the discount factor at the delivery date. forward_contract_delta = discount_rate # Flatten the book with delta hedging in forward markets. # delta + hedge-units * hedge-delta = 0 # hence: hedge-units = -delta / hedge-delta hedge_units = -delta / forward_contract_delta # Present value of cost of hedge. hedge_cost = hedge_units * discounted_simulated_price_value periods.append({ 'market_name': market_name, 'delivery_date': delivery_date, 'delta': delta, 'perturbation_name': perturbation_name, 'hedge_units': hedge_units, 'price_simulated': simulated_price_value, 'price_discounted': discounted_simulated_price_value, 'hedge_cost': hedge_cost, 'cash': -hedge_cost, }) return periods
def assert_contract_value(self, specification, expected_value, expected_deltas=None, expected_call_count=None): # Register the specification (creates call dependency graph). contract_specification = self.app.register_contract_specification( specification=specification) # Check the call count (the number of nodes of the call dependency graph). call_count = len( list( regenerate_execution_order(contract_specification.id, self.app.call_link_repo))) if expected_call_count is not None: self.assertEqual(call_count, expected_call_count) # Generate the market simulation. market_simulation = self.setup_market_simulation( contract_specification) # Generate the contract valuation ID. contract_valuation_id = create_contract_valuation_id() call_result_id = make_call_result_id(contract_valuation_id, contract_specification.id) # Listen for the call result, if possible. # Todo: Listen for results, rather than polling for results - there will be less lag. # call_result_listener = None # Start the contract valuation. self.app.start_contract_valuation(contract_valuation_id, contract_specification.id, market_simulation) # # Get the call result. # if call_result_listener: # call_result_listener.wait() main_result = self.get_result(call_result_id, call_count) # Check the call result. assert isinstance(main_result, CallResult) self.assertAlmostEqual(self.scalar(main_result.result_value), expected_value, places=2) if expected_deltas is None: return # Generate the contract valuation deltas. assert isinstance(market_simulation, MarketSimulation) for perturbation in expected_deltas.keys(): # Compute the delta. if perturbation not in main_result.perturbed_values: self.fail("There isn't a perturbed value for '{}': {}" "".format(perturbation, list(main_result.perturbed_values.keys()))) perturbed_value = main_result.perturbed_values[perturbation].mean() market_calibration = self.app.market_calibration_repo[ market_simulation.market_calibration_id] assert isinstance(market_calibration, MarketCalibration) commodity_name = perturbation.split('-')[0] simulated_price_id = make_simulated_price_id( market_simulation.id, commodity_name, market_simulation.observation_date, market_simulation.observation_date) simulated_price = self.app.simulated_price_repo[simulated_price_id] dy = perturbed_value - main_result.result_value dx = Market.PERTURBATION_FACTOR * simulated_price.value contract_delta = dy / dx # Check the delta. actual_value = contract_delta.mean() expected_value = expected_deltas[perturbation] error_msg = "{}: {} != {}".format(perturbation, actual_value, expected_value) self.assertAlmostEqual(actual_value, expected_value, places=2, msg=error_msg)
def get_result(self, contract_valuation): call_result_id = make_call_result_id( contract_valuation.id, contract_valuation.contract_specification_id) return self.call_result_repo[call_result_id]