[ 0 => 'Client', 1 => 'Expert', 2 => 'Dealer', 3 => 'Signal', 4 => 'Gateway', 5 => 'Mobile', 6 => 'Web', 7 => 'API', ], ]; /** * @inheritdoc */ public static function tableName() { return 'mt4_trades'; } /** * @return \yii\db\Connection the database connection used by this AR class. */ public static function getDb() { return Yii::$app->get('dbXcrm'); } /** * @inheritdoc */ public function rules() { return [ [['TICKET', 'LOGIN', 'SYMBOL', 'DIGITS', 'CMD', 'VOLUME', 'OPEN_TIME', 'OPEN_PRICE', 'SL', 'TP', 'CLOSE_TIME', 'EXPIRATION', 'CONV_RATE1', 'CONV_RATE2', 'COMMISSION', 'COMMISSION_AGENT', 'SWAPS', 'CLOSE_PRICE', 'PROFIT', 'TAXES', 'COMMENT', 'INTERNAL_ID', 'MARGIN_RATE', 'TIMESTAMP', 'MODIFY_TIME'], 'required'], [['TICKET', 'LOGIN', 'DIGITS', 'CMD', 'VOLUME', 'REASON', 'INTERNAL_ID', 'TIMESTAMP', 'MAGIC', 'GW_VOLUME', 'GW_OPEN_PRICE', 'GW_CLOSE_PRICE'], 'integer'], [['OPEN_TIME', 'CLOSE_TIME', 'EXPIRATION', 'MODIFY_TIME'], 'safe'], [['OPEN_PRICE', 'SL', 'TP', 'CONV_RATE1', 'CONV_RATE2', 'COMMISSION', 'COMMISSION_AGENT', 'SWAPS', 'CLOSE_PRICE', 'PROFIT', 'TAXES', 'MARGIN_RATE'], 'number'], [['SYMBOL'], 'string', 'max' => 16], [['COMMENT'], 'string', 'max' => 32], ]; } /** * @inheritdoc */ public function attributeLabels() { return [ 'TICKET' => 'Ticket', 'LOGIN' => 'Login', 'SYMBOL' => 'Symbol', 'DIGITS' => 'Digits', 'CMD' => 'Cmd', 'VOLUME' => 'Volume', 'OPEN_TIME' => 'Open Time', 'OPEN_PRICE' => 'Open Price', 'SL' => 'Sl', 'TP' => 'Tp', 'CLOSE_TIME' => 'Close Time', 'EXPIRATION' => 'Expiration', 'REASON' => 'Reason', 'CONV_RATE1' => 'Conv Rate1', 'CONV_RATE2' => 'Conv Rate2', 'COMMISSION' => 'Commission', 'COMMISSION_AGENT' => 'Commission Agent', 'SWAPS' => 'Swaps', 'CLOSE_PRICE' => 'Close Price', 'PROFIT' => 'Profit', 'TAXES' => 'Taxes', 'COMMENT' => 'Comment', 'INTERNAL_ID' => 'Internal ID', 'MARGIN_RATE' => 'Margin Rate', 'TIMESTAMP' => 'Timestamp', 'MAGIC' => 'Magic', 'GW_VOLUME' => 'Gw Volume', 'GW_OPEN_PRICE' => 'Gw Open Price', 'GW_CLOSE_PRICE' => 'Gw Close Price', 'MODIFY_TIME' => 'Modify Time', ]; } /** * 获取用户交易次数,包括总交易次数、盈利交易次数、亏损交易次数、亏损交易百分比 * @param int $login 字段LOGIN * @return array */ public function getTradeCount($login) { // 总次数 $totalCount = static::countHistoryByLogin($login); // 盈利交易次数 $winCount = static::countWinHistoryByLogin($login); // 亏损交易次数 $lossCount = static::countLossHistoryByLogin($login); // 盈利交易百分比 $winPercent = $totalCount ? number_format(($winCount / $totalCount * 100), 5, '.', '') : 0; // 亏损交易百分比 $lossPercent = $totalCount ? number_format(($lossCount / $totalCount * 100), 5, '.', '') : 0; return [ 'totalCount' => $totalCount, 'winCount' => $winCount, 'lossCount' => $lossCount, 'winPercent' => $winPercent, 'lossPercent' => $lossPercent, ]; } /** * 总收益 * @param int $login 字段LOGIN * @return array */ public function getProfitSumByDay($login) { return $this->getListByDay($login, 'PROFIT', '(`CMD` = 0 OR `CMD` = 1)'); } /** * 交易手数 * @param int $login 字段LOGIN * @return array */ public function getVolumeSumByDay($login) { return $this->getListByDay($login, 'VOLUME', '(`CMD` = 0 OR `CMD` = 1)'); } /** * 交易货币分布 * @param int $login 字段LOGIN * @return array */ public function getSymbolCount($login) { $sql = "SELECT `SYMBOL`, COUNT(*) AS cnt FROM " . self::tableName() . " WHERE (`CMD` = 0 OR `CMD` = 1) AND `CLOSE_TIME` != '1970-01-01' AND `LOGIN` = :login GROUP BY `SYMBOL`"; $values = [':login' => $login]; $rows = self::getDb()->createCommand($sql)->bindValues($values)->queryAll(); return $rows; } /** * 交易来源分布 * @param int $login 字段LOGIN * @return array */ public function getReasonCount($login) { $sql = "SELECT `REASON`, COUNT(*) AS cnt FROM " . self::tableName() . " WHERE (`CMD` = 0 OR `CMD` = 1) AND `CLOSE_TIME` != '1970-01-01' AND `LOGIN` = :login GROUP BY `REASON`"; $values = [':login' => $login]; $rows = self::getDb()->createCommand($sql)->bindValues($values)->queryAll(); foreach ($rows as $k => $v) { $rows[$k]['REASON'] = isset($this->valueTextMap['REASON'][$v['REASON']]) ? $this->valueTextMap['REASON'][$v['REASON']] : ''; } return $rows; } /** * 获取第一条记录 * @param int $login 字段LOGIN * @return array|false */ public function getFirstRecord($login) { return $this->getOneRecord($login, 'ASC'); } /** * 获取最后一条记录 * @param int $login 字段LOGIN * @return array|false */ public function getLastRecord($login) { return $this->getOneRecord($login, 'DESC'); } /** * 获取一条记录 * @param int $login 字段LOGIN * @param string $orderBy * @return array|false */ protected function getOneRecord($login, $orderBy) { $sql = "SELECT * FROM " . self::tableName() . " WHERE `LOGIN` = :login ORDER BY `TICKET` {$orderBy} LIMIT 1"; $values = [':login' => $login]; $row = self::getDb()->createCommand($sql)->bindValues($values)->queryOne(); return $row; } /** * 总笔数 * @param int $login * @return int|string */ public static function countHistoryByLogin($login) { $query = static::find(); static::addTypeCondition($query, 'history'); return $query->andWhere(['LOGIN' => $login])->count(); } /** * 盈利笔数 * @param int $login * @return int|string */ public static function countWinHistoryByLogin($login) { $query = static::find(); static::addTypeCondition($query, 'history'); return $query->andWhere(['LOGIN' => $login])->andWhere(['>=', 'PROFIT', 0])->count(); } /** * 亏损笔数 * @param int $login * @return int|string */ public static function countLossHistoryByLogin($login) { $query = static::find(); static::addTypeCondition($query, 'history'); return $query->andWhere(['LOGIN' => $login])->andWhere(['<', 'PROFIT', 0])->count(); } /** * @param int $login * @return array */ public static function sumProfitByDay($login) { $result = []; $firstTrade = static::find()->where(['LOGIN' => $login])->orderBy('TICKET asc')->asArray()->limit(1)->one(); $lastTrade = static::find()->where(['LOGIN' => $login])->orderBy('TICKET desc')->asArray()->limit(1)->one(); if ($firstTrade == null || $lastTrade == null) { return $result; } $startData = date('Y-m-d 00:00:00', strtotime($firstTrade['CLOSE_TIME'])); $endData = date('Y-m-d 00:00:00', strtotime($lastTrade['CLOSE_TIME']) + 86400); for ($s = strtotime($startData), $e = strtotime($endData); $s <= $e; $s += 86400) { // 非最后一天的数据进行缓存 $profit = static::getDb()->cache(function ($db) use ($login, $s) { $profit = static::find()->select(new Expression('IFNULL(SUM(`PROFIT`), 0)'))->where('CMD=0 or CMD=1')->andWhere(['<>', 'CLOSE_TIME', '1970-01-01'])->andWhere(['LOGIN' => $login])->andWhere(['<', 'CLOSE_TIME', date('Y-m-d 00:00:00', $s)])->scalar(); $profit = round($profit, 2); return $profit; }, $s == $e ? 0 : 31536000); $result[] = [ 'time' => $s * 1000, 'val' => $profit ]; } return $result; } /** * 获取每个MT4账户的外汇交易量 * @param string|array $logins * @param string|array $symbols * @param string $startTime * @param string $endTime * @return array 二维数组,包含了MT4和交易量 */ public function getSumVolumeEachLogin($logins, $symbols, $startTime = '', $endTime = '') { if (empty($logins) || empty($symbols)) { return []; } if (is_array($logins)) { $logins = helpers\StringHelper::buildInSql($logins); } if (is_array($symbols)) { $symbols = helpers\StringHelper::buildInSql($symbols); } $sql = "SELECT `login`, SUM(`VOLUME`) AS `total_volume` FROM " . self::tableName() . " WHERE (`CMD` = 0 OR `CMD` = 1) AND `CLOSE_TIME` != '1970-01-01' AND `LOGIN` IN ({$logins}) AND `SYMBOL` IN ({$symbols}) "; if ($startTime) { $sql .= " AND `CLOSE_TIME` > '{$startTime}' "; } if ($endTime) { $sql .= " AND `CLOSE_TIME` < '{$endTime}' "; } $sql .= " GROUP BY `LOGIN` "; $list = self::getDb()->createCommand($sql)->queryAll(); $list2 = []; foreach ($list as $k => $v) { $list2[$v['login']] = $v; } return $list2; } /** * 获取总的外汇交易量 * @param string|array $logins * @param string|array $symbols * @param string $startTime * @param string $endTime * @return float */ public function getSumVolume($logins, $symbols, $startTime = '', $endTime = '') { if (empty($logins) || empty($symbols)) { return 0; } if (is_array($logins)) { $logins = implode(',', $logins); } if (is_array($symbols)) { $symbols2 = ''; foreach ($symbols as $k => $v) { $symbols2 .= "'".trim($v)."',"; } $symbols = rtrim($symbols2, ','); } $sql = "SELECT SUM(`VOLUME`) AS total_volume FROM " . self::tableName() . " WHERE (`CMD` = 0 OR `CMD` = 1) AND `CLOSE_TIME` != '1970-01-01' AND `LOGIN` IN ({$logins}) AND `SYMBOL` IN ({$symbols}) "; if ($startTime) { $sql .= " AND `CLOSE_TIME` > '{$startTime}' "; } if ($endTime) { $sql .= " AND `CLOSE_TIME` < '{$endTime}' "; } $sum = (float) self::getDb()->createCommand($sql)->queryScalar(); return $sum; } /** * 统计信息 收益 笔数 佣金等 * @param array $logins * @param int $type * @param string $sTime * @param string $eTime * @param string $search * @return array */ public static function findSum($logins, $type, $sTime, $eTime, $search) { $result = [ 'sp' => 0, 'sv' => 0, 'ss' => 0, 'sc' => 0, ]; if (!is_array($logins)) { $logins = explode(',', $logins); } if (empty($logins)) { return $result; } $query = static::find(); $query->select('sum(PROFIT) as sp,sum(VOLUME) as sv,sum(SWAPS) as ss,sum(COMMISSION) as sc')->andWhere('LOGIN IN (' . helpers\StringHelper::buildInSql($logins) . ')'); if (!empty($type)) { static::addTypeCondition($query, $type); } $query->andFilterWhere(['>=', 'CLOSE_TIME', $sTime]); $query->andFilterWhere(['<=', 'CLOSE_TIME', $eTime]); $query->andFilterWhere(['or', ['like', 'LOGIN', $search], ['like', 'TICKET', $search]]); $data = $query->asArray()->limit(1)->one(); isset($data['sp']) && $result['sp'] = $data['sp']; isset($data['sv']) && $result['sv'] = $data['sv']; isset($data['ss']) && $result['ss'] = $data['ss']; isset($data['sc']) && $result['sc'] = $data['sc']; return $result; } /** * @param Query $query * @param string $type */ public static function addTypeCondition($query, $type) { if ($type == "deposit") { // 入金 $query->andWhere("CMD=6 and profit>0"); } else if ($type == "withdraw") { // 出金 $query->andWhere("CMD=6 and profit<0"); } else if ($type == "position") { // 持仓 $query->andWhere("(CMD=0 or CMD=1) and CLOSE_TIME='1970-01-01'"); } else if ($type == "history") { // 历史 $query->andWhere("(CMD=0 or CMD=1) and CLOSE_TIME!='1970-01-01'"); } else if ($type == "pending") { // 挂单 $query->andWhere("(CMD=2 or CMD=3 or CMD=4 or CMD=5) and CLOSE_TIME='1970-01-01'"); } else if ($type == "credit") { // 信用 $query->andWhere("CMD=7"); } else if ($type == 'all') { // 全部 } else { $query->andWhere("0=1"); } } /** * 获取总入金 * @param int $login * @return int */ public function getDepositSumByLogin($login) { $where = [ 'and', ['=', 'CMD', 6], ['>', 'PROFIT', 0], ['=', 'LOGIN', $login], ]; return (float) self::find()->where($where)->sum('PROFIT'); } /** * 获取总出金 * @param int $login * @return int */ public function getWithdrawSumByLogin($login) { $where = [ 'and', ['=', 'CMD', 6], ['<', 'PROFIT', 0], ['=', 'LOGIN', $login], ]; return (float) self::find()->where($where)->sum('PROFIT'); } /** * 获取总入金 * @param array $logins * @return int */ public function getDepositSumByLogins($logins) { $where = [ 'and', ['=', 'CMD', 6], ['>', 'PROFIT', 0], ['in', 'LOGIN', $logins], ]; return (float) self::find()->where($where)->sum('PROFIT'); } /** * 获取总出金 * @param array $logins * @return int */ public function getWithdrawSumByLogins($logins) { $where = [ 'and', ['=', 'CMD', 6], ['<', 'PROFIT', 0], ['in', 'LOGIN', $logins], ]; return (float) self::find()->where($where)->sum('PROFIT'); } /** * 获取入金第一条记录 * @param int $login 字段LOGIN * @return array|false */ public function getFirstDepositRecord($login) { return $this->getOneDepositRecord($login, 'ASC'); } /** * 获取入金最后一条记录 * @param int $login 字段LOGIN * @return array|false */ public function getLastDepositRecord($login) { return $this->getOneDepositRecord($login, 'DESC'); } /** * 获取一条记录 * @param int $login 字段LOGIN * @param string $orderBy * @return array|false */ protected function getOneDepositRecord($login, $orderBy) { $sql = "SELECT * FROM " . self::tableName() . " WHERE `LOGIN` = :login AND `CMD` = 6 ORDER BY `TICKET` {$orderBy} LIMIT 1"; $values = [':login' => $login]; $row = self::getDb()->createCommand($sql)->bindValues($values)->queryOne(); return $row; } /** * 佣金增长曲线 * @param array $logins 字段LOGIN * @return array */ public function getDepositSumByDayByLogins($logins) { return $this->getListByDay($logins, 'PROFIT', '`CMD` = 6 AND `PROFIT` > 0'); } /** * 每日列表数据 * @param array|int $logins 字段LOGIN,可以是单个或多个 * @param string $field 需要统计的字段 * @param string $where 其他条件 * @return array 返回二位数组,每个数组由毫秒时间戳和统计数据组成 */ protected function getListByDay($logins, $field, $where) { if (is_array($logins)) { $logins = implode(',', $logins); } if (empty($logins)) { return []; } $sql = "SELECT FROM_UNIXTIME(UNIX_TIMESTAMP(`CLOSE_TIME`), '%Y-%m-%d') AS `close_date`, SUM(`{$field}`) AS `sum_num` FROM " . self::tableName() . " WHERE {$where} AND `CLOSE_TIME` != '1970-01-01' AND `LOGIN` IN ($logins) GROUP BY `close_date` "; $all = self::getDb()->createCommand($sql)->queryAll(); $list = []; if ($all) { usort($all, function ($a, $b) { if ($a['close_date'] == $b['close_date']) { return 0; } return $a['close_date'] < $b['close_date'] ? -1 : 1; }); // 找出从有记录的第一天到昨天的日期 $beginTime = $all[0]['close_date']; $endTime = date('Y-m-d', strtotime('-1 days')); $dateList = helpers\DateHelper::getDaily($beginTime, $endTime); foreach ($dateList as $k => $v) { $sum = 0; foreach ($all as $k2 => $v2) { if ($v2['close_date'] > $v) { break; } $sum += (float) $v2['sum_num']; } $sum = number_format($sum, 2, '.', ''); $timestamp = strtotime($v) * 1000; $list[] = [$timestamp, $sum]; } } else { // 如果没有数据的话,那么从第一天的记录里拿到日期一直到昨天,这是为了在图像的横坐标显示日期 $beginTime = static::find()->select(['CLOSE_TIME'])->where(['in', 'LOGIN', $logins])->orderBy('TICKET asc')->asArray()->limit(1)->scalar(); if (!$beginTime) { return []; } $endTime = date('Y-m-d', strtotime('-1 days')); $dateList = helpers\DateHelper::getDaily($beginTime, $endTime); foreach ($dateList as $k => $v) { $timestamp = strtotime($v) * 1000; $list[] = [$timestamp, 0]; } } return $list; } /** * 获取每天的出金总额并缓存(计划脚本调用) * @param $login * @return array */ public function findSumDepositByDay($login) { $ft = $this->getFirstDepositRecord($login); if ($ft == null) { return []; } $fcTime = $ft['CLOSE_TIME']; if (!$fcTime) { return []; } $cache = Instance::ensure('redis', Connection::className()); $lcTime = date('Y-m-d', time()); $list = []; $dateList = helpers\DateHelper::getDailyYesterday($fcTime); foreach ($dateList as $k => $v) { $timestamp = strtotime($v) * 1000; $sum = $cache->get("findSumDepositByDay,".$timestamp.",".$login); if ($sum == null) { $sum = $this->getSumProfit($login, $v); if ($sum == null) { $sum = 0; } $cache->setex("findSumDepositByDay,".$timestamp.",".$login, 31536000, $sum); } $list[] = [$timestamp, $sum]; if ($v > $lcTime) { break; } } return $list; } /** * 获取每天的出金总额 * @param $login * @param $daytime * @return false|null|string */ private function getSumProfit($login, $daytime) { $sql = "select sum(PROFIT) from mt4_trades where CMD=6 and PROFIT>0 and CLOSE_TIME!='1970-01-01' and login=:login and CLOSE_TIME<=:close_time"; $sqlParams[':login'] = $login; $sqlParams[':close_time'] = $daytime; return self::getDb()->createCommand($sql, $sqlParams)->queryScalar(); } /** * 后台交易管理列表数据 * @param array $post * @return array */ public function getAdminList($post) { $result = ['code' => 0, 'data' => [], 'message' => '']; $type = isset($post['type']) ? $post['type'] : ''; $order = isset($post['order']) ? $post['order'] : ''; $orderBy = isset($post['orderBy']) ? strtolower($post['orderBy']) : 'desc'; $search = isset($post['search']) ? $post['search'] : ''; $start = isset($post['start']) ? (int) $post['start'] : 0; $length = isset($post['length']) ? (int) $post['length'] : 20; $draw = isset($post['draw']) ? $post['draw'] : 1; if (!in_array($type, ['deposit', 'withdraw', 'position', 'history', 'pending', 'credit'])) { $result['message'] = '参数错误'; return $result; } $where = ['and']; // 账户或姓名 if ($search) { $where[] = [ 'or', ['like', 'LOGIN', $search], ['like', 'TICKET', $search], ]; } // 排序 $allowOrderColumn = ['TICKET', 'LOGIN', 'CMD', 'SYMBOL', 'SL', 'TP', 'VOLUME', 'OPEN_PRICE', 'OPEN_TIME', 'CLOSE_PRICE', 'CLOSE_TIME', 'SWAPS', 'COMMISSION', 'PROFIT']; if (in_array($order, $allowOrderColumn) && in_array($orderBy, ['asc', 'desc'])) { if ($orderBy == 'asc') { $orderCondition = [$order => SORT_ASC]; } else { $orderCondition = [$order => SORT_DESC]; } } else { $orderCondition = ['TICKET' => SORT_DESC]; } $query = static::find(); $query->where($where) ->orderBy($orderCondition); static::addTypeCondition($query, $type); $count = $query->count(); $query->offset($start)->limit($length); $list = $query->asArray()->all(); if ($count) { foreach ($list as $k => $v) { $list[$k]['PROFIT'] = Utils::formatFloatOrInt($v['PROFIT']); } } $data['data'] = $list; $data['draw'] = $draw; $data['recordsFiltered'] = $count; $data['recordsTotal'] = $count; $result['data'] = $data; $result['code'] = 1; return $result; } /* * 获取已经平仓的订单 */ public static function findAllCloseTrade($login) { return static::find()->where("CMD=0 OR CMD=1")->andWhere(['!=', 'CLOSE_TIME', '1970-01-01'])->andWhere(['LOGIN' => $login])->asArray()->all(); } /** * 获取每个MT4账户的佣金 * @see \console\controllers\CommissionJobController::actionRun() 交易量除以100,所以在这里乘以100 * @param int $member_id * @param string|array $logins * @param int $trade_type * @param string $startTime * @param string $endTime * @param bool $is_wy 是否为外佣 * @return array 二维数组,包含了MT4和交易量 */ public function getSumCommissionEachLogin($member_id, $logins, $trade_type, $startTime = '', $endTime = '', $is_wy = false) { if (empty($logins)) { return []; } if (is_array($logins)) { $logins = implode(',', $logins); } if ($is_wy) { $like = '"W%"'; } else { $like = '"N%"'; } $sql = "SELECT cr.`user_login` AS `login`, SUM(cr.`commission`) AS `total_commission` FROM " . self::tableName() . " AS t INNER JOIN " . CommissionRecord::tableName() . " AS cr ON t.`TICKET` = cr.`trade_ticket` WHERE cr.`user_login` IN ({$logins}) AND cr.`trade_type` = {$trade_type} AND cr.`commission_rule` LIKE {$like} AND cr.`ib_id` = {$member_id} "; if ($startTime) { $sql .= " AND t.`CLOSE_TIME` >= '{$startTime}' "; } if ($endTime) { $sql .= " AND t.`CLOSE_TIME` <= '{$endTime}' "; } $sql .= " GROUP BY cr.`user_login` "; $list = self::getDb()->createCommand($sql)->queryAll(); $list2 = []; foreach ($list as $k => $v) { $v['total_commission'] = $v['total_commission'] * 100; $list2[$v['login']] = $v; } return $list2; } }