Files
repo-backend/app/LeverTransaction.php

700 lines
26 KiB
PHP

<?php
namespace App;
use App\Jobs\FollowOrderHandel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use App\Jobs\LeverClose;
use App\Jobs\LeverPushTrade;
use App\Jobs\LeverHandle;
class LeverTransaction extends Model
{
const BUY = 1; //买入
const SELL = 2; //卖出
const ENTRUST = 0; //挂单中
const TRANSACTION = 1; //交易中
const CLOSING = 2; //平仓中
const CLOSED = 3; //已平仓
const CANCEL = 4; //已撤单
protected $table = 'lever_transaction';
public $timestamps = false;
protected $guarded = [];
protected $appends = [
'time',
'symbol',
'profits',
'status_name',
'type_name',
'show_lever_id',//显示订单id
];
protected static $statusList = [
'挂单中',
'交易中',
'平仓中',
'已平仓',
'已撤单',
];
protected static $typeList = [
'',
'买入',
'卖出',
];
public static function enumStatus()
{
return self::$statusList;
}
//伪订单号
public function getShowLeverIdAttribute()
{
$id = $this->attributes['id'] ?? 0;
$uid = $id + 10000;
return $uid;
}
public function user()
{
return $this->belongsTo('App\Users', 'user_id', 'id')->withDefault();
}
public function getMobileAttribute()
{
$user = $this->user()->getResults();
return $user->phone ?? '';
}
public function getAccountNumberAttribute()
{
$user = $this->user()->getResults();
return $user->account_number ?? '';
}
public function getStatusNameAttribute()
{
$status = $this->attributes['status'] ?? 0;
return self::$statusList[$status];
}
public function getTypeNameAttribute()
{
$type = $this->attributes['type'] ?? 0;
return self::$typeList[$type];
}
public function getTimeAttribute()
{
return date('Y-m-d H:i:s', $this->attributes['create_time'] ?? 0);
}
public function getHandleTimeAttribute()
{
$handle_time = intval($this->attributes['handle_time']);
return $handle_time != 0 ? date('Y-m-d H:i:s', $handle_time) : '';
}
public function getTransactionTimeAttribute()
{
$transaction_time = intval($this->attributes['transaction_time']);
return $transaction_time != 0 ? date('Y-m-d H:i:s', $transaction_time) : '';
}
public function getCompleteTimeAttribute()
{
$complete_time = intval($this->attributes['complete_time']);
return $complete_time != 0 ? date('Y-m-d H:i:s', $complete_time) : '';
}
public function getSymbolAttribute()
{
$currency_id = $this->getAttribute('currency');
$legal_id = $this->getAttribute('legal');
$currency_match = CurrencyMatch::where('currency_id', $currency_id)
->where('legal_id', $legal_id)
->first();
return $currency_match ? $currency_match->symbol : '';
}
/**
* 取每单盈利
*
* @return void
*/
public function getProfitsAttribute()
{
$profits = 0;
$type = $this->getAttribute('type');
$number = $this->getAttribute('number');
$status = $this->getAttribute('status');
if ($status == self::ENTRUST || $status == self::CANCEL) {
return 0.00;
}
//$multiple = $this->getAttribute('multiple');
//$multiple_number = bc_mul($number, $multiple);
$update_price = $this->getAttribute('update_price');
$price = $this->getAttribute('price');
$diff = $type == self::BUY ? bc_sub($update_price, $price) : bc_sub($price, $update_price);
$profits = bc_mul($diff, $number);
return $profits;
}
public static function leverMultiple($key = 0,$currency_id)
{
$data["muit"] = LeverMultiple::where("type","=",1)->where("currency_id",$currency_id)->select("value")->get()->toArray();
$data["share"] = LeverMultiple::where("type","=",2)->where("currency_id",$currency_id)->select("value")->get()->toArray();
// var_dump($data);die;
// $data = array(
// "5" => "5倍",
// "10" => "10倍",
// "20" => "20倍",
// "50" => "50倍",
// );
if (!empty($key) && in_array($key, $data)) {
return $data[$key];
} else {
return $data;
}
}
/**
* 订单价格更新
*
* @param integer $legal_id 法币id
* @param integer $currency_id 交易币id
* @param float $price 当前交易对最新价格
* @param float $timestamp 毫秒级时间戳
* @return void
*/
public static function newPrice($legal_id, $currency_id, $price, $timestamp = null)
{
$timestamp == null && $timestamp = microtime(true);
if (empty($legal_id) || empty($currency_id) || empty($price)) {
return false;
}
$params = [
'legal_id' => $legal_id,
'currency_id' => $currency_id,
'now_price' => $price,
'now' => $timestamp,
];
$start = microtime(true);
//先批量更新指定交易对未平仓的交易的最新价格
LeverTransaction::where("legal", $legal_id)
->where("currency", $currency_id)
->where("status", '<=', self::TRANSACTION)
->update([
'update_price' => $price,
'update_time' => $timestamp,
]);
$end = microtime(true);
LeverHandle::dispatch($params)->onQueue('lever:handle')->onConnection('sync');
// echo '更新持仓订单价格消耗:' . ($end - $start) . '秒' . PHP_EOL;
}
/**
* 价格变动处理逻辑
*
* @param integer $legal_id 法币id
* @param integer $currency_id 交易币id
* @param float $price 当前交易对最新价格
* @param float $timestamp 毫秒级时间戳
* @return void
*/
public static function tradeHandle($legal_id, $currency_id, $price, $timestamp = null)
{
$timestamp == null && $timestamp = microtime(true);
if (empty($legal_id) || empty($currency_id) || empty($price)) {
return false;
}
$total_start = microtime(true);
$start = microtime(true);
//激活满足条件的挂单
self::entrustActivate();
$end = microtime(true);
//echo '激活订单消耗:' . ($end - $start) . '秒' . PHP_EOL.
$start = microtime(true);
//止盈止亏处理
self::checkNeedStopPriceTrade(0, $legal_id, $currency_id);
$end = microtime(true);
// echo '止盈止亏消耗:' . ($end - $start) . '秒' . PHP_EOL;
$start = microtime(true);
$levers = LeverTransaction::where("legal", $legal_id)
->where("currency", $currency_id)
->where("status", self::TRANSACTION)
->select("id", "user_id", "create_time")
->groupBy('user_id')
->get();
if (count($levers) > 0) {
foreach ($levers as $trade) {
//推送风险率和订单
LeverPushTrade::dispatch($trade->user_id, $legal_id, $currency_id)->onQueue('lever:push:trade');
self::handleUserLever($trade->user_id, $legal_id);
}
} else {
echo '法币id:' . $legal_id . ',交易币id:' . $currency_id . '无数据' . PHP_EOL;
}
$end = microtime(true);
//echo '检测风险率消耗:' . ($end - $start) . '秒' . PHP_EOL;
// echo '总计消耗:' . ($end - $total_start) . '秒' . PHP_EOL;
}
/**
* 更新用户指定交易对的杠杆交易
*
* @param integer $user_id 用户id
* @param integer $legal_id 法币id
* @param integer $currency_id 交易币id
* @return void
*/
protected static function handleUserLever($user_id, $legal_id, $currency_id = 0)
{
if (empty($user_id)) {
return false;
}
DB::beginTransaction();
try {
$lever_burst_hazard_rate = Setting::getValueByKey('lever_burst_hazard_rate', 0);
$legal_wallet = UsersWallet::where("user_id", $user_id)
->where("currency", $legal_id)
->lockForUpdate()
->first();
if (empty($legal_wallet)) {
throw new \Exception('钱包不存在');
}
//取交易对总盈利和总保证金
$profit_results = self::getUserProfit($user_id, $legal_id, $currency_id);
extract($profit_results);
//是否满足爆仓条件
$need_burst = self::checkBurst($legal_wallet);
if (!$need_burst) {
throw new \Exception('不满足爆仓条件');
}
//对用户交易对做平仓中标记
$affect_result = self::setUserLeverCover($user_id, $legal_id, $currency_id);
if (count($affect_result) <= 0) {
throw new \Exception('爆仓状态标记失败');
}
$diff = 0;
$change = bc_add($profits_total, $caution_money_total);
$after_balance = bc_add($legal_wallet->lever_balance, $change);
//如果余额不够扣就抹去不够扣的金额
// if (bc_comp($after_balance, 0) < 0) {
// $diff = $after_balance;
// $change = -$legal_wallet->lever_balance;
// }
$legal_name = $legal_wallet->currency_name;
$extra_data = serialize([
'legal_name' => $legal_name,
'affect_result' => $affect_result,
'balance' => $legal_wallet->lever_balance,
'caution_money_total' => $caution_money_total,
'profits_total' => $profits_total,
'diff' => $diff,
'lever_burst_hazard_rate' => $lever_burst_hazard_rate,
]);
$result = change_wallet_balance(
$legal_wallet,
3,
$change,
AccountLog::LEVER_TRANSACTION_FROZEN,
'暴仓处理' . $legal_name . '余额(退回保证金:' . $caution_money_total . ',结算总盈亏:' . $profits_total . ')',
false,
0,
$diff == 0 ? 0 : 1, //1代表有差额
$extra_data,
true,
true
);
if ($result !== true) {
throw new \Exception($result);
}
DB::commit();
LeverClose::dispatch($affect_result, false)->onQueue('lever:close');
return true;
} catch (\Exception $ex) {
DB::rollBack();
// $path = base_path() . '/storage/logs/lever/';
// $filename = date('Ymd') . '.log';
// file_exists($path) || @mkdir($path);
// error_log(date('Y-m-d H:i:s') . ' File:' . $ex->getFile() . ', Line:' . $ex->getLine() . ', Message:' . $ex->getMessage() . PHP_EOL, 3, $path . $filename);
return false;
}
}
/**
* 取用户盈利和保证金
*
* @param integer $user_id 用户id
* @param integer $legal_id 法币id
* @param integer $currency_id 交易币id
* @return array
*/
public static function getUserProfit($user_id, $legal_id = 0, $currency_id = 0)
{
$profits_total = 0; //交易对盈亏总额
$caution_money_total = 0; //交易对可用本金总额
$origin_caution_money_total = 0; //交易对原始保证金
try {
//优先让数据库计算盈亏和保证金
$user_profit = LeverTransaction::where('status', self::TRANSACTION)
->where('user_id', $user_id)
->where(function ($query) use ($legal_id, $currency_id) {
$legal_id > 0 && $query->where('legal', $legal_id);
$currency_id > 0 && $query->where('currency', $currency_id);
})
->select('user_id')
->selectRaw('SUM((CASE `type` WHEN 1 THEN `update_price` - `price` WHEN 2 THEN `price` - `update_price` END) * `number`) AS `profits_total`')
->selectRaw('SUM(`caution_money`) AS `caution_money_total`')
->selectRaw('SUM(`origin_caution_money`) AS `origin_caution_money_total`')
->groupBy('user_id')
->first();
if (!$user_profit) {
$levers = LeverTransaction::where(function ($query) use ($legal_id, $currency_id) {
$legal_id > 0 && $query->where("legal", $legal_id);
$currency_id > 0 && $query->where("currency", $currency_id);
})->where("user_id", $user_id)->where("status", self::TRANSACTION)->get();
if (count($levers) <= 0) {
throw new \Exception('没有需要处理的交易');
}
//计算交易对总盈亏
foreach ($levers as $trade) {
$caution_money_total = bc_add($caution_money_total, $trade->caution_money);
$origin_caution_money_total = bc_add($origin_caution_money_total, $trade->origin_caution_money);
$profits_total = bc_add($profits_total, $trade->profits);
}
} else {
$caution_money_total = $user_profit->caution_money_total;
$origin_caution_money_total = $user_profit->origin_caution_money_total;
$profits_total = $user_profit->profits_total;
}
} catch (\Exception $e) {
//echo $e->getMessage();
}
return [
'profits_total' => $profits_total,
'caution_money_total' => $caution_money_total,
'origin_caution_money_total' => $origin_caution_money_total,
];
}
/**
* 平仓用户指定交易对交易(只是改变状态,避免价格再被更新)
*
* @param integer $user_id
* @param integer $legal_id
* @param integer $currency_id
* @return array
*/
protected static function setUserLeverCover($user_id, $legal_id = 0, $currency_id = 0)
{
DB::beginTransaction();
$trades = LeverTransaction::where(function ($query) use ($legal_id, $currency_id) {
$legal_id > 0 && $query->where("legal", $legal_id);
$currency_id > 0 && $query->where("currency", $currency_id);
})->where("user_id", $user_id)->where("status", self::TRANSACTION);
$list = $trades->pluck('id')->all(); //记录下标记的交易id
$result = $trades->update([
'status' => self::CLOSING,
'handle_time' => microtime(true),
]);
DB::commit();
return $result > 0 ? $list : [];
}
/**
* 平仓
*
* @param \App\LeverTransaction $lever_transaction
* @return bool
*/
public static function leverClose($lever_transaction, $is_follow = false)
{
try {
DB::beginTransaction();
if (empty($lever_transaction)) {
throw new \Exception('交易不存在');
}
$last_price = self::getLastPrice($lever_transaction->legal, $lever_transaction->currency);
$lever_transaction->refresh();
if ($lever_transaction->status != self::TRANSACTION) {
throw new \Exception('该笔交易状态异常,不能平仓' . $lever_transaction->status);
}
//更新状态
$lever_transaction->update_price = $last_price;
$lever_transaction->update_time = microtime(true);
$lever_transaction->status = self::CLOSING;
$lever_transaction->handle_time = microtime(true);
$result = $lever_transaction->save();
if (!$result) {
throw new \Exception('平仓失败:锁定交易状态失败');
}
$legal_wallet = UsersWallet::where("user_id", $lever_transaction->user_id)
->where("currency", $lever_transaction->legal)
->lockForUpdate()
->first();
if (empty($legal_wallet)) {
throw new \Exception('钱包不存在');
}
//计算盈亏
$profit = $lever_transaction->profits;
$change = bc_add($lever_transaction->caution_money, $profit); //算上本金
//从钱包处理资金
$pre_result = bc_add($legal_wallet->lever_balance, $change);
$diff = 0;
//是否余额不够扣除
// if (bc_comp($pre_result, 0) < 0) {
// $change = -$legal_wallet->lever_balance;
// $diff = $pre_result;
// }
$extra_data = [
'trade_id' => $lever_transaction->id,
'caution_money' => $lever_transaction->caution_money,
'profit' => $profit,
'diff' => $diff,
];
$result = change_wallet_balance(
$legal_wallet,
3,
$change,
AccountLog::LEVER_TRANSACTION_ADD,
'平仓资金处理',
false,
0,
$diff == 0 ? 0 : 1, //1代表有差额
serialize($extra_data),
true ,//余额为0仍然要平仓
true//允许资金扣为负数
);
if ($result !== true) {
throw new \Exception($result);
}
$lever_transaction->refresh();
$lever_transaction->status = self::CLOSED;
$lever_transaction->fact_profits = $profit;
$lever_transaction->complete_time = microtime(true);
$result = $lever_transaction->save();
if (!$result) {
throw new \Exception('平仓失败:更新处理状态失败');
}
DB::commit();
//处理跟随订单,这里用个判断防止无限递归
if($is_follow === false) {
FollowOrderHandel::dispatch($lever_transaction->id)->onQueue('FollowOrderHandel');
}
return true;
} catch (\Exception $e) {
DB::rollBack();
return false;
}
}
/**
* 取交易对最新价格(优先从行情获取)
*
* @param integer $legal_id 法币id
* @param integer $currency_id 交易币id
* @return integer
*/
public static function getLastPrice($legal_id, $currency_id)
{
//优先从行情取最新价格
$last_price = 0;
$last = CurrencyQuotation::where('legal_id', $legal_id)
->where('currency_id', $currency_id)
->first();
if (!$last) {
$last = TransactionComplete::orderBy('id', 'desc')
->where("currency", $currency_id)
->where("legal", $legal_id)
->first();
if (!empty($last)) {
$last_price = $last->price;
}
} else {
$last && $last_price = $last->now_price;
}
return $last_price;
}
/**
* 取钱包的风险率(请传法币钱包)
*
* @param App\UsersWallet $wallet
* @return float
*/
public static function getWalletHazardRate($wallet)
{
$hazard_rate = 0;
$total_money = 0;
if (!$wallet) {
return $hazard_rate;
}
$profit_result = self::getUserProfit($wallet->user_id, $wallet->currency);//取用户盈利和保证金
extract($profit_result);//函数从数组中把变量导入到当前的符号表中
$wallet->refresh();
$balance = $wallet->lever_balance;
$total_money = bc_add($balance, $origin_caution_money_total);
if (bc_comp($origin_caution_money_total, 0) <> 0) {
$hazard_rate = bc_mul(bc_div(bc_add($total_money, $profits_total), $origin_caution_money_total), 100, 2);
}
return $hazard_rate;
}
/**
* 检测是否达到爆仓条件
*
* @param \App\UsersWallet $user_wallet
* @return bool 达到返回真,否则返回假
*/
public static function checkBurst($user_wallet)
{
try {
if (!($user_wallet instanceof UsersWallet)) {
throw new \Exception('钱包无效');
}
/*
//取交易对总盈利和总保证金
$profit_results = self::getUserProfit($user_wallet->user_id, $user_wallet->legal_id);
extract($profit_results);
//判断盈亏
if (bc_comp($profits_total, 0) >= 0) {
throw new \Exception('不存在亏损,无须平仓');
}
$change = bc_add($profits_total, $caution_money_total);
//如果本金足以抵亏就返回
if (bc_comp($change, 0) >= 0) {
throw new \Exception('本金充足,无须平仓');
}
//本金亏完,判断余额是否够扣,够直接返回
//直接判断总额
$total_money = bc_add($legal_wallet->lever_balance, $caution_money_total);
$pre_total_checked = bc_add($total_money, $profits_total);
if (bc_comp($pre_total_checked, 0) > 0) {
throw new \Exception('总资金充足,无须平仓');
}
*/
//使用输赢率来控制爆仓
$lever_burst_hazard_rate = Setting::getValueByKey('lever_burst_hazard_rate', 0);
$hazard_rate = self::getWalletHazardRate($user_wallet);
$result = bc_comp($hazard_rate, $lever_burst_hazard_rate) <= 0;
$param = [
'hazard_rate' => $hazard_rate,
'lever_burst_hazard_rate' => $lever_burst_hazard_rate,
];
// $path = base_path() . '/storage/logs/lever/';
// $filename = date('Ymd') . '.log';
// file_exists($path) || @mkdir($path);
// error_log(date('Y-m-d H:i:s') . ' result: ' . var_export($result, true) . ', param:' . var_export($param, true) . PHP_EOL, 3, $path . $filename);
return $result;
} catch (\Exception $e) {
$path = base_path() . '/storage/logs/lever/';
$filename = date('Ymd') . '.log';
file_exists($path) || @mkdir($path);
error_log(date('Y-m-d H:i:s') . ' File:' . $e->getFile() . ', Line:' . $e->getLine() . ', Message:' . $e->getMessage() . PHP_EOL, 3, $path . $filename);
return false;
}
}
/**
* 取设置了止盈止亏价并且价格满足的交易
*
* @return void
*/
public static function checkNeedStopPriceTrade($user_id = 0, $legal_id = 0, $currency_id = 0)
{
DB::beginTransaction();
$need_check_lever = LeverTransaction::where('status', self::TRANSACTION)
->where(function ($query) use ($user_id, $legal_id, $currency_id) {
$user_id > 0 && $query->where('user_id', $user_id);
$legal_id > 0 && $query->where('legal', $legal_id);
$currency_id > 0 && $query->where('currency', $currency_id);
})->where(function ($query) {
$query->where(function ($query) {
$query->where('type', self::BUY)->where(function ($query) {
$query->where(function ($query) {
$query->whereRaw('`update_price` >= `target_profit_price`')->where('target_profit_price', '>', 0);
})->orWhere(function ($query) {
$query->whereRaw('`update_price` <= `stop_loss_price`')->where('stop_loss_price', '>', 0);
});
});
})->orWhere(function ($query) {
$query->where('type', self::SELL)->where(function ($query) {
$query->where(function ($query) {
$query->whereRaw('`update_price` <= `target_profit_price`')->where('target_profit_price', '>', 0);
})->orWhere(function ($query) {
$query->whereRaw('`update_price` >= `stop_loss_price`')->where('stop_loss_price', '>', 0);
});
});
});
});
$task_list = $need_check_lever->pluck('id')->all();
$result = $need_check_lever->update([
'status' => self::CLOSING,
'handle_time' => microtime(true),
]);
DB::commit();
$result > 0 && LeverClose::dispatch($task_list, true)->onQueue('lever:close');
}
/**
* 委托激活
*
* @return void
*/
public static function entrustActivate()
{
/*
逻辑:分析师认为当价格走到某个区间后会停止,然后逆向走,在停止时期进行反向投资来赚取利润
例如:
(1)用户认为某个币在指定周期内跌到5元已跌到谷底不可能再跌了,预测接下来行情会上涨,那应该设定在跌到5元时进行买入,等行情上涨赚钱
(2)用户认为某个币在指定周期内涨到1000元已经达到顶峰,预测接下来行情会下跌,那应该设定在涨到1000元时进行卖出,等行情下跌赚钱
程序逻辑:
(1)卖出:当前价格 [大于等于] 设置价格时触发(等涨到指定价格时卖出坐等下跌)
(2)买入:当前价格 [小于等于] 设置价格时触发(等跌到指定价格时买入坐等上涨)
*/
/*
SELECT *
FROM lever_transaction
WHERE
`status`=0
AND
(
`type`=1 AND `update_price` <= `origin_price`
OR
`type`=2 AND `update_price` >= `origin_price`
)
*/
$trades = LeverTransaction::where('status', LeverTransaction::ENTRUST)
->where(function ($query) {
$query->orWhere(function ($query) {
$query->where('type', LeverTransaction::BUY)
->whereRaw('`update_price` <= `origin_price`');
})->orWhere(function ($query) {
$query->orWhere('type', LeverTransaction::SELL)
->whereRaw('`update_price` >= `origin_price`');
});
});
$lists = $trades->pluck('id')->all();
$result = $trades->update([
'transaction_time' => microtime(true),
'status' => LeverTransaction::TRANSACTION,
]);
return $result > 0 ? $lists : [];
}
}