<?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;
use ccxt\coinex;
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() ;
}
private function submitLimitOrder($doctrine, $data, $crypto, $userId, $user_limit_price, $wallet_rial, $wallet_crypto, $wallet_rial_balance, $wallet_crypto_balance, $last_usdt_price, $side): array
{
$key = "8B830428825B4E8B9EF8483FB704FF62";
$secret = "3BC71E2BE239C53AB91DCA3019FFD6195AB41B072DF7C571" ;
$amount = $data->amount ?? null;
$price = $data->price ?? null;
if ($crypto === "USDT") {
return [
"status" => "400",
"msg" => 'این نوع برای usdt فعال نمی باشد'
];
}
if ($price <= 0) {
$translated = $this->translator->trans('Fill price');
return [
"status" => "400",
"msg" => $translated
];
}
try {
$exchange = new \ccxt\coinex([
'apiKey' => $key,
'secret' => $secret,
]);
$symbol = $crypto . '/USDT';
$params = ["createMarketBuyOrderRequiresPrice" => true];
$price_now = $exchange->fetchTicker($symbol)['last'];
$cost = $amount;
$deviation = abs($user_limit_price - $price_now) / $price_now * 100;
if ($deviation > 20) {
$suggested_price = round($price_now, 4);
$translated = $this->translator->trans(
'Your price is too far from the market. Suggested price: ' . $suggested_price
);
return [
"status" => "400",
"msg" => $translated
];
}
if ($side === 'buy') {
$cost = $amount * $price_now;
}
$order = $exchange->createOrder($symbol, 'limit', $side, $cost, $user_limit_price, $params);
$dealFunds = $order['cost'];
$fee_usdt = $order['fee']['cost'] ?? 0;
$fee = $fee_usdt * $last_usdt_price;
$total = $dealFunds * $last_usdt_price;
if ($side === 'buy') {
$wallet_rial->setballance($wallet_rial_balance - $total - $fee);
$wallet_crypto->setballance($wallet_crypto_balance + $amount);
} elseif ($side === 'sell') {
$wallet_rial->setballance($wallet_rial_balance + $total - $fee);
$wallet_crypto->setballance($wallet_crypto_balance - $amount);
}
$doctrine->flush();
return [
"status" => "200",
"msg" => "Order submitted",
"order_id" => $order,
"order_status" => "PENDING",
"fee" => $fee,
"price" => $user_limit_price
];
} catch (\Exception $e) {
return [
"status" => "400",
"msg" => "ERROR",
"e" => $e->getMessage()
];
}
}
#[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;
$user_limit_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);
$real_price_now = $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') {
if ($price <= 0) {
$translated = $this->translator->trans('Price is too low');
return new JsonResponse(['status' => '400', 'msg' => $translated]);
}
$priceDifference = abs(($price - $real_price_now) / $real_price_now) * 100;
if ($priceDifference > 20) {
$translated = $this->translator->trans('Price must be within ±20% of market price');
return new JsonResponse(['status' => '400', 'msg' => $translated , 's' => $crypto_price_buy]);
}
}
// 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();
}
$key = "8B830428825B4E8B9EF8483FB704FF62";
$secret = "3BC71E2BE239C53AB91DCA3019FFD6195AB41B072DF7C571" ;
if ($type === 'market') {
try {
// Set up your CoinEx API credentials
$api_key = $key;
$api_secret = $secret;
// Instantiate the CoinEx exchange class
$exchange = new coinex([
'apiKey' => $api_key,
'secret' => $api_secret,
]);
// Define the trading pair and order details
$symbol = $crypto . '/USDT'; // Market pair
$params = ["createMarketBuyOrderRequiresPrice" => false];
$cost = $amount;
$price_now = $exchange->fetchTicker($symbol)['last'];
if ($side === 'buy') {
$cost = $amount * $price_now;
}
try {
$api_order = $exchange->createOrder($symbol, 'market', $side, $cost, 0, $params);
$order_submited = true;
} catch (\ccxt\NetworkError $e) {
return new JsonResponse([
"status" => "400",
"msg" => "Network Error"
]);
} catch (\ccxt\ExchangeError $e) {
return new JsonResponse([
"status" => "400",
"msg" => $e->getMessage()
]);
} catch (Exception $e) {
return new JsonResponse([
"status" => "400",
"msg" => $e->getMessage()
]);
}
} catch (Exception $e) {
return new JsonResponse([
"status" => "400",
"msg" => $e->getMessage()
]);
}
$robot_id = '666621eea320d65e3f6878ec';
$buy_user_id = $side === 'buy' ? $user_id : $robot_id;
$sell_user_id = $side === 'sell' ? $user_id : $robot_id;
$this->updateWallets($buy_user_id, $sell_user_id, $amount, $crypto, $price , $market_symbol);
} else {
if ($type === 'limit') {
$result = $this->submitLimitOrder(
$this->doc,
$data,
$crypto,
$user_id,
$user_limit_price,
$wallet_rial,
$wallet_crypto,
$wallet_rial->getballance(),
$wallet_crypto->getballance(),
$usdt_buy_price,
$side
);
$api_order = $result['order_id'];
}
}
$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(json_encode($api_order));
$order->setStatus("Pending");
$this->doc->persist($order);
$this->doc->flush();
$translated = $this->translator->trans('Order Submited');
return new JsonResponse(['status' => '200', 'msg' => $translated]);
}
// #[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') {
return new JsonResponse(['status' => '400', 'msg' => 'Order not found or not cancelable']);
}
// CoinEx API cancel logic
$apiinfo = json_decode($order->getApi(), true);
$key = "8B830428825B4E8B9EF8483FB704FF62";
$secret = "3BC71E2BE239C53AB91DCA3019FFD6195AB41B072DF7C571" ;
try {
$exchange = new \ccxt\coinex([
'apiKey' => $key,
'secret' => $secret,
]);
$symbol = $order->getPair() . '/USDT';
$exchange->cancelOrder($apiinfo['id'], $symbol);
} catch (\ccxt\NetworkError | \ccxt\ExchangeError | \Exception $e) {
return new JsonResponse(['status' => '400', 'msg' => $e->getMessage()]);
}
// Update local order status
$this->doc->createQueryBuilder(MarketOrder::class)
->field('id')->equals($order_id)
->findAndUpdate()
->field('status')->set('cancel')
->getQuery()
->execute();
// Unfreeze logic
$setting = $this->doc->createQueryBuilder(Setting::class)
->limit(1)
->getQuery()
->getSingleResult();
$usdt_buy_price = $setting->getUsdtpricebuy();
$usdt_sell_price = $setting->getUsdtpricesell();
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() * $usdt_buy_price);
} 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]);
}
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();
}
}