├── .gitattributes ├── composer.json ├── example.php ├── README.md └── class.php /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "troodi/trading-view-websocket", 3 | "description": "This package allows you to receive quotes from tradingview in real time from websocket", 4 | "type": "library", 5 | "require": { 6 | "textalk/websocket": "dev-master" 7 | }, 8 | "authors": [ 9 | { 10 | "name": "Rodion Larin", 11 | "email": "troodi@ya.ru" 12 | } 13 | ], 14 | "minimum-stability": "dev" 15 | } 16 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | registerTicker($ticker); // Add ticker 21 | } 22 | 23 | foreach($obj->tickerData as $data){ 24 | if($array_tmp[$data["original_name"]] != $data["lp"]) { 25 | var_dump(date('H:i:s ') . $data["original_name"] . ' - ' . $data["lp"]); // Read info from websocket 26 | $array_tmp[$data["original_name"]] = $data["lp"]; 27 | //$obj->closeSocket = true; // Uncomment this, if you run in webrowser as webpage 28 | } 29 | } 30 | } 31 | } 32 | 33 | $array_tmp = []; 34 | new Main('Login', 'Password'); 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TradingViewWebsocket 2 | 3 | This package allows you to receive quotes from tradingview in real time from websocket. You can get any pair that is on the site. Working in real time allows you to process quotes as quickly as possible. 4 | 5 | # How do I run the code? 6 | 7 | Сheck example.php file - there is an example of how this class works. 8 | ```php 9 | class Main { 10 | public function __construct() 11 | { 12 | $ws = new TradingViewWebsocket(__CLASS__); 13 | } 14 | 15 | public static function readQuotes($obj) { 16 | $obj->registerTicker("EURUSD"); // Add ticker 17 | var_dump($obj->tickerData); // Read info from websocket 18 | } 19 | } 20 | ``` 21 | 22 | # What response will TradingView send? 23 | 24 | The response will contain all the necessary data, including the last price: 25 | ``` 26 | array(1) { 27 | ["EURUSD"]=> 28 | array(23) { 29 | ["bid"]=> 30 | float(1.1831) 31 | ["ask"]=> 32 | float(1.18311) 33 | ["volume"]=> 34 | int(379938) 35 | ["update_mode"]=> 36 | string(9) "streaming" 37 | ["type"]=> 38 | string(5) "forex" 39 | ["short_name"]=> 40 | string(6) "EURUSD" 41 | ["pro_name"]=> 42 | string(9) "FX:EURUSD" 43 | ["pricescale"]=> 44 | int(100000) 45 | ["prev_close_price"]=> 46 | float(1.17914) 47 | ["original_name"]=> 48 | string(9) "FX:EURUSD" 49 | ["open_price"]=> 50 | float(1.17914) 51 | ["minmove2"]=> 52 | int(10) 53 | ["minmov"]=> 54 | int(1) 55 | ["lp"]=> 56 | float(1.18309) 57 | ["low_price"]=> 58 | float(1.17311) 59 | ["is_tradable"]=> 60 | bool(true) 61 | ["high_price"]=> 62 | float(1.18342) 63 | ["fractional"]=> 64 | bool(false) 65 | ["exchange"]=> 66 | string(4) "FXCM" 67 | ["description"]=> 68 | string(19) "Euro Fx/U.S. Dollar" 69 | ["current_session"]=> 70 | string(6) "market" 71 | ["chp"]=> 72 | float(0.33) 73 | ["ch"]=> 74 | float(0.00395) 75 | } 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /class.php: -------------------------------------------------------------------------------- 1 | class = $class; 19 | $this->login = $login; 20 | $this->password = $password; 21 | $this->resetWebSocket(); 22 | } 23 | 24 | private function generateSession() 25 | { 26 | return "qs_" . $this->generateRandomString(12); 27 | } 28 | 29 | function generateRandomString($length = 10) 30 | { 31 | $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 32 | $charactersLength = strlen($characters); 33 | $randomString = ''; 34 | for ($i = 0; $i < $length; $i++) { 35 | $randomString .= $characters[rand(0, $charactersLength - 1)]; 36 | } 37 | return $randomString; 38 | } 39 | 40 | private function sendMessage($func, $args) 41 | { 42 | $this->websocket->send($this->createMessage($func, $args)); 43 | } 44 | 45 | public function registerTicker($ticker) 46 | { 47 | if (in_array($ticker, $this->subscriptions)) { 48 | return; 49 | } 50 | $this->subscriptions[] = $ticker; 51 | $this->websocket->send($this->createMessage("quote_add_symbols", [$this->session, $ticker, ['flags' => ["force_permission"]]])); 52 | } 53 | 54 | public function unregisterTicker($ticker) 55 | { 56 | $index = array_search($ticker, $this->subscriptions); 57 | if ($index === false) { 58 | return; 59 | } 60 | unset($this->subscriptions[$index]); 61 | sort($this->subscriptions); 62 | } 63 | 64 | public function resetWebSocket() 65 | { 66 | $this->tickerData = []; 67 | $this->subscriptions = []; 68 | $this->session = $this->generateSession(); 69 | $this->sessionRegistered = false; 70 | if ($this->login and $this->password) { 71 | $wss = "wss://prodata.tradingview.com/socket.io/websocket"; 72 | } else { 73 | $wss = "wss://data.tradingview.com/socket.io/websocket"; 74 | } 75 | $this->websocket = new WebSocket\Client( 76 | $wss, 77 | [ 78 | 'timeout' => 60, // 1 minute time out 79 | 'headers' => [ 80 | 'Origin' => 'https://data.tradingview.com', 81 | ], 82 | ] 83 | ); 84 | while (true) { 85 | if ($this->closeSocket) { 86 | break; 87 | } 88 | try { 89 | $string = $this->websocket->receive(); 90 | $packets = $this->parseMessages($string); 91 | foreach ($packets as $packet) { 92 | if (is_array($packet) and $packet["~protocol~keepalive~"]) { 93 | $this->sendRawMessage("~h~" . $packet["~protocol~keepalive~"]); 94 | } elseif (isset($packet->session_id)) { 95 | if ($this->login and $this->password) { 96 | $request_headers = [ 97 | "accept: */*", 98 | "accept-encoding: gzip, deflate, br", 99 | "accept-language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,lt;q=0.6", 100 | "cache-control: no-cache", 101 | "content-type: application/x-www-form-urlencoded", 102 | "origin: https://www.tradingview.com", 103 | "pragma: no-cache", 104 | "referer: no-cache", 105 | "sec-fetch-dest: empty", 106 | "sec-fetch-mode: cors", 107 | "sec-fetch-site: same-origin", 108 | "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36", 109 | "x-language: en", 110 | "x-requested-with: XMLHttpRequest" 111 | ]; 112 | $ch = curl_init(); 113 | curl_setopt($ch, CURLOPT_POST, true); 114 | curl_setopt($ch, CURLOPT_URL, "https://www.tradingview.com/accounts/signin/"); 115 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 116 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 117 | curl_setopt($ch, CURLOPT_COOKIEFILE, __DIR__ . '/cookie.txt'); 118 | curl_setopt($ch, CURLOPT_COOKIEJAR, __DIR__ . '/cookie.txt'); 119 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 120 | curl_setopt($ch, CURLOPT_HTTPHEADER, $request_headers); 121 | curl_setopt($ch, CURLOPT_ENCODING, "gzip"); 122 | curl_setopt($ch, CURLOPT_POSTFIELDS, "feature_source=Header&username=$this->login&password=$this->password&remember=on"); 123 | $curl_exec = curl_exec($ch); 124 | curl_close($ch); 125 | $json = json_decode($curl_exec); 126 | $auth_token = $json->user->auth_token; 127 | $this->sendMessage("set_auth_token", [$auth_token]); 128 | } else { 129 | $this->sendMessage("set_auth_token", ["unauthorized_user_token"]); 130 | } 131 | $this->sendMessage("quote_create_session", [$this->session]); 132 | $this->sendMessage( 133 | "quote_set_fields", 134 | [ 135 | $this->session, 136 | "ch", 137 | "chp", 138 | "current_session", 139 | "description", 140 | "local_description", 141 | "language", 142 | "exchange", 143 | "fractional", 144 | "is_tradable", 145 | "lp", 146 | "minmov", 147 | "minmove2", 148 | "original_name", 149 | "pricescale", 150 | "pro_name", 151 | "short_name", 152 | "type", 153 | "update_mode", 154 | "volume", 155 | "ask", 156 | "bid", 157 | "fundamentals", 158 | "high_price", 159 | "is_tradable", 160 | "low_price", 161 | "open_price", 162 | "prev_close_price", 163 | "rch", 164 | "rchp", 165 | "rtc", 166 | "status", 167 | "basic_eps_net_income", 168 | "beta_1_year", 169 | "earnings_per_share_basic_ttm", 170 | "industry", 171 | "market_cap_basic", 172 | "price_earnings_ttm", 173 | "sector", 174 | "volume", 175 | "dividends_yield" 176 | ] 177 | ); 178 | $this->sessionRegistered = true; 179 | } elseif ($packet->m && $packet->m === "qsd" && isset($packet->p) && $packet->p[0] === $this->session) { 180 | $tticker = $packet->p[1]; 181 | $tickerName = $tticker->n; 182 | $tickerStatus = $tticker->s; 183 | $tickerUpdate = $tticker->v; 184 | foreach ($tickerUpdate as $key => $value) $this->tickerData[$tickerName][$key] = $value; 185 | } 186 | } 187 | call_user_func(array($this->class, 'readQuotes'), $this); 188 | } catch (\WebSocket\ConnectionException $e) {} 189 | } 190 | } 191 | 192 | private function sendRawMessage($message) 193 | { 194 | $this->websocket->send($this->prependHeader($message)); 195 | } 196 | 197 | // IO methods 198 | private function parseMessages($str) 199 | { 200 | $packets = []; 201 | $x = preg_split('/~m~(\d+)~m~/', $str); 202 | foreach ($x as $pack) { 203 | if (!strlen($pack)) continue; 204 | $pack = strrev($pack); 205 | $str = strpos($pack, "}"); 206 | $pack = substr($pack, $str, strlen($pack)); 207 | $pack = strrev($pack); 208 | if (strpos($pack, '~h~') !== false) { 209 | $packets[] = ["~protocol~keepalive~" => substr($pack, 3)]; 210 | } else { 211 | $packets[] = json_decode($pack); 212 | } 213 | } 214 | return $packets; 215 | } 216 | 217 | private function prependHeader($str) 218 | { 219 | return "~m~" . strlen($str) . "~m~" . $str; 220 | } 221 | 222 | private function createMessage($func, $paramList) 223 | { 224 | return $this->prependHeader($this->constructMessage($func, $paramList)); 225 | } 226 | 227 | private function constructMessage($func, $paramList) 228 | { 229 | return json_encode( 230 | [ 231 | 'm' => $func, 232 | 'p' => $paramList 233 | ] 234 | ); 235 | } 236 | } 237 | --------------------------------------------------------------------------------