Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
 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]
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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)
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
    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)
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
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)
Ejemplo n.º 11
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
Ejemplo n.º 12
0
 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]
Ejemplo n.º 13
0
    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)
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
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))
Ejemplo n.º 16
0
    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
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
 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]