src/Bundles/Portfolios/Controller/PortfolioCommittedForwardController.php line 47

Open in your IDE?
  1. <?php
  2. namespace Bundles\Portfolios\Controller;
  3. use Bundles\AiStudio\forms\PolicyType2;
  4. use App\Entity\CurrencyHistory;
  5. use App\Entity\Customer;
  6. use Bundles\AiStudio\forms\PolicyType1;
  7. use Symfony\Component\HttpFoundation\Request;
  8. use Psr\Log\LoggerInterface;
  9. use Symfony\Component\Cache\Adapter\AdapterInterface;
  10. use Bundles\Portfolios\Form\PortfolioType;
  11. use Bundles\Portfolios\Entity\Portfolio;
  12. use Bundles\Instruments\Base\Entity\CashFlows;
  13. use Bundles\Instruments\Base\Entity\Forward;
  14. use Bundles\Instruments\Base\Entity\ForwardOperations;
  15. use Bundles\Instruments\Base\Form\CashFlowType;
  16. use Bundles\Instruments\CommittedForward\Form\ForwardType;
  17. use Bundles\Instruments\CommittedForward\Model\HedgingPolicy as HedgingPolicyModel;
  18. use App\Entity\CurrencyPair;
  19. use Bundles\Instruments\CommittedForward\Form\TradesOperationsType;
  20. use App\Models\Spot;
  21. use App\Models\ForwardPoints;
  22. use Symfony\Component\HttpFoundation\JsonResponse;
  23. use Bundles\Instruments\Base\Model\CalcsVaR;
  24. use App\Entity\Counterparty;
  25. use Bundles\Instruments\Base\Entity\ReportFilter;
  26. use Bundles\Instruments\Base\Form\ReportFilterType;
  27. use Bundles\Instruments\CommittedForward\Model\ImportValidator;
  28. use Doctrine\DBAL\Connection;
  29. use Bundles\Messenger\Entity\Event;
  30. use Bundles\Messenger\Model\Actions;
  31. use Psr\Cache\CacheItemPoolInterface;
  32. class PortfolioCommittedForwardController extends PortfolioController {
  33. /** @var AdapterInterface */
  34. protected $model;
  35. protected $skipOtherTabs = false;
  36. protected $export = false;
  37. protected $url = '/portfolios/committed-forward?action=list&menuIndex=1';
  38. protected $importDataOverwrite = false;
  39. protected $importDataAsSpotTrades = false;
  40. protected $importCorpay = false;
  41. public function index(LoggerInterface $analyticsLogger, CacheItemPoolInterface $cache, Request $request, Connection $connection) {
  42. $this->init($analyticsLogger, $cache, $request, $connection);
  43. if ($this->step == self::STEP_PORTFOLIO) {
  44. $this->portfolioId = 0;
  45. $this->session->remove('portfolioId');
  46. }
  47. $this->portfolioId = $request->get('portfolioId') ?? (int)$this->session->get('portfolioId');
  48. $this->cashFlowId = $request->get('cashFlowId') ?? 0;
  49. $this->tradeId = $request->get('tradeId') ?? 0;
  50. if (!empty($this->portfolioId)) {
  51. $this->session->set('portfolioId', $this->portfolioId);
  52. $userPortfolios = $this->getUser()->getUserPortfolios();
  53. if ($this->getUser()->isUser() && !$userPortfolios->isEmpty()) {
  54. $portfolio = $userPortfolios->filter(function ($portfolio) {
  55. return $portfolio->getId() == $this->portfolioId;
  56. });
  57. $portfolio = $portfolio[0] ?? null;
  58. } else {
  59. $portfolio = $this->em->getRepository(Portfolio::class)->findOneBy([
  60. 'id' => $this->portfolioId,
  61. 'status' => 1,
  62. 'customer' => $this->getCustomer()->getId(),
  63. ]);
  64. }
  65. if (empty($portfolio) || $portfolio->getType() !== 'committed_forward') {
  66. $this->addFlash('warning', 'portfolio id '.$this->portfolioId.' not found or not allowed to access');
  67. return $this->redirect($this->url.'&step='.self::STEP_PORTFOLIO);
  68. }
  69. $this->data['portfolio'] = $portfolio;
  70. }
  71. $this->data['sumGroup'] = $this->sumGroup ?? 'month';
  72. $this->data['tradeFilter'] = $this->tradeFilter ?? '';
  73. $this->getPortfolios($request);
  74. if ($this->step > self::STEP_PORTFOLIO) {
  75. $this->getCashFlows($request);
  76. $this->getPolicies();
  77. $this->getTrades($request);
  78. $this->getTradesOperations($request);
  79. $this->getAnalysis();
  80. $this->getReport($request);
  81. $this->notificationsAsSeen();
  82. }
  83. if (!empty($this->export)) {
  84. return $this->export;
  85. }
  86. if ($this->redirect) {
  87. return $this->redirect($this->url.'&step='.$this->step);
  88. }
  89. $this->data['summaryTotals'] = $this->getModel()->getSummaryTotals();
  90. $this->data['step'] = $this->step;
  91. $this->data['title'] = 'Portfolios';
  92. $this->data['action'] = 'portfolio';
  93. $this->data['edit'] = $this->edit;
  94. $this->data['url'] = $this->url;
  95. $this->data['show'] = $this->show;
  96. $this->data['operation'] = $this->tradeOperation ?? null;
  97. $this->data['tradeTypes'] = array_flip($this->tradeTypes);
  98. if (!empty($this->getCustomer()->getRemoteUserCode()) && $this->getUser()->isAccessRemoteSystems()) {
  99. $this->data['remoteSystem'] = $this->getCustomer()->getRemoteSystem();
  100. }
  101. return $this->render('@committedForward/index.html.twig', $this->data);
  102. }
  103. /**
  104. * @param Forward $tradeParent
  105. * @param Forward $newTrade
  106. * @return void
  107. */
  108. protected function updateCashflows($tradeParent, $newTrade = null, $action = 'extension') {
  109. if (empty($tradeParent->getCommittedCashflows())) {
  110. return;
  111. }
  112. $this->needFlush = false;
  113. $cashflows = $tradeParent->getCommittedCashflows();
  114. foreach ($cashflows as $cashflow) {
  115. $this->updateCashflow($action, $cashflow, $tradeParent, $newTrade);
  116. }
  117. if ($this->needFlush) {
  118. $this->em->flush();
  119. }
  120. }
  121. /**
  122. * @param ['extension', 'drawdown' ] $action
  123. * @param CashFlows $cashflow
  124. * @param Forward $tradeParent
  125. * @param Forward $tradeUpdated
  126. * @return void
  127. * @throws \Doctrine\ORM\Exception\ORMException
  128. */
  129. protected function updateCashflow($action, $cashflow, $tradeParent, $newTrade = null) {
  130. $newDate = $newTrade?->getDateDelivery() ?? $tradeParent->getDateDelivery();
  131. $restAmount = $tradeParent->getAmountMinor() - ($newTrade?->getAmountMinor() ?? 0);
  132. $newAmount = $newTrade?->getAmountMinor() ?? 0;
  133. $this->collectOldDataForRestore($cashflow);
  134. $this->needFlush = true;
  135. // jeigu keiciasi tik data, tai pakeiciam ir cashflowsui tik data
  136. // jeigu keiciasi tik amount, tai pakeiciam ir cashflowsui tik amount
  137. // jeigu keiciasi ir data ir amount, tai pakeiciam ir data ir amount
  138. switch ($action) {
  139. case 'update':
  140. $cashflow->setFlowDate($newDate);
  141. $this->em->persist($cashflow);
  142. return;
  143. case 'extension':
  144. if (empty($restAmount)) {
  145. $cashflow->setFlowDate($newDate);
  146. $this->em->persist($cashflow);
  147. return;
  148. }
  149. // reiksias tokiai sumai reikia naujo flowso ir ji susieti su nauju tradu
  150. if ($restAmount > 0 && $newAmount > 0) {
  151. $newCashFlows = clone $cashflow;
  152. $newCashFlows->setId(null);
  153. $newCashFlows->setCashAmountMinor($newAmount);
  154. $newCashFlows->setFlowDate($newDate);
  155. $cashflow->setCashAmountMinor($restAmount);
  156. $newTrade->removeCommittedCashflows($cashflow);
  157. $newTrade->addCommittedCashflows($newCashFlows);
  158. $this->em->persist($newCashFlows);
  159. $this->em->persist($cashflow);
  160. $this->em->persist($newTrade);
  161. return;
  162. }
  163. case 'drawdown':
  164. $cashflow->setCashAmountMinor($tradeParent->getAmountMinor());
  165. $this->em->persist($cashflow);
  166. return;
  167. }
  168. }
  169. protected function getCashFlows($request) {
  170. if (empty($this->portfolioId) || empty($this->data['portfolio']) || $this->redirect) {
  171. return false;
  172. }
  173. $step = self::STEP_CASH_FLOWS;
  174. $form = $this->getForm($step, null, ['portfolioId' => $this->portfolioId]);
  175. $form->handleRequest($request);
  176. $importDataCashFlow = $request->request->get('importDataCashFlow');
  177. $this->importDataOverwrite = $request->request->get('dataOverwriteCashFlow');
  178. if (($form->isSubmitted() && $form->isValid()) || !empty($importDataCashFlow)) {
  179. $this->actionsCashFlows($form, $importDataCashFlow);
  180. }
  181. if (!empty($this->cashFlowId)) {
  182. $cashFlow = $this->em->getRepository(CashFlows::class)->find($this->cashFlowId);
  183. if (!empty($cashFlow)) {
  184. $cashFlow->setRate($this->getRate($cashFlow->getCurrencyPair()));
  185. $this->data['cashFlow'] = $cashFlow ?? null;
  186. }
  187. }
  188. $cashFlows = $this->em->getRepository(CashFlows::class)->findByDates([
  189. 'Portfolio' => $this->portfolioId,
  190. 'show' => $this->show,
  191. ]);
  192. $this->data['cashFlows'] = $cashFlows;
  193. $this->data['cashFlowSumAmount'] = $this->getModel()->getCashFlowSums($cashFlows, 'minor');
  194. $this->data['cashFlowSumAmountMajor'] = $this->getModel()->getCashFlowSums($cashFlows);
  195. $this->data['eventAlerts'] = $this->getEventAlertStatuses();
  196. $this->data['marginCalls'] = $this->marginCalls;
  197. $this->data['spot'] = $this->getRate($this->data['portfolio']->getCurrencyPair());
  198. $this->data['form'.$step] = $this->getForm($step, ($this->data['cashFlow'] ?? null))->createView();
  199. if ($form->isSubmitted()) {
  200. return $this->redirect($this->url.'&step='.$step);
  201. }
  202. }
  203. protected function getPolicies() {
  204. $options = [
  205. 'customerPairs' => [$this->data['portfolio']->getCurrencyPair()->getId() ?? []],
  206. ];
  207. $this->data['policyForm2'] = $this->createForm(PolicyType2::class, null, $options)->createView();
  208. $this->data['symbol'] = $this->data['portfolio']->getCurrencyPair()->getCurrencyNameByNumber(2);
  209. }
  210. protected function getTrades($request) {
  211. if (empty($this->portfolioId) || empty($this->data['portfolio']) || $this->redirect) {
  212. return false;
  213. }
  214. $step = self::STEP_TRADES;
  215. $options = ['portfolioId' => $this->portfolioId, 'customer' => $this->getCustomer()->getId()];
  216. $form = $this->getForm($step, null, $options);
  217. $form->handleRequest($request);
  218. $importDataTrades = $request->request->get('importDataTrades');
  219. $this->importCorpay = $request->request->get('importCorpay');
  220. $this->importDataOverwrite = $request->request->get('dataOverwriteTrades');
  221. $this->importDataAsSpotTrades = $request->request->get('importSpotTrades') ?? false;
  222. if ($form->isSubmitted() || !empty($importDataTrades)) {
  223. $this->actionsTrades($form, $importDataTrades);
  224. }
  225. if (!empty($this->tradeId)) {
  226. $trade = $this->em->getRepository(Forward::class)->find($this->tradeId);
  227. if (!empty($trade) && !empty($trade->getCommittedCashflows())) {
  228. $options['cashflows'] = [];
  229. foreach ($trade->getCommittedCashflows() as $item) {
  230. $options['cashflows'][] = $item->getId();
  231. }
  232. }
  233. $this->data['trade'] = $trade;
  234. }
  235. $tradesOrigin = $this->em->getRepository(Forward::class)->findByDates([
  236. 'Portfolio' => $this->portfolioId,
  237. 'show' => $this->show,
  238. 'tradeFilter' => $this->tradeFilter,
  239. 'type' => 'forward',
  240. ]);
  241. $spotTrades = $this->em->getRepository(Forward::class)->findByDates([
  242. 'Portfolio' => $this->portfolioId,
  243. 'show' => $this->show,
  244. 'tradeFilter' => $this->tradeFilter,
  245. 'type' => 'spot',
  246. ]);
  247. $cashFlows = $this->em->getRepository(CashFlows::class)->findByDates([
  248. 'Portfolio' => $this->portfolioId,
  249. 'show' => $this->show,
  250. ]);
  251. $this->data['trades'] = $tradesOrigin;
  252. $this->data['spotTrades'] = $spotTrades;
  253. $this->data['tradeFilter'] = $this->tradeFilter;
  254. $this->data['portfolioOperations'] = $this->em->getRepository(ForwardOperations::class)->findAllByPortfolio($this->portfolioId, 'DESC');
  255. $this->data['valuation'] = $this->getModel()->tradeValuation($this->data['trades'], 0, [], $cashFlows, $this->sumGroup);
  256. $this->data['form'.$step] = $this->getForm($step, ($this->data['trade'] ?? null), $options)->createView();
  257. if ($form->isSubmitted()) {
  258. return $this->redirect($this->url.'&step='.$step);
  259. }
  260. }
  261. /**
  262. * @param $form
  263. * @return bool|void
  264. */
  265. protected function actionsCashFlows($form, $importData = '') {
  266. $this->portfolioId = $this->session->get('portfolioId');
  267. $action = $form->get('delete')->isClicked() ? 'delete' : 'add';
  268. $formData = $form->getData();
  269. $cashFlowId = $formData->getId();
  270. if (!empty($cashFlowId)) {
  271. $cashFlowRecord = $this->em->getRepository(CashFlows::class)->find($cashFlowId);
  272. }
  273. if ($action == 'delete') {
  274. if (empty($cashFlowId)) {
  275. $this->addFlash('warning', 'No cash flow ID submited');
  276. return false;
  277. }
  278. if (empty($cashFlowRecord)) {
  279. $this->addFlash('warning', 'No cash flow record was found');
  280. return false;
  281. }
  282. //todo: padaryti isjungima per status=0
  283. //$this->em->remove($cashFlowRecord);
  284. $cashFlowRecord->setStatus(0);
  285. $this->em->persist($cashFlowRecord);
  286. $this->em->flush();
  287. $this->addFlash('success', 'Cash flow record was deleted successfully');
  288. $this->redirect = true;
  289. } elseif (empty($importData)) { //add/edit
  290. if (empty($this->portfolioId)) {
  291. $this->addFlash('warning', 'No portfolio found');
  292. return false;
  293. }
  294. if (empty($cashFlowId)) {
  295. $cashFlowRecord = new CashFlows();
  296. $cashFlowRecord->setPortfolio($this->data['portfolio']);
  297. }
  298. $cashFlowRecord->setCurrencyPair($formData->getCurrencyPair());
  299. $cashFlowRecord->setFlowDate($formData->getFlowDate());
  300. $cashFlowRecord->setCashAmount(0);
  301. $cashFlowRecord->setCashAmountMinor($formData->getCashAmountMinor());
  302. $cashFlowRecord->setRate(0);
  303. $cashFlowRecord->setBudgetRate($formData->getBudgetRate());
  304. $cashFlowRecord->setHedgeRatio($formData->getHedgeRatio());
  305. $cashFlowRecord->setComment($formData->getComment());
  306. $cashFlowRecord->setAdditionalInfo($formData->getAdditionalInfo());
  307. $cashFlowRecord->setStatus(true);
  308. if (empty($cashFlowRecord->getDaysToExpire())) {
  309. $cashFlowRecord->setDaysToExpire($formData->getFlowDate());
  310. }
  311. $this->em->persist($cashFlowRecord);
  312. $this->em->flush();
  313. $this->addFlash('success', 'Cash flow was '.($cashFlowId > 0 ? 'updated' : 'created').' successfully');
  314. $this->callAlert($cashFlowRecord->getId(), 'flow');
  315. $this->redirect = true;
  316. } elseif (!empty($importData)) {
  317. try {
  318. $importDataList = json_decode($importData, true, 512, JSON_THROW_ON_ERROR);
  319. if (empty($importDataList)) {
  320. return false;
  321. }
  322. $recordNr = 0;
  323. $successSaved = [];
  324. $allErrors = [];
  325. $portfolioCurrency = $this->data['portfolio']->getCurrencyPair();
  326. foreach ($importDataList as $item) {
  327. $recordNr++;
  328. $errors = $this->getImportValidatorModel()->validImportDataCashFlow($item, $portfolioCurrency);
  329. if (!empty($errors)) {
  330. $allErrors[$recordNr] = $errors;
  331. continue;
  332. }
  333. $date = date_create($item[0])->format('Y-m-d');
  334. $cashFlowRecord = $this->em->getRepository(CashFlows::class)->findOneBy([
  335. 'flowDate' => $date,
  336. 'Portfolio' => $this->data['portfolio']->getId(),
  337. ], ['id' => 'DESC']);
  338. if (empty($cashFlowRecord) || empty($this->importDataOverwrite)) {
  339. $cashFlowRecord = new CashFlows();
  340. }
  341. $cashFlowRecord->setPortfolio($this->data['portfolio']);
  342. $cashFlowRecord->setCurrencyPair($portfolioCurrency);
  343. $cashFlowRecord->setFlowDate($date);
  344. $amountMinor = (int)$item[2];
  345. if ($amountMinor < 0 && $cashFlowRecord->getCashAmountMinor() + $amountMinor < 0) {
  346. $allErrors[$recordNr][] = 'Incorrect amount value';
  347. continue;
  348. }
  349. if ($amountMinor < 0 && $cashFlowRecord->getCashAmountMinor() >= $amountMinor) { //jei ivesta neigiama, isminusuojam is esamo
  350. $amountMinor = (int)$cashFlowRecord->getCashAmountMinor() + $amountMinor;
  351. }
  352. $budgetRate = (float)$item[3];
  353. if ($budgetRate <= 0) {
  354. $allErrors[$recordNr][] = 'Incorrect budget rate value';
  355. continue;
  356. }
  357. $hedgeRatio = (float)$item[4];
  358. if ($hedgeRatio < 0 || $hedgeRatio > 100) {
  359. $allErrors[$recordNr][] = 'Incorrect hedge ratio (range is 0-100)';
  360. continue;
  361. }
  362. $cashFlowRecord->setCashAmountMinor($amountMinor);
  363. $cashFlowRecord->setRate(1);
  364. $cashFlowRecord->setBudgetRate($budgetRate);
  365. $cashFlowRecord->setHedgeRatio($hedgeRatio);
  366. $cashFlowRecord->setComment(trim(($item[5] ?? ''), '"'));
  367. $cashFlowRecord->setStatus(true);
  368. $this->em->persist($cashFlowRecord);
  369. $this->em->flush();
  370. $successSaved[] = $recordNr;
  371. }
  372. } catch (\Exception $e) {
  373. $this->addFlash('warning', 'Internal error, import data process stopped.'.$e->getMessage());
  374. }
  375. if (!empty($successSaved)) {
  376. $this->addFlash('success', 'Import data created successfully for '.count($successSaved).' records from '.count($importDataList));
  377. $this->redirect = true;
  378. }
  379. if (!empty($allErrors)) {
  380. $errorString = 'Errors found on these records:<br>';
  381. foreach ($allErrors as $id => $errors) {
  382. $errorString .= 'Record nr. '.$id.'<br>';
  383. foreach ($errors as $key => $error) {
  384. $errorString .= 'Column '.$key.': '.$error.'<br>';
  385. }
  386. $errorString .= '<hr>';
  387. }
  388. $this->addFlash('warning', $errorString);
  389. }
  390. }
  391. }
  392. /**
  393. * @param $form
  394. * @return bool|void
  395. */
  396. protected function actionsTrades($form, $importData = []) {
  397. $action = $form->get('delete')->isClicked() ? 'delete' : 'add';
  398. $formData = $form->getData();
  399. // dump($formData);
  400. $tradeId = $formData->getId();
  401. if (!empty($tradeId)) {
  402. $tradeRecord = $this->em->getRepository(Forward::class)->find($tradeId);
  403. }
  404. if ($action == 'delete') {
  405. if (empty($tradeId)) {
  406. $this->addFlash('warning', 'No trade ID submited');
  407. return false;
  408. }
  409. if (empty($tradeRecord)) {
  410. $this->addFlash('warning', 'No trade record was found');
  411. return false;
  412. }
  413. //todo: padaryti isjungima per status=0
  414. $this->em->remove($tradeRecord);
  415. $this->em->flush();
  416. $this->addFlash('success', 'Trade record was deleted successfully');
  417. $this->tradeId = 0;
  418. } elseif (empty($importData)) { //add/edit
  419. if (empty($this->portfolioId)) {
  420. $this->addFlash('warning', 'No portfolio found');
  421. return false;
  422. }
  423. if (empty($tradeId)) {
  424. $tradeRecord = new Forward();
  425. $tradeRecord->setPortfolio($this->data['portfolio']);
  426. }
  427. $tradeRecord->setCurrencyPair($formData->getCurrencyPair());
  428. $tradeRecord->setAmount($formData->getAmount());
  429. $tradeRecord->setAmountMinor($formData->getAmountMinor());
  430. $tradeRecord->setDateExpire(date('Y-m-d'));
  431. $tradeRecord->setDateDelivery($formData->getDateDelivery());
  432. $tradeRecord->setExposureDirection($formData->getExposureDirection());
  433. $tradeRecord->setStatus(true);
  434. $tradeRecord->setStrike($formData->getStrike());
  435. $tradeRecord->setCounterparty($formData->getCounterparty());
  436. $tradeRecord->setComment($formData->getComment());
  437. $tradeRecord->setTradeNumber($formData->getTradeNumber());
  438. $tradeRecord->setType($formData->getType());
  439. if (empty((float)$tradeRecord->getRate())) {
  440. $tradeRecord->setRate($this->getRate($tradeRecord->getCurrencyPair(), $tradeRecord->getDateExpire()));
  441. }
  442. if (!empty($tradeRecord->getCommittedCashflows()) && !empty($formData->getCommittedCashflows())) {
  443. foreach ($tradeRecord->getCommittedCashflows() as $item) {
  444. $tradeRecord->removeCommittedCashflows($item);
  445. }
  446. }
  447. // $this->em->persist($tradeRecord);
  448. // $tradeRecord->resetCommittedCashflows();
  449. if (!empty($formData->getCommittedCashflows())) {
  450. foreach ($formData->getCommittedCashflows() as $item) {
  451. $tradeRecord->addCommittedCashflows($item);
  452. }
  453. }
  454. $this->em->persist($tradeRecord);
  455. $this->em->flush();
  456. $this->addFlash('success', 'Trade was '.($tradeId > 0 ? 'updated' : 'created').' successfully');
  457. $this->redirect = true;
  458. } elseif (!empty($importData)) {
  459. $importData = str_replace('\r', '', $importData);
  460. try {
  461. $importDataList = json_decode($importData, true, 512, JSON_THROW_ON_ERROR);
  462. if (empty($importDataList)) {
  463. return false;
  464. }
  465. $recordNr = 0;
  466. $successSaved = [];
  467. $allErrors = [];
  468. $portfolioCurrency = $this->data['portfolio']->getCurrencyPair();
  469. $counterparties = $this->em->getRepository(Counterparty::class)->findBy(['Customer' => $this->getCustomer()]);
  470. foreach ($importDataList as $item) {
  471. $recordNr++;
  472. $errors = $this->getImportValidatorModel()->validImportDataTrades($item, $portfolioCurrency, $counterparties, $allErrors);
  473. if (!empty($errors)) {
  474. $allErrors[$recordNr] = $errors;
  475. continue;
  476. }
  477. $date = date_create($item[0])->format('Y-m-d');
  478. $tradeRecord = $this->em->getRepository(Forward::class)->findOneBy([
  479. 'tradeNumber' => $item[1],
  480. 'Portfolio' => $this->data['portfolio']->getId(),
  481. ]);
  482. if (empty($tradeRecord) || empty($this->importDataOverwrite)) {
  483. $tradeRecord = new Forward();
  484. }
  485. $tradeRecord->setPortfolio($this->data['portfolio']);
  486. $tradeRecord->setCurrencyPair($portfolioCurrency);
  487. $amountMinor = (int)$item[3];
  488. if ($amountMinor < 0 && $tradeRecord->getAmountMinor() + $amountMinor < 0) {
  489. $allErrors[$recordNr][] = 'Incorrect amount value';
  490. continue;
  491. }
  492. if ($amountMinor < 0 && $tradeRecord->getAmountMinor() >= $amountMinor) { //jei ivesta neigiama, isminusuojam is esamo
  493. $amountMinor = (int)$tradeRecord->getAmountMinor() + $amountMinor;
  494. }
  495. $hedgeRate = (float)str_replace(',', '.', $item[4]);
  496. $tradeRecord->setAmount($amountMinor / $hedgeRate);
  497. $tradeRecord->setAmountMinor($amountMinor);
  498. $tradeRecord->setDateExpire(date('Y-m-d'));
  499. $tradeRecord->setDateDelivery($date);
  500. $tradeRecord->setExposureDirection($this->data['portfolio']->getDirection());
  501. $tradeRecord->setStatus(true);
  502. $tradeRecord->setStrike($hedgeRate);
  503. if (!empty($this->importCorpay)) {
  504. $tradeRecord->setType('spot');
  505. if (strpos($item[1], 'FD') !== false) { //OFD, EFD - forwardai
  506. $tradeRecord->setType('forward');
  507. }
  508. } else {
  509. $tradeRecord->setType($this->importDataAsSpotTrades ? 'spot' : 'forward');
  510. }
  511. $counterParty = $this->getModel()->findCounterParty($counterparties, $item[5] ?? '');
  512. if (!empty($counterParty)) {
  513. $tradeRecord->setCounterparty($counterParty);
  514. }
  515. $tradeRecord->setComment($item[6] ?? '');
  516. $tradeRecord->setTradeNumber($item[1]);
  517. //todo kai turesime spotu istorija, bus galima imti is istorijos pagal $tradeRecord->getDateExpire() data
  518. // kolkas isivedam ivedimo dienos spota limito skaiciavimams
  519. $tradeRecord->setRate($this->getRate($tradeRecord->getCurrencyPair()));
  520. $this->em->persist($tradeRecord);
  521. $this->em->flush();
  522. $successSaved[] = $recordNr;
  523. }
  524. } catch (\Exception $e) {
  525. $this->addFlash('warning', 'Internal error, process stopped. ImportData. Error: '.$e->getMessage());
  526. }
  527. if (!empty($successSaved)) {
  528. $this->addFlash('success', 'Import data created successfully for '.count($successSaved).' records from '.count($importDataList));
  529. }
  530. if (!empty($allErrors)) {
  531. if (empty($successSaved)) {
  532. $errorString = 'Globals errors are:<br>';
  533. $globalErrors = '';
  534. $errorList = $allErrors[1] ?? [];
  535. if (!empty($errorList['counterparty'])) {
  536. $globalErrors .= '<li>'.$errorList['counterparty'].'</li>';
  537. }
  538. if (!empty($errorList['currencyPair'])) {
  539. $globalErrors .= '<li>'.$errorList['currencyPair'].'</li>';
  540. }
  541. if (!empty($globalErrors)) {
  542. $errorString .= '<ul>'.$globalErrors.'</ul>';
  543. $this->addFlash('warning', $errorString);
  544. }
  545. } else {
  546. $errorString = 'Errors found on these records:<br>';
  547. foreach ($allErrors as $id => $errors) {
  548. $errorString .= 'Record nr. '.$id.'<br>';
  549. foreach ($errors as $key => $error) {
  550. $errorString .= 'Column '.$key.': '.$error.'<br>';
  551. }
  552. $errorString .= '<hr>';
  553. }
  554. $this->addFlash('warning', $errorString);
  555. // dump($errorString);
  556. }
  557. } else {
  558. $this->redirect = true;
  559. }
  560. }
  561. }
  562. protected function getForm($step, $object = null, $options = []) {
  563. switch ($step) {
  564. case self::STEP_PORTFOLIO:
  565. if (empty($object)) {
  566. $object = new Portfolio();
  567. }
  568. $objetType = PortfolioType::class;
  569. break;
  570. case self::STEP_CASH_FLOWS:
  571. if (empty($object)) {
  572. $object = new CashFlows();
  573. }
  574. $objetType = CashFlowType::class;
  575. break;
  576. case self::STEP_TRADES:
  577. if (empty($object)) {
  578. $object = new Forward();
  579. }
  580. $objetType = ForwardType::class;
  581. break;
  582. case 'reportFilter':
  583. if (empty($object)) {
  584. $object = new ReportFilter();
  585. }
  586. $objetType = ReportFilterType::class;
  587. $step = self::STEP_REPORT;
  588. break;
  589. case 'tradesOperations':
  590. if (empty($object)) {
  591. $object = new ForwardOperations();
  592. }
  593. $objetType = TradesOperationsType::class;
  594. $step = self::STEP_TRADES.'&tradeId='.$this->tradeId.'&operation='.$this->tradeOperation;
  595. break;
  596. }
  597. $options['portfolioId'] = $this->portfolioId;
  598. $options['url'] = $this->url.'&step='.$step;
  599. return $this->createForm($objetType, $object, $options);
  600. }
  601. protected function getAnalysis() {
  602. if (empty($this->portfolioId) || $this->redirect) {
  603. return false;
  604. }
  605. $this->data['trades'] = $this->em->getRepository(Forward::class)->findByDates([
  606. 'Portfolio' => $this->portfolioId,
  607. 'show' => $this->show,
  608. 'tradeFilter' => $this->tradeFilter,
  609. 'type' => 'forward',
  610. ]);
  611. $this->data['summary'] = $this->getModel()->totalPortfolioSummary($this->data, $this->sumGroup);
  612. $this->data['summaryMonthly'] = $this->data['summary'];
  613. if ($this->sumGroup !== 'month') {
  614. $this->getModel()->emptyTotals();
  615. $this->data['summaryMonthly'] = $this->getModel()->totalPortfolioSummary($this->data, 'month');
  616. }
  617. $analysis = [];
  618. if (empty($this->data['summary'])) {
  619. return false;
  620. }
  621. /** @var CurrencyPair $portfolioCurrencyPair */
  622. $portfolioCurrencyPair = $this->data['portfolio']->getCurrencyPair();
  623. $analysis['majorCurrency'] = $portfolioCurrencyPair->getCurrencyNameByNumber(1);
  624. $analysis['minorCurrency'] = $portfolioCurrencyPair->getCurrencyNameByNumber(2);
  625. $spot['bid'] = $this->getRate($this->data['portfolio']->getCurrencyPair());
  626. $forwardPoints = [0 => $spot];
  627. $forwardPointList = (new ForwardPoints())->get($this->data['portfolio']->getCurrencyPair());
  628. if (!empty($forwardPointList)) {
  629. $forwardPoints += $forwardPointList;
  630. } else {
  631. //reikia siusti pranesima adminams - negavom kursu
  632. }
  633. $this->data['currentMarketRate'] = $spot; //nusistatom current spot rate
  634. foreach ($forwardPoints as $days => $points) {
  635. $period = date_create(' + '.$days.' days')->format('Y-m');
  636. $analysis['forwards'][$period] = $points['bid'];
  637. if (empty($analysis['budgetRates'][$period])) {
  638. $analysis['budgetRates'][$period] = 'null';
  639. $analysis['averageHedgedRate'][$period] = 'null';
  640. }
  641. $analysis['ratePeriods'][] = $period;
  642. }
  643. foreach ($this->data['summaryMonthly'] as $period => $item) {
  644. if ($period < date('Y-m')) {
  645. continue;
  646. }
  647. $analysis['periods'][] = $period;
  648. $analysis['cashFlows'][] = isset($item['cashFlows']) ? number_format(($item['cashFlows']), 0, '', '') : 0;
  649. $analysis['hedgeRequired'][] = !empty($item['hedgeRequiredAmount']) ? number_format($item['hedgeRequiredAmount'], 0, '', '') : 'null';
  650. $analysis['trades'][] = isset($item['trades']) ? number_format($item['trades'], 0, '', '') : 0;
  651. $analysis['averageHedgedRate'][$period] = $item['averageHedgedRate'] ?? 'null';
  652. $analysis['budgetRates'][$period] = $item['budgetRate'] ?? 'null';
  653. if (empty($this->data['currentBudgetRate']) && $analysis['budgetRates'][$period] != 'null') {
  654. $this->data['currentBudgetRate'] = $analysis['budgetRates'][$period]; //nusistatom current budget rate
  655. }
  656. //jeigu reiksmiu daugiau nei metiniu forwardu, sulyginame su flowsu/tradu periodais, kad grafikas negriutu.
  657. // Privaloma, kad butu tiek periodu, kiek yra flowsu/treidu periodu suvesta i ateiti
  658. if (empty($analysis['forwards'][$period])) {
  659. $analysis['forwards'][$period] = 'null';
  660. $analysis['ratePeriods'][] = $period;
  661. }
  662. }
  663. ksort($analysis['budgetRates']);
  664. ksort($analysis['averageHedgedRate']);
  665. $analysis['scaleMax'] = $this->getModel()->getMax($analysis['forwards'], $analysis['budgetRates'], $analysis['averageHedgedRate']);
  666. $analysis['scaleMin'] = $this->getModel()->getMin($analysis['forwards'], $analysis['budgetRates'], $analysis['averageHedgedRate']);
  667. $analysis['analysisMethod'] = $this->analysisMethod;
  668. $analysis['cvarMethod'] = $this->cvarMethod;
  669. if (!empty($this->sensitivity)) {
  670. $this->data['sensitivity'] = $this->sensitivity;
  671. $this->data['budgetRates'] = $analysis['budgetRates'];
  672. $analysis['analysisTable'] = $this->getModel()->getAnalysisTable($spot, $this->data, $this->analysisMethod, $this->data['portfolio']->getDirection());
  673. }
  674. $calcVar = new CalcsVaR($this->em, $this->session);
  675. $calcVar->calc($this->data['portfolio']->getCurrencyPair()->getId());
  676. // $trades = $this->cvarMethod == 'full' ? [] : $analysis['trades'];
  677. // $analysis['varChartData'] = $calcVar->calcLossAmounts($this->cvarMethod, $analysis['cashFlows'], $trades);
  678. // $analysis['varChartDataMonthly'] = $calcVar->calcLossMonthly($analysis['periods'], $this->cvarMethod, $analysis['cashFlows'], $trades);
  679. $this->data['analysis'] = $analysis;
  680. }
  681. protected function getReport(Request $request) {
  682. if (empty($this->portfolioId) || $this->redirect) {
  683. return false;
  684. }
  685. $reportFilter = $this->getForm('reportFilter');
  686. $reportFilter->handleRequest($request);
  687. if ($reportFilter->isSubmitted() && $reportFilter->isValid()) {
  688. $reportData = $reportFilter->getData();
  689. $this->export = $this->getReportModel()->getReport($reportData, $this->portfolioId, 'committedCashflow');
  690. if (empty($this->export)) {
  691. $this->addFlash('warning', 'No data found. Try to change report type or period of data');
  692. }
  693. }
  694. $this->data['reportForm'] = $reportFilter->createView();
  695. }
  696. protected function getBudgetRateList($data = []) {
  697. $summary = $this->getModel()->totalPortfolioSummary($data);
  698. return $this->getModel()->getBudgetRatesFromSummary($summary);
  699. }
  700. public function tradeOperationAjax(?int $tradeId, $tradeOperation, Request $request) {
  701. parent::initialize($request);
  702. $trade = $this->em->getRepository(Forward::class)->find($tradeId);
  703. if (empty($trade)) {
  704. return new JsonResponse('trade not found', 400);
  705. }
  706. $portfolio = $this->em->getRepository(Portfolio::class)->findOneBy([
  707. 'id' => $trade->getPortfolio(),
  708. 'customer' => $this->getCustomer()->getId(),
  709. ]);
  710. if (empty($portfolio)) {
  711. return new JsonResponse('unauthorized access', 400);
  712. }
  713. $this->tradeId = $tradeId;
  714. $this->tradeOperation = $tradeOperation;
  715. $this->portfolioId = $portfolio->getId();
  716. $object = new ForwardOperations();
  717. $object->setRate($trade->getStrike());
  718. $object->setAmount($trade->getAmountMinor());
  719. $object->setTradeNumber($trade->getTradeNumber());
  720. $object->setRate($trade->getStrike());
  721. $form = $this->getForm('tradesOperations', $object);
  722. $operationFormData = [
  723. 'trade' => $trade,
  724. 'operation' => $this->tradeOperation,
  725. 'form' => $form->createView(),
  726. ];
  727. $operationForm = $this->render('@instrumentsBase/operation_form.html.twig', $operationFormData)->getContent();
  728. return new JsonResponse($operationForm, 200);
  729. }
  730. public function cashflowAjax(?int $cashflowId, LoggerInterface $analyticsLogger, CacheItemPoolInterface $cache, Request $request, Connection $connection) {
  731. $this->init($analyticsLogger, $cache, $request, $connection);
  732. $cashflow = $this->em->getRepository(CashFlows::class)->find($cashflowId);
  733. if (empty($cashflow)) {
  734. return new JsonResponse('cashflow not found', 400);
  735. }
  736. $portfolio = $this->em->getRepository(Portfolio::class)->findOneBy([
  737. 'id' => $cashflow->getPortfolio(),
  738. 'customer' => $this->getCustomer()->getId(),
  739. ]);
  740. if (empty($portfolio)) {
  741. return new JsonResponse('unauthorized access', 400);
  742. }
  743. // $this->portfolioId = $portfolio->getId();
  744. $pairId = $portfolio->getCurrencyPair()->getId();
  745. //pasiimam jau paskaiciuota var data is sesijos
  746. $varData = $this->session->get('var_result_pairId_'.$pairId);
  747. $dailyVolatility = abs($varData['daily']['VaR'][95]);
  748. $calcAll = $request->get('all') ?? 0; //jei yra get parametras, tai skaiciuojam visus aktyvius flowsus siam portfeliui
  749. if (!empty($calcAll)) {
  750. $qb = $this->em->createQueryBuilder();
  751. $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'));
  752. $cashflows = $qb->getQuery()->getResult();
  753. } else {
  754. $cashflows = [$cashflow];
  755. }
  756. // reikia surasti paskutine expire date.
  757. $dateExpireNewest = $this->getModel()->getDateBy('new', $cashflows);
  758. //$leftDays = $this->getModel()->getLeftDays($cashflows);
  759. $leftDays = $request->get('spotHistory') ?? 60;
  760. if ($dateExpireNewest < date_create()->format('Y-m-d')) {
  761. $forecastDataPoints = 0;
  762. $spot = $this->getRate($portfolio->getCurrencyPair(), $dateExpireNewest);
  763. } else {
  764. $forecastDataPoints = 2; //kiek dienu i ateiti prognozuosime
  765. $spot = $this->getRate($portfolio->getCurrencyPair());
  766. }
  767. $result = $this->em->getRepository(CurrencyHistory::class)->getSpotsBy($pairId, $dateExpireNewest, $leftDays);
  768. $spots = array_column($result, 'rate');
  769. $dates = array_column($result, 'date');
  770. if ($dateExpireNewest > date_create()->format('Y-m-d')) { //prediction idedam tik tada, jei dar nepasibaiges flowsas
  771. $dates[] = date_create(' + 1 days')->format('Y-m-d');
  772. $dates[] = date_create(' + 2 days')->format('Y-m-d');
  773. }
  774. $portfolioDirection = $portfolio->getDirection();
  775. // skaiciuojam riskus
  776. $riskData = $this->getModel()->getRiskCalcs($cashflows, $dailyVolatility, $portfolioDirection, $spot);
  777. $averageHedgeRate = $riskData['averageHedgeRate'];
  778. $serieData = [];
  779. foreach ($dates as $key => $date) {
  780. if (!isset($serie0)) {
  781. $serie0 = [];
  782. $serie1 = [];
  783. $serie2 = [];
  784. $serie3 = [];
  785. }
  786. // skaiciuojame spot rate range lyginant su praejusios dienos spot rate
  787. $temKey = $key > 0 ? $key - 1 : 0;
  788. $temSpot = $spots[$temKey] ?? $spot;
  789. $spotMin = round($temSpot - ($temSpot * $dailyVolatility), 4);
  790. $spotMax = round($temSpot + ($temSpot * $dailyVolatility), 4);
  791. $serie0[] = ['x' => $date, 'y' => [$spotMin, $spotMax]];
  792. $serie1[] = ['x' => $date, 'y' => $spots[$key] ?? $temSpot];
  793. $serie2[] = ['x' => $date, 'y' => $riskData['budgetRateAvg']];
  794. if (!empty($averageHedgeRate)) {
  795. $serie3[] = ['x' => $date, 'y' => round($averageHedgeRate, 5)];
  796. }
  797. if ($spotMin < $riskData['minRate']) {
  798. $riskData['minRate'] = $spotMin;
  799. }
  800. if ($spotMax > $riskData['maxRate']) {
  801. $riskData['maxRate'] = $spotMax;
  802. }
  803. }
  804. $serieData[] = ['type' => 'rangeArea', 'name' => 'Spot range', 'data' => $serie0];
  805. $serieData[] = ['type' => 'line', 'name' => 'Spot', 'data' => $serie1];
  806. $serieData[] = ['type' => 'line', 'name' => 'Budget Rate', 'data' => $serie2];
  807. if (!empty($averageHedgeRate)) {
  808. $serieData[] = ['type' => 'line', 'name' => 'Hedge Rate', 'data' => $serie3];
  809. }
  810. if (empty($spots)) {
  811. $spots = $serie1;
  812. }
  813. $calcs = [
  814. 'dates' => $dates,
  815. 'spotSeries' => $serieData,
  816. 'forecastDataPoints' => $forecastDataPoints,
  817. ];
  818. $calcs = array_merge_recursive($calcs, $riskData);
  819. $params = [
  820. 'calcs' => $calcs ?? null,
  821. 'minorCurrency' => $portfolio->getCurrencyPair()->getCurrencyNameByNumber(2),
  822. 'spots' => $spots,
  823. ];
  824. $content = $this->render('@instrumentsBase/cashflow_details.html.twig', $params)->getContent();
  825. $response = [
  826. 'content' => $content,
  827. 'calcs' => $calcs ?? [],
  828. ];
  829. return new JsonResponse($response, 200);
  830. }
  831. /**
  832. * @return HedgingPolicyModel
  833. */
  834. protected function getModel($renew = false) {
  835. if (empty($this->model) || $renew) {
  836. $this->model = new HedgingPolicyModel($this->em, $this->logger);
  837. }
  838. return $this->model;
  839. }
  840. /**
  841. * @param $renew
  842. * @return ImportValidator
  843. */
  844. protected function getImportValidatorModel($renew = false) {
  845. if (empty($this->importValidatorModel) || $renew) {
  846. $this->importValidatorModel = new ImportValidator();
  847. }
  848. return $this->importValidatorModel;
  849. }
  850. }