def check_autoflow_limits(self): ccy = self.get_ccy() key_max = f'TradingParameters/CurrencyKeys/{ccy}/FXNDF/MaximumAmountAutoFlow' max_autoflow = get_or_raise( key_max, 'Erro ao consultar limite superior para notional') if max_autoflow is not None and self.amount > max_autoflow: msg = 'Notional acima do parâmetro e-sales' raise RejectException(msg) key_min = f'TradingParameters/CurrencyKeys/{ccy}/FXNDF/MinimumAmountAutoFlow' min_autoflow = get_or_raise( key_min, 'Erro ao consultar limite inferior para notional') if min_autoflow is not None and self.amount < min_autoflow: msg = 'Notional abaixo do parâmetro e-sales' raise RejectException(msg) key_max = f'FXSupplierControl/{ccy}/MaxQuantity' max_autoflow_supplier = get_or_raise( key_max, 'Erro ao consultar limite superior para notinal (fx supplier)') if max_autoflow_supplier is not None and self.amount > max_autoflow_supplier: msg = 'Notional acima do parâmetro fx-supplier' raise RejectException(msg)
def check_dates_and_get_day_count(self): sett_date_brl = self.get_settlement_date(self.get_brl()) sett_date_ccy = self.get_settlement_date(self.get_ccy()) sett_grid_ccy = self.get_settlement_grid(self.get_ccy()) if sett_date_brl not in sett_grid_ccy: dates_str = ' '.join(map(str, sett_grid_ccy)) raise RejectException(f'Data de settlement inválida (datas válidas: {dates_str})') if sett_date_ccy not in sett_grid_ccy: dates_str = ' '.join(map(str, sett_grid_ccy)) raise RejectException(f'Data de settlement inválida (datas válidas: {dates_str})') du_brl = sett_grid_ccy.index(sett_date_brl) du_ccy = sett_grid_ccy.index(sett_date_ccy) market_type = get_or_raise(f"LegalEntities/{self.cnpj}/FXMarketType", "Erro ao consultar tipo de mercado") if market_type is None: raise RejectException(f'Erro ao consultar tipo de mercado') elif str(market_type) == '2': # cliente interbancário if du_brl != du_ccy: raise RejectException(f'Settlement descasado para operação no mercado secundário') return du_brl, du_ccy
def price(self): '''implementação do modelo de precificação de um instrumento SPOT''' brl = self.get_brl() ccy = self.get_ccy() today = self.get_today() sett_date_ccy = self.get_settlement_date(self.ccy) du_brl = self.du_brl du_ccy = self.du_ccy self.check_autoflow_limits(du_ccy) self.check_cutoff(du_ccy) dc = np.int64((sett_date_ccy - today) / np.timedelta64(1, 'D')) r_brl = self.get_settlement_rate(brl) r_ccy = self.get_settlement_rate(ccy) usd_future = self.get_usdbrl_future(self.get_bid_or_ask()) casado = self.get_casado() usdbrl_quote = usd_future - casado s_cost = self.get_scost(ccy, self.get_bid_or_ask()) s_cost_ccybrl = s_cost * ((1 + r_ccy) ** ((2 - dc) / 360.0)) / ((1 + r_brl) ** ((2 - du_brl) / 252.0)) precision = self.get_currency_precision(ccy) s_cost_ccybrl = round(s_cost_ccybrl, precision) cnpj = self.get_cnpj() spread = self.get_spread(cnpj, ccy, self.get_side(), du_ccy) if self.get_side() == 'Buy': s_client = s_cost_ccybrl - float(spread) else: s_client = s_cost_ccybrl + float(spread) if s_client <= 0.0: raise RejectException('Notional abaixo do Parâmetro e-sales') if self.result: if not self.result.has_changed(s_client, precision): raise ResultHasntChanged() s_client = round(s_client, precision) self.result = PriceSpotBase.Result() self.result.quote = s_client self.result.spread = float(spread) self.result.settlement_ccy = du_ccy self.result.settlement_brl = du_brl self.result.amount_brl = round(self.get_amount() * s_client, 2) self.result.amount_usd = round(self.get_amount() * usdbrl_quote) self.result.revenue_brl = round(self.get_amount() * float(spread), 2) self.result.settlement_date_ccy = str(sett_date_ccy) self.result.settlement_date_brl = str(self.get_settlement_date(self.brl)) self.result.usdbrl_quote = usdbrl_quote self.result.s_cost = s_cost self.result.precision = precision return self.result
def check_cutoff(self, settlement): market_type = get_or_raise(f"LegalEntities/{self.cnpj}/FXMarketType", "Erro ao consultar tipo de mercado") key = 'SpotConfig/CutOffTimes/' if market_type and str(market_type) == '2': # cliente interbancário cutoff = get_or_raise(f"{key}Secundary/Any/d{settlement}", "Erro ao consultar cutoff") else: ccy = self.get_ccy() cutoff = get_or_raise(f"{key}Primary/{ccy}/d{settlement}", f"Erro ao consultar cutoff (ccy)") if cutoff == '-': raise RejectException('Transação acima do horário de corte') hh_cutoff, mm_cutoff = map(int, cutoff.split(':')) now = get_local_time().time() # dt.datetime.now().time() if now.hour > hh_cutoff or (now.hour == hh_cutoff and now.minute > mm_cutoff): raise RejectException('Transação acima do horário de corte')
def check_max_days_to_maturity(self, days): ccy = self.get_ccy() key = f'TradingParameters/CurrencyKeys/{ccy}/FXNDF/UpperLimitDays2Maturity' limit = get_or_raise( key, f'Erro ao consultar limite máximo para vencimento ({ccy})') if days > limit: msg = 'Vencimento acima do parâmetro e-sales (moeda)' raise RejectException(msg) key = f'TradingParameters/CounterpartyKeys/{self.cnpj}/FXNDF/UpperLimitDays2Maturity' limit = get_or_raise( key, 'Erro ao consultar limite máximo para vencimento (contraparte)') if days > limit: msg = 'Vencimento acima do parâmetro e-sales (contraparte)' raise RejectException(msg)
def get_spread(self, cnpj, ccy, dc): def get_bucket(buckets): bucket = None for index in range(0, len(buckets)): if int(buckets[index]) >= dc: bucket = index break return bucket buckets = databus.get( f'ClientSpreads/CounterpartySpreads/{cnpj}/FXNDF/Buckets') spreads = databus.get( f'ClientSpreads/CounterpartySpreads/{cnpj}/FXNDF/Spreads/{ccy}/BUYSELL' ) bucket = get_bucket(buckets) if buckets is not None else None counterparty_valid_spread = True if None in (bucket, spreads) or (spreads and spreads[bucket] is None): counterparty_valid_spread = False if not counterparty_valid_spread: group_name = databus.get( f'LegalEntitiesRelationships/Groups_Spreads_FXNDF_Memberships/{cnpj}' ) if group_name is None or group_name == "": raise RejectException(f'Spread não cadastrado para CNPJ/Grupo') buckets = databus.get( f'ClientSpreads/GroupSpreads/{group_name}/FXNDF/Buckets') spreads = databus.get( f'ClientSpreads/GroupSpreads/{group_name}/FXNDF/Spreads/{ccy}/BUYSELL' ) bucket = get_bucket(buckets) if buckets is not None else None if None in (bucket, spreads) or (spreads and spreads[bucket] is None): raise RejectException('Spread não cadastrado para CNPJ/Grupo') return spreads[bucket]
def get_spread(self, cnpj, ccy, side, du_ccy): side = side.upper() # side foi colocado em upper no barramento. spreads = databus.get(f'ClientSpreads/CounterpartySpreads/{cnpj}/FXSPOT/{ccy}/{side}') if spreads is None or spreads[du_ccy] is None: group_name = databus.get(f'LegalEntitiesRelationships/Groups_Spreads_FXSPOT_Memberships/{cnpj}') if group_name is None or group_name == "": raise RejectException('Spread não cadastrado para CNPJ/Grupo') spreads = databus.get(f'ClientSpreads/GroupSpreads/{group_name}/FXSPOT/{ccy}/{side}') raise_if_none(spreads, 'Spread não cadastrado para CNPJ/Grupo') spread = spreads[du_ccy] raise_if_none(spread, 'Spread não cadastrado para CNPJ/Grupo') else: spread = spreads[du_ccy] return spread
def get_or_raise(key, message): value = databus.get(key) if value is None: raise RejectException(message) return value
def price(self): self.check_autoflow_limits() brl = self.get_brl() ccy = self.get_ccy() today = self.get_today() fixing = self.get_fixing() maturity = self.get_maturity() amount = self.get_amount() holidays_brl = get_holidays(brl) d_n = count_business_days(fixing, maturity, holidays_brl) maturity_adjusted = get_maturity_adjusted(maturity, d_n, holidays=holidays_brl) dc = count_days(today, maturity_adjusted) du = count_business_days(today, maturity_adjusted, holidays=holidays_brl) self.check_max_days_to_maturity(dc) ccy_discount_curve_name = self.get_discount_curve_name(ccy) brl_discount_curve_name = self.get_discount_curve_name(brl) ccy_curve_dates = self.get_discount_curve_dates( ccy_discount_curve_name) brl_curve_dates = self.get_discount_curve_dates( brl_discount_curve_name) side = self.get_side() if side == 'Buy': ys_brl = self.get_discount_curve_values(brl_discount_curve_name, 'bid') ys_ccy = self.get_discount_curve_values(ccy_discount_curve_name, 'offer') else: ys_brl = self.get_discount_curve_values(brl_discount_curve_name, 'offer') ys_ccy = self.get_discount_curve_values(ccy_discount_curve_name, 'bid') dus_brl = list( map(lambda x: count_business_days(today, x, holidays=holidays_brl), brl_curve_dates)) dcs_ccy = list(map(lambda x: count_days(today, x), ccy_curve_dates)) y_brl = round(interpolation(dus_brl, ys_brl, du, 252), 6) y_ccy = interpolation(dcs_ccy, ys_ccy, dc, 360) y_ccy = round(((1 + y_ccy)**(dc / 360.0) - 1) * (360.0 / dc), 6) precision = self.get_currency_precision(ccy) s_cost = self.get_scost(ccy, 'Ask' if side != 'Buy' else 'Bid') s_cost = round(s_cost, precision) f_cost = s_cost * ((1 + y_brl)**(du / 252.0)) / (1 + y_ccy * (dc / 360.0)) f_cost = round(f_cost, precision) lower_limit_revenue = self.get_lower_limit_revenue(ccy) fmin = (lower_limit_revenue / amount) * ((1 + y_brl)**(du / 252)) spread = self.get_spread(self.get_cnpj(), ccy, dc) fop = max(fmin, spread) # TODO: add ceiling signal = -1 if side == 'Sell' else 1 quote = f_cost - (fop * signal) y_ccy_custo = ((s_cost / quote) * (1 + y_brl)**(du / 252.0) - 1) * (360.0 / dc) quote = round(quote, precision) if quote <= 0.0: raise RejectException( 'Notional abaixo do parâmetro e-sales (receita)') if side == 'Buy': s_client = y_ccy_custo - y_ccy revenue = amount * abs(quote - f_cost) / ( (1 + y_brl)**(du / 252.0)) else: s_client = y_ccy - y_ccy_custo revenue = amount * abs(f_cost - quote) / ( (1 + y_brl)**(du / 252.0)) y_ccy_custo = round(y_ccy_custo, 6) s_client = round(s_client, 6) if self.result: if not self.result.has_changed(quote, precision, revenue): raise ResultHasntChanged() # cálculo do valor presente, se robo esta vendendo, valor deve ser negativo; present_value_brl = round( s_cost * (amount / (1 + y_ccy * (dc / 360.0))) * signal, 2) present_value_ccy = round(present_value_brl / s_cost, precision) # TODO: apagar esse trecho de código. O propósito desse trecho abaixo é para conseguir realizar # alguns testes no BV. if os.getenv('DATA_CONSULTA_PFE'): data_consulta_dt = dt.datetime.strptime( os.getenv('DATA_CONSULTA_PFE'), '%Y-%m-%d') log_info(f'data da consulta pdf = {data_consulta_dt}') else: data_consulta_dt = self.get_today().astype(dt.datetime) pfe_inst = PotentialFutureExposure() try: pfe_success = pfe_inst.execute(ccy=self.get_ccy(), side=self.get_side(), prazo_consumo=dc, data_consulta=data_consulta_dt) except Exception: raise # TODO: tratar erro de maneira adequada f_pfe = pfe_success.value # prep_and_get_pfe(self, dc) brl_risk = f_pfe * abs(present_value_brl) + revenue spread_risk = (1 + revenue / brl_risk)**(360.0 / dc) - 1 spread_notional = (1 + revenue / abs(present_value_brl))**(360.0 / dc) - 1 self.result = PriceNDFBase.Result() self.result.spread = fop self.result.pre_brl = y_brl self.result.cupom_ccy = y_ccy self.result.fop = fop self.result.fmin = fmin self.result.quote = quote self.result.value_brl = round(quote * amount, 2) self.result.revenue_brl = revenue self.result.present_value_brl = present_value_brl self.result.present_value_ccy = present_value_ccy self.result.brl_risk = brl_risk self.result.spread_risk = spread_risk self.result.spread_notional = spread_notional self.result.dc = dc self.result.du = du self.result.dn = d_n # business days entre fixing e settlement. self.result.s_cost = s_cost self.result.f_cost = f_cost self.result.s_client = s_client self.result.forward_points = round(quote - s_cost, precision) self.result.y_brl = y_brl self.result.y_ccy = y_ccy self.result.y_ccy_client = y_ccy_custo self.result.f_pfe = f_pfe self.result.side = side self.result.adjusted_maturity = str(maturity_adjusted) self.result.precision = precision return self.result