예제 #1
0
class TestSimulationSubscriber(unittest.TestCase):
    def setUp(self):
        assert_event_handlers_empty()
        market_simulation_repo = MagicMock(spec=MarketSimulationRepository)
        market_calibration_repo = MagicMock(spec=MarketCalibrationRepository)
        simulated_price_repo = MagicMock(spec=SimulatedPriceRepository)
        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo, market_simulation_repo,
            simulated_price_repo)

    def tearDown(self):
        self.simulation_subscriber.close()
        assert_event_handlers_empty()

    @patch(
        'quantdsl.infrastructure.simulation_subscriber.generate_simulated_prices'
    )
    def test_simulation_subscriber(self, generate_simulated_prices):
        # Check that when an event is published, the domain service is called.
        market_simulation_created = MarketSimulation.Created(
            entity_id='1',
            market_calibration_id='1',
            market_names=[],
            fixing_dates=[],
            observation_date=datetime.date(
                2011,
                1,
                1,
            ),
            path_count=2,
            interest_rate=2.5,
        )
        self.assertEqual(generate_simulated_prices.call_count, 0)
        publish(market_simulation_created)
        self.assertEqual(generate_simulated_prices.call_count, 1)
예제 #2
0
class TestSimulationSubscriber(unittest.TestCase):

    def setUp(self):
        market_simulation_repo = MagicMock(spec=MarketSimulationRepository)
        market_calibration_repo = MagicMock(spec=MarketCalibrationRepository)

        self.simulation_subscriber = SimulationSubscriber(market_calibration_repo, market_simulation_repo)

    def tearDown(self):
        self.simulation_subscriber.close()

    @patch('quantdsl.infrastructure.simulation_subscriber.generate_simulated_prices')
    def test_simulation_subscriber(self, generate_simulated_prices):
        # Check that when an event is published, the domain service is called.
        market_simulation_created = MarketSimulation.Created(
            entity_id='1',
            market_calibration_id='1',
            market_names=[],
            fixing_dates=[],
            observation_date=datetime.date(2011, 1, 1,),
            path_count=2,
            interest_rate=2.5,
        )
        self.assertEqual(generate_simulated_prices.call_count, 0)
        publish(market_simulation_created)
        self.assertEqual(generate_simulated_prices.call_count, 1)
예제 #3
0
 def setUp(self):
     assert_event_handlers_empty()
     market_simulation_repo = MagicMock(spec=MarketSimulationRepository)
     market_calibration_repo = MagicMock(spec=MarketCalibrationRepository)
     simulated_price_repo = MagicMock(spec=SimulatedPriceRepository)
     self.simulation_subscriber = SimulationSubscriber(
         market_calibration_repo, market_simulation_repo,
         simulated_price_repo)
 def setUp(self):
     assert_event_handlers_empty()
     market_simulation_repo = MagicMock(spec=MarketSimulationRepository)
     market_calibration_repo = MagicMock(spec=MarketCalibrationRepository)
     simulated_price_repo = MagicMock(spec=SimulatedPriceRepository)
     self.simulation_subscriber = SimulationSubscriber(
         market_calibration_repo,
         market_simulation_repo,
         simulated_price_repo
     )
예제 #5
0
 def __init__(self):
     super(BaseQuantDslApplication, self).__init__()
     self.contract_specification_repo = ContractSpecificationRepo(event_store=self.event_store)
     self.contract_valuation_repo = ContractValuationRepo(event_store=self.event_store)
     self.market_calibration_repo = MarketCalibrationRepo(event_store=self.event_store)
     self.market_simulation_repo = MarketSimulationRepo(event_store=self.event_store)
     self.simulated_price_repo = SimulatedPriceRepo(event_store=self.event_store)
     self.call_requirement_repo = CallRequirementRepo(event_store=self.event_store)
     self.call_dependencies_repo = CallDependenciesRepo(event_store=self.event_store)
     self.call_dependents_repo = CallDependentsRepo(event_store=self.event_store)
     self.call_link_repo = CallLinkRepo(event_store=self.event_store)
     self.call_result_repo = CallResultRepo(event_store=self.event_store)
     self.simulation_subscriber = SimulationSubscriber(
         market_calibration_repo=self.market_calibration_repo,
         market_simulation_repo=self.market_simulation_repo
     )
     self.dependency_graph_subscriber = DependencyGraphSubscriber(
         contract_specification_repo=self.contract_specification_repo,
         call_dependencies_repo=self.call_dependencies_repo,
         call_dependents_repo=self.call_dependents_repo
     )
예제 #6
0
    def __init__(self, call_evaluation_queue=None, max_dependency_graph_size=DEFAULT_MAX_DEPENDENCY_GRAPH_SIZE,
                 dsl_classes=None, *args, **kwargs):
        super(QuantDslApplication, self).__init__(*args, **kwargs)
        self.contract_specification_repo = ContractSpecificationRepo(event_store=self.event_store, use_cache=True)
        self.contract_valuation_repo = ContractValuationRepo(event_store=self.event_store, use_cache=True)
        self.market_calibration_repo = MarketCalibrationRepo(event_store=self.event_store, use_cache=True)
        self.market_simulation_repo = MarketSimulationRepo(event_store=self.event_store, use_cache=True)
        self.perturbation_dependencies_repo = PerturbationDependenciesRepo(event_store=self.event_store,
                                                                           use_cache=True)
        self.simulated_price_requirements_repo = SimulatedPriceRequirementsRepo(event_store=self.event_store,
                                                                                use_cache=True)
        # self.simulated_price_repo = SimulatedPriceRepo(event_store=self.event_store, use_cache=True)
        self.simulated_price_repo = {}
        self.call_requirement_repo = CallRequirementRepo(event_store=self.event_store, use_cache=True)
        self.call_dependencies_repo = CallDependenciesRepo(event_store=self.event_store, use_cache=True)
        self.call_dependents_repo = CallDependentsRepo(event_store=self.event_store, use_cache=True)
        self.call_leafs_repo = CallLeafsRepo(event_store=self.event_store, use_cache=True)
        self.call_link_repo = CallLinkRepo(event_store=self.event_store, use_cache=True)
        # self.call_result_repo = CallResultRepo(event_store=self.event_store, use_cache=True)
        self.call_result_repo = {}
        self.call_evaluation_queue = call_evaluation_queue
        self.max_dependency_graph_size = max_dependency_graph_size
        self.dsl_classes = dsl_classes

        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo=self.market_calibration_repo,
            market_simulation_repo=self.market_simulation_repo,
            simulated_price_repo=self.simulated_price_repo
        )
        self.dependency_graph_subscriber = DependencyGraphSubscriber(
            contract_specification_repo=self.contract_specification_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_dependents_repo=self.call_dependents_repo,
            call_leafs_repo=self.call_leafs_repo,
            call_requirement_repo=self.call_requirement_repo,
            max_dependency_graph_size=self.max_dependency_graph_size,
            dsl_classes=self.dsl_classes,
        )
        self.evaluation_subscriber = EvaluationSubscriber(
            contract_valuation_repo=self.contract_valuation_repo,
            call_link_repo=self.call_link_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_requirement_repo=self.call_requirement_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_evaluation_queue=self.call_evaluation_queue,
            call_leafs_repo=self.call_leafs_repo,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.simulated_price_requirements_repo,
        )
        self.call_result_policy = CallResultPolicy(self.call_result_repo, self.call_evaluation_queue)
예제 #7
0
파일: base.py 프로젝트: pjcrosbie/quantdsl
    def __init__(self,
                 call_evaluation_queue=None,
                 result_counters=None,
                 usage_counters=None,
                 *args,
                 **kwargs):
        super(QuantDslApplication, self).__init__(*args, **kwargs)
        self.contract_specification_repo = ContractSpecificationRepo(
            event_store=self.event_store, use_cache=True)
        self.contract_valuation_repo = ContractValuationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_calibration_repo = MarketCalibrationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_simulation_repo = MarketSimulationRepo(
            event_store=self.event_store, use_cache=True)
        self.perturbation_dependencies_repo = PerturbationDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.simulated_price_requirements_repo = SimulatedPriceRequirementsRepo(
            event_store=self.event_store, use_cache=True)
        self.simulated_price_repo = SimulatedPriceRepo(
            event_store=self.event_store, use_cache=True)
        self.call_requirement_repo = CallRequirementRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependencies_repo = CallDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependents_repo = CallDependentsRepo(
            event_store=self.event_store, use_cache=True)
        self.call_leafs_repo = CallLeafsRepo(event_store=self.event_store,
                                             use_cache=True)
        self.call_link_repo = CallLinkRepo(event_store=self.event_store,
                                           use_cache=True)
        self.call_result_repo = CallResultRepo(event_store=self.event_store,
                                               use_cache=True)
        self.call_evaluation_queue = call_evaluation_queue
        self.result_counters = result_counters
        self.usage_counters = usage_counters

        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo=self.market_calibration_repo,
            market_simulation_repo=self.market_simulation_repo,
        )
        self.dependency_graph_subscriber = DependencyGraphSubscriber(
            contract_specification_repo=self.contract_specification_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_dependents_repo=self.call_dependents_repo,
            call_leafs_repo=self.call_leafs_repo,
            call_requirement_repo=self.call_requirement_repo,
        )
        self.evaluation_subscriber = EvaluationSubscriber(
            contract_valuation_repo=self.contract_valuation_repo,
            call_link_repo=self.call_link_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_requirement_repo=self.call_requirement_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_evaluation_queue=self.call_evaluation_queue,
            call_leafs_repo=self.call_leafs_repo,
            result_counters=self.result_counters,
            usage_counters=self.usage_counters,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.
            simulated_price_requirements_repo,
        )
예제 #8
0
파일: base.py 프로젝트: pjcrosbie/quantdsl
class QuantDslApplication(EventSourcingApplication):
    """

    Flow of user stories:

    Register contract specification (DSL text).  --> gives required market names
    Generate compile call dependency graph using contract specification (and observation time?).  --> gives required 
    fixing times

    Register price histories.
    Generate market calibration for required market names using available price histories and observation time.

    Generate market simulation for required market names from market calibration, observation time, and fixing times.

    Evaluate contract given call dependency graph and market simulation.
    """
    def __init__(self,
                 call_evaluation_queue=None,
                 result_counters=None,
                 usage_counters=None,
                 *args,
                 **kwargs):
        super(QuantDslApplication, self).__init__(*args, **kwargs)
        self.contract_specification_repo = ContractSpecificationRepo(
            event_store=self.event_store, use_cache=True)
        self.contract_valuation_repo = ContractValuationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_calibration_repo = MarketCalibrationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_simulation_repo = MarketSimulationRepo(
            event_store=self.event_store, use_cache=True)
        self.perturbation_dependencies_repo = PerturbationDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.simulated_price_requirements_repo = SimulatedPriceRequirementsRepo(
            event_store=self.event_store, use_cache=True)
        self.simulated_price_repo = SimulatedPriceRepo(
            event_store=self.event_store, use_cache=True)
        self.call_requirement_repo = CallRequirementRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependencies_repo = CallDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependents_repo = CallDependentsRepo(
            event_store=self.event_store, use_cache=True)
        self.call_leafs_repo = CallLeafsRepo(event_store=self.event_store,
                                             use_cache=True)
        self.call_link_repo = CallLinkRepo(event_store=self.event_store,
                                           use_cache=True)
        self.call_result_repo = CallResultRepo(event_store=self.event_store,
                                               use_cache=True)
        self.call_evaluation_queue = call_evaluation_queue
        self.result_counters = result_counters
        self.usage_counters = usage_counters

        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo=self.market_calibration_repo,
            market_simulation_repo=self.market_simulation_repo,
        )
        self.dependency_graph_subscriber = DependencyGraphSubscriber(
            contract_specification_repo=self.contract_specification_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_dependents_repo=self.call_dependents_repo,
            call_leafs_repo=self.call_leafs_repo,
            call_requirement_repo=self.call_requirement_repo,
        )
        self.evaluation_subscriber = EvaluationSubscriber(
            contract_valuation_repo=self.contract_valuation_repo,
            call_link_repo=self.call_link_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_requirement_repo=self.call_requirement_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_evaluation_queue=self.call_evaluation_queue,
            call_leafs_repo=self.call_leafs_repo,
            result_counters=self.result_counters,
            usage_counters=self.usage_counters,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.
            simulated_price_requirements_repo,
        )

    @abstractmethod
    def create_stored_event_repo(self, **kwargs):
        raise NotImplementedError()

    def close(self):
        self.evaluation_subscriber.close()
        self.dependency_graph_subscriber.close()
        self.simulation_subscriber.close()
        super(QuantDslApplication, self).close()

    # Todo: Register historical data.

    def compute_market_calibration_params(self, price_process_name,
                                          historical_data):
        """
        Returns market calibration params for given price process name and historical data.
        """
        return compute_market_calibration_params(price_process_name,
                                                 historical_data)

    def register_contract_specification(self, specification):
        """
        The contract specification is a Quant DSL module.
        """
        return register_contract_specification(specification=specification)

    def register_market_calibration(self, price_process_name,
                                    calibration_params):
        """
        Calibration params result from fitting a model of market dynamics to historical data.
        """
        assert isinstance(price_process_name, six.string_types)
        assert isinstance(calibration_params, (dict, list))
        return register_market_calibration(price_process_name,
                                           calibration_params)

    def register_market_simulation(self, market_calibration_id,
                                   observation_date, requirements, path_count,
                                   interest_rate):
        """
        A market simulation has simulated prices at specified times across a set of markets.
        """
        return register_market_simulation(market_calibration_id,
                                          observation_date, requirements,
                                          path_count, interest_rate)

    def register_dependency_graph(self, contract_specification_id):
        return register_dependency_graph(contract_specification_id)

    def register_call_requirement(self, call_id, dsl_source,
                                  effective_present_time):
        """
        A call requirement is a node of the dependency graph.
        """
        return register_call_requirement(
            call_id=call_id,
            dsl_source=dsl_source,
            effective_present_time=effective_present_time)

    def register_call_dependencies(self, call_id, dependencies):
        return register_call_dependencies(call_id=call_id,
                                          dependencies=dependencies)

    def register_call_dependents(self, call_id, dependents):
        return register_call_dependents(call_id=call_id, dependents=dependents)

    def register_call_link(self, link_id, call_id):
        return register_call_link(link_id, call_id)

    def identify_simulation_requirements(self, contract_specification,
                                         observation_date, requirements):
        assert isinstance(contract_specification,
                          ContractSpecification), contract_specification
        assert isinstance(requirements, set)
        return identify_simulation_requirements(
            contract_specification.id, self.call_requirement_repo,
            self.call_link_repo, self.call_dependencies_repo,
            self.perturbation_dependencies_repo, observation_date,
            requirements)

    def start_contract_valuation(self, entity_id, dependency_graph_id,
                                 market_simulation):
        assert isinstance(dependency_graph_id,
                          six.string_types), dependency_graph_id
        assert isinstance(market_simulation, MarketSimulation)
        return start_contract_valuation(entity_id, dependency_graph_id,
                                        market_simulation.id)

    def loop_on_evaluation_queue(self,
                                 call_result_lock,
                                 compute_pool=None,
                                 result_counters=None,
                                 usage_counters=None):
        loop_on_evaluation_queue(
            call_evaluation_queue=self.call_evaluation_queue,
            contract_valuation_repo=self.contract_valuation_repo,
            call_requirement_repo=self.call_requirement_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.
            simulated_price_requirements_repo,
            call_result_lock=call_result_lock,
            compute_pool=compute_pool,
            result_counters=result_counters,
            usage_counters=usage_counters,
        )

    def evaluate_call_and_queue_next_calls(self, contract_valuation_id,
                                           dependency_graph_id, call_id, lock):
        evaluate_call_and_queue_next_calls(
            contract_valuation_id=contract_valuation_id,
            dependency_graph_id=dependency_graph_id,
            call_id=call_id,
            call_evaluation_queue=self.call_evaluation_queue,
            contract_valuation_repo=self.contract_valuation_repo,
            call_requirement_repo=self.call_requirement_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.
            simulated_price_requirements_repo,
            call_result_lock=lock,
        )

    def compile(self, specification):
        return self.register_contract_specification(
            specification=specification)

    def simulate(self,
                 contract_specification,
                 market_calibration,
                 observation_date,
                 path_count=20000,
                 interest_rate='2.5'):
        simulation_requirements = set()
        self.identify_simulation_requirements(contract_specification,
                                              observation_date,
                                              simulation_requirements)
        market_simulation = self.register_market_simulation(
            market_calibration_id=market_calibration.id,
            requirements=list(simulation_requirements),
            observation_date=observation_date,
            path_count=path_count,
            interest_rate=interest_rate,
        )
        return market_simulation

    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]
예제 #9
0
    def setUp(self):
        market_simulation_repo = MagicMock(spec=MarketSimulationRepository)
        market_calibration_repo = MagicMock(spec=MarketCalibrationRepository)

        self.simulation_subscriber = SimulationSubscriber(market_calibration_repo, market_simulation_repo)
예제 #10
0
    def __init__(self,
                 call_evaluation_queue=None,
                 max_dependency_graph_size=DEFAULT_MAX_DEPENDENCY_GRAPH_SIZE,
                 dsl_classes=None,
                 *args,
                 **kwargs):
        super(QuantDslApplication, self).__init__(*args, **kwargs)
        self.contract_specification_repo = ContractSpecificationRepo(
            event_store=self.event_store, use_cache=True)
        self.contract_valuation_repo = ContractValuationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_calibration_repo = MarketCalibrationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_simulation_repo = MarketSimulationRepo(
            event_store=self.event_store, use_cache=True)
        self.perturbation_dependencies_repo = PerturbationDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.simulated_price_requirements_repo = SimulatedPriceRequirementsRepo(
            event_store=self.event_store, use_cache=True)
        # self.simulated_price_repo = SimulatedPriceRepo(event_store=self.event_store, use_cache=True)
        self.simulated_price_repo = {}
        self.call_requirement_repo = CallRequirementRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependencies_repo = CallDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependents_repo = CallDependentsRepo(
            event_store=self.event_store, use_cache=True)
        self.call_leafs_repo = CallLeafsRepo(event_store=self.event_store,
                                             use_cache=True)
        self.call_link_repo = CallLinkRepo(event_store=self.event_store,
                                           use_cache=True)
        # self.call_result_repo = CallResultRepo(event_store=self.event_store, use_cache=True)
        self.call_result_repo = {}
        self.call_evaluation_queue = call_evaluation_queue
        self.max_dependency_graph_size = max_dependency_graph_size
        self.dsl_classes = dsl_classes

        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo=self.market_calibration_repo,
            market_simulation_repo=self.market_simulation_repo,
            simulated_price_repo=self.simulated_price_repo)
        self.dependency_graph_subscriber = DependencyGraphSubscriber(
            contract_specification_repo=self.contract_specification_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_dependents_repo=self.call_dependents_repo,
            call_leafs_repo=self.call_leafs_repo,
            call_requirement_repo=self.call_requirement_repo,
            max_dependency_graph_size=self.max_dependency_graph_size,
            dsl_classes=self.dsl_classes,
        )
        self.evaluation_subscriber = EvaluationSubscriber(
            contract_valuation_repo=self.contract_valuation_repo,
            call_link_repo=self.call_link_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_requirement_repo=self.call_requirement_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_evaluation_queue=self.call_evaluation_queue,
            call_leafs_repo=self.call_leafs_repo,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.
            simulated_price_requirements_repo,
        )
        self.call_result_policy = CallResultPolicy(self.call_result_repo,
                                                   self.call_evaluation_queue)
예제 #11
0
class QuantDslApplication(EventSourcingApplication):
    """

    Flow of user stories:

    Register contract specification (DSL text).  --> gives required market names
    Generate compile call dependency graph using contract specification (and observation time?).  --> gives required 
    fixing times

    Register price histories.
    Generate market calibration for required market names using available price histories and observation time.

    Generate market simulation for required market names from market calibration, observation time, and fixing times.

    Evaluate contract given call dependency graph and market simulation.
    """
    def __init__(self,
                 call_evaluation_queue=None,
                 max_dependency_graph_size=DEFAULT_MAX_DEPENDENCY_GRAPH_SIZE,
                 dsl_classes=None,
                 *args,
                 **kwargs):
        super(QuantDslApplication, self).__init__(*args, **kwargs)
        self.contract_specification_repo = ContractSpecificationRepo(
            event_store=self.event_store, use_cache=True)
        self.contract_valuation_repo = ContractValuationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_calibration_repo = MarketCalibrationRepo(
            event_store=self.event_store, use_cache=True)
        self.market_simulation_repo = MarketSimulationRepo(
            event_store=self.event_store, use_cache=True)
        self.perturbation_dependencies_repo = PerturbationDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.simulated_price_requirements_repo = SimulatedPriceRequirementsRepo(
            event_store=self.event_store, use_cache=True)
        # self.simulated_price_repo = SimulatedPriceRepo(event_store=self.event_store, use_cache=True)
        self.simulated_price_repo = {}
        self.call_requirement_repo = CallRequirementRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependencies_repo = CallDependenciesRepo(
            event_store=self.event_store, use_cache=True)
        self.call_dependents_repo = CallDependentsRepo(
            event_store=self.event_store, use_cache=True)
        self.call_leafs_repo = CallLeafsRepo(event_store=self.event_store,
                                             use_cache=True)
        self.call_link_repo = CallLinkRepo(event_store=self.event_store,
                                           use_cache=True)
        # self.call_result_repo = CallResultRepo(event_store=self.event_store, use_cache=True)
        self.call_result_repo = {}
        self.call_evaluation_queue = call_evaluation_queue
        self.max_dependency_graph_size = max_dependency_graph_size
        self.dsl_classes = dsl_classes

        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo=self.market_calibration_repo,
            market_simulation_repo=self.market_simulation_repo,
            simulated_price_repo=self.simulated_price_repo)
        self.dependency_graph_subscriber = DependencyGraphSubscriber(
            contract_specification_repo=self.contract_specification_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_dependents_repo=self.call_dependents_repo,
            call_leafs_repo=self.call_leafs_repo,
            call_requirement_repo=self.call_requirement_repo,
            max_dependency_graph_size=self.max_dependency_graph_size,
            dsl_classes=self.dsl_classes,
        )
        self.evaluation_subscriber = EvaluationSubscriber(
            contract_valuation_repo=self.contract_valuation_repo,
            call_link_repo=self.call_link_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_requirement_repo=self.call_requirement_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_evaluation_queue=self.call_evaluation_queue,
            call_leafs_repo=self.call_leafs_repo,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.
            simulated_price_requirements_repo,
        )
        self.call_result_policy = CallResultPolicy(self.call_result_repo,
                                                   self.call_evaluation_queue)

    def create_persistence_subscriber(self):
        return PersistencePolicy(event_store=self.event_store)

    def close(self):
        self.evaluation_subscriber.close()
        self.dependency_graph_subscriber.close()
        self.simulation_subscriber.close()
        self.call_result_policy.close()
        super(QuantDslApplication, self).close()

    def register_contract_specification(self,
                                        source_code,
                                        observation_date=None):
        """
        Registers a new contract specification, from given Quant DSL source code.
        """
        return register_contract_specification(
            source_code=source_code, observation_date=observation_date)

    def register_market_calibration(self, price_process_name,
                                    calibration_params):
        """
        Calibration params result from fitting a model of market dynamics to historical data.
        """
        assert isinstance(price_process_name, six.string_types)
        assert isinstance(calibration_params, (dict, list))
        return register_market_calibration(price_process_name,
                                           calibration_params)

    def register_market_simulation(self,
                                   market_calibration_id,
                                   observation_date,
                                   requirements,
                                   path_count,
                                   interest_rate,
                                   perturbation_factor=0.001):
        """
        A market simulation has simulated prices at specified times across a set of markets.
        """
        return register_market_simulation(market_calibration_id,
                                          observation_date, requirements,
                                          path_count, interest_rate,
                                          perturbation_factor)

    def register_call_dependencies(self, call_id, dependencies):
        return register_call_dependencies(call_id=call_id,
                                          dependencies=dependencies)

    def register_call_dependents(self, call_id, dependents):
        return register_call_dependents(call_id=call_id, dependents=dependents)

    def identify_simulation_requirements(self, contract_specification,
                                         observation_date, requirements,
                                         periodisation):
        assert isinstance(contract_specification,
                          ContractSpecification), type(contract_specification)
        assert isinstance(requirements, set)
        return identify_simulation_requirements(contract_specification.id,
                                                self.call_requirement_repo,
                                                self.call_link_repo,
                                                self.call_dependencies_repo,
                                                observation_date, requirements,
                                                periodisation)

    def start_contract_valuation(self, contract_specification_id,
                                 market_simulation_id, periodisation,
                                 is_double_sided_deltas):
        return start_contract_valuation(contract_specification_id,
                                        market_simulation_id, periodisation,
                                        is_double_sided_deltas)

    def loop_on_evaluation_queue(self):
        loop_on_evaluation_queue(
            call_evaluation_queue=self.call_evaluation_queue,
            contract_valuation_repo=self.contract_valuation_repo,
            call_requirement_repo=self.call_requirement_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.
            simulated_price_requirements_repo,
        )

    def compile(self, source_code, observation_date=None):
        return self.register_contract_specification(
            source_code=source_code, observation_date=observation_date)

    def simulate(self,
                 contract_specification,
                 price_process_name,
                 calibration_params,
                 observation_date,
                 path_count=20000,
                 interest_rate='2.5',
                 perturbation_factor=0.01,
                 periodisation=None):

        market_calibration = self.register_market_calibration(
            price_process_name, calibration_params)

        simulation_requirements = set()
        self.identify_simulation_requirements(contract_specification,
                                              observation_date,
                                              simulation_requirements,
                                              periodisation)
        market_simulation = self.register_market_simulation(
            market_calibration_id=market_calibration.id,
            requirements=list(simulation_requirements),
            observation_date=observation_date,
            path_count=path_count,
            interest_rate=interest_rate,
            perturbation_factor=perturbation_factor,
        )
        return market_simulation

    def evaluate(self,
                 contract_specification_id,
                 market_simulation_id,
                 periodisation=None,
                 is_double_sided_deltas=False):
        return self.start_contract_valuation(contract_specification_id,
                                             market_simulation_id,
                                             periodisation,
                                             is_double_sided_deltas)

    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 wait_results(self, contract_valuation):
        self.get_result(contract_valuation)

    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 calc_call_count(self, contract_specification_id):
        # Todo: Return the call count from the compilation method?
        return len(
            list(
                regenerate_execution_order(contract_specification_id,
                                           self.call_link_repo)))

    def calc_counts_and_costs(self, contract_specification_id,
                              is_double_sided_deltas):
        """Returns a dict of call IDs -> perturbation requirements."""
        costs = {}
        counts = {}
        for call_id in regenerate_execution_order(contract_specification_id,
                                                  self.call_link_repo):

            # Get estimated cost of evaluating the expression once.
            call_requirement = self.call_requirement_repo[call_id]
            estimated_cost_of_expr = call_requirement.cost

            # Get the perturbation requirements for this call.
            perturbation_dependencies = self.perturbation_dependencies_repo[
                call_id]
            assert isinstance(perturbation_dependencies,
                              PerturbationDependencies)
            # "1 + 2 * number of dependencies" because of the double sided delta.
            num_perturbation_dependencies = len(
                perturbation_dependencies.dependencies)
            num_perturbations = (2 if is_double_sided_deltas else
                                 1) * num_perturbation_dependencies
            num_evaluations = 1 + num_perturbations

            # Cost is cost of doing it once, times the number of times it needs doing.
            costs[call_id] = num_evaluations * estimated_cost_of_expr
            counts[call_id] = num_evaluations

        return counts, costs
예제 #12
0
class BaseQuantDslApplication(EventSourcingApplication):
    """

    Flow of user stories:

    Register contract specification (DSL text).  --> gives required market names
    Generate compile call dependency graph using contract specification (and observation time?).  --> gives required fixing times

    Register price histories.
    Generate market calibration for required market names using available price histories and observation time.

    Generate market simulation for required market names from market calibration, observation time, and fixing times.

    Evaluate contract given call dependency graph and market simulation.
    """

    def __init__(self):
        super(BaseQuantDslApplication, self).__init__()
        self.contract_specification_repo = ContractSpecificationRepo(event_store=self.event_store)
        self.contract_valuation_repo = ContractValuationRepo(event_store=self.event_store)
        self.market_calibration_repo = MarketCalibrationRepo(event_store=self.event_store)
        self.market_simulation_repo = MarketSimulationRepo(event_store=self.event_store)
        self.simulated_price_repo = SimulatedPriceRepo(event_store=self.event_store)
        self.call_requirement_repo = CallRequirementRepo(event_store=self.event_store)
        self.call_dependencies_repo = CallDependenciesRepo(event_store=self.event_store)
        self.call_dependents_repo = CallDependentsRepo(event_store=self.event_store)
        self.call_link_repo = CallLinkRepo(event_store=self.event_store)
        self.call_result_repo = CallResultRepo(event_store=self.event_store)
        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo=self.market_calibration_repo,
            market_simulation_repo=self.market_simulation_repo
        )
        self.dependency_graph_subscriber = DependencyGraphSubscriber(
            contract_specification_repo=self.contract_specification_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_dependents_repo=self.call_dependents_repo
        )

    def close(self):
        self.dependency_graph_subscriber.close()
        self.simulation_subscriber.close()
        super(BaseQuantDslApplication, self).close()


    # Todo: Register historical data.

    def compute_market_calibration_params(self, price_process_name, historical_data):
        """
        Returns market calibration params for given price process name and historical data.
        """
        return compute_market_calibration_params(price_process_name, historical_data)

    def register_contract_specification(self, specification):
        """
        The contract specification is a Quant DSL module.
        """
        return register_contract_specification(specification=specification)

    def register_market_calibration(self, price_process_name, calibration_params):
        """
        Calibration params result from fitting a model of market dynamics to historical data.
        """
        assert isinstance(price_process_name, six.string_types)
        assert isinstance(calibration_params, dict)
        return register_market_calibration(price_process_name, calibration_params)

    def register_market_simulation(self, market_calibration_id, market_names, fixing_dates, observation_date,
                                   path_count, interest_rate):
        """
        A market simulation has simulated prices at specified times across a set of markets.
        """
        return register_market_simulation(market_calibration_id, market_names, fixing_dates, observation_date,
                                          path_count, interest_rate)

    def register_dependency_graph(self, contract_specification_id):
        return register_dependency_graph(contract_specification_id)

    def register_call_requirement(self, call_id, dsl_source, effective_present_time):
        """
        A call requirement is a node of the dependency graph.
        """
        return register_call_requirement(
            call_id=call_id,
            dsl_source=dsl_source,
            effective_present_time=effective_present_time
        )

    def register_call_dependencies(self, call_id, dependencies):
        return register_call_dependencies(call_id=call_id, dependencies=dependencies)

    def register_call_dependents(self, call_id, dependents):
        return register_call_dependents(call_id=call_id, dependents=dependents)

    def register_call_link(self, link_id, call_id):
        return register_call_link(link_id, call_id)

    def generate_dependency_graph(self, contract_specification):
        return generate_dependency_graph(contract_specification, self.call_dependencies_repo,
                                         self.call_dependents_repo)

    def register_call_result(self, call_id, result_value):
        return register_call_result(call_id=call_id, result_value=result_value)

    def register_contract_valuation(self, dependency_graph_id):
        return register_contract_valuation(dependency_graph_id)

    def generate_contract_valuation(self, dependency_graph_id, market_simulation):
        assert isinstance(dependency_graph_id, six.string_types), dependency_graph_id
        assert isinstance(market_simulation, MarketSimulation)
        v = self.register_contract_valuation(dependency_graph_id)

        for call_id in regenerate_execution_order(dependency_graph_id, self.call_link_repo):

            call = self.call_requirement_repo[call_id]
            assert isinstance(call, CallRequirement)

            # Evaluate the call requirement.
            dependency_values = get_dependency_values(call_id, self.call_dependencies_repo, self.call_result_repo)

            # - parse the expr
            stubbed_module = dsl_parse(call.dsl_source)

            assert isinstance(stubbed_module, Module), "Parsed stubbed expr string is not a module: %s" % stubbed_module

            # - build a namespace from the dependency values
            dsl_locals = DslNamespace(dependency_values)

            # - compile the parsed expr
            dsl_expr = stubbed_module.body[0].reduce(dsl_locals=dsl_locals, dsl_globals=DslNamespace())
            assert isinstance(dsl_expr, DslExpression), dsl_expr

            # - evaluate the compiled expr
            first_market_name = market_simulation.market_names[0] if market_simulation.market_names else None
            evaluation_kwds = {
                'simulated_price_repo': self.simulated_price_repo,
                'simulation_id': market_simulation.id,
                'interest_rate': market_simulation.interest_rate,
                'present_time': call.effective_present_time or market_simulation.observation_date,
                'first_market_name': first_market_name,
            }
            result_value = dsl_expr.evaluate(**evaluation_kwds)

            # - store the result
            register_call_result(call_id=call_id, result_value=result_value)
예제 #13
0
class QuantDslApplication(EventSourcingApplication):
    """

    Flow of user stories:

    Register contract specification (DSL text).  --> gives required market names
    Generate compile call dependency graph using contract specification (and observation time?).  --> gives required 
    fixing times

    Register price histories.
    Generate market calibration for required market names using available price histories and observation time.

    Generate market simulation for required market names from market calibration, observation time, and fixing times.

    Evaluate contract given call dependency graph and market simulation.
    """

    def __init__(self, call_evaluation_queue=None, max_dependency_graph_size=DEFAULT_MAX_DEPENDENCY_GRAPH_SIZE,
                 dsl_classes=None, *args, **kwargs):
        super(QuantDslApplication, self).__init__(*args, **kwargs)
        self.contract_specification_repo = ContractSpecificationRepo(event_store=self.event_store, use_cache=True)
        self.contract_valuation_repo = ContractValuationRepo(event_store=self.event_store, use_cache=True)
        self.market_calibration_repo = MarketCalibrationRepo(event_store=self.event_store, use_cache=True)
        self.market_simulation_repo = MarketSimulationRepo(event_store=self.event_store, use_cache=True)
        self.perturbation_dependencies_repo = PerturbationDependenciesRepo(event_store=self.event_store,
                                                                           use_cache=True)
        self.simulated_price_requirements_repo = SimulatedPriceRequirementsRepo(event_store=self.event_store,
                                                                                use_cache=True)
        # self.simulated_price_repo = SimulatedPriceRepo(event_store=self.event_store, use_cache=True)
        self.simulated_price_repo = {}
        self.call_requirement_repo = CallRequirementRepo(event_store=self.event_store, use_cache=True)
        self.call_dependencies_repo = CallDependenciesRepo(event_store=self.event_store, use_cache=True)
        self.call_dependents_repo = CallDependentsRepo(event_store=self.event_store, use_cache=True)
        self.call_leafs_repo = CallLeafsRepo(event_store=self.event_store, use_cache=True)
        self.call_link_repo = CallLinkRepo(event_store=self.event_store, use_cache=True)
        # self.call_result_repo = CallResultRepo(event_store=self.event_store, use_cache=True)
        self.call_result_repo = {}
        self.call_evaluation_queue = call_evaluation_queue
        self.max_dependency_graph_size = max_dependency_graph_size
        self.dsl_classes = dsl_classes

        self.simulation_subscriber = SimulationSubscriber(
            market_calibration_repo=self.market_calibration_repo,
            market_simulation_repo=self.market_simulation_repo,
            simulated_price_repo=self.simulated_price_repo
        )
        self.dependency_graph_subscriber = DependencyGraphSubscriber(
            contract_specification_repo=self.contract_specification_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_dependents_repo=self.call_dependents_repo,
            call_leafs_repo=self.call_leafs_repo,
            call_requirement_repo=self.call_requirement_repo,
            max_dependency_graph_size=self.max_dependency_graph_size,
            dsl_classes=self.dsl_classes,
        )
        self.evaluation_subscriber = EvaluationSubscriber(
            contract_valuation_repo=self.contract_valuation_repo,
            call_link_repo=self.call_link_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_requirement_repo=self.call_requirement_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_evaluation_queue=self.call_evaluation_queue,
            call_leafs_repo=self.call_leafs_repo,
            call_dependents_repo=self.call_dependents_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.simulated_price_requirements_repo,
        )
        self.call_result_policy = CallResultPolicy(self.call_result_repo, self.call_evaluation_queue)

    def create_persistence_subscriber(self):
        return PersistencePolicy(event_store=self.event_store)

    def close(self):
        self.evaluation_subscriber.close()
        self.dependency_graph_subscriber.close()
        self.simulation_subscriber.close()
        self.call_result_policy.close()
        super(QuantDslApplication, self).close()

    def register_contract_specification(self, source_code, observation_date=None):
        """
        Registers a new contract specification, from given Quant DSL source code.
        """
        return register_contract_specification(source_code=source_code, observation_date=observation_date)

    def register_market_calibration(self, price_process_name, calibration_params):
        """
        Calibration params result from fitting a model of market dynamics to historical data.
        """
        assert isinstance(price_process_name, six.string_types)
        assert isinstance(calibration_params, (dict, list))
        return register_market_calibration(price_process_name, calibration_params)

    def register_market_simulation(self, market_calibration_id, observation_date, requirements, path_count,
                                   interest_rate, perturbation_factor=0.001):
        """
        A market simulation has simulated prices at specified times across a set of markets.
        """
        return register_market_simulation(market_calibration_id, observation_date, requirements, path_count,
                                          interest_rate, perturbation_factor)

    def register_call_dependencies(self, call_id, dependencies):
        return register_call_dependencies(call_id=call_id, dependencies=dependencies)

    def register_call_dependents(self, call_id, dependents):
        return register_call_dependents(call_id=call_id, dependents=dependents)

    def identify_simulation_requirements(self, contract_specification, observation_date, requirements, periodisation):
        assert isinstance(contract_specification, ContractSpecification), type(contract_specification)
        assert isinstance(requirements, set)
        return identify_simulation_requirements(contract_specification.id,
                                                self.call_requirement_repo,
                                                self.call_link_repo,
                                                self.call_dependencies_repo,
                                                observation_date,
                                                requirements,
                                                periodisation)

    def start_contract_valuation(self, contract_specification_id, market_simulation_id, periodisation,
                                 is_double_sided_deltas):
        return start_contract_valuation(contract_specification_id, market_simulation_id, periodisation,
                                        is_double_sided_deltas)

    def loop_on_evaluation_queue(self):
        loop_on_evaluation_queue(
            call_evaluation_queue=self.call_evaluation_queue,
            contract_valuation_repo=self.contract_valuation_repo,
            call_requirement_repo=self.call_requirement_repo,
            market_simulation_repo=self.market_simulation_repo,
            call_dependencies_repo=self.call_dependencies_repo,
            call_result_repo=self.call_result_repo,
            simulated_price_repo=self.simulated_price_repo,
            perturbation_dependencies_repo=self.perturbation_dependencies_repo,
            simulated_price_requirements_repo=self.simulated_price_requirements_repo,
        )

    def compile(self, source_code, observation_date=None):
        return self.register_contract_specification(source_code=source_code, observation_date=observation_date)

    def simulate(self, contract_specification, price_process_name, calibration_params, observation_date,
                 path_count=20000, interest_rate='2.5', perturbation_factor=0.01, periodisation=None):

        market_calibration = self.register_market_calibration(price_process_name, calibration_params)

        simulation_requirements = set()
        self.identify_simulation_requirements(contract_specification, observation_date, simulation_requirements,
                                              periodisation)
        market_simulation = self.register_market_simulation(
            market_calibration_id=market_calibration.id,
            requirements=list(simulation_requirements),
            observation_date=observation_date,
            path_count=path_count,
            interest_rate=interest_rate,
            perturbation_factor=perturbation_factor,
        )
        return market_simulation

    def evaluate(self, contract_specification_id, market_simulation_id, periodisation=None,
                 is_double_sided_deltas=False):
        return self.start_contract_valuation(contract_specification_id, market_simulation_id, periodisation,
                                             is_double_sided_deltas)

    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 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 calc_call_count(self, contract_specification_id):
        # Todo: Return the call count from the compilation method?
        return len(list(regenerate_execution_order(contract_specification_id, self.call_link_repo)))

    def calc_counts_and_costs(self, contract_specification_id, is_double_sided_deltas):
        """Returns a dict of call IDs -> perturbation requirements."""
        costs = {}
        counts = {}
        for call_id in regenerate_execution_order(contract_specification_id, self.call_link_repo):

            # Get estimated cost of evaluating the expression once.
            call_requirement = self.call_requirement_repo[call_id]
            estimated_cost_of_expr = call_requirement.cost

            # Get the perturbation requirements for this call.
            perturbation_dependencies = self.perturbation_dependencies_repo[call_id]
            assert isinstance(perturbation_dependencies, PerturbationDependencies)
            # "1 + 2 * number of dependencies" because of the double sided delta.
            num_perturbation_dependencies = len(perturbation_dependencies.dependencies)
            num_perturbations = (2 if is_double_sided_deltas else 1) * num_perturbation_dependencies
            num_evaluations = 1 + num_perturbations

            # Cost is cost of doing it once, times the number of times it needs doing.
            costs[call_id] = num_evaluations * estimated_cost_of_expr
            counts[call_id] = num_evaluations

        return counts, costs