富途牛牛APP下载地址:
https://www.futunn.com/?lang=zh-CN
1.下载APP后更直观查看接口调用结果
2.可以使用模拟盘去测试相应接口
富途OpenD下载地址:
https://www.futunn.com/download/OpenAPI
1.新注册的账号在使用命令行登陆openD前需要先完成调查问卷
2.第一次使用命令行登陆openD需要一个手机号验证码验证
3.每个用户需要运行一个openD客户端,不同用户可以分配不同的端口去启动openD,然后openAPI调用不同的端口去链接不同用户的openD进行不同的操作
解压后命令行启动命令:
./FutuOpenD -login_account=账户 -login_pwd=密码 -lang=chs -api_port=11111
1.密码不是账号的登录密码,是注册号设置的交易密码
2.更详细的命令参考https://openapi.futunn.com/futu-api-doc/opend/opend-cmd.html
富途OpenAPI介绍文档地址:
https://openapi.futunn.com/futu-api-doc/
1.PHP安装了swoole 4.7.1扩展
PHP封装的openAPI类(class.ftnn.php)
Github仓库地址:
https://github.com/hkargv/PHP-For-FutuOpenD
<?php
/**
* 富途行情及交易接口
*/
class futu{
/**
* @var Swoole\\Client
*/
private $cli = null;
private $die = false;
private $push = false; //是否推送模式
private $host = '127.0.0.1';
private $port = '11111';
private $pass = '';
/**
* 请求的序列号
* @var integer
*/
private $sequence = 0;
/**
* 心跳包定时器
* @var integer
*/
private $timer = 0;
/**
* 初始化的返回
* @var integer
*/
private $serverVer = 0;
/**
* 初始化的返回,心跳间隔
* @var integer
*/
private $keepAliveInterval = 5;
/**
* AES加密通信CBC加密模式的iv,固定为16字节长字符串
* @var string
*/
private $aesCBCiv = '';
/**
* 同一个连接只解锁一次
* @var string
*/
public $unlock = null;
/**
* 订单推送订阅
*/
public $accPush = false;
/**
* 行情市场:0未知1港股2港期11美股12美期21沪股22深股
* @var integer
*/
public $market = 1;
/**
* 交易环境:0仿真1真实
* @var integer
*/
public $trdEnv = 1;
/**
* 交易市场:0未知1港股2美股3大陆4香港A股通5期货市场
* @var integer
*/
public $trdMarket = 1;
/**
* 是否通讯加密,需同时设置$private_key(对性能肯定有影响)
* @var bool
*/
public $encrypt = false;
/**
* 密钥文件绝对路径,与FutuOpenD配置文件中的一致
* @var string
*/
public $private_key = __DIR__.'/private.key';
/**
* 交易账号列表
* @var array
*/
public $accList = [];
public $connID = 0;
public $loginUserID = 0;
/**
* 初始化接口返回的加密密码
* @var string
*/
public $connAESKey = '';
/**
* futu constructor.
* @param string $host
* @param string $port
* @param string $pass 交易解锁密码
* @param int $trdEnv 0:模拟 1真实
*/
public function __construct($host, $port, $pass='',$trdEnv){
$this->host = $host;
$this->port = $port;
$this->pass = $pass;
$this->trdEnv = $trdEnv;
if(! class_exists('Swoole\\Client')){
//强制使用
die('http://pecl.php.net/package/swoole');
}
}
/**
* @param Swoole\\Coroutine\\Client $cli
* @return boolean
*/
public function push($cli){
if(! $cli instanceof Swoole\\Coroutine\\Client){
var_dump("客户端有问题");
return false;
}
$this->cli = $cli;
$this->push = true; //是否推送模式
}
private function connect(){
if($this->cli === null){
$this->timer = time(); //初始化心跳时间
$this->cli = new Swoole\\Client(preg_match('/^[0-9\\.]+$/', $this->host)?(SWOOLE_SOCK_TCP/**|SWOOLE_KEEP**/):(SWOOLE_SOCK_UNIX_STREAM/**|SWOOLE_KEEP**/));
$this->cli->set(array(
'socket_buffer_size' => 1024*1024*32, //32M缓存区
'open_length_check' => 1,
'package_length_type' => 'L',
'package_length_offset' => 12, //第N个字节是包长度的值
'package_body_offset' => 44, //第几个字节开始计算长度
'package_max_length' => 8*1024*1024, //协议最大长度
'open_tcp_nodelay' => false
));
if(! @$this->cli->connect($this->host, $this->port, 8)){
$this->errorlog('Connect Error.'.socket_strerror($this->cli->errCode), 0);
}
}
if(! $this->cli){
$this->errorlog('Client Error.', 0);
}
if($this->timer && (time() - $this->timer >= $this->keepAliveInterval)){
//每N秒发一次心跳(只有同步模式会初始化时间)
$this->timer = time(); //锁住
$this->KeepAlive();
}
return $this->cli;
}
/**
* 关闭连接,销毁之前的数据
*/
public function close(){
if($this->cli instanceof Swoole\\Coroutine\\Client){
$this->cli->close();
exit(0);
}
if($this->cli instanceof Swoole\\Client){
$this->cli->isConnected() && $this->cli->close(true);
}
$this->cli = null;
$this->connID = 0;
$this->unlock = false;
}
/**
* 初始化连接
* @return string
*/
public function InitConnect(){
if($this->connID){
//已经初始化过
return $this->connID;
}
$C2S = array(
"clientVer" => 0,
'clientID' => '0',
'recvNotify' => $this->push ? true : false,
'packetEncAlgo' => $this->encrypt ? 0 : -1, //0富途修改过的AES的ECB加密模式;-1不加密;1标准的AES的ECB加密模式;2标准的AES的CBC加密模式
'pushProtoFmt' => 1, //0:pb,1:json
'programmingLanguage' => ''
);
if(! $ret = $this->send('1001', $C2S)){
return '链接失败';
}
// var_dump($ret);
$this->serverVer = (int)$ret['serverVer'];
$this->loginUserID = (string)$ret['loginUserID']; //uint64
$this->connID = (string)$ret['connID']; //uint64
$this->connAESKey = (string)$ret['connAESKey'];
$this->keepAliveInterval = (int)$ret['keepAliveInterval'];
$this->aesCBCiv = (string)$ret['aesCBCiv'];
return $this->connID;
}
/**
* 获取全局状态
* @return array
*/
public function GetGlobalState(){
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'userID' => 0 //历史原因,目前已废弃,填0即可
);
if(! $ret = $this->send('1002', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 保活心跳
* @return int
*/
public function KeepAlive(){
if(! $this->InitConnect()){
return 0;
}
$C2S = array(
'time' => (int)time(),
);
if(! $ret = $this->send('1004', $C2S)){
return 0;
}
return (int)$ret['time'];
}
/**
* 获取当前用户信息
* @param int $flag 1昵称用户头像牛牛号;2API权限信息;4市场的行情权限;8免责;16升级类型;1024WebKey
* @return array
*/
public function GetUserInfo($flag=-1){
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'flag' => (int)$flag,
);
if(! $ret = $this->send('1005', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 订阅或者反订阅,同时注册或者取消推送(股票个数*K线种类<=100)
* @param array $codes
* @param array $subTypeList 1报价;2摆盘;4逐笔;5分时;6日K;7五分K;8十五分K;9三十K;10六十K;11一分K;12周K;13月K;14经纪队列;15季K;16年K;17三分K;18委托明细
* @param bool $isSubOrUnSub true订阅false反订阅
* @param bool $isRegOrUnRegPush 是否注册或反注册该连接上面行情的推送,该参数不指定不做注册反注册操作
* @param array $regPushRehabTypeList 复权类型:0不复权1前复权2后复权
* @param bool $isFirstPush 注册后如果本地已有数据是否首推一次已存在数据
* @param bool $isUnsubAll 一键取消当前连接的所有订阅,当被设置为true时忽略其他参数
* @param bool $isSubOrderBookDetail 是否订阅摆盘明细
* @param bool $extendedTime 是否允许美股盘前盘后数据
* @return bool
*/
public function Qot_Sub($codes, $subTypeList, $isSubOrUnSub=true, $isRegOrUnRegPush=null, $regPushRehabTypeList=[], $isFirstPush=false, $isUnsubAll=false, $isSubOrderBookDetail=false, $extendedTime=false){
if(! $this->InitConnect()){
return false;
}
$C2S = array(
'isSubOrUnSub' => (bool)$isSubOrUnSub, //true订阅false反订阅
'isFirstPush' => (bool)$isFirstPush,
'isUnsubAll' => (bool)$isUnsubAll
);
$securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(($isUnsubAll == false) && empty($securityList)){
return false;
}
if($securityList){
$C2S['securityList'] = $securityList;
}
if($subTypeList = (array)$subTypeList){
//订阅数据类型
$C2S['subTypeList'] = array_unique(array_values($subTypeList));
}
if($isRegOrUnRegPush !== null){
$C2S['isRegOrUnRegPush'] = (bool)$isRegOrUnRegPush;
}
if($isRegOrUnRegPush && $regPushRehabTypeList){
$C2S['regPushRehabTypeList'] = (array)$regPushRehabTypeList;
}
if($isSubOrderBookDetail){
$C2S['isSubOrderBookDetail'] = (bool)$isSubOrderBookDetail;
}
if($extendedTime){
$C2S['extendedTime'] = (bool)$extendedTime;
}
if(! $ret = $this->send('3001', $C2S)){
return false;
}
return isset($ret['result']) ? (bool)$ret['result'] : true;
}
/**
* 获取订阅信息
* @param bool $isReqAllConn 是否返回所有连接的订阅状态,不传或者传false只返回当前连接数据
*/
public function Qot_GetSubInfo($isReqAllConn=false){
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'isReqAllConn' => (bool)$isReqAllConn
);
if(! $ret = $this->send('3003', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 获取股票基本行情
* @param array $codes
* @return array
*/
public function Qot_GetBasicQot($codes){
if(! $this->Qot_Sub($codes, [1], true)){
return array();
}
$securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $securityList){
return array();
}
$C2S = array(
'securityList' => (array)$securityList,
);
if(! $ret = $this->send('3004', $C2S)){
return array();
}
return (array)$ret['basicQotList'];
}
/**
* 获取K线
* @param string $code
* @param int $klType K线类型:1一分K;2日K;3周K;4月K;5年K;6五分K;7十五分K;8三十分K;9六十分K;10三分K;11季K
* @param int $reqNum K线条数
* @param int $rehabType 复权类型:0不复权1前复权2后复权
*/
public function Qot_GetKL($code, $klType, $reqNum=1000, $rehabType=1){
$map = array(
1 => 11,
2 => 6,
3 => 12,
4 => 13,
5 => 16,
6 => 7,
7 => 8,
8 => 9,
9 => 10,
10 => 17,
11 => 15
);
if(! $this->Qot_Sub($code, [$map[$klType]], true)){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
),
'klType' => (int)$klType,
'reqNum' => (int)$reqNum,
'rehabType' => (int)$rehabType,
);
if(! $ret = $this->send('3006', $C2S)){
return array();
}
return (array)$ret['klList'];
}
/**
* 获取分时
* @param string $code
* @return array
*/
public function Qot_GetRT($code){
if(! $this->Qot_Sub($code, [5], true)){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
)
);
if(! $ret = $this->send('3008', $C2S)){
return array();
}
return (array)$ret['rtList'];
}
/**
* 获取逐笔
* @param string $code
* @param int $maxRetNum
* @return array
*/
public function Qot_GetTicker($code, $maxRetNum=1000){
if(! $this->Qot_Sub($code, [4], true)){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
),
'maxRetNum' => (int)$maxRetNum,
);
if(! $ret = $this->send('3010', $C2S)){
return array();
}
return (array)$ret['tickerList'];
}
/**
* 获取买卖盘
* @param string $code
* @param int $num
* @return array
*/
public function Qot_GetOrderBook($code, $num=10){
if(! $this->Qot_Sub($code, [2], true)){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
),
'num' => (int)$num,
);
if(! $ret = $this->send('3012', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 获取经纪队列
* @param string $code
* @return array
*/
public function Qot_GetBroker($code){
if(! $this->Qot_Sub($code, [14], true)){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
)
);
if(! $ret = $this->send('3014', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 获取委托明细
* @param string $code
* @return array
*/
public function Qot_GetOrderDetail($code){
if(! $this->Qot_Sub($code, [18], true)){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
)
);
if(! $ret = $this->send('3016', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 获取单只股票一段历史K线(有限额)分K提供最近2年数据,日K及以上提供近10年数据
* @param string $code
* @param int $klType K线类型:1一分K;2日K;3周K;4月K;5年K;6五分K;7十五分K;8三十分K;9六十分K;10三分K;11季K
* @param int $beginTime
* @param int $endTime
* @param int $maxAckKLNum 最多返回多少根K线,如果未指定表示不限制
* @param int $nextReqKey 分页请求key(在返回的数组中)
* @param int $needKLFieldsFlag 指定返回K线结构体特定某几项数据,KLFields枚举值或组合,如果未指定返回全部字段
* @param int $rehabType 复权类型
* @param bool $extendedTime 是否获取美股盘前盘后数据,当前仅支持1分K
* @return array
*/
public function Qot_RequestHistoryKL($code, $klType, $beginTime, $endTime, $maxAckKLNum=0, $nextReqKey=null, $needKLFieldsFlag=[], $rehabType=1, $extendedTime=false){
if($nextReqKey == null){
if(! $this->limit(__LINE__, 30, 60)){
return array();
}
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
),
'klType' => (int)$klType,
'beginTime' => date("Y-m-d H:i:s", $beginTime),
'endTime' => date("Y-m-d H:i:s", $endTime),
'rehabType' => (int)$rehabType,
);
if($maxAckKLNum){
$C2S['maxAckKLNum'] = (int)$maxAckKLNum;
}
if($nextReqKey){
$C2S['nextReqKey'] = (string)$nextReqKey;
}
if($needKLFieldsFlag){
$C2S['needKLFieldsFlag'] = (array)$needKLFieldsFlag;
}
if($extendedTime){
$C2S['extendedTime'] = (bool)$extendedTime;
}
if(! $ret = $this->send('3103', $C2S)){
return array();
}
return (array)$ret['klList'];
}
/**
* 拉取历史K线已经用掉的额度
* @param bool $bGetDetail 是否拉取详细列表
* @return array
*/
public function Qot_RequestHistoryKLQuota($bGetDetail=false){
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'bGetDetail' => (bool)$bGetDetail,
);
if(! $ret = $this->send('3104', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 获取复权信息
* @param array $codes
* @return array/false
*/
public function Qot_RequestRehab($code){
if(! $this->limit(__LINE__, 30, 60)){
return false;
}
if(! $this->InitConnect()){
return false;
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
)
);
if(! $ret = $this->send('3105', $C2S)){
return array();
}
return (array)$ret['rehabList'];
}
/**
* 获取股票列表
* @param int $secType 0未知或指定股票1债券2权证3正股4基金5涡轮6指数7板块8期权9板块集合
* @param array $codes 股票,若该参数存在,忽略其他参数
* @return array
*/
public function Qot_GetStaticInfo($secType, $codes=array()){
if(! $this->InitConnect()){
return array();
}
$securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $securityList){
$C2S = array(
'market' => $this->market,
'secType' => (int)$secType,
);
}
if($securityList){
$C2S = array(
'securityList' => (array)$securityList,
);
}
if(! $ret = $this->send('3202', $C2S)){
return array();
}
return (array)$ret['staticInfoList'];
}
/**
* 获取一批股票的快照信息,每次最多400支
* @param array $codes
* @return array
*/
public function Qot_GetSecuritySnapshot($codes){
if(! $this->limit(__LINE__, 30, 60)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$securityList = array();
foreach ((array)$codes as $code){
if($GLOBALS['FUTU_DELISTED'][$code]){
continue;
}
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $securityList){
return array();
}
$C2S = array(
'securityList' => (array)$securityList,
);
if(! $ret = $this->send('3203', $C2S)){
return array();
}
return (array)$ret['snapshotList'];
}
/**
* 获取板块集合下的板块(30秒10次)
* @param int $plateSetType 0所有版块1行业板块2地域板块3概念版块
* @return array
*/
public function Qot_GetPlateSet($plateSetType){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'plateSetType' => (int)$plateSetType,
'market' => $this->market,
);
if(! $ret = $this->send('3204', $C2S)){
return array();
}
$gets = array();
foreach ((array)$ret['plateInfoList'] as $v){
$gets[] = array('market'=>$v['plate']['market'],'code'=>$v['plate']['code'],'name'=>$v['name']);
}
return (array)$gets;
}
/**
* 获取板块下的股票(30秒10次)
* @param string $code 版块编号(如期货板块:BK1986)
* @param int $sortField 排序字段
* @param bool $ascend 是否升序
* @return array
*/
public function Qot_GetPlateSecurity($code, $sortField=1, $ascend=true){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'plate' => array(
'code' => (string)$code,
'market' => $this->market,
),
'sortField' => (int)$sortField,
'ascend' => (bool)$ascend,
);
if(! $ret = $this->send('3205', $C2S)){
return array();
}
return (array)$ret['staticInfoList'];
}
/**
* 获取正股相关股票
* @param string $code 正股代码
* @param int $referenceType 1正股相关的窝轮;2期货主连的相关合约
* @return array
*/
public function Qot_GetReference($code, $referenceType=1){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
),
'referenceType' => (int)$referenceType,
);
if(! $ret = $this->send('3206', $C2S)){
return array();
}
return (array)$ret['staticInfoList'];
}
/**
* 获取股票所属板块(30秒10次)
* @param array $codes 最多200个,仅支持正股和指数
*/
public function Qot_GetOwnerPlate($codes){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $securityList){
return array();
}
$C2S = array(
'securityList' => (array)$securityList,
);
if(! $ret = $this->send('3207', $C2S)){
return array();
}
$gets = array();
foreach ((array)$ret['ownerPlateList'] as $v){
foreach ((array)$v['plateInfoList'] as $vv){
$vv['plate_code'] = $vv['plate']['code'];
unset($vv['plate']);
$gets[$v['security']['code']][] = $vv;
}
}
return (array)$gets;
}
/**
* 获取持股变化列表
*/
public function Qot_GetHoldingChangeList(){
}
/**
* 获取期权链
*/
public function Qot_GetOptionChain(){
}
/**
* 获取涡轮
* @param array $filter
* @param number $begin 数据起始点
* @param number $num 请求数据个数,最大200
* @param number $sortField 根据哪个字段排序
* @param string $ascend 升序ture,降序false
*/
public function Qot_GetWarrant($code, $filter=array(), $begin=0, $num=200, $sortField=10, $ascend=false){
if(! $this->limit(__LINE__, 30, 60)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'begin' => (int)$begin,
'num' => (int)$num,
'sortField' => (int)$sortField,
'ascend' => (bool)$ascend
);
foreach ((array)$filter as $k => $v){
$C2S[$k] = $v;
}
if($code = trim($code)){
$C2S['owner'] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $ret = $this->send('3210', $C2S)){
return array();
}
$gets = array();
foreach ((array)$ret['warrantDataList'] as $v){
if(! $v['code'] = (string)$v['stock']['code']){
continue;
}
if(! $v['owner'] = (string)$v['owner']['code']){
continue;
}
unset($v['stock']);
$gets[$v['code']] = $v;
}
return (array)$gets;
}
/**
* 获取资金流向,仅支持正股、窝轮和基金
* @param string $code
* @return array
*/
public function Qot_GetCapitalFlow($code){
if(! $this->limit(__LINE__, 30, 30)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
)
);
if(! $ret = $this->send('3211', $C2S)){
return array();
}
return (array)$ret['flowItemList'];
}
/**
* 获取资金分布
* @param string $code
* @return array
*/
public function Qot_GetCapitalDistribution($code){
if(! $this->limit(__LINE__, 30, 30)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
)
);
if(! $ret = $this->send('3212', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 获取自选股分组下的股票
* @param string $groupName
* @return array
*/
public function Qot_GetUserSecurity($groupName){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'groupName' => (string)$groupName
);
if(! $ret = $this->send('3213', $C2S)){
return array();
}
return (array)$ret['staticInfoList'];
}
/**
* 修改自选股分组下的股票
* @param string $groupName
* @param int $op 1新增 2删除3移出
* @param array $codes
* @return boolean
*/
public function Qot_ModifyUserSecurity($groupName, $op, $codes){
if(! $this->limit(__LINE__, 30, 10)){
return false;
}
if(! $this->InitConnect()){
return false;
}
$securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $securityList){
return false;
}
$C2S = array(
'groupName' => (string)$groupName,
'op' => (int)$op,
'securityList' => (array)$securityList,
);
if(! $ret = $this->send('3214', $C2S)){
return false;
}
return (bool)$ret;
}
/**
* 获取条件选股
* @param array $filter
* @param number $plate
* @param number $begin
* @param number $num
* @return array
*/
public function Qot_StockFilter($filter=array(), $plate=0, $begin=0, $num=200){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'begin' => (int)$begin,
'num' => (int)$num,
'market' => $this->market
);
foreach ((array)$filter as $k => $v){
$C2S[$k] = $v;
}
if($plate = trim($plate)){
$C2S['plate'] = array(
'market' => $this->market,
'code' => (string)$plate,
);
}
if(! $ret = $this->send('3215', $C2S)){
return array();
}
$gets = array();
foreach ($ret['dataList'] as $v){
if(! $v['code'] = $v['security']['code']){
continue;
}
$v['market'] = $v['security']['market'];
unset($v['security']);
$gets[$v['code']] = $v;
}
return (array)$gets;
}
/**
* 获取股票代码变更信息
* @param array $codes
* @param array $typeList 根据类型筛选1创业板转主板2买卖未缴款供股权3更改买卖单位4拆股5合股6股份先并后拆7股份先拆后并8其他
* @param array $timeFilter 根据时间筛选
* @return array
*/
public function Qot_GetCodeChange($codes=array(), $typeList=array(), $timeFilter=array()){
if(! $this->InitConnect()){
return array();
}
$C2S = $securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if($securityList){
$C2S['securityList'] = $securityList;
}
if($typeList = (array)$typeList){
$C2S['typeList'] = $typeList;
}
if($timeFilter = (array)$timeFilter){
$C2S['timeFilter'] = $timeFilter;
}
if($placeHolder = (int)$placeHolder){
//占位
$C2S['placeHolder'] = (int)$placeHolder;
}
$C2S or ($C2S['placeHolder'] = 0); //返回全部
if(! $C2S){
return array();
}
if(! $ret = $this->send('3216', $C2S)){
return array();
}
return (array)$ret['codeChangeList'];
}
/**
* 获取IPO信息
* @return array
*/
public function Qot_GetIpoList(){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'market' => $this->market
);
if(! $ret = $this->send('3217', $C2S)){
return array();
}
$gets = array();
foreach ((array)$ret['ipoList'] as $v){
if($v['basic']){
$v['basic']['code'] = $v['basic']['security']['code'];
$v['basic']['market'] = $v['basic']['security']['market'];
unset($v['basic']['security']);
}
$a = array_merge((array)$v['basic'], (array)$v['hkExData']);
if(! $a['code']){
continue;
}
$gets[] = $a;
}
return (array)$gets;
}
/**
* 获取期货合约资料
* @param array $codes 传入股票最多 200 个
* @return array|boolean
*/
public function Qot_GetFutureInfo($codes){
if(! $this->limit(__LINE__, 30, 30)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $securityList){
return array();
}
$C2S = array(
'securityList' => (array)$securityList
);
if(! $ret = $this->send('3218', $C2S)){
return array();
}
$gets = array();
foreach ((array)$ret['futureInfoList'] as $v){
if($v['basic']){
$v['basic']['code'] = $v['basic']['security']['code'];
$v['basic']['market'] = $v['basic']['security']['market'];
unset($v['basic']['security']);
}
if($v['warrantExData']){
$v['warrantExData']['owner_code'] = $v['warrantExData']['owner']['code'];
$v['warrantExData']['owner_market'] = $v['warrantExData']['owner']['market'];
unset($v['warrantExData']['owner']);
}
$v['code'] = $v['security']['code'];
$v['market'] = $v['security']['market'];
unset($v['security']);
$v['owner_code'] = $v['owner']['code'];
$v['owner_market'] = $v['owner']['market'];
unset($v['owner']);
$gets[$v['code']] = $v;
}
return (array)$gets;
}
/**
* 在线请求交易日
* @param int $beginTime
* @param int $endTime
* @return array tradeDateType: 0全天;1上午;2下午
*/
public function Qot_RequestTradeDate($beginTime, $endTime){
if(! $this->limit(__LINE__, 30, 30)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'market' => $this->trdMarket,
'beginTime' => date('Y-m-d', $beginTime), //开始时间字符串
'endTime' => date('Y-m-d', $endTime) //结束时间字符串
);
if(! $ret = $this->send('3219', $C2S)){
return array();
}
return (array)$ret['tradeDateList'];
}
/**
* 设置到价提醒
* @param string $code
* @param int $op 操作类型:1新增 2删除 3启用 4禁用 5修改6删除该支股票下所有到价提醒
* @param int $key 到价提醒的标识,Qot_GetPriceReminder协议可获得,用于指定要操作的到价提醒项,对于新增的情况不需要填
* @param int $type Qot_Common::PriceReminderType,提醒类型,删除/启用/禁用的情况下会忽略该字段
* @param int $freq Qot_Common::PriceReminderFreq,提醒频率类型,删除/启用/禁用的情况下会忽略该字段 1持续提醒 2每日一次 3仅提醒一次
* @param float $value 提醒值,删除/启用/禁用的情况下会忽略该字段
* @param string $note 用户设置到价提醒时的标注,最多10个字符,删除/启用/禁用的情况下会忽略该字段
*/
public function Qot_SetPriceReminder($code, $op, $key=0, $type=0, $freq=0, $value=0, $note=''){
if(! $this->limit(__LINE__, 30, 60)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'security' => array(
'market' => $this->market,
'code' => (string)$code,
),
'op' => (int)$op
);
if($key = (int)$key){
$C2S['key'] = $key;
}
if($type = (int)$type){
$C2S['type'] = $type;
}
if($freq = (int)$freq){
$C2S['freq'] = $freq;
}
if($value = (float)$value){
$C2S['value'] = $value;
}
if($note = (string)$note){
$C2S['note'] = $note;
}
if(! $ret = $this->send('3220', $C2S)){
return array();
}
return (array)$ret;
}
/**
* 获取到价提醒
* @param string $code
* @return array
*/
public function Qot_GetPriceReminder($code=null){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'market' => $this->market
);
if($code = trim($code)){
$C2S['security'] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $ret = $this->send('3221', $C2S)){
return array();
}
$gets = array();
foreach ((array)$ret['priceReminderList'] as $v){
if(! $v['code'] = $v['security']['code']){
continue;
}
$v['market'] = $v['security']['market'];
unset($v['security']);
$gets[$v['code']] = $v;
}
return (array)$gets;
}
/**
* 获取自选股分组列表
* @param int $groupType 1自定义分组2系统分组3全部分组
* @return array
*/
public function Qot_GetUserSecurityGroup($groupType){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'groupType' => (int)$groupType
);
if(! $ret = $this->send('3222', $C2S)){
return array();
}
$gets = array();
foreach ((array)$ret['groupList'] as $v){
$gets[] = $v;
}
return (array)$gets;
}
/**
* 获取指定品种的市场状态
* @return array
*/
public function Qot_GetMarketState($codes){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
if(! $this->InitConnect()){
return array();
}
$securityList = array();
foreach ((array)$codes as $code){
$securityList[] = array(
'market' => $this->market,
'code' => (string)$code,
);
}
if(! $securityList = (array)$securityList){
return array();
}
$C2S = array(
'securityList' => $securityList,
);
if(! $ret = $this->send('3223', $C2S)){
return array();
}
return (array)$ret['marketInfoList'];
}
/**
* 获取交易账户列表
* @return array
*/
public function Trd_GetAccList(){
if($this->accList){
return $this->accList;
}
if(! $this->InitConnect()){
return array();
}
$C2S = array(
'userID' => (string)$this->loginUserID,
);
if(! $ret = $this->send('2001', $C2S)){
return array();
}
// trdMarket :此账户能交易哪些市场 0:未知市场 1:香港市场(证券、期权)2:美国市场(证券、期权) 3:A股市场(仅用于模拟交易) 4:A股通市场(股票)5:期货市场(环球期货)
// TrdEnv:交易环境 0:模拟环境 1:真实环境
// accType:交易环境 0; 未知类型 1:现金账户 2:保证金账户
// simAccType:交易环境 0; 未知类型 1:股票模拟账户(仅用于交易证券类产品,不支持交易期权 2:期权模拟账户(仅用于交易期权,不支持交易股票证券类产品)
// accID:业务账号
// cardNum:卡号
foreach ((array)$ret['accList'] as $v){
foreach ((array)$v['trdMarketAuthList'] as $trdMarket){
//可拥有多个交易市场权限,目前仅单个
if ($v['trdEnv'] == 0){
$this->accList[$trdMarket][$v['trdEnv']][$v['accType']][$v['simAccType']] = (string)$v['accID'];
}else{
$this->accList[$trdMarket][$v['trdEnv']][$v['accType']] = (string)$v['accID'];
}
}
}
return (array)$this->accList;
}
/**
* 根据环境选择交易账户
*
* @param int $accType 交易环境 0; 未知类型 1:现金账户 2:保证金账户
* @param int $simAccType 交易环境 0; 未知类型 1:股票模拟账户 2:期权模拟账户
* @return string
*/
private function chooseAccount($accType=1,$simAccType=1){
$list = $this->accList[$this->trdMarket][$this->trdEnv][$accType];
if ($this->trdEnv == 0){
//模拟环境
return (string)$list[$simAccType];
}else{
//真实环境
return (string)$list;
}
}
/**
* 解锁交易(30秒10次)
* @param string $unlock true解锁false锁定
* @return bool
*/
public function Trd_UnlockTrade($unlock){
$unlock = (bool)$unlock;
if($this->unlock === $unlock){
return true;
}
if($this->trdEnv == 0){
//仿真环境无需解锁
return $this->unlock = true;
}
if(! $this->limit(__LINE__, 30, 10)){
return false;
}
if(! $this->InitConnect()){
return false;
}
$C2S = array(
'unlock' => (bool)$unlock,
'pwdMD5' => strtolower(md5($this->pass,FALSE)),
'securityFirm' => 1,
);
if(! $ret = $this->send('2005', $C2S)){
return false;
}
$this->unlock = $unlock;
return true;
}
/**
* 订阅接收交易账户的推送数据
* @return array
*/
public function Trd_SubAccPush(){
if($this->accPush){
return true;
}
if(! $this->Trd_UnlockTrade(true)){
return false;
}
if(! $this->Trd_GetAccList()){
return false;
}
$accIDList = array();
foreach ([1,5] as $trdMarket){
//接收港股及期货的推送
if($accID = (string)$this->accList[$trdMarket][$this->trdEnv]){
$accIDList[] = $accID;
}
}
if(! $accIDList){
return false;
}
$C2S = array(
'accIDList' => (array)$accIDList
);
if(! $ret = $this->send('2008', $C2S)){
return array();
}
return $this->accPush = (bool)$ret;
}
/**
* 获取账户资金
* @param bool $refreshCache 是否强制从服务器获取数据
* @param int $currency 1港币,2美元,3离岸人民币;货币种类,期货账户必填,其它账户忽略
* @return array
*/
public function Trd_GetFunds($refreshCache=false, $currency=1){
if(! $this->Trd_UnlockTrade(true)){
return array();
}
if(! $this->Trd_GetAccList()){
return array();
}
if(! $accID = (string)$this->chooseAccount()){
return array();
}
if($refreshCache){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'refreshCache' => (bool)$refreshCache,
);
if(in_array($this->trdMarket, [5])){
//期货账户必填,其它账户忽略
$C2S['currency'] = (int)$currency;
}
if(! $ret = $this->send('2101', $C2S)){
return array();
}
return (array)$ret['funds'];
}
/**
* 获取持仓列表
* @param array $codeList 代码过滤,只返回包含这些代码的数据,没传不过滤
* @param array $idList ID 主键过滤,只返回包含这些 ID 的数据,没传不过滤
* @param number $filterPLRatioMin 过滤盈亏百分比下限,高于此比例的会返回,比如传10.0,返回盈亏比例大于10%的持仓
* @param number $filterPLRatioMax 过滤盈亏百分比上限,低于此比例的会返回,比如传20.0,返回盈亏比例小于20%的持仓
* @param bool $refreshCache 立即刷新 OpenD 缓存的此数据
* @return array
*/
public function Trd_GetPositionList($codeList=[], $idList=[], $filterPLRatioMin=0, $filterPLRatioMax=0, $refreshCache=false){
if(! $this->Trd_UnlockTrade(true)){
return array();
}
if(! $this->Trd_GetAccList()){
return array();
}
if(! $accID = (string)$this->chooseAccount()){
return array();
}
if($refreshCache){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
// 'accID' => "7246205",
),
'refreshCache' => (bool)$refreshCache
);
if($codeList){
$C2S['filterConditions']['codeList'] = (array)$codeList;
}
if($idList){
$C2S['filterConditions']['idList'] = (array)$idList;
}
if($filterPLRatioMin){
$C2S['filterPLRatioMin'] = (float)$filterPLRatioMin;
}
if($filterPLRatioMax){
$C2S['filterPLRatioMax'] = (float)$filterPLRatioMax;
}
if(! $ret = $this->send('2102', $C2S)){
return array();
}
return (array)(isset($ret['positionList']) ? $ret['positionList'] : array());
}
/**
* 获取最大交易数量(30秒10次)
* @param string $code
* @param float $price
* @param int $orderType 1普通单2市价单(仅美股)5绝对限价订单6竞价订单7竞价限价订单8特别限价订单
* @param number $orderID
* @param string $adjustPrice
* @param number $adjustSideAndLimit
* @return array
*/
public function Trd_GetMaxTrdQtys($code, $price, $orderType=1, $orderID=0, $adjustPrice=false, $adjustSideAndLimit=0){
if(! $this->Trd_UnlockTrade(true)){
return array();
}
if(! $this->Trd_GetAccList()){
return array();
}
if(! $accID = (string)$this->chooseAccount()){
return array();
}
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'orderType' => (int)$orderType,
'code' => (string)$code,
'price' => (float)$price,
);
if($orderID){
$C2S['orderID'] = (string)$orderID;
}
if($adjustPrice){
$C2S['adjustPrice'] = (bool)$adjustPrice;
}
if($adjustSideAndLimit){
$C2S['adjustSideAndLimit'] = (float)$adjustSideAndLimit;
}
if(! $ret = $this->send('2111', $C2S)){
return array();
}
return (array)$ret['maxTrdQtys'];
}
/**
* 获取订单列表
* @param array $filterStatusList 状态-1未知0未提交1等待提交2提交中3提交失败4处理超时结果未知5已提交待成交10部分成交11全部成交12撤单剩余部分13撤单中14剩余部分撤单成功15全部已撤单21下单失败22已失效23已删除
* @param array $codeList
* @param array $idList
* @param bool $refreshCache
* @return array
*/
public function Trd_GetOrderList($filterStatusList=[], $beginTime=0, $endTime=0, $codeList=[], $idList=[], $refreshCache=false){
if(! $this->Trd_UnlockTrade(true)){
return array();
}
if(! $this->Trd_GetAccList()){
return array();
}
if(! $accID = (string)$this->chooseAccount()){
return array();
}
if($refreshCache){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'refreshCache' => (bool)$refreshCache
);
if($filterStatusList){
$C2S['filterStatusList'] = (array)$filterStatusList;
}
if($beginTime){
$C2S['filterConditions']['beginTime'] = date('Y-m-d H:i:s', $beginTime);
}
if($endTime){
$C2S['filterConditions']['endTime'] = date('Y-m-d H:i:s', $endTime);
}
if($codeList){
$C2S['filterConditions']['codeList'] = (array)$codeList;
}
if($idList){
$C2S['filterConditions']['idList'] = (array)$idList;
}
if(! $ret = $this->send('2201', $C2S)){
return array();
}
return (array)(isset($ret['orderList']) ? $ret['orderList'] : array());
}
/**
* 下单(30秒15次)
* @param string $code 股票代码
* @param int $trdSide 0未知1买入2卖出3卖空(是美股订单时有此方向)4买回(目前不存在)
* @param float $qty 数量,期权单位是"张"(精确到小数点后 0 位,超出部分会被舍弃。期权期货单位是"张")
* @param float $price 价格,(证券账户精确到小数点后 3 位,期货账户精确到小数点后 9 位,超出部分会被舍弃)
* @param int $orderType 0未知1普通单2市价单(仅美股)5绝对限价订单6竞价订单7竞价限价订单8特别限价订单
* @param bool $adjustPrice 是否调整价格:如果挂单价格不合理是否调整到合理的档位
* @param float $adjustSideAndLimit 如果调整价格,是向上调整(正)还是向下调整(负),最多调整多少百分比
* @param int $secMarket 证券所属市场 1港股正股/涡轮;2美股正股/期权;31沪市;32深市
* @param string $remark 用户备注字符串,最多只能传64字节,可用于标识订单唯一信息等,下单填上订单结构就会带上
* @param int $timeInForce 0当日有效1撤单前有效最多持续90自然日
* @param bool $fillOutsideRTH 是否允许盘前盘后成交仅适用于美股限价单
* @return id 订单ID
*/
public function Trd_PlaceOrder($code, $trdSide, $qty, $price, $orderType=1, $adjustPrice=false, $adjustSideAndLimit=0, $secMarket=1, $remark='', $timeInForce=0, $fillOutsideRTH=false){
if(! $this->Trd_UnlockTrade(true)){
return 0;
}
if(! $this->Trd_GetAccList()){
return 0;
}
if(! $accID = (string)$this->chooseAccount()){
return 0;
}
if(! $this->limit(__LINE__, 30, 15)){
return 0;
}
if(! $this->limit(__LINE__, 1, 5)){
return 0;
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'packetID' => array(
'connID' => (string)$this->connID,
'serialNo' => (int)$this->sequence
),
'code' => (string)$code,
'trdSide' => (int)$trdSide,
'orderType' => (int)$orderType,
'qty' => (float)$qty,
'price' => (float)$price,
'secMarket' => (int)$secMarket,
'remark' => (string)$remark
);
if($adjustPrice){
$C2S['adjustPrice'] = (bool)$adjustPrice;
}
if($adjustSideAndLimit){
$C2S['adjustSideAndLimit'] = (float)$adjustSideAndLimit;
}
if($timeInForce){
$C2S['timeInForce'] = (int)$timeInForce;
}
if($fillOutsideRTH){
$C2S['fillOutsideRTH'] = (bool)$fillOutsideRTH;
}
if(! $ret = $this->send('2202', $C2S)){
return 0;
}
return (string)$ret['orderID'];
}
/**
* 修改订单(改价/改量/改状态等)(30秒20次)
* @param string $orderID $forAll为true时传0
* @param int $modifyOrderOp 0未知1改单(价格/数量)2撤单3失效4生效5删除
//下面的字段仅针对单个订单,且 modifyOrderOp 为 ModifyOrderOp_Normal 有效
* @param float $qty 数量,期权单位是"张"(精确到小数点后 0 位,超出部分会被舍弃)
* @param float $price 价格,(证券账户精确到小数点后 3 位,期货账户精确到小数点后 9 位,超出部分会被舍弃)
//以下为调整价格使用,都传才有效,对港、A 股有意义,因为港股有价位,A 股2位精度,美股可不传
* @param bool $forAll 是否对此业务账户的全部订单操作,true 是,false 否(对单个订单),无此字段代表 false,仅对单个订单
* @param bool $adjustPrice 是否调整价格,如果价格不合法,是否调整到合法价位,true 调整,false 不调整。如果价格不合法又不允许调整,则会返回错误
* @param float $adjustSideAndLimit 调整方向和调整幅度百分比限制,正数代表向上调整,负数代表向下调整,具体值代表调整幅度限制,如:0.015代表向上调整且幅度不超过1.5%;-0.01代表向下调整且幅度不超过
* @return number
*/
public function Trd_ModifyOrder($orderID, $modifyOrderOp, $qty=0, $price=0, $forAll=false, $adjustPrice=false, $adjustSideAndLimit=0){
if(! $this->Trd_UnlockTrade(true)){
return 0;
}
if(! $this->Trd_GetAccList()){
return 0;
}
if(! $accID = (string)$this->chooseAccount()){
return 0;
}
if(! $this->limit(__LINE__, 30, 20)){
return 0;
}
if(! $this->limit(__LINE__, 1, 5)){
return 0;
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'packetID' => array(
'connID' => (string)$this->connID,
'serialNo' => (int)$this->sequence
),
'orderID' => (string)$orderID,
'modifyOrderOp' => (int)$modifyOrderOp,
'forAll' => (bool)$forAll,
);
if($modifyOrderOp == 1){
$C2S['qty'] = (float)$qty;
$C2S['price'] = (float)$price;
}
if($adjustPrice){
$C2S['adjustPrice'] = (bool)$adjustPrice;
}
if($adjustSideAndLimit){
$C2S['adjustSideAndLimit'] = (float)$adjustSideAndLimit;
}
if(! $ret = $this->send('2205', $C2S)){
return 0;
}
return (string)$ret['orderID'];
}
/**
* 获取成交列表
* @param array $codeList
* @param array $idList
* @param bool $refreshCache
* @return array
*/
public function Trd_GetOrderFillList($codeList=[], $idList=[], $refreshCache=false){
if(! $this->Trd_UnlockTrade(true)){
return array();
}
if(! $this->Trd_GetAccList()){
return array();
}
if(! $accID = (string)$this->chooseAccount()){
return array();
}
if($refreshCache){
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'refreshCache' => (bool)$refreshCache
);
if($codeList){
$C2S['filterConditions']['codeList'] = (array)$codeList;
}
if($idList){
$C2S['filterConditions']['idList'] = (array)$idList;
}
if(! $ret = $this->send('2211', $C2S)){
return array();
}
return (array)$ret['orderFillList'];
}
/**
* 获取历史订单列表(30秒10次)
* @param int $beginTime
* @param int $endTime
* @param array $filterStatusList 状态-1未知0未提交1等待提交2提交中3提交失败4处理超时结果未知5已提交待成交10部分成交11全部成交12撤单剩余部分13撤单中14剩余部分撤单成功15全部已撤单21下单失败22已失效23已删除
* @param array $codeList 股票代码过滤['00700','00388']
* @param array $idList 订单ID过滤
* @return array
*/
public function Trd_GetHistoryOrderList($beginTime, $endTime, $filterStatusList=[], $codeList=[], $idList=[]){
if(! $this->Trd_UnlockTrade(true)){
return array();
}
if(! $this->Trd_GetAccList()){
return array();
}
if(! $accID = (string)$this->chooseAccount()){
return array();
}
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'filterConditions' => array(
'beginTime' => date('Y-m-d H:i:s', $beginTime),
'endTime' => date('Y-m-d H:i:s', $endTime),
),
);
if($codeList){
$C2S['filterConditions']['codeList'] = (array)$codeList;
}
if($idList){
$C2S['filterConditions']['idList'] = (array)$idList;
}
if($filterStatusList){
$C2S['filterStatusList'] = (array)$filterStatusList;
}
if(! $ret = $this->send('2221', $C2S)){
return array();
}
return (array)$ret['orderList'];
}
/**
* 获取历史成交列表(30秒10次)
* @param int $beginTime
* @param int $endTime
* @param array $codeList
* @param array $idList
* @return array
*/
public function Trd_GetHistoryOrderFillList($beginTime, $endTime, $codeList=[], $idList=[]){
if(! $this->Trd_UnlockTrade(true)){
return array();
}
if(! $this->Trd_GetAccList()){
return array();
}
if(! $accID = (string)$this->chooseAccount()){
return array();
}
if(! $this->limit(__LINE__, 30, 10)){
return array();
}
$C2S = array(
'header' => array(
'trdEnv' => $this->trdEnv,
'trdMarket' => $this->trdMarket,
'accID' => (string)$accID,
),
'filterConditions' => array(
'beginTime' => date('Y-m-d H:i:s', $beginTime),
'endTime' => date('Y-m-d H:i:s', $endTime),
),
);
if($codeList){
$C2S['filterConditions']['codeList'] = (array)$codeList;
}
if($idList){
$C2S['filterConditions']['idList'] = (array)$idList;
}
if(! $ret = $this->send('2222', $C2S)){
return array();
}
return (array)$ret['orderFillList'];
}
/**
* 编码
* @param int $proto
* @param string $C2S
* @return boolean|string
*/
public function encode($proto, $C2S){
if(! $proto = (int)$proto){
return false;
}
$body = $C2S; //默认不加密
if($this->encrypt && ($proto == 1001)){
$private_pkey = openssl_pkey_get_private(file_get_contents($this->private_key));
$details_pkey = openssl_pkey_get_details($private_pkey); //由私钥计算得到公钥
$public_pkey = openssl_pkey_get_public($details_pkey['key']);
$C2S_encrypted = '';
for($i=0, $s=substr($C2S, 0, 100); $s; $i++,$s=substr($C2S, $i*100, 100)){
$encrypted = '';
openssl_public_encrypt($s, $encrypted, $public_pkey, OPENSSL_PKCS1_PADDING);
$C2S_encrypted .= $encrypted;
}
$body = $C2S_encrypted;
}
if($this->encrypt && ($proto != 1001)){
$mod = strlen($C2S)%16;
$multiplier = $mod ? (16 - $mod) : 0;
$body = openssl_encrypt($C2S . str_repeat("\\0", $multiplier), 'AES-128-ECB', $this->connAESKey, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING);
$body .= str_repeat("\\0", 15);
$body .= chr($mod);
}
$ret = 'FT';
$ret .= pack("L", (int)$proto); //协议ID
$ret .= pack("C", 1); //协议格式类型,0为Protobuf格式,1为Json格式
$ret .= pack("C", 0); //协议版本,用于迭代兼容
$ret .= pack("L", ++$this->sequence); //包序列号,用于对应请求包和回包
$ret .= pack("L", strlen($body)); //包体长度
$ret .= sha1($C2S, true); //包体原始数据(解密后)的SHA1哈希值
$ret .= pack("@8");//保留8字节扩展
$ret .= $body;
return (string)$ret;
}
/**
* 解码回包
* @param string $recv
* @param string $C2S
* @return array
*/
public function decode($recv, $C2S){
if(empty($recv) === true){
return array();
}
$head = substr($recv, 0, 44);
$body = substr($recv, 44);
$pack = unpack("CF/CT/Lproto/CProtoFmtType/CProtoVer/LSerialNo/LBodyLen", $head, 0);
if($this->encrypt && ($pack['proto'] == 1001)){
$private_pkey = openssl_pkey_get_private(file_get_contents($this->private_key));
$body_decrypted = '';
for($i=0, $s=substr($body, 0, 128); $s; $i++,$s=substr($body, $i*128, 128)){
$decrypted = '';
openssl_private_decrypt($s, $decrypted, $private_pkey, OPENSSL_PKCS1_PADDING);
$body_decrypted .= $decrypted;
}
$body = $body_decrypted;
}
if($this->encrypt && ($pack['proto'] != 1001)){
$mod = ord(substr($body, -1)); //补了多少个0
$body = openssl_decrypt(substr($body, 0, -16), 'AES-128-ECB', $this->connAESKey, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING);
$body = $mod ? substr($body, 0, $mod-16) : $body;
}
if(! $ret = json_decode($body, true)){
$this->errorlog("json Error:{
$pack['proto']}:{
$C2S} - {
$body}", 1);
return array();
}
echo "返回数据:".PHP_EOL,
var_dump($ret);
if($ret['retType'] != 0){
if(in_array($pack['proto'], array(3001,3203)) && preg_match('/(\\d{5}$)/i', trim($ret['retMsg']), $m)){
$GLOBALS['FUTU_DELISTED'][$m[1]] = 1;
}else{
$this->errorlog("ret Error:{
$pack['proto']}:{
$C2S} - {
$ret['retType']}:{
$ret['retMsg']}", in_array($ret['retType'], array(-1)) ? 4 : 0);
}
return array();
}
if($ret['errCode'] != 0){
$this->errorlog("err Error:{
$pack['proto']}:{
$C2S} - {
$ret['errCode']}:{
$ret['retMsg']}", 1);
return array();
}
$f = __DIR__ . "/futu_{
$pack['proto']}.txt";
if(false && is_file($f)){
//记录服务器的返回
file_put_contents($f, var_export($pack, true)."\
".var_export($ret, true));
}
return array('proto'=>$pack['proto'], 's2c'=>isset($ret['s2c'])?$ret['s2c']:['result'=>true]);
}
/**
* 私有限额方法
* @param int $typ 限额类型
* @param int $sec 多少秒
* @param int $cnt 多少次 比如订单为30秒20次
* @return boolean 是否在限额内
*/
private function limit($typ, $sec, $cnt){
if(! $typ = $this->checkInt($typ)){
return false;
}
if(! $sec = $this->checkInt($sec)){
return false;
}
if(! $cnt = $this->checkInt($cnt)){
return false;
}
//此处实现限额逻辑,可以用一个环形的存储结构
return true;
}
/**
*
* @param unknown $Protocol
* @param unknown $ReqParam
* @return array
*/
private function send($proto, $C2S){
if(! $this->connect()){
var_dump("链接失败");
return array();
}
if(! $C2S = json_encode(array('c2s' => $C2S))){
return array();
}
echo "发送协议代码:".$proto.PHP_EOL."发送数据:";
print_r($C2S.PHP_EOL);
if(! $data = $this->encode($proto, $C2S)){
return array();
}
if(! $length = @$this->cli->send("{
$data}")){
$this->errorlog("Send Error:{
$proto} - {
$C2S} - ".swoole_last_error()." - ".swoole_strerror(swoole_last_error(), 9), 0);
return array();
}
if($this->push){
//推送模式不需要接收返回(此处必须返回空值)
return array();
}
if(! $recv = @$this->cli->recv()){
$this->errorlog("Recv Error:{
$proto} - {
$C2S} - ".swoole_last_error()." - ".swoole_strerror(swoole_last_error(), 9), 0);
return array();
}
if(! $ret = $this->decode($recv, $C2S)){
var_dump("解码失败");
return array();
}
// echo "返回结果:";
// var_dump($ret);
if($ret['proto'] && ($ret['proto'] != $proto) && ! in_array($proto, [3001,3002])){
$this->errorlog("proto Error:{
$proto} - {
$C2S}", 1);
return array();
}
return (array)$ret['s2c'];
}
/**
* 记录错误,加上断线自动重连
* @param unknown $msg
* @param number $level 0退出+日志1断线+日志2退出3断线4日志
* @return boolean
*/
private function errorlog($msg, $level=0){
var_dump($msg);
//in_array($level, [0,1,4]) && file_put_contents(); //此处记录日志
in_array($level, [1,3]) && $this->close();
in_array($level, [0,2]) && die();
$this->die && die("失败");
return false;
}
public function __destruct(){
$this->close();
}
/**
* @param $number
* @return bool|int
*/
private function checkInt($number){
if (!is_int($number)){
return false;
}
return (int)$number;
}
}
PHP测试的同步调用文件(futu_get_test.php)
<?php
/**
* Created by PhpStorm.
* Date: 2021/9/27
* Time: 2:38 PM
*/
/**
* 同步模式
*/
include('class.ftnn.php');
//$o = new futu('127.0.0.1', 11111, '739146');
//$trdEnv = 0; //0:模拟账户 1:真实账户
$o = new futu('127.0.0.1', 11111, '123456',0);
/**
* 获取股票板块列表
*
$l = $o->Qot_GetPlateSet(0);
foreach ($l as $v){
echo "行情市场:{$v['market']},代码:{$v['code']},名称:{$v['name']}".PHP_EOL;
}
//print_r($l);
die();
*/
/**
* 获取板块下的股票(30秒10次)
*
$l=$o->Qot_GetPlateSecurity("BK1283");
foreach($l as $v){
$vv = $v["basic"];
echo "行情市场:{$vv['security']['market']},代码:{$vv['security']['code']},股票ID:{$vv['id']},每手数量:{$vv['lotSize']},股票类型:{$vv['secType']},股票名字:{$vv['name']},上市时间:{$vv['listTime']},是否退市:{$vv['delisting']},上市时间戳:{$vv['listTimestamp']},所属交易所:{$vv['exchType']}".PHP_EOL;
}
die();
*/
//$l = $o->Qot_GetStaticInfo(3);
//$l = $o->Qot_RequestHistoryKLQuota(3);
//$l = $o->GetUserInfo();
//$l = $o->Qot_GetSubInfo();
//$l = $o->Qot_Sub(["301069"],[1]);//先订阅
//var_dump($l);
//die();
//sleep(5);
/**
* 获取股票基本行情
$l = $o->Qot_GetBasicQot(["06055"]);
//$o->Qot_Sub(["06055"],[1],false);//先订阅
foreach($l as $k => $v){
$string_info = "";
$string_info .= "行情市场:{$v['security']['market']},";
$string_info .= "代码:{$v['security']['code']},";
$v['isSuspended'] = $v['isSuspended']?'是':'否';
$string_info .= "是否停牌:{$v['isSuspended']},";
$string_info .= "上市日期:{$v['listTime']},";
$string_info .= "价差:{$v['priceSpread']},";
$string_info .= "最新价的更新时间:{$v['updateTime']},";
$string_info .= "最高价:{$v['highPrice']},";
$string_info .= "开盘价:{$v['openPrice']},";
$string_info .= "最低价:{$v['lowPrice']},";
$string_info .= "最新价:{$v['curPrice']},";
$string_info .= "昨收价:{$v['lastClosePrice']},";
$string_info .= "成交量:{$v['volume']},";
$string_info .= "成交额:{$v['turnover']},";
$string_info .= "换手率:{$v['turnoverRate']}%,";
$string_info .= "振幅:{$v['amplitude']}%,";
switch($v['darkStatus']){
case 0:
$v['darkStatus'] = "无暗盘交易";
break;
case 1:
$v['darkStatus'] = "暗盘交易中";
break;
case 2:
$v['darkStatus'] = "暗盘交易结束";
break;
}
$string_info .= "暗盘交易状态:{$v['darkStatus']},";
$string_info .= "上市日期时间戳:{$v['listTimestamp']},";
$string_info .= "最新价的更新时间戳:{$v['updateTimestamp']},";
$string_info .= "股票状态:{$v['secStatus']}";
echo $string_info.PHP_EOL;
}
//print_r($l);
*/
/**
* 获取交易业务账户列表
$l = $o->Trd_GetAccList();
//print_r($l);
//die();
foreach ($l as $k => $v){
switch($k){
case 0:
$k = "未知市场";
break;
case 1:
$k = "香港市场";
break;
case 2:
$k = "美国市场";
break;
case 3:
$k = "A股市场(仅用于模拟交易)";
break;
case 4:
$k = "A股通市场";
break;
case 5:
$k = "期货市场";
break;
}
foreach ($v as $kk => $vv){
switch($kk){
case 0:
$kk = "模拟环境";
$trdEnv = 0;
break;
case 1:
$kk = "真实环境";
$trdEnv = 1;
break;
}
foreach ($vv as $kkk => $vvv){
switch($kkk){
case 0:
$kkk = "未知类型";
break;
case 1:
$kkk = "现金";
break;
case 2:
$kkk = "保证金";
break;
}
if ($trdEnv == 0){
foreach ($vvv as $kkkk => $vvvv){
switch($kkkk){
case 0:
$kkkk = "未知类型";
break;
case 1:
$kkkk = "股票模拟";
break;
case 2:
$kkkk = "期权模拟";
break;
}
echo $k."-".$kk."-".$kkk."-".$kkkk."-"."账户:".$vvvv.PHP_EOL;
}
continue;
}
echo $k."-".$kk."-".$kkk."-"."账户:".$vvv.PHP_EOL;
}
}
}
//print_r($l);
//die();
*/
/**
* 获取账户资金
$l = $o->Trd_GetFunds();
print_r($l);
die();
*/
/**
* 查询今日订单
* (下午时候查不到今日订单,待查原因)
$beginTime = time()-86400;
$endTime = time()+86400;
$l = $o->Trd_GetOrderList([0,-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],$beginTime,$endTime);
print_r($l);
*/
/**
* 查询当日成交
* 模拟交易不支持成交数据
$l = $o->Trd_GetOrderFillList();
print_r($l);
*/
/**
* 查询历史成交
* 模拟交易不支持成交数据
$beginTime = time()-86400;
$endTime = time()+86400;
$l = $o->Trd_GetHistoryOrderFillList($beginTime,$endTime);
print_r($l);
*/
/**
* 获取静态数据(股票基本信息)
*/
$l = $o->Qot_GetStaticInfo(0,["00700"]);
if(empty(!$l)){
foreach ($l as $k => $v){
if (isset($v["basic"])){
$basic = $v["basic"];
$info = "";
$info .= "市场:".$basic["security"]["market"];
$info .= "_代码:".$basic["security"]["code"];
$info .= "_id:".$basic["id"];
$info .= "_每手数量:".$basic["lotSize"];
$info .= "_股票类型:".$basic["secType"];
$info .= "_股票名字:".$basic["name"];
$info .= "_上市时间:".$basic["listTime"];
$info .= "_是否退市:".$basic["delisting"];
$info .= "_上市时间戳:".$basic["listTimestamp"];
$info .= "_所属交易所:".$basic["exchType"];
print_r($info);
}
}
}
/**
* 买入
$l = $o->Trd_PlaceOrder("09988",1,100,141.1,1);
$orderID = $l;
if($orderID >0){
print_r("成功,订单号:".$orderID);
}else{
print_r("失败");
}
*/
/**
* 查询历史订单
$beginTime = time()-86400;
$endTime = time()+86400;
//$l = $o->Trd_GetHistoryOrderList($beginTime,$endTime,[0,-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23]); //查询全部
$l = $o->Trd_GetHistoryOrderList($beginTime,$endTime,[0,-1,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],["09988"],["4635407981036560905"]); //查询单个
print_r($l);
die();
*/
/**
* 查询持仓
$l = $o->Trd_GetPositionList();
print_r($l);
*/
/**
* 撤单
$l = $o->Trd_ModifyOrder("4635483404958038286",2,100,141.1,false);
$orderID = $l;
if($orderID >0){
print_r("成功,订单号:".$orderID);
}else{
print_r("失败");
}
*/
/**
* 卖出
$l = $o->Trd_PlaceOrder("00700",2,100,456,1); //卖出单个股票部分
$orderID = $l;
if($orderID >0){
print_r("成功,订单号:".$orderID);
}else{
print_r("失败");
}
*/
PHP测试的异步调用文件(futu_push_test.php)
<?php
/**
* Created by PhpStorm.
* Date: 2021/9/27
* Time: 2:31 PM
*/
/**
* 异步模式
*/
include('class.ftnn.php');
if(php_sapi_name() !== 'cli'){
var_dump("不是cli");
die();
}
$GLOBALS['o'] = new futu('127.0.0.1', 11111, '123456');
Swoole\\Coroutine::create(function(){
try{
$cli = new Swoole\\Coroutine\\Client(SWOOLE_SOCK_TCP);
$cli->set(array(
'socket_buffer_size' => 1024*1024*32, //32M缓存区
'open_length_check' => 1,
'package_length_type' => 'L',
'package_length_offset' => 12, //第N个字节是包长度的值
'package_body_offset' => 44, //第几个字节开始计算长度
'package_max_length' => 8*1024*1024, //协议最大长度
'open_tcp_nodelay' => false
));
if($cli->connect('127.0.0.1', '11111', 20) === false){
var_dump("链接失败");
$cli->close();
return true;
}
$GLOBALS['o']->push($cli); //关键
$cli->lasttime = time(); //最后心跳时间
$Timer = Swoole\\Timer::tick(1000, function($Timer, $cli){
//每N秒执行
var_dump(111);
if(! $GLOBALS['o']->InitConnect()){
//初始化连接
var_dump("InitConnect失败");
return true;
}
var_dump(23333);
if(! $GLOBALS['o']->Trd_UnlockTrade(true)){
//解锁交易
var_dump("Trd_UnlockTrade失败");
return true;
}
var_dump(2222);
if(! $GLOBALS['o']->Trd_GetAccList()){
//获取账户
var_dump("Trd_GetAccList失败");
return true;
}
if(! $GLOBALS['o']->Trd_SubAccPush()){
//订阅订单推送
var_dump("Trd_SubAccPush失败");
return true;
}
if(time() - $cli->lasttime >= 10){
$cli->lasttime = time();
$GLOBALS['o']->KeepAlive(); //保持连接
}
$GLOBALS['o']->Qot_Sub('00700', 1, true, true, [1], false);
}, $cli);
while($data = $cli->recv()){
if(! $a = $GLOBALS['o']->decode($data, '')){
exit(0);
}
if(! $proto = (int)$a['proto']){
exit(0);
}
if(! $a = $a['s2c']){
exit(0);
}
switch ($proto){
case 1001: //初始化连接
$GLOBALS['o']->connID = (string)$a['connID'];
$GLOBALS['o']->loginUserID = (string)$a['loginUserID'];
break;
case 1003: //系统推送通知
break;
case 1004: //保持连接
break;
case 2001: //获取交易账号
foreach ((array)$a['accList'] as $v){
foreach ((array)$v['trdMarketAuthList'] as $vv){
//可拥有多个交易市场权限,目前仅单个
$GLOBALS['o']->accList[$vv][$v['trdEnv']] = (string)$v['accID'];
}
}
break;
case 2005: //解锁完成
$GLOBALS['o']->unlock = true;
break;
case 2008: //订阅订单推送(说明环境准备就绪)
$GLOBALS['o']->accPush = true;
break;
case 2208: //推送订单更新(说明订单有了变化)
break;
case 2218: //推送新成交(说明持仓有了变化)
break;
case 3001: //订阅或者反订阅
break;
case 3005: //推送股票基本报价
break;
case 3007: //推送K线
print_r($a);
break;
case 3009: //推送分时
break;
case 3011: //推送逐笔
$code = $a['security']['code'];
break;
case 3013: //推送买卖盘
$code = $a['security']['code'];
break;
case 3015: //推送经纪队列
break;
case 3019: //到价提醒通知
break;
default:
break;
}
};
var_dump("结束");
}catch(Swoole\\ExitException $e){
var_dump("报错".$e->getMessage());
Swoole\\Timer::clear($Timer);
}
});