<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ODM\MongoDB\DocumentManager;
use App\Document\Order;
use App\Document\MarketOrder;
use App\Document\Currencies;
use App\Document\Wallet;
use App\Document\Trade;
use App\Document\Price;
use App\Document\User;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Document\Setting;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
use KuCoin\SDK\Auth;
use KuCoin\SDK\Http\SwooleHttp;
use KuCoin\SDK\KuCoinApi;
use KuCoin\SDK\PrivateApi\Order as KuCoinOrder;
use KuCoin\SDK\PrivateApi\Account;
class EngineController extends AbstractController
{
private $translator;
private $doc;
public function __construct(TranslatorInterface $translator, DocumentManager $doctrine)
{
$this->translator = $translator;
$this->doc = $doctrine;
}
private function getLastPrice($crypto,$type='SELL'): float
{
$trade = $this->doc->createQueryBuilder(Price::class)
->field('pair')->equals($crypto)
->field('type')->equals($type)
->getQuery()
->getSingleResult();
return is_null($trade) ? 0 : $trade->getPrice() ;
}
#[Route('/api/order/marketsubmit', methods: ['POST'], name: 'order_marketsubmit')]
public function marketSubmit(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), false);
$user = $this->getUser();
$user_id = $user->getId();
$status = $user->getStatus();
$market_symbol = $data->market ;
if ($status < 1) {
$translated = $this->translator->trans('your account not active');
return new JsonResponse(['status' => '400', 'msg' => $translated]);
}
$setting = $this->doc->createQueryBuilder(Setting::class)
->limit(1)
->getQuery()
->getSingleResult();
$amount = floatval($data->amount);
$side = $data->side;
$type = $data->type;
$crypto = $data->crypto;
$price = isset($data->price) ? $data->price : 0;
if ($amount <= 0) {
$translated = $this->translator->trans('amount must larger than 1');
return new JsonResponse(['status' => '400', 'msg' => $translated]);
}
$wallet_rial = $this->doc->createQueryBuilder(Wallet::class)
->field('userid')->equals($user_id)
->field('pair')->equals($market_symbol)
->getQuery()
->getSingleResult();
$wallet_crypto = $this->doc->createQueryBuilder(Wallet::class)
->field('userid')->equals($user_id)
->field('pair')->equals($crypto)
->getQuery()
->getSingleResult();
$usdt_buy_price = $setting->getUsdtpricebuy();
$usdt_sell_price = $setting->getUsdtpricesell();
if ($side === 'sell' && $amount > $wallet_crypto->getballance()) {
$translated = $this->translator->trans('ballance is LOw');
return new JsonResponse(['status' => '400', 'msg' => $translated]);
}
$crypto_buy = $this->getLastPrice($crypto);
$crypto_buy += (($crypto_buy * 0.1) / 100);
if($market_symbol === "RIAL") {
$crypto_price_buy = $crypto_buy * $usdt_buy_price ;
}else {
$crypto_price_buy = $crypto_buy ;
}
if ($type === 'limit' && $price <= 0) {
$translated = $this->translator->trans('Price is Low');
return new JsonResponse(['status' => '400', 'msg' => $translated]);
}
$priceDifference = abs(($price - $crypto_price_buy) / $crypto_price_buy) * 100;
if ($type === 'limit' && ($price <= 0 || $priceDifference > 2)) {
$translated = $this->translator->trans('Price must be within ±2% range');
return new JsonResponse(['status' => '400', 'msg' => $translated]);
}
// if ($type === 'limit' && !$this->isPriceWithinRange($crypto_buy, $price)) {
// $translated = $this->translator->trans('price not correct');
// return new JsonResponse(['status' => '400', 'msg' => $translated]);
// }
if ($type === 'market') {
$price = $crypto_buy;
}
if ($side === 'buy' && ($amount * $crypto_price_buy) >= $wallet_rial->getballance()) {
$translated = $this->translator->trans('ballance is LOw');
return new JsonResponse(['status' => '400', 'msg' => $translated]);
}
if ($type === 'limit') {
// Freeze funds
if ($side === 'buy') {
$wallet_rial->setballance($wallet_rial->getballance() - ($amount * $crypto_price_buy));
$wallet_rial->setFrozen(($amount * $crypto_price_buy) + $wallet_rial->getFrozen());
} else {
$wallet_crypto->setballance($wallet_crypto->getballance() - $amount);
$wallet_crypto->setFrozen($amount + $wallet_crypto->getFrozen());
}
$this->doc->persist($wallet_rial);
$this->doc->persist($wallet_crypto);
$this->doc->flush();
}
$order = new MarketOrder();
$order->setUserid($user_id);
$order->setAmount($amount);
$order->setPair($crypto);
$order->setSide($side);
$order->setType($type);
$order->setPrice($price);
$order->setTakeprofit("0");
$order->setStoploss("0");
$order->setTime(time());
$order->setFee(0);
$order->setApi('');
$order->setStatus("Pending");
$this->doc->persist($order);
$this->doc->flush();
if ($type === 'market') {
// Create an opposite order for the robot
$robot_id = '666621eea320d65e3f6878ec';
// Mark the original order as filled
$order->setStatus('Filled');
$this->doc->persist($order);
$this->doc->flush();
// Create a new trade
$trade_id = \Ramsey\Uuid\Uuid::uuid4()->toString(); // Generate a unique trade ID
$buy_user_id = $side === 'buy' ? $user_id : $robot_id;
$sell_user_id = $side === 'sell' ? $user_id : $robot_id;
$this->createNewTrade($buy_user_id, $sell_user_id, $amount, $crypto,'market', $price, $trade_id, 'Completed');
// Update wallets
$this->updateWallets($buy_user_id, $sell_user_id, $amount, $crypto, $price , $market_symbol);
}
$translated = $this->translator->trans('Order Submited');
return new JsonResponse(['status' => '200', 'msg' => $translated]);
}
#[Route('/api/order/cancelmarketsubmit', methods: ['POST'], name: 'order_cancelmarketsubmit')]
public function cancelMarketSubmit(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), false);
$user = $this->getUser();
$order_id = $data->id;
$order = $this->doc->createQueryBuilder(MarketOrder::class)
->field('id')->equals($order_id)
->getQuery()
->getSingleResult();
if ($order && $order->getStatus() === 'Pending') {
$this->doc->createQueryBuilder(MarketOrder::class)
->field('id')->equals($order_id)
->findAndUpdate()
->field('status')->set('cancel')
->getQuery()
->execute();
// Unfreeze funds
if ($order->getSide() === 'buy') {
$wallet_rial = $this->doc->createQueryBuilder(Wallet::class)
->field('userid')->equals($user->getId())
->field('pair')->equals("RIAL")
->getQuery()
->getSingleResult();
$this->unfreezeFunds($wallet_rial, $order->getAmount() * $order->getPrice());
} else {
$wallet_crypto = $this->doc->createQueryBuilder(Wallet::class)
->field('userid')->equals($user->getId())
->field('pair')->equals($order->getPair())
->getQuery()
->getSingleResult();
$this->unfreezeFunds($wallet_crypto, $order->getAmount());
}
$translated = $this->translator->trans('Order Canceled');
return new JsonResponse(['status' => '200', 'msg' => $translated]);
}
return new JsonResponse(['status' => '400', 'msg' => 'Order not found or not cancelable']);
}
private function unfreezeFunds($wallet, $amount)
{
$wallet->setballance($wallet->getballance() + $amount);
$wallet->setFrozen($wallet->getFrozen() - $amount);
$this->doc->persist($wallet);
$this->doc->flush();
}
private function createNewTrade($buy_user_id, $sell_user_id, $amount, $pair , $type, $price, $trade_id, $status)
{
$trade = new Trade();
$trade->setAmount($amount);
$trade->setPair($pair);
$trade->setType($type);
$trade->setPrice($price);
$trade->setTime(time());
$trade->setStatus($status);
$trade->setFee(0);
$trade->setTradeid($trade_id);
$trade->setbuyUserid($buy_user_id);
$trade->setsellUserid($sell_user_id);
$this->doc->persist($trade);
$this->doc->flush();
}
private function createOrder($user_id, $amount, $pair, $side, $type, $price, $robot, $status)
{
$order = new MarketOrder();
$order->setUserid($user_id);
$order->setAmount($amount);
$order->setPair($pair);
$order->setSide($side);
$order->setType($type);
$order->setPrice($price);
$order->setTakeprofit("0");
$order->setStoploss("0");
$order->setTime(time());
$order->setFee(0);
$order->setRobot($robot);
$order->setStatus($status);
$this->doc->persist($order);
$this->doc->flush();
}
private function updateOrder($order)
{
$this->doc->persist($order);
$this->doc->flush();
}
private function updateWallets($buy_user_id, $sell_user_id, $amount, $pair, $price , $market_symbol)
{
$usdt_buy_price = $this->doc->createQueryBuilder(Setting::class)
->limit(1)
->getQuery()
->getSingleResult()
->getUsdtpricebuy();
if($market_symbol === "RIAL") {
$rial_amount = $amount * $price * $usdt_buy_price;
} else {
$rial_amount = $amount * $price ; // Corrected calculation of rial_amount
}
// Update buyer's wallet
$this->addMoney($buy_user_id, $pair, $amount);
$this->removeMoney($buy_user_id, $market_symbol, $rial_amount);
// Update seller's wallet
$this->addMoney($sell_user_id, $market_symbol, $rial_amount);
$this->removeMoney($sell_user_id, $pair, $amount);
}
private function addMoney($user_id, $pair, $amount)
{
$wallet = $this->getWallet($user_id, $pair);
if ($wallet) {
$wallet->setballance($wallet->getballance() + $amount);
$this->updateWallet($wallet);
}
}
private function removeMoney($user_id, $pair, $amount)
{
$wallet = $this->getWallet($user_id, $pair);
if ($wallet) {
$wallet->setballance($wallet->getballance() - $amount);
$this->updateWallet($wallet);
}
}
private function getWallet($user_id, $pair)
{
return $this->doc->createQueryBuilder(Wallet::class)
->field('userid')->equals($user_id)
->field('pair')->equals($pair)
->getQuery()
->getSingleResult();
}
private function updateWallet($wallet)
{
$this->doc->persist($wallet);
$this->doc->flush();
}
private function isPriceWithinRange($lastPrice, $newPrice): bool
{
$minPrice = $lastPrice * 0.1;
$maxPrice = $lastPrice * 0.02;
return $newPrice >= $minPrice && $newPrice <= $maxPrice;
}
#[Route('/pub/order/matchengine', methods: ['GET'], name: 'trade_matchengine')]
public function matchEngine(): JsonResponse
{
$pairs = ["BTC", "ETH", "DOGE", "ADA", "SOL", "DOT", "TRX", "BNB", "LTC"];
foreach ($pairs as $pair) {
// Retrieve all limit buy and sell orders
$buy_limit_orders = $this->getMarketOrderList($pair, 'buy', 'limit');
$sell_limit_orders = $this->getMarketOrderList($pair, 'sell', 'limit');
// Get the last price of the pair
$last_price_buy = round($this->getLastPrice($pair,'BUY'), 6);
$last_price_sell = round($this->getLastPrice($pair), 6);
// Process buy limit orders
foreach ($buy_limit_orders as $buy_order) {
if ($buy_order->getPrice() >= $last_price_buy) {
$this->processMatchingOrder($buy_order, $buy_order->getPrice());
}
}
// Process sell limit orders
foreach ($sell_limit_orders as $sell_order) {
echo "o" . $sell_order->getPrice() . 'o' . $last_price_sell ;
if ($sell_order->getPrice() <= $last_price_sell) {
$this->processMatchingOrder($sell_order, $sell_order->getPrice());
}
}
}
return new JsonResponse(['status' => '200', 'msg' => 'Match Engine Run Successfully']);
}
private function processMatchingOrder($order, $last_price)
{
$user_id = $order->getUserid();
$amount = $order->getAmount();
$pair = $order->getPair();
$side = $order->getSide();
$opposite_side = $side === 'buy' ? 'sell' : 'buy';
$robot_id = '666621eea320d65e3f6878ec';
// Create an opposite order for the robot
$this->createOrder($robot_id, $amount, $pair, $opposite_side, 'market', $last_price, true, 'Filled');
// Mark the original order as filled
$order->setStatus('Filled');
$this->updateOrder($order);
$setting = $this->doc->createQueryBuilder(Setting::class)
->limit(1)
->getQuery()
->getSingleResult();
$usdt_buy_price = $setting->getUsdtpricebuy();
$usdt_sell_price = $setting->getUsdtpricesell();
$crypto_price_buy = $this->getLastPrice($pair , 'BUY') ;
$crypto_price_sell = $this->getLastPrice($pair) ;
// Create a new trade
$trade_id = \Ramsey\Uuid\Uuid::uuid4()->toString();
$buy_user_id = $side === 'buy' ? $user_id : $robot_id;
$sell_user_id = $side === 'sell' ? $user_id : $robot_id;
$this->createNewTrade($buy_user_id, $sell_user_id, $amount, $pair ,'limit', $last_price, $trade_id, 'Completed');
$wallet_rial = $this->doc->createQueryBuilder(Wallet::class)
->field('userid')->equals($user_id)
->field('pair')->equals("RIAL")
->getQuery()
->getSingleResult();
$wallet_crypto = $this->doc->createQueryBuilder(Wallet::class)
->field('userid')->equals($user_id)
->field('pair')->equals($order->getPair())
->getQuery()
->getSingleResult();
if ($order->getSide() === 'buy') {
$wallet_rial->setFrozen( $wallet_rial->getFrozen() - (($amount * $crypto_price_buy) * $usdt_buy_price) );
$wallet_crypto->setballance($wallet_crypto->getballance() + $amount);
} else {
$wallet_crypto->setFrozen( $wallet_crypto->getFrozen() - $amount);
$wallet_rial->setballance($wallet_rial->getballance() + (($amount * $crypto_price_buy) * $usdt_buy_price));
}
$this->updateWallet($wallet_rial);
$this->updateWallet($wallet_crypto);
// Update wallets
// $this->updateWallets($buy_user_id, $sell_user_id, $amount, $pair, $last_price);
}
private function getMarketOrderList($pair, $side, $type)
{
return $this->doc->createQueryBuilder(MarketOrder::class)
->field('side')->equals($side)
->field('type')->equals($type)
->field('status')->equals('Pending')
->field('pair')->equals($pair)
->limit(2000)
->sort('time', 'asc')
->getQuery()
->execute();
}
}