<?php
namespace Bundles\Portfolios\Controller;
use Bundles\AiStudio\forms\PolicyType2;
use App\Entity\CurrencyHistory;
use App\Entity\Customer;
use Bundles\AiStudio\forms\PolicyType1;
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Bundles\Portfolios\Form\PortfolioType;
use Bundles\Portfolios\Entity\Portfolio;
use Bundles\Instruments\Base\Entity\CashFlows;
use Bundles\Instruments\Base\Entity\Forward;
use Bundles\Instruments\Base\Entity\ForwardOperations;
use Bundles\Instruments\Base\Form\CashFlowType;
use Bundles\Instruments\CommittedForward\Form\ForwardType;
use Bundles\Instruments\CommittedForward\Model\HedgingPolicy as HedgingPolicyModel;
use App\Entity\CurrencyPair;
use Bundles\Instruments\CommittedForward\Form\TradesOperationsType;
use App\Models\Spot;
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\CommittedForward\Model\ImportValidator;
use Doctrine\DBAL\Connection;
use Bundles\Messenger\Entity\Event;
use Bundles\Messenger\Model\Actions;
use Psr\Cache\CacheItemPoolInterface;
class PortfolioCommittedForwardController extends PortfolioController {
/** @var AdapterInterface */
protected $model;
protected $skipOtherTabs = false;
protected $export = false;
protected $url = '/portfolios/committed-forward?action=list&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 ($this->step == self::STEP_PORTFOLIO) {
$this->portfolioId = 0;
$this->session->remove('portfolioId');
}
$this->portfolioId = $request->get('portfolioId') ?? (int)$this->session->get('portfolioId');
$this->cashFlowId = $request->get('cashFlowId') ?? 0;
$this->tradeId = $request->get('tradeId') ?? 0;
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() !== 'committed_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->getCashFlows($request);
$this->getPolicies();
$this->getTrades($request);
$this->getTradesOperations($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);
if (!empty($this->getCustomer()->getRemoteUserCode()) && $this->getUser()->isAccessRemoteSystems()) {
$this->data['remoteSystem'] = $this->getCustomer()->getRemoteSystem();
}
return $this->render('@committedForward/index.html.twig', $this->data);
}
/**
* @param Forward $tradeParent
* @param Forward $newTrade
* @return void
*/
protected function updateCashflows($tradeParent, $newTrade = null, $action = 'extension') {
if (empty($tradeParent->getCommittedCashflows())) {
return;
}
$this->needFlush = false;
$cashflows = $tradeParent->getCommittedCashflows();
foreach ($cashflows as $cashflow) {
$this->updateCashflow($action, $cashflow, $tradeParent, $newTrade);
}
if ($this->needFlush) {
$this->em->flush();
}
}
/**
* @param ['extension', 'drawdown' ] $action
* @param CashFlows $cashflow
* @param Forward $tradeParent
* @param Forward $tradeUpdated
* @return void
* @throws \Doctrine\ORM\Exception\ORMException
*/
protected function updateCashflow($action, $cashflow, $tradeParent, $newTrade = null) {
$newDate = $newTrade?->getDateDelivery() ?? $tradeParent->getDateDelivery();
$restAmount = $tradeParent->getAmountMinor() - ($newTrade?->getAmountMinor() ?? 0);
$newAmount = $newTrade?->getAmountMinor() ?? 0;
$this->collectOldDataForRestore($cashflow);
$this->needFlush = true;
// jeigu keiciasi tik data, tai pakeiciam ir cashflowsui tik data
// jeigu keiciasi tik amount, tai pakeiciam ir cashflowsui tik amount
// jeigu keiciasi ir data ir amount, tai pakeiciam ir data ir amount
switch ($action) {
case 'update':
$cashflow->setFlowDate($newDate);
$this->em->persist($cashflow);
return;
case 'extension':
if (empty($restAmount)) {
$cashflow->setFlowDate($newDate);
$this->em->persist($cashflow);
return;
}
// reiksias tokiai sumai reikia naujo flowso ir ji susieti su nauju tradu
if ($restAmount > 0 && $newAmount > 0) {
$newCashFlows = clone $cashflow;
$newCashFlows->setId(null);
$newCashFlows->setCashAmountMinor($newAmount);
$newCashFlows->setFlowDate($newDate);
$cashflow->setCashAmountMinor($restAmount);
$newTrade->removeCommittedCashflows($cashflow);
$newTrade->addCommittedCashflows($newCashFlows);
$this->em->persist($newCashFlows);
$this->em->persist($cashflow);
$this->em->persist($newTrade);
return;
}
case 'drawdown':
$cashflow->setCashAmountMinor($tradeParent->getAmountMinor());
$this->em->persist($cashflow);
return;
}
}
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);
if (!empty($cashFlow)) {
$cashFlow->setRate($this->getRate($cashFlow->getCurrencyPair()));
$this->data['cashFlow'] = $cashFlow ?? null;
}
}
$cashFlows = $this->em->getRepository(CashFlows::class)->findByDates([
'Portfolio' => $this->portfolioId,
'show' => $this->show,
]);
$this->data['cashFlows'] = $cashFlows;
$this->data['cashFlowSumAmount'] = $this->getModel()->getCashFlowSums($cashFlows, 'minor');
$this->data['cashFlowSumAmountMajor'] = $this->getModel()->getCashFlowSums($cashFlows);
$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 getPolicies() {
$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 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() || !empty($importDataTrades)) {
$this->actionsTrades($form, $importDataTrades);
}
if (!empty($this->tradeId)) {
$trade = $this->em->getRepository(Forward::class)->find($this->tradeId);
if (!empty($trade) && !empty($trade->getCommittedCashflows())) {
$options['cashflows'] = [];
foreach ($trade->getCommittedCashflows() as $item) {
$options['cashflows'][] = $item->getId();
}
}
$this->data['trade'] = $trade;
}
$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,
]);
$this->data['trades'] = $tradesOrigin;
$this->data['spotTrades'] = $spotTrades;
$this->data['tradeFilter'] = $this->tradeFilter;
$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 $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);
$cashFlowRecord->setStatus(0);
$this->em->persist($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->setCashAmount(0);
$cashFlowRecord->setCashAmountMinor($formData->getCashAmountMinor());
$cashFlowRecord->setRate(0);
$cashFlowRecord->setBudgetRate($formData->getBudgetRate());
$cashFlowRecord->setHedgeRatio($formData->getHedgeRatio());
$cashFlowRecord->setComment($formData->getComment());
$cashFlowRecord->setAdditionalInfo($formData->getAdditionalInfo());
$cashFlowRecord->setStatus(true);
if (empty($cashFlowRecord->getDaysToExpire())) {
$cashFlowRecord->setDaysToExpire($formData->getFlowDate());
}
$this->em->persist($cashFlowRecord);
$this->em->flush();
$this->addFlash('success', 'Cash flow was '.($cashFlowId > 0 ? 'updated' : 'created').' successfully');
$this->callAlert($cashFlowRecord->getId(), 'flow');
$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(),
], ['id' => 'DESC']);
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;
}
$budgetRate = (float)$item[3];
if ($budgetRate <= 0) {
$allErrors[$recordNr][] = 'Incorrect budget rate value';
continue;
}
$hedgeRatio = (float)$item[4];
if ($hedgeRatio < 0 || $hedgeRatio > 100) {
$allErrors[$recordNr][] = 'Incorrect hedge ratio (range is 0-100)';
continue;
}
$cashFlowRecord->setCashAmountMinor($amountMinor);
$cashFlowRecord->setRate(1);
$cashFlowRecord->setBudgetRate($budgetRate);
$cashFlowRecord->setHedgeRatio($hedgeRatio);
$cashFlowRecord->setComment(trim(($item[5] ?? ''), '"'));
$cashFlowRecord->setStatus(true);
$this->em->persist($cashFlowRecord);
$this->em->flush();
$successSaved[] = $recordNr;
}
} 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();
// dump($formData);
$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');
$this->tradeId = 0;
} elseif (empty($importData)) { //add/edit
if (empty($this->portfolioId)) {
$this->addFlash('warning', 'No portfolio found');
return false;
}
if (empty($tradeId)) {
$tradeRecord = new Forward();
$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());
if (empty((float)$tradeRecord->getRate())) {
$tradeRecord->setRate($this->getRate($tradeRecord->getCurrencyPair(), $tradeRecord->getDateExpire()));
}
if (!empty($tradeRecord->getCommittedCashflows()) && !empty($formData->getCommittedCashflows())) {
foreach ($tradeRecord->getCommittedCashflows() as $item) {
$tradeRecord->removeCommittedCashflows($item);
}
}
// $this->em->persist($tradeRecord);
// $tradeRecord->resetCommittedCashflows();
if (!empty($formData->getCommittedCashflows())) {
foreach ($formData->getCommittedCashflows() as $item) {
$tradeRecord->addCommittedCashflows($item);
}
}
$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()]);
foreach ($importDataList as $item) {
$recordNr++;
$errors = $this->getImportValidatorModel()->validImportDataTrades($item, $portfolioCurrency, $counterparties, $allErrors);
if (!empty($errors)) {
$allErrors[$recordNr] = $errors;
continue;
}
$date = date_create($item[0])->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);
if (!empty($this->importCorpay)) {
$tradeRecord->setType('spot');
if (strpos($item[1], 'FD') !== false) { //OFD, EFD - forwardai
$tradeRecord->setType('forward');
}
} else {
$tradeRecord->setType($this->importDataAsSpotTrades ? 'spot' : 'forward');
}
$counterParty = $this->getModel()->findCounterParty($counterparties, $item[5] ?? '');
if (!empty($counterParty)) {
$tradeRecord->setCounterparty($counterParty);
}
$tradeRecord->setComment($item[6] ?? '');
$tradeRecord->setTradeNumber($item[1]);
//todo kai turesime spotu istorija, bus galima imti is istorijos pagal $tradeRecord->getDateExpire() data
// kolkas isivedam ivedimo dienos spota limito skaiciavimams
$tradeRecord->setRate($this->getRate($tradeRecord->getCurrencyPair()));
$this->em->persist($tradeRecord);
$this->em->flush();
$successSaved[] = $recordNr;
}
} catch (\Exception $e) {
$this->addFlash('warning', 'Internal error, process stopped. ImportData. Error: '.$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 = 'Globals errors are:<br>';
$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 .= '<ul>'.$globalErrors.'</ul>';
$this->addFlash('warning', $errorString);
}
} else {
$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);
// 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_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 '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 getAnalysis() {
if (empty($this->portfolioId) || $this->redirect) {
return false;
}
$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);
$spot['bid'] = $this->getRate($this->data['portfolio']->getCurrencyPair());
$forwardPoints = [0 => $spot];
$forwardPointList = (new ForwardPoints())->get($this->data['portfolio']->getCurrencyPair());
if (!empty($forwardPointList)) {
$forwardPoints += $forwardPointList;
} else {
//reikia siusti pranesima adminams - negavom kursu
}
$this->data['currentMarketRate'] = $spot; //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;
$this->data['budgetRates'] = $analysis['budgetRates'];
$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);
$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, 'committedCashflow');
if (empty($this->export)) {
$this->addFlash('warning', 'No data found. Try to change report type or period of data');
}
}
$this->data['reportForm'] = $reportFilter->createView();
}
protected function getBudgetRateList($data = []) {
$summary = $this->getModel()->totalPortfolioSummary($data);
return $this->getModel()->getBudgetRatesFromSummary($summary);
}
public function tradeOperationAjax(?int $tradeId, $tradeOperation, Request $request) {
parent::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(),
];
$operationForm = $this->render('@instrumentsBase/operation_form.html.twig', $operationFormData)->getContent();
return new JsonResponse($operationForm, 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;
if ($dateExpireNewest < date_create()->format('Y-m-d')) {
$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 > date_create()->format('Y-m-d')) { //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);
$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['budgetRateAvg']];
if (!empty($averageHedgeRate)) {
$serie3[] = ['x' => $date, 'y' => round($averageHedgeRate, 5)];
}
if ($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);
}
/**
* @return HedgingPolicyModel
*/
protected function getModel($renew = false) {
if (empty($this->model) || $renew) {
$this->model = new HedgingPolicyModel($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;
}
}