<?php
namespace Bundles\Portfolios\Controller;
use App\Entity\CurrencyHistory;
use App\Models\systems\broker\Corpay;
use Bundles\AiStudio\forms\PolicyType1;
use Bundles\AiStudio\forms\PolicyType2;
use Bundles\Instruments\CashflowForward\Entity\ResidualCash;
use Bundles\RemoteSystems\Controller\CorpayController;
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
use Bundles\Portfolios\Form\PortfolioType;
use Bundles\Instruments\CashflowForward\Form\HedgingPoliticsType;
use Bundles\Portfolios\Entity\Portfolio;
use Bundles\Instruments\CashflowForward\Entity\HedgingPolitics;
use Bundles\Instruments\CashflowForward\Entity\BudgetRates;
use Bundles\Instruments\Base\Entity\CashFlows;
use Bundles\Instruments\Base\Entity\Forward;
use Bundles\Instruments\Base\Entity\ForwardOperations;
use Bundles\Instruments\CashflowForward\Entity\PolicyGenerator;
use Bundles\Instruments\CashflowForward\Form\BudgetRateType;
use Bundles\Instruments\CashflowForward\Form\CashFlowType;
use Bundles\Instruments\Base\Form\ForwardType;
use Bundles\Instruments\CashflowForward\Model\HedgingPolicy;
use Bundles\Instruments\CashflowForward\Form\PolicyGeneratorType;
use App\Entity\CurrencyPair;
use Bundles\Instruments\CashflowForward\Form\TradesOperationsType;
use App\Models\ForwardPoints;
use Symfony\Component\HttpFoundation\JsonResponse;
use Bundles\Instruments\Base\Model\CalcsVaR;
use App\Entity\Counterparty;
use Bundles\Instruments\Base\Entity\ReportFilter;
use Bundles\Instruments\Base\Form\ReportFilterType;
use Bundles\Instruments\CashflowForward\Model\ImportValidator;
use Doctrine\DBAL\Connection;
use Psr\Cache\CacheItemPoolInterface;
class PortfolioCashflowForwardController extends PortfolioController {
protected $export = false;
protected $url = '/portfolios/cashflow-forward?menuIndex=1';
protected $importDataOverwrite = false;
protected $importDataAsSpotTrades = false;
protected $importCorpay = false;
public function index(LoggerInterface $analyticsLogger, CacheItemPoolInterface $cache, Request $request, Connection $connection) {
$this->init($analyticsLogger, $cache, $request, $connection);
if (!empty($this->portfolioId)) {
$this->session->set('portfolioId', $this->portfolioId);
$userPortfolios = $this->getUser()->getUserPortfolios();
if ($this->getUser()->isUser() && !$userPortfolios->isEmpty()) {
$portfolio = $userPortfolios->filter(function ($portfolio) {
return $portfolio->getId() == $this->portfolioId;
});
$portfolio = $portfolio[0] ?? null;
} else {
$portfolio = $this->em->getRepository(Portfolio::class)->findOneBy([
'id' => $this->portfolioId,
'status' => 1,
'customer' => $this->getCustomer()->getId(),
]);
}
if (empty($portfolio) || $portfolio->getType() !== 'cashflow_forward') {
$this->addFlash('warning', 'portfolio id '.$this->portfolioId.' not found or not allowed to access');
return $this->redirect($this->url.'&step='.self::STEP_PORTFOLIO);
}
$this->data['portfolio'] = $portfolio;
}
$this->data['sumGroup'] = $this->sumGroup ?? 'month';
$this->data['tradeFilter'] = $this->tradeFilter ?? '';
$this->getPortfolios($request);
if ($this->step > self::STEP_PORTFOLIO) {
$this->getPolicies($request);
if ($this->redirect) {
return $this->redirect($this->url.'&step='.$this->step);
}
$this->getBudgetRates($request);
$this->getCashFlows($request);
$this->getTrades($request);
$this->getTradesOperations($request);
// $this->getResidualCash($request);
$this->getAnalysis();
$this->getReport($request);
$this->notificationsAsSeen();
}
if (!empty($this->export)) {
return $this->export;
}
if ($this->redirect) {
return $this->redirect($this->url.'&step='.$this->step);
}
$this->data['summaryTotals'] = $this->getModel()->getSummaryTotals();
$this->data['step'] = $this->step;
$this->data['title'] = 'Portfolios';
$this->data['action'] = 'portfolio';
$this->data['edit'] = $this->edit;
$this->data['url'] = $this->url;
$this->data['show'] = $this->show;
$this->data['operation'] = $this->tradeOperation ?? null;
$this->data['tradeTypes'] = array_flip($this->tradeTypes);
$instrument = '@cashflowForward';
// dump($this->data);
if (!empty($this->getCustomer()->isUseRemoteApi()) && !empty($this->getCustomer()->getRemoteUserCode()) && $this->getUser()->isAccessRemoteSystems()) {
$this->data['remoteSystem'] = $this->getCustomer()->getRemoteSystem();
}
return $this->render($instrument.'/index.html.twig', $this->data);
}
protected function getPolicies($request) {
if (empty($this->portfolioId) || $this->redirect) {
return false;
}
$step = self::STEP_POLICY;
$formGenerator = $this->getForm('policyGenerator');
$formGenerator->handleRequest($request);
if ($formGenerator->isSubmitted() && $formGenerator->isValid()) {
$currencyPairSelected = $this->data['portfolio']->getCurrencyPair();
$existsCurrency = $this->em->getRepository(HedgingPolitics::class)->findOneBy([
'Portfolio' => $this->portfolioId,
'CurrencyPair' => $currencyPairSelected,
]);
if (!empty($existsCurrency) && $existsCurrency->getModel() != 'static_policy') {
$this->addFlash('error', 'Selected currency pair '.$currencyPairSelected->getName().' is already in policy. Delete existing or add policy manually');
} else {
$policyGenerator = $formGenerator->getData();
$policyGenerator->setCurrencyPair($currencyPairSelected);
$policyGenerator->setExposureDirection($this->data['portfolio']->getDirection() == 'pay' ? 'Down' : 'Up');
$policyRanges = $this->getModel()->generatePolicies($policyGenerator);
if (!empty($policyRanges)) {
if (!empty($existsCurrency)) {
$this->actionsPolicies($policyGenerator, [], 'deleteAll'); //trinam sena
}
$this->actionsPolicies($policyGenerator, $policyRanges);
}
}
}
$this->data['formGeneratorPolicy'] = $formGenerator->createView();
$form = $this->getForm($step);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->actionsPolicies($form);
}
if (!empty($this->policyId)) {
$this->data['policy'] = $this->em->getRepository(HedgingPolitics::class)->find($this->policyId);
}
$policies = $this->em->getRepository(HedgingPolitics::class)->findBy(['Portfolio' => $this->portfolioId], ['boundaryLow' => 'ASC']);
if (!empty($policies) && $policies[0]->getModel() == 'dynamic_policy') {
$currentRate = $this->getModel()->getRate($this->data['portfolio']->getCurrencyPair());
$needRegenerate = true;
foreach ($policies as &$policy) {
if ($currentRate >= (float)$policy->getBoundaryLow() && $currentRate < (float)$policy->getBoundaryUpper()) {
$policy->setCurrentRate($currentRate);
$this->currrentHedgingPolicy = $policy;
$needRegenerate = false;
break;
}
}
if (!empty($request->get('regeneratePolicy'))) {
$needRegenerate = true;// inicijuota naudotojo, forcinam perkurima
}
if ($needRegenerate) {
$formGenerator = new PolicyGenerator();
$formGenerator->setCurrencyPair($this->data['portfolio']->getCurrencyPair());
$formGenerator->setExposureDirection($this->data['portfolio']->getDirection() == 'pay' ? 'Down' : 'Up');
$formGenerator->setStepRange(1.5);
$formGenerator->setNumberRanges(9);
$policyRanges = $this->getModel()->generatePolicies($formGenerator);
if (!empty($policyRanges)) {
$this->actionsPolicies($formGenerator, [], 'deleteAll'); //trinam sena
$this->actionsPolicies($formGenerator, $policyRanges); //kuriam nauja
}
}
}
// static policy turi kas menesi atsinaujinti schedule
elseif (!empty($policies) && $policies[0]->getModel() == 'static_policy') {
$policy = $policies[0];
$schedule = $policy->getSchedule();
if ($schedule[1]['executionDate'] < date_create()->format('Y-m-d')) {
$formGenerator = $this->getForm('policyGenerator');
$formGenerator->getData()->setMonths(count($policy->getSchedule()));
$formGenerator->getData()->setModel($policy->getModel());
$policy->setSchedule($this->getModel()->generatePolicies($formGenerator->getData()));
$this->em->persist($policy);
$this->em->flush();
$this->addFlash('success', 'Static policy schedule is updated');
}
$policies = [$policy];
}
$this->data['policies'] = $policies;
$this->data['policyModel'] = count($policies) == 1 ? 'static_policy' : 'dynamic_policy';
$this->data['form'.$step] = $this->getForm($step, ($this->data['policy'] ?? null))->createView();
$options = [
'customerPairs' => [$this->data['portfolio']->getCurrencyPair()->getId() ?? []],
];
$this->data['policyForm2'] = $this->createForm(PolicyType2::class, null, $options)->createView();
$this->data['symbol'] = $this->data['portfolio']->getCurrencyPair()->getCurrencyNameByNumber(2);
}
protected function getBudgetRates($request) {
if (empty($this->portfolioId) || $this->redirect) {
return false;
}
$step = self::STEP_BUDGET_RATES;
$form = $this->getForm($step, null, ['portfolioId' => $this->portfolioId]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->actionsBudgetRates($form);
}
if (!empty($this->budgetRateId)) {
$this->data['budgetRate'] = $this->em->getRepository(BudgetRates::class)->find($this->budgetRateId);
}
$this->data['budgetRates'] = $this->em->getRepository(BudgetRates::class)->findByDates([
'Portfolio' => $this->portfolioId,
// 'show' => $this->show,
]);
$this->data['form'.$step] = $this->getForm($step, ($this->data['budgetRate'] ?? null))->createView();
}
protected function getCashFlowByTrade($trade) {
return $this->em->getRepository(CashFlows::class)->findByFixedDates([
'Portfolio' => $trade->getPortfolio()->getId(),
'dateFrom' => date_create($trade->getDateDelivery())->format('Y-m-01'),
'dateTo' => date_create($trade->getDateDelivery())->format('Y-m-t'),
]);
}
protected function getCashFlows($request) {
if (empty($this->portfolioId) || empty($this->data['portfolio']) || $this->redirect) {
return false;
}
$step = self::STEP_CASH_FLOWS;
$form = $this->getForm($step, null, ['portfolioId' => $this->portfolioId]);
$form->handleRequest($request);
$importDataCashFlow = $request->request->get('importDataCashFlow');
$this->importDataOverwrite = $request->request->get('dataOverwriteCashFlow');
if (($form->isSubmitted() && $form->isValid()) || !empty($importDataCashFlow)) {
$this->actionsCashFlows($form, $importDataCashFlow);
}
if (!empty($this->cashFlowId)) {
$cashFlow = $this->em->getRepository(CashFlows::class)->find($this->cashFlowId);
$cashFlow->setRate($this->getRate($cashFlow->getCurrencyPair()));
$this->data['cashFlow'] = $cashFlow;
//dump($cashFlow);
}
$cashFlows = $this->em->getRepository(CashFlows::class)->findByDates([
'Portfolio' => $this->portfolioId,
'show' => $this->show,
]);
$this->data['cashFlowSumAmount'] = $this->getModel()->getCashFlowSums($cashFlows, 'minor');
$this->data['cashFlowSumAmountMajor'] = $this->getModel()->getCashFlowSums($cashFlows);
$this->data['cashFlows'] = $this->getModel()->setCashFlowBudgetRates($cashFlows, $this->data['budgetRates']);
$this->data['eventAlerts'] = $this->getEventAlertStatuses();
$this->data['marginCalls'] = $this->marginCalls;
$this->data['spot'] = $this->getRate($this->data['portfolio']->getCurrencyPair());
$this->data['form'.$step] = $this->getForm($step, ($this->data['cashFlow'] ?? null))->createView();
if ($form->isSubmitted()) {
return $this->redirect($this->url.'&step='.$step);
}
}
protected function getTrades($request) {
if (empty($this->portfolioId) || empty($this->data['portfolio']) || $this->redirect) {
return false;
}
$step = self::STEP_TRADES;
$options = ['portfolioId' => $this->portfolioId, 'customer' => $this->getCustomer()->getId()];
$form = $this->getForm($step, null, $options);
$form->handleRequest($request);
$importDataTrades = $request->request->get('importDataTrades');
$this->importCorpay = $request->request->get('importCorpay');
$this->importDataOverwrite = $request->request->get('dataOverwriteTrades');
$this->importDataAsSpotTrades = $request->request->get('importSpotTrades') ?? false;
if (($form->isSubmitted() && $form->isValid()) || !empty($importDataTrades)) {
$this->actionsTrades($form, $importDataTrades);
}
if (!empty($this->tradeId)) {
$this->data['trade'] = $this->em->getRepository(Forward::class)->find($this->tradeId);
}
$tradesOrigin = $this->em->getRepository(Forward::class)->findByDates([
'Portfolio' => $this->portfolioId,
'show' => $this->show,
'tradeFilter' => $this->tradeFilter,
'type' => 'forward',
]);
$spotTrades = $this->em->getRepository(Forward::class)->findByDates([
'Portfolio' => $this->portfolioId,
'show' => $this->show,
'tradeFilter' => $this->tradeFilter,
'type' => 'spot',
]);
$cashFlows = $this->em->getRepository(CashFlows::class)->findByDates([
'Portfolio' => $this->portfolioId,
'show' => $this->show,
]);
/// rikia pasiaiskinti kas kviecia. Nezinau ar dar reikalingas!
if (!empty($request->get('updatePolicy'))) {
//pasiimam tradus
$allTrades = $this->em->getRepository(Forward::class)->findBy([
'status' => true,
'show' => 'current',
'type' => 'forward',
]);
$policies = $this->em->getRepository(HedgingPolitics::class)->findBy(['Portfolio' => $this->portfolioId], ['boundaryLow' => 'ASC']);
if (empty($policies)) {
return true;
}
foreach ($allTrades as $item) {
$currentRate = $item->getRate();
foreach ($policies as &$policy) {
if ($currentRate >= (float)$policy->getBoundaryLow() && $currentRate < (float)$policy->getBoundaryUpper()) {
$this->currrentHedgingPolicy = $policy;
break;
}
}
$dateExpire = date_create($item->getDateDelivery());
$hedgePolicyRatio = $this->getHedgePolicy($this->currrentHedgingPolicy, $dateExpire->format('Y'), $dateExpire->format('W'));
$item->setPolicyRatio($hedgePolicyRatio);
$this->em->persist($item);
}
$this->em->flush();
}
$this->data['trades'] = $tradesOrigin;
$this->data['spotTrades'] = $spotTrades;
$this->data['tradeFilter'] = $this->tradeFilter;
foreach ($tradesOrigin as $item) {
$this->data['tradeList'][$item->getId()] = $item;
}
$this->data['portfolioOperations'] = $this->em->getRepository(ForwardOperations::class)->findAllByPortfolio($this->portfolioId, 'DESC');
$this->data['valuation'] = $this->getModel()->tradeValuation($this->data['trades'], 0, [], $cashFlows, $this->sumGroup);
$this->data['form'.$step] = $this->getForm($step, ($this->data['trade'] ?? null), $options)->createView();
if ($form->isSubmitted()) {
return $this->redirect($this->url.'&step='.$step);
}
}
/**
* @param PolicyGenerator $policyGenerator
* @param $policyRanges
* @return false|void
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
protected function actionsPolicies($form, $policyRanges = [], $action = '') {
$formData = $form;
if (empty($action) && !empty($form) && method_exists($form, 'getData')) {
$formData = $form->getData();
$action = $form->get('delete')->isClicked() ? 'delete' : 'add';
if ($form->get('deleteAll')->isClicked()) {
$action = 'deleteAll';
}
$policyId = $formData->getId();
if (!empty($policyId)) {
$policyRecord = $this->em->getRepository(HedgingPolitics::class)->find($policyId);
}
} elseif (!empty($policyRanges)) {
$action = 'autoPolicy';
}
if ($action == 'delete') {
if (empty($policyId)) {
$this->addFlash('warning', 'No policys ID submited');
return false;
}
if (empty($policyRecord)) {
$this->addFlash('warning', 'No policy record was found');
return false;
}
//todo: padaryti isjungima per status=0
$this->em->remove($policyRecord);
$this->em->flush();
$this->addFlash('success', 'Hedging policy was deleted successfully');
} elseif ($action == 'deleteAll') {
$records = $this->em->getRepository(HedgingPolitics::class)->findBy([
'Portfolio' => $this->portfolioId,
]);
foreach ($records as $record) {
$this->em->remove($record);
}
$this->em->flush();
// $this->addFlash('success', 'Hedging policies were deleted successfully for the currency pair '.$policyRecord->getCurrencyPair()->getName());
} elseif ($action == 'autoPolicy' && !empty($formData->getModel())) { //generated policy
if ($formData->getModel() == 'static_policy') {
$policyRecord = new HedgingPolitics();
$policyRecord->setCustomer($this->getCustomer());
$policyRecord->setPortfolio($this->data['portfolio']);
$policyRecord->setCurrencyPair($formData->getCurrencyPair());
$policyRecord->setModel($formData->getModel());
$policyRecord->setSchedule($policyRanges);
$this->em->persist($policyRecord);
} else {
foreach ($policyRanges as $policy) {
$policyRecord = new HedgingPolitics();
$policyRecord->setCustomer($this->getCustomer());
$policyRecord->setPortfolio($this->data['portfolio']);
$policyRecord->setCurrencyPair($formData->getCurrencyPair());
$policyRecord->setBoundaryLow($policy['low']);
$policyRecord->setBoundaryUpper($policy['high']);
$policyRecord->setd30($policy['30']);
$policyRecord->setd60($policy['60']);
$policyRecord->setd90($policy['90']);
$policyRecord->setd120($policy['120']);
$policyRecord->setd150($policy['150']);
$policyRecord->setd180($policy['180']);
$policyRecord->setModel($formData->getModel());
$this->em->persist($policyRecord);
}
}
$this->em->flush();
$this->addFlash('success', 'Policy was created successfully');
$this->redirect = true;
} elseif ($formData->getModel() != 'static_policy') { //add/edit
if (empty($this->portfolioId)) {
$this->addFlash('warning', 'No Policy found');
return false;
}
if (empty($policyRecord)) {
$policyRecord = new HedgingPolitics();
$policyRecord->setCustomer($this->getCustomer());
$policyRecord->setPortfolio($this->data['portfolio']);
}
$policyRecord->setCurrencyPair($formData->getCurrencyPair());
$policyRecord->setBoundaryLow($formData->getBoundaryLow());
$policyRecord->setBoundaryUpper($formData->getBoundaryUpper());
$policyRecord->setd30($formData->getd30());
$policyRecord->setd60($formData->getd60());
$policyRecord->setd90($formData->getd90());
$policyRecord->setd120($formData->getd120());
$policyRecord->setd150($formData->getd150());
$policyRecord->setd180($formData->getd180());
$this->em->persist($policyRecord);
$this->em->flush();
$this->addFlash('success', 'Policy was '.($policyId > 0 ? 'updated' : 'created').' successfully');
$this->redirect = true;
}
}
/**
* @param $form
* @return bool|void
*/
protected function actionsBudgetRates($form) {
$action = $form->get('delete')->isClicked() ? 'delete' : 'add';
$formData = $form->getData();
$budgetRateId = $formData->getId();
if (!empty($budgetRateId)) {
$budgetRateRecord = $this->em->getRepository(BudgetRates::class)->find($budgetRateId);
}
if ($action == 'delete') {
if (empty($budgetRateId)) {
$this->addFlash('warning', 'No budget rate ID submited');
return false;
}
if (empty($budgetRateRecord)) {
$this->addFlash('warning', 'No budget rate record was found');
return false;
}
//todo: padaryti isjungima per status=0
$this->em->remove($budgetRateRecord);
$this->em->flush();
$this->addFlash('success', 'Budget rate was deleted successfully');
$this->redirect = true;
} else { //add/edit
if (empty($this->portfolioId)) {
$this->addFlash('warning', 'No portfolio found');
return false;
}
if (empty($budgetRateId)) {
$budgetRateRecord = new BudgetRates();
$budgetRateRecord->setPortfolio($this->data['portfolio']);
}
$budgetRateRecord->setCurrencyPair($formData->getCurrencyPair());
$budgetRateRecord->setDateStart($formData->getDateStart());
$budgetRateRecord->setDateEnd($formData->getDateEnd());
$budgetRateRecord->setRate($formData->getRate());
$budgetRateRecord->setStatus(true);
$this->em->persist($budgetRateRecord);
$this->em->flush();
$this->addFlash('success', 'budget rate was '.($budgetRateId > 0 ? 'updated' : 'created').' successfully');
$this->redirect = true;
}
}
/**
* @param $form
* @return bool|void
*/
protected function actionsCashFlows($form, $importData = '') {
$this->portfolioId = $this->session->get('portfolioId');
$action = $form->get('delete')->isClicked() ? 'delete' : 'add';
$formData = $form->getData();
$cashFlowId = $formData->getId();
if (!empty($cashFlowId)) {
$cashFlowRecord = $this->em->getRepository(CashFlows::class)->find($cashFlowId);
}
if ($action == 'delete') {
if (empty($cashFlowId)) {
$this->addFlash('warning', 'No cash flow ID submited');
return false;
}
if (empty($cashFlowRecord)) {
$this->addFlash('warning', 'No cash flow record was found');
return false;
}
//todo: padaryti isjungima per status=0
$this->em->remove($cashFlowRecord);
$this->em->flush();
$this->addFlash('success', 'Cash flow record was deleted successfully');
$this->redirect = true;
} elseif (empty($importData)) { //add/edit
if (empty($this->portfolioId)) {
$this->addFlash('warning', 'No portfolio found');
return false;
}
if (empty($cashFlowId)) {
$cashFlowRecord = new CashFlows();
$cashFlowRecord->setPortfolio($this->data['portfolio']);
}
$cashFlowRecord->setCurrencyPair($formData->getCurrencyPair());
$cashFlowRecord->setFlowDate($formData->getFlowDate());
$cashFlowRecord->setCashAmountMinor($formData->getCashAmountMinor());
$cashFlowRecord->setComment($formData->getComment());
$cashFlowRecord->setStatus(true);
$this->em->persist($cashFlowRecord);
$this->em->flush();
$this->addFlash('success', 'Cash flow was '.($cashFlowId > 0 ? 'updated' : 'created').' successfully');
$this->redirect = true;
} elseif (!empty($importData)) {
try {
$importDataList = json_decode($importData, true, 512, JSON_THROW_ON_ERROR);
if (empty($importDataList)) {
return false;
}
$recordNr = 0;
$successSaved = [];
$allErrors = [];
$portfolioCurrency = $this->data['portfolio']->getCurrencyPair();
foreach ($importDataList as $item) {
$recordNr++;
$errors = $this->getImportValidatorModel()->validImportDataCashFlow($item, $portfolioCurrency);
if (!empty($errors)) {
$allErrors[$recordNr] = $errors;
continue;
}
$date = date_create($item[0])->format('Y-m-d');
$cashFlowRecord = $this->em->getRepository(CashFlows::class)->findOneBy([
'flowDate' => $date,
'Portfolio' => $this->data['portfolio']->getId(),
]);
if (empty($cashFlowRecord) || empty($this->importDataOverwrite)) {
$cashFlowRecord = new CashFlows();
}
$cashFlowRecord->setPortfolio($this->data['portfolio']);
$cashFlowRecord->setCurrencyPair($portfolioCurrency);
$cashFlowRecord->setFlowDate($date);
$amountMinor = (int)$item[2];
if ($amountMinor < 0 && $cashFlowRecord->getCashAmountMinor() + $amountMinor < 0) {
$allErrors[$recordNr][] = 'Incorrect amount value';
continue;
}
if ($amountMinor < 0 && $cashFlowRecord->getCashAmountMinor() >= $amountMinor) { //jei ivesta neigiama, isminusuojam is esamo
$amountMinor = (int)$cashFlowRecord->getCashAmountMinor() + $amountMinor;
}
$cashFlowRecord->setCashAmountMinor($amountMinor);
$cashFlowRecord->setRate(1);
$cashFlowRecord->setComment(trim(($item[4] ?? ''), '"'));
$cashFlowRecord->setStatus(true);
$this->em->persist($cashFlowRecord);
$successSaved[] = $recordNr;
}
$this->em->flush();
} catch (\Exception $e) {
$this->addFlash('warning', 'Internal error, import data process stopped.'.$e->getMessage());
}
if (!empty($successSaved)) {
$this->addFlash('success', 'Import data created successfully for '.count($successSaved).' records from '.count($importDataList));
$this->redirect = true;
}
if (!empty($allErrors)) {
$errorString = 'Errors found on these records:<br>';
foreach ($allErrors as $id => $errors) {
$errorString .= 'Record nr. '.$id.'<br>';
foreach ($errors as $key => $error) {
$errorString .= 'Column '.$key.': '.$error.'<br>';
}
$errorString .= '<hr>';
}
$this->addFlash('warning', $errorString);
}
}
}
/**
* @param $form
* @return bool|void
*/
protected function actionsTrades($form, $importData = []) {
$action = $form->get('delete')->isClicked() ? 'delete' : 'add';
$formData = $form->getData();
$tradeId = $formData->getId();
if (!empty($tradeId)) {
$tradeRecord = $this->em->getRepository(Forward::class)->find($tradeId);
}
if ($action == 'delete') {
if (empty($tradeId)) {
$this->addFlash('warning', 'No trade ID submited');
return false;
}
if (empty($tradeRecord)) {
$this->addFlash('warning', 'No trade record was found');
return false;
}
//todo: padaryti isjungima per status=0
$this->em->remove($tradeRecord);
$this->em->flush();
$this->addFlash('success', 'Trade record was deleted successfully');
} elseif (empty($importData)) { //add/edit
if (empty($this->portfolioId)) {
$this->addFlash('warning', 'No portfolio found');
return false;
}
if (empty($tradeId)) {
$tradeRecord = $formData;
$tradeRecord->setPortfolio($this->data['portfolio']);
}
$tradeRecord->setCurrencyPair($formData->getCurrencyPair());
$tradeRecord->setAmount($formData->getAmount());
$tradeRecord->setAmountMinor($formData->getAmountMinor());
$tradeRecord->setDateExpire(date('Y-m-d'));
$tradeRecord->setDateDelivery($formData->getDateDelivery());
$tradeRecord->setExposureDirection($formData->getExposureDirection());
$tradeRecord->setStatus(true);
$tradeRecord->setStrike($formData->getStrike());
$tradeRecord->setCounterparty($formData->getCounterparty());
$tradeRecord->setComment($formData->getComment());
$tradeRecord->setTradeNumber($formData->getTradeNumber());
$tradeRecord->setType($formData->getType());
$tradeRecord->setRate($this->getRate($tradeRecord->getCurrencyPair()));
$hedgePolicyRatio = $this->getModel()->getHedgeRequired($tradeRecord->getDateDelivery(), $tradeRecord->getCurrencyPair(), $tradeRecord->getPortfolio());
$tradeRecord->setPolicyRatio($hedgePolicyRatio / 100);
$this->em->persist($tradeRecord);
$this->em->flush();
$this->addFlash('success', 'Trade was '.($tradeId > 0 ? 'updated' : 'created').' successfully');
$this->redirect = true;
} elseif (!empty($importData)) {
$importData = str_replace('\r', '', $importData);
try {
$importDataList = json_decode($importData, true, 512, JSON_THROW_ON_ERROR);
if (empty($importDataList)) {
return false;
}
$recordNr = 0;
$successSaved = [];
$allErrors = [];
$portfolioCurrency = $this->data['portfolio']->getCurrencyPair();
$counterparties = $this->em->getRepository(Counterparty::class)->findBy(['Customer' => $this->getCustomer()]);
$brokerForwardsData = $this->cache->getItem('broker_forwards_'.$this->getUser()->getId())->get() ?? [];
foreach ($importDataList as $item) {
$recordNr++;
$errors = $this->getImportValidatorModel()->validImportDataTrades($item, $portfolioCurrency, $counterparties);
if (!empty($errors)) {
$allErrors[$recordNr] = $errors;
// dump($counterparties);
continue;
}
$date = date_create($item[0]);
if (!is_object($date)) {
$date = date_create(str_replace('/', '-', $item[0]));
}
$date = $date->format('Y-m-d');
$tradeRecord = $this->em->getRepository(Forward::class)->findOneBy([
'tradeNumber' => $item[1],
'Portfolio' => $this->data['portfolio']->getId(),
]);
if (empty($tradeRecord) || empty($this->importDataOverwrite)) {
$tradeRecord = new Forward();
}
$tradeRecord->setPortfolio($this->data['portfolio']);
$tradeRecord->setCurrencyPair($portfolioCurrency);
$amountMinor = (int)$item[3];
if ($amountMinor < 0 && $tradeRecord->getAmountMinor() + $amountMinor < 0) {
$allErrors[$recordNr][] = 'Incorrect amount value';
continue;
}
if ($amountMinor < 0 && $tradeRecord->getAmountMinor() >= $amountMinor) { //jei ivesta neigiama, isminusuojam is esamo
$amountMinor = (int)$tradeRecord->getAmountMinor() + $amountMinor;
}
$hedgeRate = (float)str_replace(',', '.', $item[4]);
$tradeRecord->setAmount($amountMinor / $hedgeRate);
$tradeRecord->setAmountMinor($amountMinor);
$tradeRecord->setDateExpire(date('Y-m-d'));
$tradeRecord->setDateDelivery($date);
$tradeRecord->setExposureDirection($this->data['portfolio']->getDirection());
$tradeRecord->setStatus(true);
$tradeRecord->setStrike($hedgeRate);
$tradeRecord->setTradeNumber($item[1]);
if (!empty($this->importCorpay)) {
$tradeRecord->setType('spot');
if (strpos($item[1], 'FD') !== false) { //OFD, EFD - forwardai
$tradeRecord->setType('forward');
}
$tradeRecord->setUsedRemoteSystem(true);
if (!empty($brokerForwardsData)) {
foreach ($brokerForwardsData as $forward) {
if ($forward['ordNum'] == $tradeRecord->getTradeNumber()) {
$tradeRecord->setBrokerTradeData(json_encode($forward));
break;
}
}
}
} else {
$tradeRecord->setType($this->importDataAsSpotTrades ? 'spot' : 'forward');
}
$dateExpire = date_create($tradeRecord->getDateExpire());
$tradeRecord->setRate($this->getRate($tradeRecord->getCurrencyPair(), $dateExpire->format('Y-m-d')));
$hedgePolicyRatio = $this->getHedgePolicy($this->currrentHedgingPolicy, $dateExpire->format('Y'), $dateExpire->format('W'));
$tradeRecord->setPolicyRatio($hedgePolicyRatio);
$counterpartyName = trim($item[5] ?? 0, '"');
$counterParty = $this->getModel()->findCounterParty($counterparties, $counterpartyName);
if (!empty($counterParty)) {
$tradeRecord->setCounterparty($counterParty);
}
$tradeRecord->setComment($item[6] ?? '');
$this->em->persist($tradeRecord);
$this->em->flush();
$successSaved[] = $recordNr;
}
} catch (\Exception $e) {
$this->addFlash('warning', 'Internal error, process stopped.'.$e->getMessage());
}
if (!empty($successSaved)) {
$this->addFlash('success', 'Import data created successfully for '.count($successSaved).' records from '.count($importDataList));
}
if (!empty($allErrors)) {
if (empty($successSaved)) {
$errorString = '<p>Global errors are:</p>';
// $this->addFlash('warning', $errorString);
$globalErrors = '';
$errorList = $allErrors[1] ?? [];
if (!empty($errorList['counterparty'])) {
$globalErrors .= '<li>'.$errorList['counterparty'].'</li>';
}
if (!empty($errorList['currencyPair'])) {
$globalErrors .= '<li>'.$errorList['currencyPair'].'</li>';
}
if (!empty($globalErrors)) {
$errorString .= $globalErrors;
$this->addFlash('warning', $errorString);
}
} else {
$errorString = 'Errors found on these records:<br>';
$count = 0;
foreach ($allErrors as $id => $errors) {
$count++;
$errorString .= 'Record nr. '.$id.'<br>';
foreach ($errors as $key => $error) {
$errorString .= 'Column '.$key.': '.$error.'<br>';
}
$errorString .= '<hr>';
if ($count > 10) {
$errorString .= '...<br>Too many errors, only first 10 shown';
break;
}
}
$this->addFlash('warning', $errorString);
// dump($errorString);
}
} else {
$this->redirect = true;
}
}
}
protected function getForm($step, $object = null, $options = []) {
switch ($step) {
case self::STEP_PORTFOLIO:
if (empty($object)) {
$object = new Portfolio();
}
$objetType = PortfolioType::class;
break;
case self::STEP_POLICY:
if (empty($object)) {
$object = new HedgingPolitics();
}
$objetType = HedgingPoliticsType::class;
break;
case self::STEP_BUDGET_RATES:
if (empty($object)) {
$object = new BudgetRates();
}
$objetType = BudgetRateType::class;
break;
case self::STEP_CASH_FLOWS:
if (empty($object)) {
$object = new CashFlows();
}
$objetType = CashFlowType::class;
break;
case self::STEP_TRADES:
if (empty($object)) {
$object = new Forward();
}
$objetType = ForwardType::class;
break;
case 'reportFilter':
if (empty($object)) {
$object = new ReportFilter();
}
$objetType = ReportFilterType::class;
$step = self::STEP_REPORT;
break;
case 'policyGenerator':
if (empty($object)) {
$object = new PolicyGenerator();
}
$objetType = PolicyGeneratorType::class;
$step = self::STEP_POLICY;
break;
case 'tradesOperations':
if (empty($object)) {
$object = new ForwardOperations();
}
$objetType = TradesOperationsType::class;
$step = self::STEP_TRADES.'&tradeId='.$this->tradeId.'&operation='.$this->tradeOperation;
break;
}
$options['portfolioId'] = $this->portfolioId;
$options['url'] = $this->url.'&step='.$step;
return $this->createForm($objetType, $object, $options);
}
protected function getResidualCash($request) {
if (empty($this->portfolioId) || $this->redirect) {
return false;
}
if (!empty($request->query->get('deleteYears'))) {
$this->deleteYears = $request->query->get('deleteYears');
}
if (!empty($request->request->get('years')) || !empty($this->deleteYears)) {
$this->actionsResidualCash($request->request);
}
$filter = [
'Portfolio' => $this->portfolioId,
'status' => true,
];
$residualCashRecords = $this->em->getRepository(ResidualCash::class)->findBy($filter);
$residualCash = [];
if (!empty($residualCashRecords)) {
/** @var ResidualCash $item */
foreach ($residualCashRecords as $item) {
$residualCash[$item->getYears()][$item->getWeek()] = [
'funcAmount' => round($item->getFuncAmount(), 3),
'nonFuncAmount' => round($item->getNonFuncAmount(), 3),
'amountsInMajorCurr' => $item->isAmountsInMajorCurr(),
'amountsInKilos' => $item->isAmountsInKilos(),
'total' => round((float)$item->getNonFuncAmount() + (float)$item->getFuncAmount(), 3),
];
}
}
ksort($residualCash);
if (!empty($request->query->get('years'))) {
$this->data['years'] = $request->query->get('years');
}
$residualCash = $this->addHedgeInfo($residualCash);
$chartData = [];
foreach ($residualCash as $years => $weeks) {
foreach ($weeks as $week => $item) {
if (empty($item['funcAmount']) && empty($item['nonFuncAmount'])) {
continue;
}
$chartData[$years]['funcAmount'][$week] = $item['funcAmountM'];
$chartData[$years]['nonFuncAmount'][$week] = $item['nonFuncAmountM'];
$chartData[$years]['amountToHedge'][$week] = $item['amountToHedgeM'];
$chartData[$years]['hedgedAmount'][$week] = $item['hedgedAmount'];
$chartData[$years]['funcAmountAfterHedge'][$week] = $item['funcAmountAfterHedgeM'];
$chartData[$years]['nonFuncAmountAfterHedge'][$week] = $item['nonFuncAmountAfterHedgeM'];
$chartData[$years]['totalAfterHedge'][$week] = $item['totalAfterHedgeM'];
}
}
//isvesti grafikus pries trade ir po
// dump($residualCash);
// dump($chartData);
if (!empty($this->data['years'])) {
$years = (int)$this->data['years'];
} else {
$years = date('Y');
}
$this->data['middleYearWeek'] = 27;
$this->data['afterMiddleYearWeek'] = 28;
$this->data['lastYearWeek'] = 54;
if (!empty($years)) {
$this->data['middleYearWeek'] = (int)date_create($years.'-06-31')->format('W');
$this->data['afterMiddleYearWeek'] = $this->data['middleYearWeek'] + 1;
$this->data['lastYearWeek'] = (int)date_create($years.'-12-25')->format('W');
}
$this->data['residualCash'] = $residualCash;
$this->data['charts'] = $chartData;
$this->data['residualFunctionalCurrency'] = $this->data['majorCurrency'];
$this->data['residualNonFunctionalCurrency'] = $this->data['minorCurrency'];
$customerFunctionalCurrency = $this->getCustomer()->getFunctionalCurrency();
if ($customerFunctionalCurrency != $this->data['majorCurrency']) {
$this->data['residualFunctionalCurrency'] = $this->data['minorCurrency'];
$this->data['residualNonFunctionalCurrency'] = $this->data['majorCurrency'];
}
}
protected function actionsResidualCash($formData) {
$years = (int)$formData->get('years');
$funCurr = $formData->get('funCurr'); //minor valiuta
$nonFunCurr = $formData->get('nonFunCurr');
$amountsInMajorCurr = !empty($formData->get('amountsInMajorCurr'));
$amountsInKilos = !empty($formData->get('amountsInKilos'));
//trynimas
if (!empty($this->deleteYears)) {
$residualCash = $this->em->getRepository(ResidualCash::class)->findBy([
'status' => true,
'Portfolio' => $this->data['portfolio']->getId(),
'years' => $this->deleteYears,
]);
foreach ($residualCash as $item) {
$item->setStatus(false);
$this->em->persist($item);
}
$this->em->flush();
// trinam visus susijusius cashFlows tiems metams tik tam portfeliui
$repository = $this->em->getRepository(CashFlows::class);
$query = $repository->createQueryBuilder('a')->where('a.status = 1')->andWhere('a.flowDate LIKE :years')->setParameter('years', '%'.$this->deleteYears.'%')->andWhere('a.Portfolio ='.$this->data['portfolio']->getId())->getQuery();
$results = $query->getResult();
foreach ($results as $item) {
$item->setStatus(false);
$this->em->persist($item);
}
$this->em->flush();
$this->addFlash('success', 'Residual cash data for '.$this->deleteYears.' years was deleted successfully');
$this->redirect = true; //reikia perkrauti, kad cashflowsai atsinaujintu
return true;
}
if (empty($years)) {
return false;
}
$this->cashFlowSorted = [];
$cashFlows = $this->em->getRepository(CashFlows::class)->findBy([
'Portfolio' => $this->data['portfolio'],
]);
if (!empty($cashFlows)) {
foreach ($cashFlows as $item) {
$date = date_create($item->getFlowDate());
$yy = $date->format('Y');
$ww = (int)$date->format('W');
$this->cashFlowSorted[$yy][$ww][] = $item;
}
}
$amounts = $funCurr;
//Jeigu poreikis yra Major valiutos, portfelis turi buti "receive" tipo.
//Amountai imami is Major valiutos eilutes
if ($this->data['portfolio']->getDirection() == 'receive') {
$amounts = $nonFunCurr;
}
//kurimas, atnaujinimas
foreach ($amounts as $week => $amount) {
$residualCash = $this->em->getRepository(ResidualCash::class)->findOneBy([
'years' => $years,
'week' => $week,
'Portfolio' => $this->data['portfolio']->getId(),
]);
if (empty($residualCash)) {
$residualCash = new ResidualCash();
$residualCash->setPortfolio($this->data['portfolio']);
$residualCash->setYears($years);
$residualCash->setWeek($week);
}
$amount = (float)$amount;
$residualCash->setStatus(true);
$residualCash->setFuncAmount((float)$funCurr[$week]);
$residualCash->setAmountsInMajorCurr($amountsInMajorCurr);
$residualCash->setAmountsInKilos($amountsInKilos);
$residualCash->setNonFuncAmount((float)$nonFunCurr[$week]);
$this->em->persist($residualCash);
if ($amount > 0) {
//jeigu reiksme > 0, reiskias CF kurti nereikia, bet reikia patikrinti, ar nebuvo seno,
// kuri reikia nutrinti, todel ji keiciam i 0 ir tkrinam, radus - trinam
$amount = 0;
}
if (!empty($amount) && $amountsInKilos) {
$amount = $amount * 1000;
}
$this->updateCashFlowByResidualCash($years, $week, abs($amount), $amountsInMajorCurr);
}
$this->data['editedYears'] = $years;
$this->addFlash('success', 'Residual cash data for '.$years.' years was updated successfully');
$this->em->flush();
$this->redirect = true; //reikia perkrauti, kad cashflowsai atsinaujintu
}
protected function updateCashFlowByResidualCash(int $years, int $week, int $amount, $amountsInMajorCurr = false) {
//surandam kiek procentu policy siulo hedginti
// $hedgePolicy = $this->getHedgePolicy($this->currrentHedgingPolicy, $years, $week);
// if (!empty($hedgePolicy)) {
// $amount = $amount * $hedgePolicy;
// }
// kuriam nauja cash flow, tik jeigu amountas > 0
if (empty($this->cashFlowSorted[$years][$week])) {
$date = $this->getDateFromWeekNumber($years, $week);
$yearByWeek = date_create($date)->format('Y');
// jei nera amounto, ar savaite jau lipa i kitus metus, skipinam
if (empty($amount) || $yearByWeek != $years) {
return false;
}
$cashFlowRecord = new CashFlows();
$cashFlowRecord->setPortfolio($this->data['portfolio']);
$cashFlowRecord->setCurrencyPair($this->data['portfolio']->getCurrencyPair());
$cashFlowRecord->setFlowDate($date);
$direction = $this->data['portfolio']->getDirection();
if ($amountsInMajorCurr) {
//jeigu amountas ivestas major valiuta ir portfolio direction == pay, reikia ji pakonvertuoti pagal kursa, nes sistemoje visada saugojame minor valiuta
if ($direction == 'pay') {
$rate = $this->getRate($this->data['portfolio']->getCurrencyPair());
$amount = (int)($amount * $rate);
}
//jeigu amountas yra minor valiuta ir portfolio direction == receive, konvertuoti nereikia, nes sistemoje visada saugojame minor valiuta
} else {
//jeigu amountas yra major valiuta ir portfolio direction == receive, konvertuoti reikia, nes sistemoje visada saugojame minor valiuta
if ($direction == 'receive') {
$rate = $this->getRate($this->data['portfolio']->getCurrencyPair());
$amount = (int)($amount * $rate);
}
}
$cashFlowRecord->setCashAmountMinor($amount);
$cashFlowRecord->setRate(1);
$cashFlowRecord->setComment('autogenerated from residual cash insert/update');
$cashFlowRecord->setStatus(true);
$this->em->persist($cashFlowRecord);
$this->em->flush();
return true;
} else {
// atnaujinam esancius. visus isjungiam, koreguojam tik pirmaji
$updateFirstFlag = false;
/** @var CashFlows $item */
foreach ($this->cashFlowSorted[$years][$week] as $item) {
if ($updateFirstFlag || $amount == 0) {
$item->setStatus(false);
} else {
$updateFirstFlag = true;
$direction = $this->data['portfolio']->getDirection();
if ($amountsInMajorCurr) {
//jeigu amountas ivestas major valiuta ir portfolio direction == pay, reikia ji pakonvertuoti pagal kursa, nes sistemoje visada saugojame minor valiuta
if ($direction == 'pay') {
$rate = $this->getRate($this->data['portfolio']->getCurrencyPair());
$amount = (int)($amount * $rate);
}
//jeigu amountas yra minor valiuta ir portfolio direction == receive, konvertuoti nereikia, nes sistemoje visada saugojame minor valiuta
} else {
//jeigu amountas yra major valiuta ir portfolio direction == receive, konvertuoti reikia, nes sistemoje visada saugojame minor valiuta
if ($direction == 'receive') {
$rate = $this->getRate($this->data['portfolio']->getCurrencyPair());
$amount = (int)($amount * $rate);
}
}
$item->setCashAmountMinor($amount);
$item->setStatus(true);
}
$this->em->persist($item);
}
$this->em->flush();
$this->addFlash('success', 'Cash flow info updated successfully');
}
}
protected function addHedgeInfo($residualCash) {
/** @var Forward[] $trades */
$trades = $this->em->getRepository(Forward::class)->findByDates([
'Portfolio' => $this->data['portfolio'],
]);
$tradesList = [];
if (!empty($trades)) {
/** @var Forward $item */
foreach ($trades as $item) {
$year = date_create($item->getDateDelivery())->format('Y');
$week = (int)date_create($item->getDateDelivery())->format('W');
$rate = 1;
// hedge amounta reikia konvertuoti atgal i mayor valiuta, jei residual yra nurodyta, kad poreikiai mayor valiuta
if (!empty($residualCash[$year][$week]['amountsInMajorCurr'])) {
$rate = $item->getStrike();
}
$amount = (int)($item->getAmountMinor() / $rate);
if (!empty($tradesList[$year][$week])) {
$tradesList[$year][$week] += $amount;
} else {
$tradesList[$year][$week] = $amount;
}
}
}
foreach ($residualCash as $years => $weeks) {
$totalHedged = 0;
foreach ($weeks as $week => $item) {
$item['funcAmountM'] = $item['funcAmount'];
$item['nonFuncAmountM'] = $item['nonFuncAmount'];
if ($item['amountsInKilos']) {
$multiple = 1000;
$item['funcAmountM'] = $item['funcAmount'] * $multiple;
$item['nonFuncAmountM'] = $item['nonFuncAmount'] * $multiple;
}
//naudojame aktualia, gal veliau reiks pakeisti i trado turima policy, nes reali galejo buti pasikeitus nuo tradinimo dienos
$item['hedgePolicy'] = $this->getHedgePolicy($this->currrentHedgingPolicy, $years, $week);
$item['amountToHedge'] = $item['funcAmount'] < 0 ? abs($item['funcAmount']) * $item['hedgePolicy'] : 0;
$item['amountToHedgeM'] = $item['funcAmountM'] < 0 ? abs($item['funcAmountM']) * $item['hedgePolicy'] : 0;
$portfolioDirection = $this->data['portfolio']->getDirection();
if ($portfolioDirection == 'receive') {
$item['amountToHedge'] = $item['nonFuncAmount'] < 0 ? abs($item['nonFuncAmount']) * $item['hedgePolicy'] : 0;
$item['amountToHedgeM'] = $item['nonFuncAmountM'] < 0 ? abs($item['nonFuncAmountM']) * $item['hedgePolicy'] : 0;
}
$item['hedgedAmount'] = $tradesList[$years][$week] ?? 0;
$totalHedged += $item['hedgedAmount'];
if ($item['funcAmount'] + $item['nonFuncAmount'] == 0) {
$totalHedged = 0;
}
$item['totalHedged'] = $totalHedged;
$item['funcAmountAfterHedge'] = $item['funcAmount'] + $item['hedgedAmount'];
$item['funcAmountAfterHedgeM'] = $item['funcAmountM'] + $item['hedgedAmount'];
$item['nonFuncAmountAfterHedge'] = $item['nonFuncAmount'] - $item['hedgedAmount'];
$item['nonFuncAmountAfterHedgeM'] = $item['nonFuncAmountM'] - $item['hedgedAmount'];
if ($portfolioDirection == 'receive') {
$item['funcAmountAfterHedge'] = $item['funcAmount'] - $item['hedgedAmount'];
$item['funcAmountAfterHedgeM'] = $item['funcAmountM'] - $item['hedgedAmount'];
$item['nonFuncAmountAfterHedge'] = $item['nonFuncAmount'] + $item['hedgedAmount'];
$item['nonFuncAmountAfterHedgeM'] = $item['nonFuncAmountM'] + $item['hedgedAmount'];
}
$item['totalAfterHedge'] = $item['funcAmountAfterHedge'] + $item['nonFuncAmountAfterHedge'];
$item['totalAfterHedgeM'] = $item['funcAmountAfterHedgeM'] + $item['nonFuncAmountAfterHedgeM'];
$residualCash[$years][$week] = $item;
}
}
return $residualCash;
}
protected function getHedgePolicy(HedgingPolitics $currentPolicy = null, $years, $week) {
if (empty($currentPolicy)) {
return 0;
}
$period = 30;
$diff = date_diff(date_create(), date_create($this->getDateFromWeekNumber($years, $week)))->days;
if ($diff >= 180) {
$period = 180;
} elseif ($diff >= 150) {
$period = 150;
} elseif ($diff >= 120) {
$period = 120;
} elseif ($diff >= 90) {
$period = 90;
} elseif ($diff >= 60) {
$period = 60;
}
$method = 'getD'.$period;
return $currentPolicy->$method() / 100;
}
protected function getAnalysis() {
if (empty($this->portfolioId) || $this->redirect) {
return false;
}
//pasiimam is naujo portfolio tradus, nes gali buti pakeisti valuationai
$this->data['trades'] = $this->em->getRepository(Forward::class)->findByDates([
'Portfolio' => $this->portfolioId,
'show' => $this->show,
'tradeFilter' => $this->tradeFilter,
'type' => 'forward',
]);
$this->data['summary'] = $this->getModel()->totalPortfolioSummary($this->data, $this->sumGroup);
$this->data['summaryMonthly'] = $this->data['summary'];
if ($this->sumGroup != 'month') {
$this->getModel()->emptyTotals();
$this->data['summaryMonthly'] = $this->getModel()->totalPortfolioSummary($this->data, 'month');
}
$analysis = [];
if (empty($this->data['summary'])) {
return false;
}
/** @var CurrencyPair $portfolioCurrencyPair */
$portfolioCurrencyPair = $this->data['portfolio']->getCurrencyPair();
$analysis['majorCurrency'] = $portfolioCurrencyPair->getCurrencyNameByNumber(1);
$analysis['minorCurrency'] = $portfolioCurrencyPair->getCurrencyNameByNumber(2);
//todo: perdaryti ant cachiniu
$spot['bid'] = $this->getRate($this->data['portfolio']->getCurrencyPair());
$forwardPoints = [0 => $spot];
$forwardPointList = (new ForwardPoints($this->logger))->get($this->data['portfolio']->getCurrencyPair());
if (!empty($forwardPointList)) {
$forwardPoints += $forwardPointList;
} else {
//reikia siusti pranesima adminams - negavom kursu
}
$this->data['currentMarketRate'] = $spot['bid'] ?? 1; //nusistatom current spot rate
foreach ($forwardPoints as $days => $points) {
$period = date_create(' + '.$days.' days')->format('Y-m');
$analysis['forwards'][$period] = $points['bid'];
if (empty($analysis['budgetRates'][$period])) {
$analysis['budgetRates'][$period] = 'null';
$analysis['averageHedgedRate'][$period] = 'null';
}
$analysis['ratePeriods'][] = $period;
}
foreach ($this->data['summaryMonthly'] as $period => $item) {
if ($period < date('Y-m')) {
continue;
}
$analysis['periods'][] = $period;
$analysis['cashFlows'][] = isset($item['cashFlows']) ? number_format(($item['cashFlows']), 0, '', '') : 0;
$analysis['hedgeRequired'][] = !empty($item['hedgeRequiredAmount']) ? number_format($item['hedgeRequiredAmount'], 0, '', '') : 'null';
$analysis['trades'][] = isset($item['trades']) ? number_format($item['trades'], 0, '', '') : 0;
$analysis['averageHedgedRate'][$period] = $item['averageHedgedRate'] ?? 'null';
$analysis['budgetRates'][$period] = $item['budgetRate'] ?? 'null';
if (empty($this->data['currentBudgetRate']) && $analysis['budgetRates'][$period] != 'null') {
$this->data['currentBudgetRate'] = $analysis['budgetRates'][$period]; //nusistatom current budget rate
}
//jeigu reiksmiu daugiau nei metiniu forwardu, sulyginame su flowsu/tradu periodais, kad grafikas negriutu.
// Privaloma, kad butu tiek periodu, kiek yra flowsu/treidu periodu suvesta i ateiti
if (empty($analysis['forwards'][$period])) {
$analysis['forwards'][$period] = 'null';
$analysis['ratePeriods'][] = $period;
}
}
ksort($analysis['budgetRates']);
ksort($analysis['averageHedgedRate']);
$analysis['scaleMax'] = $this->getModel()->getMax($analysis['forwards'], $analysis['budgetRates'], $analysis['averageHedgedRate']);
$analysis['scaleMin'] = $this->getModel()->getMin($analysis['forwards'], $analysis['budgetRates'], $analysis['averageHedgedRate']);
$analysis['analysisMethod'] = $this->analysisMethod;
$analysis['cvarMethod'] = $this->cvarMethod;
if (!empty($this->sensitivity)) {
$this->data['sensitivity'] = $this->sensitivity;
$analysis['analysisTable'] = $this->getModel()->getAnalysisTable($spot, $this->data, $this->analysisMethod, $this->data['portfolio']->getDirection());
}
$calcVar = new CalcsVaR($this->em, $this->session);
$calcVar->calc($this->data['portfolio']->getCurrencyPair()->getId());
// $trades = $this->cvarMethod == 'full' ? [] : $analysis['trades'];
// $analysis['varChartData'] = $calcVar->calcLossAmounts($this->cvarMethod, $analysis['cashFlows'], $trades);
// $analysis['varChartDataMonthly'] = $calcVar->calcLossMonthly($analysis['periods'], $this->cvarMethod, $analysis['cashFlows'], $trades);
// if (empty($analysis['varChartDataMonthly'])) {
// $this->addFlash('No monthly currency data for this currencypair '.$this->data['portfolio']->getCurrencyPair()->getName(), 'warning');
// }
$this->data['analysis'] = $analysis;
}
protected function getReport(Request $request) {
if (empty($this->portfolioId) || $this->redirect) {
return false;
}
$reportFilter = $this->getForm('reportFilter');
$reportFilter->handleRequest($request);
if ($reportFilter->isSubmitted() && $reportFilter->isValid()) {
$reportData = $reportFilter->getData();
$this->export = $this->getReportModel()->getReport($reportData, $this->portfolioId);
if (empty($this->export)) {
$this->addFlash('warning', 'No data found. Try to change report type or period of data');
}
}
$this->data['reportForm'] = $reportFilter->createView();
}
public function tradeOperationAjax(?int $tradeId, $tradeOperation, Request $request) {
$this->initialize($request);
$trade = $this->em->getRepository(Forward::class)->find($tradeId);
if (empty($trade)) {
return new JsonResponse('trade not found', 400);
}
$portfolio = $this->em->getRepository(Portfolio::class)->findOneBy([
'id' => $trade->getPortfolio(),
'customer' => $this->getCustomer()->getId(),
]);
if (empty($portfolio)) {
return new JsonResponse('unauthorized access', 400);
}
$this->tradeId = $tradeId;
$this->tradeOperation = $tradeOperation;
$this->portfolioId = $portfolio->getId();
$object = new ForwardOperations();
$object->setRate($trade->getStrike());
$object->setAmount($trade->getAmountMinor());
$object->setTradeNumber($trade->getTradeNumber());
$object->setRate($trade->getStrike());
$form = $this->getForm('tradesOperations', $object);
$operationFormData = [
'trade' => $trade,
'operation' => $this->tradeOperation,
'form' => $form->createView(),
];
$operatinForm = $this->render('@instrumentsBase/operation_form.html.twig', $operationFormData)->getContent();
return new JsonResponse($operatinForm, 200);
}
public function cashflowAjax(?int $cashflowId, LoggerInterface $analyticsLogger, CacheItemPoolInterface $cache, Request $request, Connection $connection) {
$this->init($analyticsLogger, $cache, $request, $connection);
$cashflow = $this->em->getRepository(CashFlows::class)->find($cashflowId);
if (empty($cashflow)) {
return new JsonResponse('cashflow not found', 400);
}
$portfolio = $this->em->getRepository(Portfolio::class)->findOneBy([
'id' => $cashflow->getPortfolio(),
'customer' => $this->getCustomer()->getId(),
]);
if (empty($portfolio)) {
return new JsonResponse('unauthorized access', 400);
}
// $this->portfolioId = $portfolio->getId();
$pairId = $portfolio->getCurrencyPair()->getId();
//pasiimam jau paskaiciuota var data is sesijos
$varData = $this->session->get('var_result_pairId_'.$pairId);
$dailyVolatility = abs($varData['daily']['VaR'][95]);
$calcAll = $request->get('all') ?? 0; //jei yra get parametras, tai skaiciuojam visus aktyvius flowsus siam portfeliui
if (!empty($calcAll)) {
$qb = $this->em->createQueryBuilder();
$qb->select('c')->from(CashFlows::class, 'c')->where('c.Portfolio = :portfolio')->andWhere('c.flowDate >= :flowDate')->andWhere('c.status = 1')->setParameter('portfolio', $portfolio)->setParameter('flowDate', date_create()->format('Y-m-d'));
$cashflows = $qb->getQuery()->getResult();
} else {
$cashflows = [$cashflow];
}
// reikia surasti paskutine expire date.
$dateExpireNewest = $this->getModel()->getDateBy('new', $cashflows);
// $leftDays = $this->getModel()->getLeftDays($cashflows);
$leftDays = $request->get('spotHistory') ?? 60;
$now = date_create()->format('Y-m-d');
if ($dateExpireNewest < $now) {
$forecastDataPoints = 0;
$spot = $this->getRate($portfolio->getCurrencyPair(), $dateExpireNewest);
} else {
$forecastDataPoints = 2; //kiek dienu i ateiti prognozuosime
$spot = $this->getRate($portfolio->getCurrencyPair());
}
$result = $this->em->getRepository(CurrencyHistory::class)->getSpotsBy($pairId, $dateExpireNewest, $leftDays);
$spots = array_column($result, 'rate');
$dates = array_column($result, 'date');
if ($dateExpireNewest > $now) { //prediction idedam tik tada, jei dar nepasibaiges flowsas
$dates[] = date_create(' + 1 days')->format('Y-m-d');
$dates[] = date_create(' + 2 days')->format('Y-m-d');
}
$portfolioDirection = $portfolio->getDirection();
// skaiciuojam riskus
$riskData = $this->getModel()->getRiskCalcs($cashflows, $dailyVolatility, $portfolioDirection, $spot, $dates);
$averageHedgeRate = $riskData['averageHedgeRate'];
$serieData = [];
foreach ($dates as $key => $date) {
if (!isset($serie0)) {
$serie0 = [];
$serie1 = [];
$serie2 = [];
$serie3 = [];
}
// skaiciuojame spot rate range lyginant su praejusios dienos spot rate
$temKey = $key > 0 ? $key - 1 : 0;
$temSpot = $spots[$temKey] ?? $spot;
$spotMin = round($temSpot - ($temSpot * $dailyVolatility), 4);
$spotMax = round($temSpot + ($temSpot * $dailyVolatility), 4);
$serie0[] = ['x' => $date, 'y' => [$spotMin, $spotMax]];
$serie1[] = ['x' => $date, 'y' => $spots[$key] ?? $temSpot];
$serie2[] = ['x' => $date, 'y' => $riskData['budgetRates'][$date] ?? null];
if (!empty($averageHedgeRate)) {
$serie3[] = ['x' => $date, 'y' => $riskData['hedgeRates'][$date] ?? null];
}
if (!empty($spotMin) && $spotMin < $riskData['minRate']) {
$riskData['minRate'] = $spotMin;
}
if ($spotMax > $riskData['maxRate']) {
$riskData['maxRate'] = $spotMax;
}
}
$serieData[] = ['type' => 'rangeArea', 'name' => 'Spot range', 'data' => $serie0];
$serieData[] = ['type' => 'line', 'name' => 'Spot', 'data' => $serie1];
$serieData[] = ['type' => 'line', 'name' => 'Budget Rate', 'data' => $serie2];
if (!empty($averageHedgeRate)) {
$serieData[] = ['type' => 'line', 'name' => 'Hedge Rate', 'data' => $serie3];
}
if (empty($spots)) {
$spots = $serie1;
}
$calcs = [
'dates' => $dates,
'spotSeries' => $serieData,
'forecastDataPoints' => $forecastDataPoints,
];
$calcs = array_merge_recursive($calcs, $riskData);
$params = [
'calcs' => $calcs ?? null,
'minorCurrency' => $portfolio->getCurrencyPair()->getCurrencyNameByNumber(2),
'spots' => $spots,
];
$content = $this->render('@instrumentsBase/cashflow_details.html.twig', $params)->getContent();
$response = [
'content' => $content,
'calcs' => $calcs ?? [],
];
return new JsonResponse($response, 200);
}
protected function getBudgetRateList($data = []) {
return $data['budgetRates'];
}
/**
* @return HedgingPolicy
*/
protected function getModel($renew = false) {
if (empty($this->model) || $renew) {
$this->model = new HedgingPolicy($this->em, $this->logger);
}
return $this->model;
}
/**
* @param $renew
* @return ImportValidator
*/
protected function getImportValidatorModel($renew = false) {
if (empty($this->importValidatorModel) || $renew) {
$this->importValidatorModel = new ImportValidator();
}
return $this->importValidatorModel;
}
protected function getDateFromWeekNumber($year, $weekNumber, $dayOfWeek = 1) {
$dayOfWeek = $dayOfWeek >= 1 && $dayOfWeek <= 7 ? $dayOfWeek : 1;
$date = date_create();
$date->setISODate($year, $weekNumber, $dayOfWeek);
if ($date->format('Y') != $year && $weekNumber == 1) {
$date = date_create($year.'-01-01');
}
return $date->format('Y-m-d');
}
protected function updateCashflows($oldTrade, $newTrade = null, $action = 'extension') {
//reikia pasitikrinti ar budget rate turime tam periodui, jei ne - reik sukurti
if ($action != 'drawdown') {
$this->checkBudgetRate($oldTrade, $newTrade);
}
//kazkada reiks padaryti ir rollbacka budget rate atstatyti
return;
}
/**
* @param Forward $forwardNew
* @param Forward $forwardOld
* @return void
*/
protected function checkBudgetRate($forwardOld, $forwardNew = null) {
$budgetRateOld = $this->getModel()->getBudgetRateByDate($forwardOld->getPortfolio()->getId(), $forwardOld->getDateDelivery());
$budgetRate = $this->getModel()->getBudgetRateByDate($forwardNew->getPortfolio()->getId(), $forwardNew->getDateDelivery());
if (empty($budgetRate)) {
$bugdetRate = new BudgetRates();
$bugdetRate->setPortfolio($forwardNew->getPortfolio());
$bugdetRate->setRate($budgetRateOld ?? $forwardOld->getStrike());
$bugdetRate->setCurrencyPair($forwardOld->getCurrencyPair());
$bugdetRate->setDateStart($forwardNew->getDateDelivery());
$bugdetRate->setDateEnd(date_create($forwardNew->getDateDelivery())->modify('+1 day')->format('Y-m-d'));
$bugdetRate->setStatus(true);
$this->em->persist($bugdetRate);
$this->em->flush();
}
}
}