Community. StockSharphttps://stocksharp.com/handlers/atom.ashx?category=community&page=1Copyright @ StockSharp Platform LLC 2010 - 20242024-03-29T10:56:26Zhttps://stocksharp.com/images/logo.pnghttps://stocksharp.com/topic/25640/Get Started with web3 Development Easily Based on Ethereum Using FMZ (2)2024-03-29T07:12:48Z2024-03-29T07:12:48ZFMZ Quanthttps://stocksharp.com/users/185929/info@stocksharp.com<b><span style="font-size:140%">Send ETH</span></b><br /><br />In the previous course chapters, we have learned how to configure private keys. How do we know the wallet address corresponding to this private key for the configured exchange object? On FMZ, you can use the ```exchange.IO("address")``` function to obtain the wallet address corresponding to the configured private key.<br /><br />The following content in this chapter uses Goerli testnet environment, so the node I am using is: ```https://goerli.infura.io/v3/*******```, and Infura assigns different node addresses for each registered user. Here the ```*******``` hides specific content.<br /><br />```javascript<br />function main() {<br /> let walletAddress = exchange.IO("address")<br /> Log("Testnet goerli wallet address:", walletAddress)<br />}<br />```<br /><br />After knowing your wallet address, you can use Ethereum's RPC method ```eth_getTransactionCount``` to query the transaction count of the wallet address. In Ethereum, this count is very common, and it's actually the ```nonce``` parameter that needs to be passed in during transfer operations. In Ethereum, nonce is a unique number used to ensure that each transaction is unique. It is an increasing number, and it will automatically increase every time a new transaction is sent. Therefore, when you send a transaction to a smart contract, you need to provide a nonce to ensure that the transaction is unique and in the correct order. We can find this information in some materials and documents:<br /><br />> <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAU8ivLAm8kDh6AhOTPtRnisN-vATyP1RRq2wgVwVIsKw" title="https://goethereumbook.org/en/
">https://goethereumbook.org/en/
</a><br /><a href="https://stocksharp.com/file/149802
" title="https://stocksharp.com/file/149802
">https://stocksharp.com/file/149802
</a><br /><br />Here, the ```PendingNonceAt``` function in the Ethereum library of Go language is actually calling the ```eth_getTransactionCount``` method. In previous courses, we have also learned how to call RPC methods. Here we use the ```exchange.IO("api", "eth", ...)``` function again.<br /><br />```javascript<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />function main() {<br /> let walletAddress = exchange.IO("address")<br /> Log("Testnet goerli wallet address:", walletAddress)<br /><br /> /**<br /> * eth_getTransactionCount<br /> * @param address - string - The address from which the transaction count to be checked.<br /> * @param blockNumber - string - The block number as a string in hexadecimal format or tags.<br /> * @returns The integer of the number of transactions sent from an address encoded as hexadecimal.<br /> */<br /> let nonce: string = exchange.IO("api", "eth", "eth_getTransactionCount", walletAddress, "pending")<br /> Log("wallet address:", walletAddress, "current nonce:", nonce, ", convert to decimal:", toAmount(nonce, 0))<br />}<br />```<br /><br />Before explaining the transfer operation, let's briefly understand some concepts. When transferring on Ethereum, a certain amount of ETH tokens will be consumed (as gas fees). The gas fee is determined by two parameters:<br /><br />- gasPrice<br /><br /> However, the gas fees on the Ethereum network always fluctuate according to market demand and the fees users are willing to pay, so writing a fixed gas fee in the code is sometimes not an ideal choice. We can use the ```eth_gasPrice``` method we learned before, which can obtain the average gas price.<br /><br />- gasLimit<br /><br /> A standard Ether transfer has a gas limit of 21,000 units.<br /><br />After understanding the concepts of ```nonce```, ```gasPrice```, and ```gasLimit```, you can test the transfer. FMZ provides a very simple and easy-to-use transfer function.<br /><br />```javascript<br />exchange.IO("api", "eth", "send", toAddress, toAmount)<br />```<br /><br />When it's used for transfers, the third parameter of ```exchange.IO``` is fixed as "send", the ```toAddress``` parameter is the address receiving ETH during the transfer, and ```toAmount``` is the amount of ETH transferred.<br /><br />The parameters ```nonce```, ```gasPrice```, and ```gasLimit``` can all use system default values automatically obtained on FMZ. They can also be specified: <br /><br />```javascript<br />exchange.IO("api", "eth", "send", toAddress, toAmount, {gasPrice: 5000000000, gasLimit: 21000, nonce: 100})<br />```<br /><br />Next, we will transfer a certain amount of ETH to a specific address on the goerli test network:<br /><br />```javascript<br />function toInnerAmount(s, decimals) {<br /> return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)<br />}<br /><br />function main() {<br /> let walletAddress = exchange.IO("address")<br /> Log("Testnet goerli wallet address:", walletAddress)<br /><br /> let ret = exchange.IO("api", "eth", "send", "0x4D75a08E870674E68cAE611f329A27f446A66813", toInnerAmount(0.01, 18))<br /> return ret // return Transaction Hash : 0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e<br />}<br />```<br /><br />Because the unit of Ethereum transfer amount is ```wei```, a custom function ```toInnerAmount``` needs to be used to process the value in ```wei``` units.<br /><br />Query Transaction Hash: ```0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e``` on ```https://etherscan.io/```.<br /><a href="https://stocksharp.com/file/149803
" title="https://stocksharp.com/file/149803
">https://stocksharp.com/file/149803
</a><br /><br />You can also write code to query transfer hash ```0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e```, using the ```eth_getTransactionReceipt``` method for queries.<br /><br />```javascript<br />function main() {<br /> let transHash = "0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e"<br /> let info = exchange.IO("api", "eth", "eth_getTransactionReceipt", transHash)<br /> return info<br />}<br />```<br /><br />Query result:<br /><br />```run<br />{<br /> "cumulativeGasUsed": "0x200850",<br /> "effectiveGasPrice": "0x1748774421",<br /> "transactionHash": "0xa6f9f51b00d8ae850b0f204380b59da98f4bbce34b813577d3d948f61de4734e",<br /> "type": "0x0",<br /> "blockHash": "0x6bdde8b0f0453ecd24eecf7c634d65306f05511e0e8f09f9ed3f59eee2d06ac7",<br /> "contractAddress": null,<br /> "blockNumber": "0x868a50",<br /> "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",<br /> "gasUsed": "0x5208",<br /> "to": "0x4d75a08e870674e68cae611f329a27f446a66813",<br /> "status": "0x1",<br /> "transactionIndex": "0x23",<br /> "from": "0x6b3f11d807809b0b1e5e3243df04a280d9f94bf4",<br /> "logs": []<br />}<br />```<br /><br />Description corresponding to each field:<br /><br />```desc<br />blockHash - The hash value of the block where the transaction is located.<br />blockNumber - The block number of the block where the transaction is located, encoded in hexadecimal.<br />contractAddress - If it's a contract creation, the address of the contract; otherwise null.<br />cumulativeGasUsed - The total gas used when executing this transaction in the block.<br />effectiveGasPrice - Total base fee plus tip per unit of gas.<br />from - Sender's address.<br />gasUsed - Gas used by this specific transaction.<br />logs - Array of log objects generated by this transaction.<br /> address - Address that generated this log.<br /> topics - Data array with 0 to 4 indexed log parameters, each with 32 bytes. In Solidity, first topic is event signature hash (e.g., Deposit(address,bytes32,uint256)), unless you declare an event using anonymous specifier.<br /> data - Non-indexed parameters for logs with length of 32 bytes.<br /> blockNumber - The block number of the block where this log is located.<br /> transactionHash - Transaction hash at time when log was created. Null if pending state.<br /> transactionIndex - Index position during creation. Null if pending state.<br /> blockHash - The hash value for containing block.<br /> logIndex - Hexadecimal-encoded integer index position within containing block. Null if pending state.<br /> removed - True if deleted due to chain reorganization; false for valid logs.<br />logsBloom - Bloom filter for retrieving related logs.<br />status - Hexadecimal-encoded value either being '1' (success) or '0' (failure).<br />to - Receiving party's address; null for contract creation transactions.<br />transactionHash - The hash value associated with given transaction.<br />transactionIndex - Hexadecimal-encoded index position within its respective containing-block.<br />type - Type value.<br />```<br /><br /><hr /><br /><b><span style="font-size:140%">Call Ethereum Smart Contract</span></b><br />In the chapter on "Reading Contract Information", we used a complete example to call the method of ENS contract deployed on Ethereum to obtain Vitalik Buterin's wallet address. These methods belong to ```Read``` methods, and calling these methods does not require ```gas``` (remember what we talked about gas before?). In this chapter, we will call some ```Write``` methods of smart contracts on Ethereum and pay for ```gas```. These operations will be verified by each node and miner in the entire network and change the blockchain state.<br /><br /><b>ERC20</b><br /><br />For the ERC20 contract (ERC20 token contract), FMZ platform lists the ABI of the ERC20 contract ABI as a common ABI built directly into the system, eliminating the step of registering the ABI. We have also learned about ABI in previous tutorials when we registered ENS contract's ABI before calling ENS contract methods.<br /><br />To better understand ABI, you can check it out before using it. Here is the ABI for ERC20 contracts:<br /><br />```javascript<br />[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]<br />```<br /><br />This section uses the ```Goerli``` testnet environment.<br /><br /><em>balanceOf</em><br /><br />Next, let's practice again how to call the ```Read``` method of a contract to read contract information, call the ```balanceOf``` method of ERC20 contract to query token balance, the balanceOf method has only one parameter, which is not named, but can be identified by its type as an address (i.e., the address of the token being queried). Since the returned data is not in units of one token, we also need the precision data of tokens for conversion. The precision of tokens can be obtained by using ```decimals``` method in ERC20 contracts. We will use Ethereum testnet ```goerli``` for testing. Please note that token contract addresses may vary on different chains.<br /><br />```javascript<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />function main() {<br /> let walletAddress = exchange.IO("address")<br /> <br /> // goerli WETH address <br /> let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"<br /> // goerli LINK address <br /> let linkAddress = "0x326C977E6efc84E512bB9C30f76E30c160eD06FB"<br /><br /> // Since it is an ERC20 contract, FMZ has built-in ABI registration, so there is no need to register ERC20 ABI here.<br /> let wethDecimals = exchange.IO("api", wethAddress, "decimals")<br /> let linkDecimals = exchange.IO("api", linkAddress, "decimals")<br /><br /> let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress)<br /> let linkBalance = exchange.IO("api", linkAddress, "balanceOf", walletAddress)<br /> Log("WETH precision:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals))<br /> Log("LINK precision:", linkDecimals, "linkBalance:", toAmount(linkBalance, linkDecimals))<br />}<br />```<br /><br />By running the above code, you can query the current wallet's WETH and LINK token balance:<br /><br />> WETH precision: 18 wethBalance: 0<br />> LINK precision: 18 linkBalance: 51.41374973681053<br /><br /><em>deposit</em><br />We can see that the balance of WETH tokens in the wallet is 0. Next, we will continue to interact with the ERC20 smart contract of WETH tokens, and this time we will call the Write type method: ```deposit```.<br />The function of the ```deposit``` method is simply to exchange a certain amount of ETH for WETH. It should be noted that there are no parameters for the ```deposit``` method (which can be observed by checking ABI). The ```payable``` attribute of this method is ```true```, so when calling it, you need to set the transferable ETH (payableAmount) quantity to specify the amount of ETH deposited.<br /><br />> Pay attention when calling smart contract methods:<br />> If the called method has a payable attribute, you need to add a transfer ETH value (the fourth parameter of the exchange.IO function) after the method name, which can be a numeric type or a string form of numeric value.<br /><br />```javascript<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />function toInnerAmount(s, decimals) {<br /> return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)<br />}<br /><br />function main() {<br /> let walletAddress = exchange.IO("address")<br /> <br /> // goerli WETH address <br /> let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"<br /><br /> // Since it is an ERC20 contract, FMZ has built-in ABI registration, so there is no need to register ERC20 ABI here.<br /> let wethDecimals = exchange.IO("api", wethAddress, "decimals")<br /><br /> let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress)<br /> Log("WETH precision:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals))<br /> let ethBalance = exchange.IO("api", "eth", "eth_getBalance", walletAddress, "latest")<br /> Log("ETH precision:", 18, "ethBalance:", toAmount(ethBalance, 18))<br /><br /> // Call the deposit method, since deposit is a method outside of the ERC20 standard, we need to register the ABI for this method here<br /> let abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]`<br /> exchange.IO("abi", wethAddress, abiWETH)<br /><br /> let payableAmount = toInnerAmount(0.01, 18)<br /> let ret = exchange.IO("api", wethAddress, "deposit", payableAmount)<br /> Log("Transaction Hash:", ret)<br />}<br />```<br /><br />> WETH precision: 18 wethBalance: 0<br />> ETH precision: 18 ethBalance: 0.14333094664072302<br />> Transaction Hash: 0xaf15b0b0e25a81eda583295e82b249e2d02e4eafebecc906470ccc2c89e23563<br /><a href="https://stocksharp.com/file/149804
" title="https://stocksharp.com/file/149804
">https://stocksharp.com/file/149804
</a><br /><br />Check the balance of WETH and ETH again:<br /><br />> WETH precision: 18 wethBalance: 0.01<br />> ETH precision: 18 ethBalance: 0.1333309358060905<br /><br />Before calling ```deposit```, ```WETH``` is 0 and ```ETH``` is 0.14333094664072302. After calling ```deposit```, ```WETH``` is 0.01 and ```ETH``` is 0.1333309358060905. It can be seen that it has exchanged 0.01```ETH``` for ```WETH``` successfully.<br /><br /><em>transfer</em><br />ERC20 tokens can also be transferred, using the ```transfer``` method to transfer 0.01 WETH tokens to the address ```0x4D75a08E870674E68cAE611f329A27f446A66813```. The ```transfer``` method has two parameters, the first parameter ```dst``` is the wallet address of the transfer recipient, and the second parameter ```wad``` is the transfer amount.<br /><br />```javascript<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />function toInnerAmount(s, decimals) {<br /> return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)<br />}<br /><br />function waitMined (tx) {<br /> for (var i = 0 ; i < 10 ; i++) {<br /> Sleep(5000)<br /> let info = exchange.IO("api", "eth", "eth_getTransactionReceipt", tx)<br /> if (info && info.gasUsed) {<br /> Log(info)<br /> return true<br /> }<br /> Log('Transaction not yet mined', tx)<br /> }<br /> return false <br />}<br /><br />function main() {<br /> let walletAddress = exchange.IO("address")<br /> <br /> // goerli WETH address <br /> let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"<br /><br /> // Since it is an ERC20 contract, FMZ has built-in ABI registration, there is no need to register ERC20 ABI here.<br /> let wethDecimals = exchange.IO("api", wethAddress, "decimals")<br /> let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress)<br /> Log("WETH precision:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals))<br /><br /> let dst = "0x4D75a08E870674E68cAE611f329A27f446A66813"<br /> let wad = toInnerAmount(0.01, wethDecimals)<br /> let tx = exchange.IO("api", wethAddress, "transfer", dst, wad) <br /> Log("Transaction Hash:", tx)<br /> waitMined(tx)<br /> <br /> wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress)<br /> Log("WETH precision:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals))<br />}<br />```<br /><br />In the above example, a custom function ```waitMined``` is used. In fact, we are not unfamiliar with the function of this function. Do you remember the ```eth_getTransactionReceipt``` method? The purpose of this waitMined custom function is to wait for the result of the passed Transaction Hash.<br /><br />For this WETH transfer, interested readers can also query the Transaction Hash ```0x2fafb62b548a1fffb0f3189429e3c5a4f57ddafb0acbc0678d8b3cf0a2f7c92c``` to view details (note that it is on testnet goerli).<br /><br /><em>withdraw</em><br /><br />This time we will exchange ```WETH``` back to ```ETH```, using the ```withdraw``` method, which has only one parameter ```wad```, simply put, it is used to set how much ETH to exchange back.<br /><br />```javascript<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />function toInnerAmount(s, decimals) {<br /> return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)<br />}<br /><br />function main() {<br /> let walletAddress = exchange.IO("address")<br /> <br /> // goerli WETH address <br /> let wethAddress = "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6"<br /><br /> // Since it is an ERC20 contract, FMZ has built-in ABI registration, there is no need to register ERC20 ABI here.<br /> let wethDecimals = exchange.IO("api", wethAddress, "decimals")<br /><br /> let wethBalance = exchange.IO("api", wethAddress, "balanceOf", walletAddress)<br /> Log("WETH precision:", wethDecimals, "wethBalance:", toAmount(wethBalance, wethDecimals))<br /> let ethBalance = exchange.IO("api", "eth", "eth_getBalance", walletAddress, "latest")<br /> Log("ETH precision:", 18, "ethBalance:", toAmount(ethBalance, 18))<br /><br /> let abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]`<br /> exchange.IO("abi", wethAddress, abiWETH)<br /><br /> let wad = toInnerAmount(0.01, 18)<br /> let ret = exchange.IO("api", wethAddress, "withdraw", wad)<br /> Log("Transaction Hash:", ret)<br />}<br />```<br /><br />> Transaction Hash: 0x446423c841451a8d04428a075b556eb5564186b09926da915f5da1c9837d2af4<br /><br />From the above code, it can be seen that it is basically the same as the previous example, except that the method called in the last step is changed to ```withdraw```, before calling:<br /><br />> WETH precision: 18 wethBalance: 0.01<br />> ETH precision: 18 ethBalance: 0.11322979983231546<br /><br />Query again:<br /><br />> WETH precision: 18 wethBalance: 0<br />> ETH precision: 18 ethBalance: 0.1231207156449464<br /><br />We can see that 0.01 ```WETH``` has been exchanged back to ```ETH```.<br /><br /><b>Uniswap V3</b><br /><br />```Uniswap V3``` is a decentralized transaction protocol built on the Ethereum blockchain, used to facilitate cryptocurrency trading and liquidity provision. It consists of a series of smart contracts, including core contracts, pool contracts, factory contracts, router contracts, etc. <br /><br />The following contracts are mainly used when querying the current exchange price and performing exchange operations using ```Uniswap```:<br /><br />- ```Router``` (Router contract)<br /> This is the contract used to execute transactions, allowing the user to perform a trade operation by specifying a trade path and parameters.<br />- ```Pool``` (Pool contract)<br /> Pool contracts are the key component in ```Uniswap``` for storing and managing the liquidity of a given asset pair. Each asset pair has a corresponding pool contract, which contains information about the funds, price range, fee settings, etc. provided by the liquidity provider. The pool contract is responsible for processing transactions, calculating the status of the pool, and ensuring the smooth execution of transactions.<br />- ```Factory``` (Factory contract)<br /> Factory contracts are the contracts used to create and manage ```Uniswap``` pool contracts. When users wish to create new asset pairs, they deploy new pool contracts by interacting with the factory contract. Factory contracts are responsible for coordinating the creation and initialization of pool contracts, enabling users to create new asset pairs dynamically.<br /><br />As we have learned in previous lessons, let's review how to register the ```ABI``` of smart contracts in strategy code on the FMZ Quant Trading Platform. The process of obtaining the ABI of smart contracts has been discussed in previous lessons and will not be repeated here. <br />Three ```Uniswap``` smart contract ```ABI```s need to be registered, using the Ethereum mainnet as an example:<br /><br />```javascript<br />var abiRoute = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]';<br />var abiPool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]'<br />var abiFactory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]'<br /><br />var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984"<br />var contractV3SwapRouterV2Address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"<br /><br />function main() {<br /> // ABI for registration of Uniswap factory contracts<br /> exchange.IO("abi", contractV3FactoryAddress, abiFactory)<br /> <br /> // Test the owner method of the Factory contract<br /> var owner = exchange.IO("api", contractV3FactoryAddress, "owner")<br /> Log("owner:", owner) // The return value should be: 0x1a9C8182C09F50C8318d769245beA52c32BE35BC<br /><br /> // Register ABI for Uniswap router contracts<br /> exchange.IO("abi", contractV3SwapRouterV2Address, abiRoute)<br /><br /> // Factory method for testing Uniswap V3 Router V2 contracts<br /> var factoryOfRouter = exchange.IO("api", contractV3SwapRouterV2Address, "factory")<br /> Log("factoryOfRouter:", factoryOfRouter) // The return value should be: 0x1F98431c8aD98523631AE4a59f267346ea31F984<br /><br /> // Get the pool address of the trading pair<br /> var tokenIn = {name : "DAI", address: "0x6b175474e89094c44da98b954eedeac495271d0f"}<br /> var tokenOut = {name : "USDT", address: "0xdac17f958d2ee523a2206206994597c13d831ec7"}<br /> var poolAddress = exchange.IO("api", contractV3FactoryAddress, "getPool", tokenIn.address, tokenOut.address, 3000)<br /> var pair = "tokenIn:" + tokenIn.name + ", tokenOut:" + tokenOut.name + ", fee:" + 3000<br /> Log("Use the getPool method of the factory contract to get", pair, "pool address:", poolAddress)<br /><br /> // Registration pool contract ABI<br /> exchange.IO("abi", poolAddress, abiPool)<br /><br /> // Test pool contract<br /> var factoryOfPool = exchange.IO("api", poolAddress, "factory")<br /> Log("factoryOfPool:", factoryOfPool) // The return value should be: 0x1F98431c8aD98523631AE4a59f267346ea31F984<br />}<br />```<br /><br />The above code registered the ABI of the factory contract, router contract and pool contract and did some tests. It can be observed that the ```factory``` method of both the router contract and the pool contract return the address ```0x1F98431c8aD98523631AE4a59f267346ea31F984```, which is the address of the Uniswap factory contract (variable ``` contractV3FactoryAddress``` in the code).<br /><br />```javascript<br />var contractV3FactoryAddress = "0x1F98431c8aD98523631AE4a59f267346ea31F984"<br />```<br /><br />After registering the ABI of the smart contract, next we look at the exchange operation using ```Uniswap V3``` first, and in the following we use the exchange of ```ERC20``` tokens (on the Ethereum mainnet) as a specific scenario.<br /><br />To be continued...https://stocksharp.com/topic/25639/Get Started with web3 Development Easily Based on Ethereum Using FMZ (1)2024-03-29T05:14:50Z2024-03-29T05:14:50ZFMZ Quanthttps://stocksharp.com/users/185929/info@stocksharp.com<b><span style="font-size:140%">EtherEaseWithFMZ Tutorial</span></b><br />> Get started with web3 development based on Ethereum using FMZ easily<br /><br />Ethereum is a smart contract platform based on blockchain technology, which provides a decentralized way to write and deploy smart contracts. Smart contracts are a special type of computer program that can automatically execute on the blockchain and implement various business logic without the need for trusting third parties.<br /><br />FMZ Quant Trading Platform (FMZ.COM) provides an easy-to-use API, allowing developers to interact more easily with the Ethereum blockchain and its ecosystem. It achieves access to decentralized exchanges (DEX), obtains on-chain data, sends transactions, and other functions.<br /><br />In this tutorial, the examples are written in ```JavaScript``` language, the testing environment uses both **Ethereum mainnet** and **Goerli testnet**. And you can also view the API interfaces and related descriptions, code examples used in the tutorial in FMZ platform's API documentation.<br /><br /><hr /><br /><b>FMZ Getting Started</b><br />Before learning to use the FMZ Quant Trading Platform, we need to familiarize ourselves with a few basic concepts:<br /><br /><em>1. FMZ Quant Trading Platform Architecture</em><br />After registering and logging in on the FMZ Quant Trading Platform official website (https://www.fmz.com), you can use various functions of the platform. The FMZ website is the management end of the whole system, and user-written programs run on the docker actually. The docker software program can be deployed on various devices, such as servers, computers, etc. When a user writes a program and creates a running instance on the FMZ website, the FMZ platform will communicate with the docker and start a program instance on it.<br /><br /><em>2. Docker</em><br />If you want to run a program instance, you must deploy a docker. The deployment of the docker is also very simple, and there are deployment tutorials on the platform. You can also use the 'One-click Deployment' provided by FMZ to deploy automatically on servers rented on behalf of FMZ.<br /><br />- Deploy docker on personal devices<br /><br />You can deploy and run the docker program on servers, personal computers and other devices, as long as the network is normal (need to be able to access the corresponding target, such as a certain exchange interface, node address, etc.). The main steps of deployment are:<br /><br /> 1. Log in or open the device where the docker program is to be deployed, such as **logging into a server** or **turning on a computer to enter the operating system**.<br /> 2. Download the corresponding version of the docker program (depending on the device operating system), download page: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAADHZKb-RfbDOdB_w3dJgQKtJmF1mN-qSlvrIMuHUnqFcA" title="https://www.fmz.com/m/add-node
">https://www.fmz.com/m/add-node
</a><br /><a href="https://stocksharp.com/file/149788
" title="https://stocksharp.com/file/149788
">https://stocksharp.com/file/149788
</a><br /> 3. What you downloaded is a compressed package, need to decompress.<br /> 4. Run the docker program, the docker program is an executable file called ```robot```. Configure the docker communication address, which is unique to each FMZ account, after logging in to FMZ, you can view your own address at ```https://www.fmz.com/m/add-node``` page (i.e., ```./robot -s node.fmz.com/xxxxx``` this string of addresses, where the content at ```xxxxx``` position is different for each FMZ account). Finally, you need to enter the password of your FMZ account. After configuring these settings, run the docker program.<br /><br />- Use FMZ platform's "One-Click Deployment" function<br /><br /> Add a docker page on the FMZ platform, address: ```https://www.fmz.com/m/add-node```<br /><a href="https://stocksharp.com/file/149789
" title="https://stocksharp.com/file/149789
">https://stocksharp.com/file/149789
</a><br /><em>3. Debugging Tool</em><br />FMZ Quant Trading Platform provides a free debugging tool that supports ```JavaScript```, ```TypeScript```, and the page is: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAADHZKb-RfbDOdB_w3dJgQKtljPQEcZ_t05nTZclK3sRzA" title="https://www.fmz.com/m/debug. ">https://www.fmz.com/m/debug. </a>Because creating instances to run is billed. During the initial learning period, you can use this debugging tool for testing and learning. Except for the maximum running time limit of 3 minutes, there is no difference between using the debugging tool and creating an instance to run.<br /><br />When using the ```TypeScript``` language, you need to write ```// @ts-check``` on the first line of code to switch to ```TypeScript``` mode; if not switched, the default is ```JavaScript``` language.<br /><br /><em>4. Platforms</em><br />On FMZ, "Platform" is a general concept. For CEX exchanges, it refers to a specific exchange account configuration. For web3, this exchange refers to a configuration information that includes node address and private key configuration.<br /><br />In the logged-in state of FMZ platform, at ```https://www.fmz.com/m/add-platform``` page, you can configure exchange information, where the exchange is a general concept.<br /><a href="https://stocksharp.com/file/149793
" title="https://stocksharp.com/file/149793
">https://stocksharp.com/file/149793
</a><br /><br />Select ```Web3```, configure RPC node address, configure private key, you can click the lower right corner "Sensitive information will be stored encrypted" to view the security mechanism.<br /><br />Nodes can be self-built nodes or nodes provided by node service providers. There are many node service providers, such as: [Infura](https://app.infura.io/login). After registration, you can view the node address of your own account. Both mainnet and testnet are available, which is quite convenient. Configure this node address in the ```Rpc Address``` control shown in the above figure. The label can be named by yourself to distinguish between configured exchange objects.<br /><a href="https://stocksharp.com/file/149792
" title="https://stocksharp.com/file/149792
">https://stocksharp.com/file/149792
</a><br /><br />In the picture, ```https://mainnet.infura.io/v3/xxxxxxxxxxxxx``` is the private Infura ETH mainnet RPC node address.<br /><br /><hr /><br /><b><span style="font-size:140%">Interact with Ethereum by using FMZ</span></b><br />After deploying the docker program and configuring the exchange object, you can use FMZ.COM's "Debugging Tool" for testing. Call Ethereum RPC methods and interact with Ethereum, in addition to the several RPC methods listed and introduced in this chapter, other RPC methods can be found by consulting materials, such as <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAZ4raNq6xr84KUx1fvz_vBeb-t1iwMMyvn0IqJb5nCZm44MQa1gJJuJ8C2wUz9P0M" title="https://www.quicknode.com/docs.
">https://www.quicknode.com/docs.
</a><br /><br />We list a few simple examples, starting from the basics. For various languages and tools, there are ways to access web3, as shown in the picture:<br /><a href="https://stocksharp.com/file/149790
" title="https://stocksharp.com/file/149790
">https://stocksharp.com/file/149790
</a><br /><br />On FMZ, RPC method calls are also encapsulated, and these functions are encapsulated in the FMZ API function ```exchange.IO```. The calling method is ```exchange.IO("api", "eth", ...)```. The first parameter is fixed to ```"api"```, the second parameter is fixed to ```"eth"```, and other parameters depend on the specific RPC method being called.<br /><br />For output information, we will use the ```Log``` function of the FMZ platform. The ```Log``` function can accept multiple parameters and then output them in the log area of the "Debug Tool" or "Bot" page on the FMZ platform. The "Debug Tool" page will be our main testing tool.<br /><br /><b>eth_getBalance</b><br />The ```eth_getBalance``` method of Ethereum is used to query the ETH balance of an address on Ethereum, and this method requires two parameters.<br /><br />- Address to be queried.<br />- Label, we usually use "latest".<br />Let's check the Ethereum founder ```Vitalik Buterin```'s ETH wallet address, the known address is: ```0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045```.<br /><br />```javascript<br />function main() {<br /> let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest")<br /> Log("ethBalance:", ethBalance)<br />}<br />```<br /><br />Already deployed the docker (in the picture: linux/amd64 ...) and configured the exchange object (in the picture: Web3 test), testing code in debugging tool:<br /><a href="https://stocksharp.com/file/149791
" title="https://stocksharp.com/file/149791
">https://stocksharp.com/file/149791
</a><br /><br />Click the "Execute"' button to run the code and display the results:<br /><br />> ethBalance: 0x117296558f185bbc4c6<br /><br />The ```log``` function prints out the ```ethBalance``` variable value as: ```0x117296558f185bbc4c6```, which is a string type. It is **the hexadecimal value of the ETH balance** in ```wei``` units, with ```1e18 wei``` being equal to 1```ETH```. Therefore, it needs to be converted to become a readable decimal ETH balance.<br /><br />Converting ```ethBalance``` into readable data:<br /><br />```javascript<br />function main() {<br /> let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest")<br /> Log("ethBalance:", ethBalance)<br /> <br /> // Converting ethBalance into readable data<br /> let vitalikEthBalance = parseInt(ethBalance.substring(2), 16) / 1e18<br /> Log("vitalikEthBalance:", vitalikEthBalance)<br />}<br />```<br /><br />Search on ```https://etherscan.io/```: <br /><a href="https://stocksharp.com/file/149796
" title="https://stocksharp.com/file/149796
">https://stocksharp.com/file/149796
</a><br /><br />However, due to the precision problem of the language itself, there will be deviations in this way of processing. Therefore, FMZ platform has built-in two functions for data processing:<br /><br />- BigInt : Convert hexadecimal string to BigInt object.<br />- BigDecimal : Convert numeric type objects into computable BigDecimal objects.<br /><br />Adjust the code again:<br /><br />```javascript<br />function main() {<br /> let ethBalance = exchange.IO("api", "eth", "eth_getBalance", "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "latest")<br /><br /> // The precision unit of ETH is 1e18<br /> let ethDecimal = 18<br /> Log("vitalikEthBalance:", Number((BigDecimal(BigInt(ethBalance)) / BigDecimal(Math.pow(10, ethDecimal))).toString()))<br />}<br />```<br /><br />> vitalikEthBalance: 5149.6244846875215<br /><br /><b>eth_chainId</b><br />```eth_chainId``` and ```net_version``` have similar purposes, so they are tested together. Both of these functions return the Id of the blockchain that the current RPC node is connected to, with the difference being that ```net_version``` returns a decimal Id and ```eth_chainId``` returns a hexadecimal Id.<br /><br />Network name corresponding to chainId<br /><br />```<br />1 - ethereum mainnet<br />2 - morden testnet (deprecated)<br />3 - ropsten testnet<br />4 - rinkeby testnet<br />5 - goerli testnet<br />11155111 - sepolia testnet<br />10 - optimism mainnet<br />69 - optimism kovan testnet<br />42 - kovan testnet<br />137 - matic/polygon mainnet<br />80001 - matic/polygon mumbai testnet<br />250 - fantom mainnet<br />100 - xdai mainnet<br />56 - bsc mainnet<br />```<br /><br />Test with the configured Ethereum testnet ```goerli``` node:<br /><br />```javascript<br />function main() {<br /> let netVersionId = exchange.IO("api", "eth", "net_version")<br /> let ethChainId = exchange.IO("api", "eth", "eth_chainId")<br /><br /> Log("netVersionId:", netVersionId)<br /> Log("ethChainId:", ethChainId, " , conversion:", parseInt(ethChainId.substring(2), 16))<br />}<br />```<br /><br /><b>eth_gasPrice</b><br /><br />Call the ```eth_gasPrice``` method to query the current ```gas price``` on the chain.<br /><br />```javascript<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />function main() {<br /> let gasPrice = exchange.IO("api", "eth", "eth_gasPrice")<br /> Log("gasPrice:", gasPrice, " , conversion:", toAmount(gasPrice, 0))<br />}<br />```<br />Here we write a function to convert the hexadecimal string into a readable numerical value: ```toAmount```. In addition, note that the unit of ```gasPrice``` is ```wei```, so pass the value 0 to the real parameter corresponding to the formal parameter ```decimals```.<br /><br /><b>eth_blockNumber</b><br /><br />```"eth_blockNumber``` is used to query the block height.<br /><br />```javascript<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />function main() {<br /> let blockNumber = exchange.IO("api", "eth", "eth_blockNumber")<br /> Log(toAmount(blockNumber, 0))<br />}<br />```<br /><br />Run in debugging tool:<br /><a href="https://stocksharp.com/file/149794
" title="https://stocksharp.com/file/149794
">https://stocksharp.com/file/149794
</a><br /><br />Search on ```https://etherscan.io/```:<br /><a href="https://stocksharp.com/file/149795
" title="https://stocksharp.com/file/149795
">https://stocksharp.com/file/149795
</a><br /><br /><b>eth_getBlockByNumber</b><br /><br />Query block information.<br /><br />```javascript<br />function main() {<br /> let blockNumber = exchange.IO("api", "eth", "eth_blockNumber") <br /> Log(blockNumber)<br /> let blockMsg = exchange.IO("api", "eth", "eth_getBlockByNumber", blockNumber, true)<br /> Log(typeof(blockMsg), blockMsg)<br /> <br /> // Due to the excessive content of Log output, it will be truncated automatically, so traverse each field of the returned block information and print them one by one<br /> for (let key in blockMsg) {<br /> Log("key:", key, ", val:", blockMsg[key])<br /> }<br />}<br />```<br /><br />Executing in the "Debugging Tool" can obtain the following information:<br /><a href="https://stocksharp.com/file/149797
" title="https://stocksharp.com/file/149797
">https://stocksharp.com/file/149797
</a><br /><br /><b><span style="font-size:140%">Read contract information</span></b><br /><br />Many smart contract applications run on Ethereum, and ```ENS``` is one of them. ```ENS```, or Ethereum Name Service, is a decentralized domain name resolution service based on the Ethereum blockchain.<br />Do you remember the example in the tutorial where we checked the balance of Ethereum founder Vitalik Buterin's wallet? One of Vitalik Buterin's wallet addresses is: ```0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045```. So how do we know the address? In fact, it can be queried through the ```ENS``` smart contract using an intuitive name ```vitalik.eth```.<br /><br />The following content in this chapter uses Ethereum mainnet environment, according to the ```ENS``` documentation, ```Hashing Names``` are required for querying Ethereum domain names. Use the following code to process ```vitalik.eth```.<br /><br />```javascript<br />function nameHash(name) {<br /> if (name == "") {<br /> return "0000000000000000000000000000000000000000000000000000000000000000"<br /> } else {<br /> let arr = name.split(".")<br /> let label = arr[0]<br /> <br /> arr.shift()<br /> let remainder = arr.join(".")<br /> return Encode("sha3.keccak256", "hex", "hex", nameHash(remainder) + Encode("sha3.keccak256", "raw", "hex", label))<br /> }<br />}<br />```<br />In the above code example, we saw another unfamiliar function ```Encode```. This function is an API function of the FMZ platform and is specifically used for encoding operations on the FMZ platform. The function supports multiple encoding methods and various hash algorithms.<br />```<br />Encode(algo, inputFormat, outputFormat, data, keyFormat, key string)<br />```<br /><br />According to the description in the ENS document, use the ```sha3.keccak256``` algorithm to process data.<br /><br />Call the ```nameHash``` function, for example: ```Log(nameHash("vitalik.eth"))```, you can get: ```ee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835```, and you need to add the "0x" prefix. ```0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835``` is used as the parameter of the ```resolver``` method in ENS smart contract.<br /><br />```javascript<br />let ensNode = "0x" + nameHash("vitalik.eth") // Prepare the parameters ensNode for calling the resolver method<br />```<br /><br />According to the ENS documentation, the contract address for ENS smart contract applications is: ```0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e```. Before calling the ```resolver``` method of the smart contract, we also need to prepare the ```ABI``` of the contract.<br /><br /><b><a href='https://stocksharp.com/file/149799/28e52c418abac1d9e03ba_png/' class='lightview' data-lightview-options="skin: 'mac'" data-lightview-group='mixed'><img src="https://stocksharp.com/file/149799/28e52c418abac1d9e03ba_png/?size=500x500" alt="28e52c418abac1d9e03ba.png" title="28e52c418abac1d9e03ba.png" /></a></b><br /><br />Upon learning this, you may ask: what is the ```ABI``` of a smart contract?<br /><br />```desc<br />ABI, or Application Binary Interface, is the interface standard for smart contracts to communicate with the external world.<br />The ABI of a smart contract defines the contract's function interfaces, parameter types, return values, and other information, as well as specifications for calling the contract and passing parameters.<br /><br />The ABI of a smart contract is usually stored in JSON format and contains the following information:<br /><br />Contract function interfaces: function names, parameter lists, return values, etc.<br />Function parameter types: such as uint256, bool, string etc.<br />Encoding methods for input and output parameters of functions: Smart contracts use an encoding method called Solidity ABI to encode input and output parameters of functions so that they can interact with Ethereum network.<br />In Ethereum network ,the ABI of a smart contract is used to call its functions. When you need to call a contract function, you need to provide the name of the function, its parameters, and bytecode encoded according to ABI encoding method.<br />Ethereum nodes will package this information into transactions and send them out on Ethereum network for execution.<br /><br />In Solidity language,the keyword 'interface' can be used define ABIs for smart contracts. Ethereum development tools like Remix IDE ,Truffle also provide editing & generation tools making it easier developers create & use ABIs.<br />```<br /><br />Extract the ```resolver``` method part from ENS's ABI, or you can use the complete ABI. You can query the contract's ABI on ```https://etherscan.io/``` or obtain the ABI through other channels (e.g., relevant project documentation).<br /><a href="https://stocksharp.com/file/149799
" title="https://stocksharp.com/file/149799
">https://stocksharp.com/file/149799
</a><br /><br />```javascript<br />let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]`<br />```<br /><br />Here we are going to learn a new invocation method on the FMZ platform, ```exchange.IO("abi", address, abiContent)```, which is used to register ABI. The ```address``` parameter is the smart contract address and the ```abiContent``` parameter is the corresponding smart contract ABI (string).<br /><br />```<br />let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]`<br />exchange.IO("abi", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", abiENS_resolver) // 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e is the address of the ENS smart contract deployed on the Ethereum mainnet<br />```<br /><br /><b>Methods for Calling Smart Contracts</b><br />Next, you can call the ```resolver``` method of the ENS smart contract, which returns the address of the ```ENS: Public Resolver``` contract.<br /><a href="https://stocksharp.com/file/149800
" title="https://stocksharp.com/file/149800
">https://stocksharp.com/file/149800
</a><br /><br />```<br />let resolverAddress = exchange.IO("api", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "resolver", ensNode)<br />```<br />Use the ```ENS: Public Resolver``` contract's ```addr``` method to obtain Vitalik Buterin's wallet address. To call the ```ENS: Public Resolver``` contract, you still need to register the ABI first. The ABI information for this smart contract can still be obtained from ```https://etherscan.io/```.<br /><br />```javascript<br />let abiENSPublicResolver = `[{"inputs":[{"internalType":"contract ENS","name":"_ens","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"coinType","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newAddress","type":"bytes"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"AuthorisationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"record","type":"bytes"}],"name":"DNSRecordChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"}],"name":"DNSRecordDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"DNSZoneCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"indexed":false,"internalType":"address","name":"implementer","type":"address"}],"name":"InterfaceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"x","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"string","name":"indexedKey","type":"string"},{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"TextChanged","type":"event"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"}],"name":"addr","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"authorisations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"clearDNSZone","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"contenthash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint16","name":"resource","type":"uint16"}],"name":"dnsRecord","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"}],"name":"hasDNSRecords","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentType","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"},{"internalType":"bytes","name":"a","type":"bytes"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"a","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"setAuthorisation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"hash","type":"bytes"}],"name":"setContenthash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setDNSRecords","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"internalType":"address","name":"implementer","type":"address"}],"name":"setInterface","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"name":"setText","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"}],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]`<br />exchange.IO("abi", resolverAddress, abiENSPublicResolver)<br />```<br /><a href="https://stocksharp.com/file/149801
" title="https://stocksharp.com/file/149801
">https://stocksharp.com/file/149801
</a><br />Finally, call the ```ENS: Public Resolver``` contract's ```addr``` method, with the parameter still being ```ensNode```.<br /><br />```javascript<br />let vitalikAddress = exchange.IO("api", resolverAddress, "addr", ensNode)<br />Log("vitalikAddress:", vitalikAddress)<br />```<br /><br />Log function output:<br /><br />```run<br />vitalikAddress: 0xd8da6bf26964af9d7eed9e03e53415d37aa96045<br />```<br /><br /><b>Call the Complete Code of ENS</b><br /><br />```javascript<br />function nameHash(name) {<br /> if (name == "") {<br /> return "0000000000000000000000000000000000000000000000000000000000000000"<br /> } else {<br /> let arr = name.split(".")<br /> let label = arr[0]<br /> <br /> arr.shift()<br /> let remainder = arr.join(".")<br /> return Encode("sha3.keccak256", "hex", "hex", nameHash(remainder) + Encode("sha3.keccak256", "raw", "hex", label))<br /> }<br />}<br /><br />function main() {<br /> // Calculate the name<br /> let ensNode = "0x" + nameHash("vitalik.eth") <br /><br /> // Register ENS contract<br /> let abiENS_resolver = `[{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"resolver","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"}]`<br /> exchange.IO("abi", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", abiENS_resolver)<br /> let resolverAddress = exchange.IO("api", "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "resolver", ensNode)<br /> <br /> // Register ENS Public Resolver contract<br /> let abiENSPublicResolver = `[{"inputs":[{"internalType":"contract ENS","name":"_ens","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"contentType","type":"uint256"}],"name":"ABIChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"address","name":"a","type":"address"}],"name":"AddrChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"coinType","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newAddress","type":"bytes"}],"name":"AddressChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"target","type":"address"},{"indexed":false,"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"AuthorisationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"hash","type":"bytes"}],"name":"ContenthashChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"},{"indexed":false,"internalType":"bytes","name":"record","type":"bytes"}],"name":"DNSRecordChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes","name":"name","type":"bytes"},{"indexed":false,"internalType":"uint16","name":"resource","type":"uint16"}],"name":"DNSRecordDeleted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"DNSZoneCleared","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"indexed":false,"internalType":"address","name":"implementer","type":"address"}],"name":"InterfaceChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"}],"name":"NameChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"x","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"PubkeyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"node","type":"bytes32"},{"indexed":true,"internalType":"string","name":"indexedKey","type":"string"},{"indexed":false,"internalType":"string","name":"key","type":"string"}],"name":"TextChanged","type":"event"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentTypes","type":"uint256"}],"name":"ABI","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"addr","outputs":[{"internalType":"address payable","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"}],"name":"addr","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"authorisations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"clearDNSZone","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"contenthash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"uint16","name":"resource","type":"uint16"}],"name":"dnsRecord","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"name","type":"bytes32"}],"name":"hasDNSRecords","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"interfaceImplementer","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"}],"name":"pubkey","outputs":[{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"contentType","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setABI","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"uint256","name":"coinType","type":"uint256"},{"internalType":"bytes","name":"a","type":"bytes"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"a","type":"address"}],"name":"setAddr","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"address","name":"target","type":"address"},{"internalType":"bool","name":"isAuthorised","type":"bool"}],"name":"setAuthorisation","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"hash","type":"bytes"}],"name":"setContenthash","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"setDNSRecords","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes4","name":"interfaceID","type":"bytes4"},{"internalType":"address","name":"implementer","type":"address"}],"name":"setInterface","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"name","type":"string"}],"name":"setName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"bytes32","name":"x","type":"bytes32"},{"internalType":"bytes32","name":"y","type":"bytes32"}],"name":"setPubkey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"name":"setText","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[{"internalType":"bytes32","name":"node","type":"bytes32"},{"internalType":"string","name":"key","type":"string"}],"name":"text","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]`<br /> exchange.IO("abi", resolverAddress, abiENSPublicResolver)<br /> let vitalikAddress = exchange.IO("api", resolverAddress, "addr", ensNode)<br /> Log("vitalikAddress:", vitalikAddress)<br />}<br />```<br />To be continued...https://stocksharp.com/topic/25638/Mastering Template Class Libraries: Crafting Custom K-Line Data Retrieval of Any Length2024-03-29T03:04:19Z2024-03-29T03:04:19ZFMZ Quanthttps://stocksharp.com/users/185929/info@stocksharp.comWhen designing trend strategies, it is often necessary to have a sufficient number of K-line bars for calculating indicators. The calculation of indicators relies on the data provided by the ```exchange.GetRecords()``` function in the FMZ platform API, which is a wrapper for the exchange's K-line interface. In the early design of cryptocurrency exchange APIs, there was no support for pagination in the K-line interface, and the exchange's K-line interface only provided a limited amount of data. As a result, some developers were unable to meet the requirements for calculating indicators with larger parameter values.<br /><br />The K-line interface of Binance's contract API supports pagination. In this article, we will use the Binance K-line API interface as an example to teach you how to implement pagination and specify the number of bars to retrieve using the FMZ platform template library.<br /><br /><b>K-line interface of Binance</b><br /><br />**K-line data**<br /><br />The opening time of each K-line in the ```GET /dapi/v1/klines``` endpoint can be considered as a unique ID.<br /><br />The weight of the request depends on the value of the "LIMIT" parameter.<br /><a href="https://stocksharp.com/file/149782
" title="https://stocksharp.com/file/149782
">https://stocksharp.com/file/149782
</a><br /><br />Parameters:<br /><a href="https://stocksharp.com/file/149783
" title="https://stocksharp.com/file/149783
">https://stocksharp.com/file/149783
</a><br /><br />First, we need to refer to the exchange's API documentation to understand the specific parameters of the K-line interface. We can see that when calling this K-line endpoint, we need to specify the type, the K-line period, the data range (start and end time), and the number of pages, etc.<br /><br />Since our design requirement is to query a specific number of K-line data, for example, to query the 1-hour K-line, 5000 bars of 1-hour K-line data from the current moment towards the past, it is evident that making a single API call to the exchange will not retrieve the desired data.<br /><br />To achieve this, we can implement pagination and divide the query into segments from the current moment towards a specific historical moment. Since we know the desired K-line data's period, we can easily calculate the start and end time for each segment. We can then query each segment in sequence towards the historical moment until we retrieve enough bars. The approach sounds simple, so let's go ahead and implement it!<br /><br /><b>Design "JavaScript version of paginated query K-line historical data template"</b><br /><br />Interface function for design templates: ```$.GetRecordsByLength(e, period, length)```.<br /><br />```<br />/**<br /> * desc: $.GetRecordsByLength is the interface function of this template library, this function is used to get the K-line data of the specified K-line length<br /> * @param {Object} e - exchange object<br /> * @param {Int} period - K-line period, in seconds<br /> * @param {Int} length - Specify the length of the acquired K-line data, which is related to the exchange interface limits<br /> * @returns {Array<Object>} - K-line data<br /> */<br />```<br /><br />Design the function ```$.GetRecordsByLength```, which is typically used in the initial stage of strategy execution to calculate indicators based on a long period of K-line data. Once this function is executed and sufficient data is obtained, only new K-line data needs to be updated. There is no need to call this function again to retrieve excessively long K-line data, as it would result in unnecessary API calls.<br /><br />Therefore, it is also necessary to design an interface for subsequent data updates: ```$.UpdataRecords(e, records, period)```.<br /><br />```<br />/**<br /> * desc: $.UpdataRecords is the interface function of this template library, this function is used to update the K-line data.<br /> * @param {Object} e - exchange object<br /> * @param {Array<Object>} records - K-line data sources that need to be updated<br /> * @param {Int} period - K-line period, needs to be the same as the K-line data period passed in the records parameter<br /> * @returns {Bool} - Whether the update was successful<br /> */<br />```<br /><br />The next step is to implement these interface functions.<br /><br />```<br />/**<br /> * desc: $.GetRecordsByLength is the interface function of this template library, this function is used to get the K-line data of the specified K-line length<br /> * @param {Object} e - exchange object<br /> * @param {Int} period - K-line period, in seconds<br /> * @param {Int} length - Specify the length of the acquired K-line data, which is related to the exchange interface limits<br /> * @returns {Array<Object>} - K-line data<br /> */<br />$.GetRecordsByLength = function(e, period, length) {<br /> if (!Number.isInteger(period) || !Number.isInteger(length)) {<br /> throw "params error!"<br /> }<br /><br /> var exchangeName = e.GetName()<br /> if (exchangeName == "Futures_Binance") {<br /> return getRecordsForFuturesBinance(e, period, length)<br /> } else {<br /> throw "not support!"<br /> }<br />}<br /><br />/**<br /> * desc: getRecordsForFuturesBinance, the specific implementation of the function to get K-line data for Binance Futures Exchange<br /> * @param {Object} e - exchange object<br /> * @param {Int} period - K-line period, in seconds<br /> * @param {Int} length - Specify the length of the acquired K-line data, which is related to the exchange interface limits<br /> * @returns {Array<Object>} - K-line data<br /> */<br />function getRecordsForFuturesBinance(e, period, length) {<br /> var contractType = e.GetContractType()<br /> var currency = e.GetCurrency()<br /> var strPeriod = String(period)<br /><br /> var symbols = currency.split("_")<br /> var baseCurrency = ""<br /> var quoteCurrency = ""<br /> if (symbols.length == 2) {<br /> baseCurrency = symbols[0]<br /> quoteCurrency = symbols[1]<br /> } else {<br /> throw "currency error!"<br /> }<br /><br /> var realCt = e.SetContractType(contractType)["instrument"]<br /> if (!realCt) {<br /> throw "realCt error"<br /> }<br /> <br /> // m -> minute; h -> hour; d -> day; w -> week; M -> month<br /> var periodMap = {}<br /> periodMap[(60).toString()] = "1m"<br /> periodMap[(60 * 3).toString()] = "3m"<br /> periodMap[(60 * 5).toString()] = "5m"<br /> periodMap[(60 * 15).toString()] = "15m"<br /> periodMap[(60 * 30).toString()] = "30m"<br /> periodMap[(60 * 60).toString()] = "1h"<br /> periodMap[(60 * 60 * 2).toString()] = "2h"<br /> periodMap[(60 * 60 * 4).toString()] = "4h"<br /> periodMap[(60 * 60 * 6).toString()] = "6h"<br /> periodMap[(60 * 60 * 8).toString()] = "8h"<br /> periodMap[(60 * 60 * 12).toString()] = "12h"<br /> periodMap[(60 * 60 * 24).toString()] = "1d"<br /> periodMap[(60 * 60 * 24 * 3).toString()] = "3d"<br /> periodMap[(60 * 60 * 24 * 7).toString()] = "1w"<br /> periodMap[(60 * 60 * 24 * 30).toString()] = "1M"<br /> <br /> var records = []<br /> var url = ""<br /> if (quoteCurrency == "USDT") {<br /> // GET <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAACnpKUNUrsvTUe50LbpAqE35ug3-ukZJm3Y63CzGHamkA" title="https://fapi.binance.com">https://fapi.binance.com</a> /fapi/v1/klines symbol , interval , startTime , endTime , limit <br /> // limit maximum value:1500<br /><br /> url = "https://fapi.binance.com/fapi/v1/klines"<br /> } else if (quoteCurrency == "USD") {<br /> // GET <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAADNUvoAL0LDgmmOE5doywpWHwnZvqhTvgsW77wJdrXSag" title="https://dapi.binance.com">https://dapi.binance.com</a> /dapi/v1/klines symbol , interval , startTime , endTime , limit<br /> // The difference between startTime and endTime can be up to 200 days.<br /> // limit maximum value:1500<br /><br /> url = "https://dapi.binance.com/dapi/v1/klines"<br /> } else {<br /> throw "not support!"<br /> }<br /><br /> var maxLimit = 1500<br /> var interval = periodMap[strPeriod]<br /> if (typeof(interval) !== "string") {<br /> throw "period error!"<br /> }<br /><br /> var symbol = realCt<br /> var currentTS = new Date().getTime()<br /><br /> while (true) {<br /> // Calculate limit<br /> var limit = Math.min(maxLimit, length - records.length)<br /> var barPeriodMillis = period * 1000<br /> var rangeMillis = barPeriodMillis * limit<br /> var twoHundredDaysMillis = 200 * 60 * 60 * 24 * 1000<br /> <br /> if (rangeMillis > twoHundredDaysMillis) {<br /> limit = Math.floor(twoHundredDaysMillis / barPeriodMillis)<br /> rangeMillis = barPeriodMillis * limit<br /> }<br /><br /> var query = `symbol=${symbol}&interval=${interval}&endTime=${currentTS}&limit=${limit}`<br /> var retHttpQuery = HttpQuery(url + "?" + query)<br /> <br /> var ret = null <br /> try {<br /> ret = JSON.parse(retHttpQuery)<br /> } catch(e) {<br /> Log(e)<br /> }<br /> <br /> if (!ret || !Array.isArray(ret)) {<br /> return null<br /> }<br /><br /> // When the data cannot be searched because it is beyond the searchable range of the exchange<br /> if (ret.length == 0 || currentTS <= 0) {<br /> break<br /> }<br /><br /> for (var i = ret.length - 1; i >= 0; i--) {<br /> var ele = ret[i]<br /> var bar = {<br /> Time : parseInt(ele[0]),<br /> Open : parseFloat(ele[1]),<br /> High : parseFloat(ele[2]),<br /> Low : parseFloat(ele[3]), <br /> Close : parseFloat(ele[4]),<br /> Volume : parseFloat(ele[5])<br /> }<br /><br /> records.unshift(bar)<br /> }<br /><br /> if (records.length >= length) {<br /> break<br /> }<br /><br /> currentTS -= rangeMillis<br /> Sleep(1000)<br /> }<br /><br /> return records<br />}<br /><br />/**<br /> * desc: $.UpdataRecords is the interface function of this template library, this function is used to update the K-line data.<br /> * @param {Object} e - exchange object<br /> * @param {Array<Object>} records - K-line data sources that need to be updated<br /> * @param {Int} period - K-line period, needs to be the same as the K-line data period passed in the records parameter<br /> * @returns {Bool} - Whether the update was successful<br /> */<br />$.UpdataRecords = function(e, records, period) {<br /> var r = e.GetRecords(period)<br /> if (!r) {<br /> return false <br /> }<br /><br /> for (var i = 0; i < r.length; i++) {<br /> if (r[i].Time > records[records.length - 1].Time) {<br /> // Add a new Bar<br /> records.push(r[i])<br /> // Update the previous Bar<br /> if (records.length - 2 >= 0 && i - 1 >= 0 && records[records.length - 2].Time == r[i - 1].Time) {<br /> records[records.length - 2] = r[i - 1]<br /> } <br /> } else if (r[i].Time == records[records.length - 1].Time) {<br /> // Update Bar<br /> records[records.length - 1] = r[i]<br /> }<br /> }<br /> return true<br />}<br />```<br /><br />In the template, we have only implemented support for the Binance futures contract K-line interface, i.e., the ```getRecordsForFuturesBinance``` function. It can also be extended to support K-line interfaces of other cryptocurrency exchanges.<br /><br /><b>Test Session</b><br /><br />As you can see, the code for implementing these functionalities in the template is not extensive, totaling less than 200 lines. After writing the template code, testing is crucial and should not be overlooked. Moreover, for data retrieval like this, it is important to conduct thorough testing.<br /><br />To test it, you need to copy both the "JavaScript Version of Pagination Query K-Line Historical Data Template" and the "Plot Library" templates to your strategy library (which can be found in the [Strategy Square](https://www.fmz.com/square) ). Then, create a new strategy and select these two templates.<br /><a href="https://stocksharp.com/file/149781
" title="https://stocksharp.com/file/149781
">https://stocksharp.com/file/149781
</a><br /><a href="https://stocksharp.com/file/149776
" title="https://stocksharp.com/file/149776
">https://stocksharp.com/file/149776
</a><br /><br />The "Plot Library" is used, because we need to draw the obtained K-line data for observation.<br /><br />```<br />function main() {<br /> LogReset(1)<br /> var testPeriod = PERIOD_M5<br /> Log("Current exchanges tested:", exchange.GetName())<br /><br /> // If futures, you need to set up a contract<br /> exchange.SetContractType("swap")<br /><br /> // Get K-line data of specified length using $.GetRecordsByLength<br /> var r = $.GetRecordsByLength(exchange, testPeriod, 8000)<br /> Log(r)<br /><br /> // Use the Plot test for easy observation<br /> $.PlotRecords(r, "k")<br /><br /> // Test data<br /> var diffTime = r[1].Time - r[0].Time <br /> Log("diffTime:", diffTime, " ms")<br /> for (var i = 0; i < r.length; i++) {<br /> for (var j = 0; j < r.length; j++) {<br /> // Check the repeat bar<br /> if (i != j && r[i].Time == r[j].Time) {<br /> Log(r[i].Time, i, r[j].Time, j)<br /> throw "With duplicate Bar"<br /> }<br /> }<br /> <br /> // Check Bar continuity<br /> if (i < r.length - 1) { <br /> if (r[i + 1].Time - r[i].Time != diffTime) {<br /> Log("i:", i, ", diff:", r[i + 1].Time - r[i].Time, ", r[i].Time:", r[i].Time, ", r[i + 1].Time:", r[i + 1].Time)<br /> throw "Bar discontinuity"<br /> } <br /> }<br /> }<br /> Log("Test passed")<br /><br /> Log("The length of the data returned by the $.GetRecordsByLength function:", r.length)<br /><br /> // Update data<br /> while (true) {<br /> $.UpdataRecords(exchange, r, testPeriod)<br /> LogStatus(_D(), "r.length:", r.length)<br /> $.PlotRecords(r, "k")<br /> Sleep(5000)<br /> }<br />}<br />```<br /><br />Here, we use the line ```var testPeriod = PERIOD_M5``` to set the 5-minute K-line period and specify to retrieve 8000 bars. Then, we can perform a plot test on the long K-line data returned by the ```var r = $.GetRecordsByLength(exchange, testPeriod, 8000)``` interface.<br /><br />```<br /> // Use the plot test for easy observation<br /> $.PlotRecords(r, "k")<br />```<br /><br />The next test for the long K-line data is:<br /><br />```<br /> // Test data<br /> var diffTime = r[1].Time - r[0].Time <br /> Log("diffTime:", diffTime, " ms")<br /> for (var i = 0; i < r.length; i++) {<br /> for (var j = 0; j < r.length; j++) {<br /> // Check the repeat Bar<br /> if (i != j && r[i].Time == r[j].Time) {<br /> Log(r[i].Time, i, r[j].Time, j)<br /> throw "With duplicate Bar"<br /> }<br /> }<br /> <br /> // Check Bar continuity<br /> if (i < r.length - 1) { <br /> if (r[i + 1].Time - r[i].Time != diffTime) {<br /> Log("i:", i, ", diff:", r[i + 1].Time - r[i].Time, ", r[i].Time:", r[i].Time, ", r[i + 1].Time:", r[i + 1].Time)<br /> throw "Bar discontinuity"<br /> } <br /> }<br /> }<br /> Log("Test passed")<br />```<br /><br />1. Check if there are any duplicate bars in the K-line data.<br />2. Check the coherence of the K-line data (whether the timestamp difference between adjacent bars is equal).<br /><br />After passing these checks, verify if the interface used to update the K-line data, ```$.UpdateRecords(exchange, r, testPeriod)```, is functioning correctly.<br /><br />```<br /> // Update data<br /> while (true) {<br /> $.UpdataRecords(exchange, r, testPeriod)<br /> LogStatus(_D(), "r.length:", r.length)<br /> $.PlotRecords(r, "k")<br /> Sleep(5000)<br /> }<br />```<br /><br />This code will continuously output K-line data on the strategy chart during live trading, allowing us to check if the K-line data updates and additions are functioning correctly.<br /><a href="https://stocksharp.com/file/149777
" title="https://stocksharp.com/file/149777
">https://stocksharp.com/file/149777
</a><br /><a href="https://stocksharp.com/file/149779
" title="https://stocksharp.com/file/149779
">https://stocksharp.com/file/149779
</a><br /><br />Using the daily K-line data, we set it to retrieve 8000 bars (knowing that there is no market data available for 8000 days ago). This serves as a brute force test:<br /><a href="https://stocksharp.com/file/149780
" title="https://stocksharp.com/file/149780
">https://stocksharp.com/file/149780
</a><br /><br />Seeing that there are only 1309 daily K-lines, compare the data on the exchange charts:<br /><a href="https://stocksharp.com/file/149784
" title="https://stocksharp.com/file/149784
">https://stocksharp.com/file/149784
</a><br /><a href="https://stocksharp.com/file/149778
" title="https://stocksharp.com/file/149778
">https://stocksharp.com/file/149778
</a><br /><br />You can see that the data also match.<br /><br /><b>END</b><br /><br />Template address: ["JavaScript Version of Pagination Query K-Line Historical Data Template"](https://www.fmz.com/strategy/418803)<br />Template address: ["Plot Library"](https://www.fmz.com/strategy/27293)<br /><br />The above template and strategy code are only for teaching and learning use, please optimize and modify according to the specific needs of the live trading.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y6_A4aDhmyjxBhqKsCugSIlTWxiO7bgFlKZoLPtdmZYtkieu2Gu_qK0ZRASROOEKE4Q2KSZHRVifxSQW10_eOV-RznDsI3EbXrh1sOwa3fpqiABn93-IBrZiJ3HHyg4NFNw" title="https://blog.mathquant.com/2023/06/30/teach-you-to-design-template-class-library-to-get-k-line-data-of-specified-length.html">https://blog.mathquant.c...of-specified-length.html</a>https://stocksharp.com/topic/25637/Harnessing the Power of "__Thread" Function in JavaScript: Revolutionizing Strategy Design2024-03-29T00:57:13Z2024-03-29T00:57:13ZFMZ Quanthttps://stocksharp.com/users/185929/info@stocksharp.comIn the initial design of FMZ strategy, if asynchronous concurrent operations are required, the ```exchange.Go()``` function can only be used to achieve concurrent execution of FMZ encapsulated interface, and it is not possible to concurrently execute some custom operations (functions). Although this design greatly improves the efficiency of the strategy program, students who have experience in concurrent design in native programming languages often feel very uncomfortable.<br /><br />Even new students who use FMZ for introductory quantitative trading may not understand the use of the ```exchange.Go()``` function. Using ```exchange.Go()``` still appears to be executing statements one by one in sequentially executed code. In this article, we will explore the use of the newly added concurrent thread functionality in the FMZ platform: the ```__Thread()``` and other related functions, as well as the asynchronous design of strategy programs.<br /><br /><b>1. Simple concurrent design</b><br /><br />If we want the main thread of the strategy to run concurrently with a sub-thread executing a custom function we have written, we can use a design similar to the following code. In the strategy code, define a custom function ```GetTickerAsync()``` and write the specific functionality of this function. This function executes an infinite loop and continuously calls the FMZ API interface ```GetTicker()``` to retrieve market data.<br /><br />Then, use the statement ```__threadSetData(0, "ticker", t)``` to write data to the main thread. The data name is ```ticker``` and the data value is ```t```, which is the return value of ```GetTicker()```.<br /><br />```<br />__threadSetData(0, "ticker", t)<br />```<br /><br />After designing the custom function for concurrent thread execution, we can write the code in the ```main()``` function. At the beginning of the ```main()``` function, we use:<br /><br />```<br />__Thread(GetTickerAsync, 0) // GetTickerAsync is a custom function that needs to be executed concurrently, and 0 is the parameter that is passed to the GetTickerAsync function.<br />```<br /><br />Create a concurrent thread that starts executing the ```GetTickerAsync()``` function. Then, the ```main()``` function starts executing its own ```while``` loop, in which it receives the data updated by the ```GetTickerAsync()``` function and prints it:<br /><br />```<br />var t = __threadGetData(0, "ticker")<br />Log(t)<br />```<br />Complete code example:<br /><br />```<br />function GetTickerAsync(index) {<br /> while (true) {<br /> var t = exchanges[index].GetTicker()<br /> __threadSetData(0, "ticker", t)<br /> Sleep(500)<br /> }<br />}<br /><br />function main() {<br /> __Thread(GetTickerAsync, 0)<br /><br /> while(true) {<br /> var t = __threadGetData(0, "ticker")<br /> Log(t)<br /> Sleep(1000)<br /> }<br />}<br />```<br />Live trading test:<br /><a href="https://stocksharp.com/file/149772
" title="https://stocksharp.com/file/149772
">https://stocksharp.com/file/149772
</a><br /><br />This is one of the simplest application designs, so let's look at some other requirement designs.<br /><br /><b>2. Concurrent order placement design</b><br /><br />We can design a function to create 10 threads simultaneously, each executing an order placement function. In the ```main()``` function, we can design a ```while``` loop to detect strategy interaction commands. When we receive the interaction command ```placeMultipleOrders```, we call the concurrent order placement function ```testPlaceMultipleOrders()```.<br /><br />```<br />if (cmd == "placeMultipleOrders") {<br /> // ...<br />}<br />```<br /><br />Add strategy interaction design on the strategy editing page by adding a button with the command: placeMultipleOrders.<br /><a href="https://stocksharp.com/file/149771
" title="https://stocksharp.com/file/149771
">https://stocksharp.com/file/149771
</a><br /><br />Complete code example:<br /><br />```<br />function placeOrder(exIndex, type, price, amount) {<br /> var id = null <br /> if (type == "Buy") {<br /> id = exchanges[exIndex].Buy(price, amount)<br /> } else if (type == "Sell") {<br /> id = exchanges[exIndex].Sell(price, amount)<br /> } else {<br /> throw "type error! type:" + type<br /> }<br />}<br /><br />function testPlaceMultipleOrders(index, beginPrice, endPrice, step, type, amount) {<br /> Log("beginPrice:", beginPrice, ", endPrice:", endPrice, ", step:", step, ", type:", type, ", amount:", amount)<br /> var tids = []<br /> for (var p = beginPrice; p <= endPrice; p += step) {<br /> var tid = __Thread(placeOrder, index, type, p, amount)<br /> tids.push(tid)<br /> Sleep(10)<br /> }<br /> Sleep(1000)<br /> for (var i = 0; i < tids.length; i++) {<br /> __threadTerminate(tids[i])<br /> }<br />}<br /><br />function main() {<br /> while(true) {<br /> LogStatus(_D())<br /> var cmd = GetCommand()<br /> if (cmd) {<br /> if (cmd == "placeMultipleOrders") {<br /> var t = _C(exchange.GetTicker)<br /> var beginPrice = t.Last * 0.8<br /> var endPrice = t.Last * 0.9<br /> var step = t.Last * 0.01<br /> testPlaceMultipleOrders(0, beginPrice, endPrice, step, "Buy", 0.01)<br /> var orders = exchange.GetOrders()<br /> for (var i = 0; i < orders.length; i++) {<br /> Log(orders[i])<br /> }<br /> }<br /> }<br /> Sleep(1000)<br /> }<br />}<br />```<br /><br />- The test adopts pending orders, incrementing from 80% to 90% of the current price, in a simulated trading environment. Click the interaction button to trigger the test order placement.<br /><br />After clicking the "placeMultipleOrders" button, a message is prompted: The placeMultipleOrders command was sent successfully, please wait for a response from the live trading!<br /><br />- Strategy log displays concurrent order placement operations:<br /><a href="https://stocksharp.com/file/149770
" title="https://stocksharp.com/file/149770
">https://stocksharp.com/file/149770
</a><br /><br /><b>3. Create a WebSocket connection in a concurrent thread execution function</b><br /><br />This requirement was raised by an FMZ user who wants a simple example demonstrating how to use a **WebSocket** connection in concurrent threads and how to pass data to the ```main()``` function in the main thread.<br /><br />Actually, it's quite simple and similar to creating concurrent threads in the previous examples. The only difference is that we use the ```__threadPeekMessage()``` and ```__threadPostMessage()``` functions for inter-thread communication. Taking the WebSocket API call for the Binance exchange as an example, we also need to handle the closing operation of the WebSocket connection. The following example demonstrates how to notify a concurrent thread to stop.<br /><br />Complete code example:<br /><br />```<br />var tid = null <br /><br />function createWS() {<br /> // wss://stream.binance.com:9443/ws/<streamName> , <symbol>@ticker<br /> <br /> var stream = "wss://stream.binance.com:9443/ws/btcusdt@ticker" <br /> var ws = Dial(stream)<br /> Log("Create a WS connection:", stream)<br /> <br /> while (true) {<br /> var data = ws.read()<br /> if (data) { <br /> __threadPostMessage(0, data)<br /> }<br /> Log("receiving data pushed by the WS link, data:", data)<br /> <br /> // __threadPeekMessage timeout parameter set to -1, no blocking<br /> var msg = __threadPeekMessage(-1)<br /> if (msg) {<br /> if (msg == "stop") {<br /> Log("Concurrent Thread Id:", __threadId(), "Received stop command")<br /> break<br /> }<br /> }<br /> }<br /><br /> Log("Concurrent threads finish execution, close ws connection")<br /> ws.close()<br />}<br /><br />function main() {<br /> tid = __Thread(createWS)<br /> Log("Create concurrent threads, thread Id:", tid)<br /><br /> while(true) {<br /> // __threadPeekMessage's timeout parameter is set to 0, blocking for data<br /> var data = __threadPeekMessage(0)<br /> Log("Received from concurrent thread", ", Id:", tid, ", the data sent, data:", data, "#FF0000")<br /> <br /> var tbl = {<br /> type : "table", <br /> title : "<symbol>@ticker channel push message",<br /> cols : ["Event Type", "Event Time", "Trading Pairs", "24 Hour Price Change", "24 Hour Price Change %", "Average Price", "Last Traded Price", "Volume in 24 Hours", "Turnover in 24 Hours"],<br /> rows : []<br /> }<br /><br /> try {<br /> data = JSON.parse(data)<br /> tbl.rows.push([data.e, _D(data.E), data.s, data.p, data.P, data.w, data.c, data.v, data.q])<br /> } catch (e) {<br /> Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message)<br /> }<br /> LogStatus(_D(), "\n`" + JSON.stringify(tbl) + "`")<br /> }<br />}<br /><br />function onexit() {<br /> Log("Finalize function, send a stop command to the concurrent thread with ID ", tid,"")<br /> __threadPostMessage(tid, "stop")<br /> Log("Wait for the concurrent thread with ID ", tid, " to stop")<br /> __threadJoin(tid)<br /> Log("Finalize function execution completed")<br />}<br />```<br /><br />During live trading testing, we can see that the ```main()``` function continuously receives market data from WebSocket connections created by concurrent threads.<br /><br />When stopping the live trading strategy, the finalize function will start working.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y617vF53pddqvVtFdQtpH2PQSabBGD9uZFmW0dEaTL4C4X9exR4I1YfxV5FN6lnVg61qA0EEB5iGC7C63GaycW_kAa3bMZc_LTaxnZoPhWPNu" title="https://blog.mathquant.com/2023/07/07/application-of-the-__thread-function-in-javascript-strategy-design.html">https://blog.mathquant.c...ipt-strategy-design.html</a>https://stocksharp.com/topic/25630/Mastering Uniswap V3: Advanced Strategies for Exchange Pool Liquidity (Part 1)2024-03-26T09:17:00Z2024-03-26T09:17:00ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.com<b>NonfungiblePositionManager Contract in Uniswap V3</b><br /><br />When we add liquidity to the Uniswap V3 liquidity pool (trading pair pool), the NonfungiblePositionManager contract returns a minted NFT to us as proof of the liquidity addition.<br /><br />The first step is to use the router contract to get the address of the corresponding NonfungiblePositionManager contract, and then use the balanceOf method of the NonfungiblePositionManager contract to get the number of position NFTs for the specified wallet address.<br /><br />Then use the tokenOfOwnerByIndex method to get the tokenId of these position NFTs, with these tokenId, you can use the positions method to continue to query the specific details of these positions.<br /><br />The following test code:<br /><br />```<br />// Uniswap ABI<br />const ABI_UniswapV3Router = `[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]`<br />const ABI_NonfungiblePositionManager = `[{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"},{"internalType":"address","name":"_tokenDescriptor_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Collect","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"DecreaseLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"IncreaseLiquidity","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint128","name":"amount0Max","type":"uint128"},{"internalType":"uint128","name":"amount1Max","type":"uint128"}],"internalType":"struct INonfungiblePositionManager.CollectParams","name":"params","type":"tuple"}],"name":"collect","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"}],"name":"createAndInitializePoolIfNecessary","outputs":[{"internalType":"address","name":"pool","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct INonfungiblePositionManager.DecreaseLiquidityParams","name":"params","type":"tuple"}],"name":"decreaseLiquidity","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Desired","type":"uint256"},{"internalType":"uint256","name":"amount1Desired","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct INonfungiblePositionManager.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Desired","type":"uint256"},{"internalType":"uint256","name":"amount1Desired","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct INonfungiblePositionManager.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"positions","outputs":[{"internalType":"uint96","name":"nonce","type":"uint96"},{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"},{"internalType":"uint128","name":"tokensOwed0","type":"uint128"},{"internalType":"uint128","name":"tokensOwed1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0Owed","type":"uint256"},{"internalType":"uint256","name":"amount1Owed","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"uniswapV3MintCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]`<br />const ABI_Pool = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"Collect\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"name\":\"CollectProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"paid1\",\"type\":\"uint256\"}],\"name\":\"Flash\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextOld\",\"type\":\"uint16\"},{\"indexed\":false,\"internalType\":\"uint16\",\"name\":\"observationCardinalityNextNew\",\"type\":\"uint16\"}],\"name\":\"IncreaseObservationCardinalityNext\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Initialize\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1Old\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol0New\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"feeProtocol1New\",\"type\":\"uint8\"}],\"name\":\"SetFeeProtocol\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"},{\"indexed\":false,\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"indexed\":false,\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"}],\"name\":\"Swap\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"}],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collect\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint128\",\"name\":\"amount0Requested\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1Requested\",\"type\":\"uint128\"}],\"name\":\"collectProtocol\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"amount0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"amount1\",\"type\":\"uint128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"fee\",\"outputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal0X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeGrowthGlobal1X128\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"flash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"}],\"name\":\"increaseObservationCardinalityNext\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"liquidity\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxLiquidityPerTick\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"},{\"internalType\":\"uint128\",\"name\":\"amount\",\"type\":\"uint128\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amount0\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount1\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"observations\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"blockTimestamp\",\"type\":\"uint32\"},{\"internalType\":\"int56\",\"name\":\"tickCumulative\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityCumulativeX128\",\"type\":\"uint160\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"secondsAgos\",\"type\":\"uint32[]\"}],\"name\":\"observe\",\"outputs\":[{\"internalType\":\"int56[]\",\"name\":\"tickCumulatives\",\"type\":\"int56[]\"},{\"internalType\":\"uint160[]\",\"name\":\"secondsPerLiquidityCumulativeX128s\",\"type\":\"uint160[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"positions\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidity\",\"type\":\"uint128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside0LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthInside1LastX128\",\"type\":\"uint256\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"tokensOwed1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"protocolFees\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"token0\",\"type\":\"uint128\"},{\"internalType\":\"uint128\",\"name\":\"token1\",\"type\":\"uint128\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"feeProtocol0\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol1\",\"type\":\"uint8\"}],\"name\":\"setFeeProtocol\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"slot0\",\"outputs\":[{\"internalType\":\"uint160\",\"name\":\"sqrtPriceX96\",\"type\":\"uint160\"},{\"internalType\":\"int24\",\"name\":\"tick\",\"type\":\"int24\"},{\"internalType\":\"uint16\",\"name\":\"observationIndex\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinality\",\"type\":\"uint16\"},{\"internalType\":\"uint16\",\"name\":\"observationCardinalityNext\",\"type\":\"uint16\"},{\"internalType\":\"uint8\",\"name\":\"feeProtocol\",\"type\":\"uint8\"},{\"internalType\":\"bool\",\"name\":\"unlocked\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"tickLower\",\"type\":\"int24\"},{\"internalType\":\"int24\",\"name\":\"tickUpper\",\"type\":\"int24\"}],\"name\":\"snapshotCumulativesInside\",\"outputs\":[{\"internalType\":\"int56\",\"name\":\"tickCumulativeInside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityInsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsInside\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"zeroForOne\",\"type\":\"bool\"},{\"internalType\":\"int256\",\"name\":\"amountSpecified\",\"type\":\"int256\"},{\"internalType\":\"uint160\",\"name\":\"sqrtPriceLimitX96\",\"type\":\"uint160\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"swap\",\"outputs\":[{\"internalType\":\"int256\",\"name\":\"amount0\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1\",\"type\":\"int256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int16\",\"name\":\"\",\"type\":\"int16\"}],\"name\":\"tickBitmap\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"name\":\"ticks\",\"outputs\":[{\"internalType\":\"uint128\",\"name\":\"liquidityGross\",\"type\":\"uint128\"},{\"internalType\":\"int128\",\"name\":\"liquidityNet\",\"type\":\"int128\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside0X128\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"feeGrowthOutside1X128\",\"type\":\"uint256\"},{\"internalType\":\"int56\",\"name\":\"tickCumulativeOutside\",\"type\":\"int56\"},{\"internalType\":\"uint160\",\"name\":\"secondsPerLiquidityOutsideX128\",\"type\":\"uint160\"},{\"internalType\":\"uint32\",\"name\":\"secondsOutside\",\"type\":\"uint32\"},{\"internalType\":\"bool\",\"name\":\"initialized\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token0\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token1\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]'<br />const ABI_Factory = '[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":true,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"FeeAmountEnabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"indexed\":false,\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"name\":\"PoolCreated\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenA\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenB\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"}],\"name\":\"createPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"pool\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"name\":\"enableFeeAmount\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"feeAmountTickSpacing\",\"outputs\":[{\"internalType\":\"int24\",\"name\":\"\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"\",\"type\":\"uint24\"}],\"name\":\"getPool\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"parameters\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token0\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token1\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"int24\",\"name\":\"tickSpacing\",\"type\":\"int24\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"setOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]'<br /><br />// Uniswap contract address<br />const UniswapV3RouterAddress = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"<br />const ContractV3Factory = "0x1F98431c8aD98523631AE4a59f267346ea31F984"<br /><br />// common constant<br />const TWO = BigInt(2)<br />const Q192 = (TWO ** BigInt(96)) ** TWO<br />const Q96 = (TWO ** BigInt(96))<br /><br />// Convert to readable amount<br />function toAmount(s, decimals) {<br /> return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())<br />}<br /><br />// Reverse conversion from readable amount to amount used for passing parameters and calculations<br />function toInnerAmount(n, decimals) {<br /> return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)<br />}<br /><br />function main(){<br /> // The address of the wallet to be searched<br /> // const walletAddress = exchange.IO("address")<br /> const walletAddress = "0x28df8b987BE232bA33FdFB8Fc5058C1592A3db26"<br /><br /> // Get the address of Uniswap V3's positionManager contract<br /> exchange.IO("abi", UniswapV3RouterAddress, ABI_UniswapV3Router)<br /> const NonfungiblePositionManagerAddress = exchange.IO("api", UniswapV3RouterAddress, "positionManager")<br /> Log("NonfungiblePositionManagerAddress:", NonfungiblePositionManagerAddress)<br /><br /> // Register ABI for positionManager contracts<br /> exchange.IO("abi", NonfungiblePositionManagerAddress, ABI_NonfungiblePositionManager)<br /><br /> // Query the number of Uniswap V3 positions NFT owned by the current account<br /> var nftBalance = exchange.IO("api", NonfungiblePositionManagerAddress, "balanceOf", walletAddress)<br /> Log("nftBalance:", nftBalance)<br /> <br /> // Query the TokenId of these NFTs<br /> var nftTokenIds = []<br /> for (var i = 0 ; i < nftBalance; i++) {<br /> var nftTokenId = exchange.IO("api", NonfungiblePositionManagerAddress, "tokenOfOwnerByIndex", walletAddress, i)<br /> nftTokenIds.push(nftTokenId)<br /> Log("nftTokenId:", nftTokenId)<br /> }<br /><br /> // Query liquidity position details based on the tokenId of the positions NFT<br /> var positions = []<br /> for (var i = 0; i < nftTokenIds.length; i++) {<br /> var pos = exchange.IO("api", NonfungiblePositionManagerAddress, "positions", nftTokenIds[i]) <br /> Log("pos:", pos)<br /><br /> // Parsing position data<br /> positions.push(parsePosData(pos))<br /> }<br /> <br /> var tbl = {<br /> type : "table",<br /> title : "LP",<br /> cols : ["range(token0 valuation)", "token0", "token1", "fee", "lowerPrice(tickLower)", "upperPrice(tickUpper)", "liquidity", "amount0", "amount1"],<br /> rows : positions<br /> }<br /> LogStatus("`" + JSON.stringify(tbl) + "`")<br />}<br /><br />// Record information about tokens queried through coingecko.com<br />var tokens = []<br />function init() {<br /> // When initializing, get information about all tokens to query the<br /> var res = JSON.parse(HttpQuery("https://tokens.coingecko.com/uniswap/all.json"))<br /> Log("fetch", res.tokens.length, "tokens from", res.name)<br /> _.each(res.tokens, function(token) {<br /> tokens.push({<br /> name : token.name,<br /> symbol : token.symbol,<br /> decimals : token.decimals,<br /> address : token.address<br /> })<br /> })<br /> Log("tokens:", tokens)<br />}<br /><br />function parsePosData(posData) {<br /> /*<br /> {<br /> "nonce": "0",<br /> "operator": "0x0000000000000000000000000000000000000000",<br /> "token1": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",<br /> "fee": "3000",<br /> "feeGrowthInside0LastX128": "552824104363438506727784685971981736468",<br /> "feeGrowthInside1LastX128": "2419576808699564757520565912733367379",<br /> "tokensOwed0": "0",<br /> "tokensOwed1": "0",<br /> "token0": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",<br /> "tickLower": "-62160",<br /> "tickUpper": "-41280",<br /> "liquidity": "19090316141441365693"<br /> }<br /> */<br /> <br /> var token0Symbol = null<br /> var token1Symbol = null<br /> // Determine the token according to the address of the token, record the information about the token<br /> for (var i in tokens) {<br /> if (tokens[i].address.toLowerCase() == posData.token0.toLowerCase()) {<br /> token0Symbol = tokens[i]<br /> } else if (tokens[i].address.toLowerCase() == posData.token1.toLowerCase()) {<br /> token1Symbol = tokens[i]<br /> }<br /> }<br /><br /> if (!token0Symbol || !token1Symbol) {<br /> Log("token0Symbol:", token0Symbol, ", token1Symbol:", token1Symbol)<br /> throw "token not found"<br /> }<br /><br /> // get Pool , obtaining data about the exchange pool<br /> var poolInfo = getPool(token0Symbol.address, token1Symbol.address, posData.fee)<br /> Log("poolInfo:", poolInfo)<br /> /* Data examples<br /> {<br /> "slot0":{<br /> "sqrtPriceX96":"4403124416947951698847768103","tick":"-57804","observationIndex":136,"observationCardinality":300,<br /> "observationCardinalityNext":300,"feeProtocol":0,"unlocked":true<br /> }<br /> }<br /> */<br /> <br /> // Calculate token0Amount, token1Amount<br /> var currentTick = parseInt(poolInfo.slot0.tick)<br /> var lowerPrice = 1.0001 ** posData.tickLower<br /> var upperPrice = 1.0001 ** posData.tickUpper<br /> var sqrtRatioA = Math.sqrt(lowerPrice)<br /> var sqrtRatioB = Math.sqrt(upperPrice)<br /> var sqrtPrice = Number(BigFloat(poolInfo.slot0.sqrtPriceX96) / Q96)<br /> <br /> var amount0wei = 0<br /> var amount1wei = 0<br /> if (currentTick <= posData.tickLower) {<br /> amount0wei = Math.floor(posData.liquidity * ((sqrtRatioB - sqrtRatioA) / (sqrtRatioA * sqrtRatioB)))<br /> } else if (currentTick > posData.tickUpper) {<br /> amount1wei = Math.floor(posData.liquidity * (sqrtRatioB - sqrtRatioA))<br /> } else if (currentTick >= posData.tickLower && currentTick < posData.tickUpper) {<br /> amount0wei = Math.floor(posData.liquidity * ((sqrtRatioB - sqrtPrice) / (sqrtPrice * sqrtRatioB)))<br /> amount1wei = Math.floor(posData.liquidity * (sqrtPrice - sqrtRatioA))<br /> }<br /> <br /> var rangeToken0 = (1.0001 ** -posData.tickUpper) + " ~ " + (1.0001 ** -posData.tickLower)<br /> var amount0 = toAmount(amount0wei, token0Symbol.decimals)<br /> var amount1 = toAmount(amount1wei, token1Symbol.decimals)<br /><br /> return [rangeToken0, token0Symbol.symbol, token1Symbol.symbol, posData.fee / 10000 + "%", <br /> `${lowerPrice}(tickLower:${posData.tickLower})`, `${upperPrice}(tickUpper:${posData.tickUpper})`, posData.liquidity, <br /> amount0, amount1]<br />}<br /><br />function getPool(token0Address, token1Address, fee) {<br /> if (BigInt(token0Address) > BigInt(token1Address)) {<br /> var tmp = token0Address<br /> token0Address = token1Address<br /> token1Address = tmp<br /> }<br /> <br /> // Calculating the contract address of an exchange pool using Uniswap's factory contract<br /> exchange.IO("abi", ContractV3Factory, ABI_Factory)<br /> var poolAddress = exchange.IO("api", ContractV3Factory, "getPool", token0Address, token1Address, fee)<br /> if (!poolAddress) {<br /> throw "getPool failed"<br /> }<br /> <br /> // ABI for registered pool contracts<br /> exchange.IO("abi", poolAddress, ABI_Pool)<br /> <br /> // Call the pool contract's slot0 method to query the current exchange pool's data<br /> var slot0 = exchange.IO("api", poolAddress, "slot0")<br /> if (!slot0) {<br /> throw "get slot0 failed"<br /> }<br /> <br /> return {<br /> "slot0": slot0<br /> }<br />}<br />```<br /><br />The retrieved liquidity position information (which needs further parsing):<br /><br />```<br />{<br /> "nonce": "0",<br /> "operator": "0x0000000000000000000000000000000000000000",<br /> "token1": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",<br /> "fee": "3000",<br /> "feeGrowthInside0LastX128": "552824104363438506727784685971981736468",<br /> "feeGrowthInside1LastX128": "2419576808699564757520565912733367379",<br /> "tokensOwed0": "0",<br /> "tokensOwed1": "0",<br /> "token0": "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",<br /> "tickLower": "-62160",<br /> "tickUpper": "-41280",<br /> "liquidity": "19090316141441365693"<br />}<br />```<br /><br /><b>Parsing Liquidity Holdings for Market Making Ranges</b><br /><br />The positions method of Uniswap's NonfungiblePositionManager contract returns tickLower and tickUpper in the liquidity position data. In Uniswap V3, liquidity can be specified within a range, whereas the initial design of Uniswap had uniform distribution of liquidity. The specification of a liquidity range in subsequent versions is aimed at improving the utilization of liquidity capital.<br /><br />> tickLower: The lower end of the tick range for the position<br />> tickUpper: The higher end of the tick range for the position<br /><br />tickLower and tickUpper represent the upper and lower limits of the liquidity. Referring to the Uniswap documentation and its complex formulas, tickLower and tickUpper are essentially price boundaries recorded in exponential form. So, how can we calculate the price range for market-making from these "exponential boundaries"?<br /><br />It's quite simple. Uniswap uses a base of ```1.0001```. Lower price: ```lowerPrice = 1.0001 ** tickLower```; Upper price: ```upperPrice = 1.0001 ** tickUpper```. The ```**``` operator represents exponentiation. For example, ```1.0001 ** 100``` calculates 1.0001 raised to the power of 100.<br /><br />This calculated price range is denominated in token1. But what if we want to see the price range denominated in token0?<br /><br />It can be calculated as follows:<br />Lower price: 1.0001 ** -tickUpper<br />Upper price: 1.0001 ** -tickLower<br /><a href="https://stocksharp.com/file/149684
" title="https://stocksharp.com/file/149684
">https://stocksharp.com/file/149684
</a><br /><br />Compare this to the NFT position we looked up on the chain:<br /><a href="https://stocksharp.com/file/149685
" title="https://stocksharp.com/file/149685
">https://stocksharp.com/file/149685
</a><br /><a href="https://stocksharp.com/file/149686
" title="https://stocksharp.com/file/149686
">https://stocksharp.com/file/149686
</a><br /><a href="https://stocksharp.com/file/149687
" title="https://stocksharp.com/file/149687
">https://stocksharp.com/file/149687
</a><br /><br /><b>Parsing the Asset Value in the Current Liquidity Position</b><br /><br />In addition to tickUpper and tickLower, the positions method of Uniswap's NonfungiblePositionManager contract returns another important data: liquidity.<br /><br />By combining the liquidity, tickUpper, tickLower data from the position information with the current price data in the exchange pool, we can calculate the asset value in the current liquidity position.<br /><br />You may wonder why we need to know the current price in the exchange pool to make the calculation.<br /><br />This is because once you add liquidity, any price changes within the effective range of your liquidity position imply that your assets have been exchanged. If the current exchange price is not within the effective range of your liquidity provision, there won't be any changes. Therefore, there are three scenarios to consider when calculating:<br /><br />- The tick of the current price is less than tickLower.<br />- The tick of the current price is greater than tickUpper.<br />- The tick of the current price is within the range of tickLower to tickUpper.<br /><br />This corresponds to the following calculation and processing in the code:<br /><br />```<br /> if (currentTick <= posData.tickLower) {<br /> amount0wei = Math.floor(posData.liquidity * ((sqrtRatioB - sqrtRatioA) / (sqrtRatioA * sqrtRatioB)))<br /> } else if (currentTick > posData.tickUpper) {<br /> amount1wei = Math.floor(posData.liquidity * (sqrtRatioB - sqrtRatioA))<br /> } else if (currentTick >= posData.tickLower && currentTick < posData.tickUpper) {<br /> amount0wei = Math.floor(posData.liquidity * ((sqrtRatioB - sqrtPrice) / (sqrtPrice * sqrtRatioB)))<br /> amount1wei = Math.floor(posData.liquidity * (sqrtPrice - sqrtRatioA))<br /> }<br />```<br /><br />To perform the above calculations, we need to obtain the tick of the current price, which can be obtained directly from the pool contract. To get the address of the pool contract, we can use the getPool method of Uniswap's factory contract. By using the ```slot0``` method of the pool contract, we can retrieve the current price information, including the tick value we need.<br /><a href="https://stocksharp.com/file/149688
" title="https://stocksharp.com/file/149688
">https://stocksharp.com/file/149688
</a><br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y6x6iLLUqIMFJAk1dYxX-PKODE2xLmQn5pc1B_afih5X8YEwANTJSRfvIDDF9IQbcJWCPI-QGIvuoune2CBaw7e3WRpW5WimOlsXMnXhuqcv5LhUdCfs_gock8XjlwRdMqQ" title="https://blog.mathquant.com/2023/07/21/fmz-quant-uniswap-v3-exchange-pool-liquidity-related-operations-guide-part-1.html">https://blog.mathquant.c...ations-guide-part-1.html</a>https://stocksharp.com/topic/25629/Thoughts on High-Frequency Trading Strategies (5)2024-03-26T07:20:05Z2024-03-26T07:20:05ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comIn the previous article, various methods for calculating mid-price were introduced, and a revised mid-price was proposed. In this article, we will delve deeper into this topic.<br /><br /><b>Data Required</b><br /><br />We need order flow data and depth data for the top ten levels of the order book, collected from live trading with an update frequency of 100ms. For the sake of simplicity, we will not include real-time updates for the bid and ask prices. To reduce the data size, we have kept only 100,000 rows of depth data and separated the tick-by-tick market data into individual columns.<br /><br />In [1]:<br />```<br />from datetime import date,datetime<br />import time<br />import pandas as pd<br />import numpy as np<br />import matplotlib.pyplot as plt<br />import ast<br />%matplotlib inline<br />```<br /><br />In [2]:<br />```<br />tick_size = 0.0001<br />```<br /><br />In [3]:<br />```<br />trades = pd.read_csv('YGGUSDT_aggTrade.csv',names=['type','event_time', 'agg_trade_id','symbol', 'price', 'quantity', 'first_trade_id', 'last_trade_id',<br /> 'transact_time', 'is_buyer_maker'])<br />```<br /><br />In [4]:<br />```<br />trades = trades.groupby(['transact_time','is_buyer_maker']).agg({<br /> 'transact_time':'last',<br /> 'agg_trade_id': 'last',<br /> 'price': 'first',<br /> 'quantity': 'sum',<br /> 'first_trade_id': 'first',<br /> 'last_trade_id': 'last',<br /> 'is_buyer_maker': 'last',<br />})<br />```<br /><br />In [5]:<br />```<br />trades.index = pd.to_datetime(trades['transact_time'], unit='ms')<br />trades.index.rename('time', inplace=True)<br />trades['interval'] = trades['transact_time'] - trades['transact_time'].shift()<br />```<br /><br />In [6]:<br />```<br />depths = pd.read_csv('YGGUSDT_depth.csv',names=['type','event_time', 'transact_time','symbol', 'u1', 'u2', 'u3', 'bids','asks'])<br />```<br /><br />In [7]:<br />```<br />depths = depths.iloc[:100000]<br />```<br /><br />In [8]:<br />```<br />depths['bids'] = depths['bids'].apply(ast.literal_eval).copy()<br />depths['asks'] = depths['asks'].apply(ast.literal_eval).copy()<br />```<br /><br />In [9]:<br />```<br />def expand_bid(bid_data):<br /> expanded = {}<br /> for j, (price, quantity) in enumerate(bid_data):<br /> expanded[f'bid_{j}_price'] = float(price)<br /> expanded[f'bid_{j}_quantity'] = float(quantity)<br /> return pd.Series(expanded)<br />def expand_ask(ask_data):<br /> expanded = {}<br /> for j, (price, quantity) in enumerate(ask_data):<br /> expanded[f'ask_{j}_price'] = float(price)<br /> expanded[f'ask_{j}_quantity'] = float(quantity)<br /> return pd.Series(expanded)<br /># Apply to each line to get a new df<br />expanded_df_bid = depths['bids'].apply(expand_bid)<br />expanded_df_ask = depths['asks'].apply(expand_ask)<br /># Expansion on the original df<br />depths = pd.concat([depths, expanded_df_bid, expanded_df_ask], axis=1)<br />```<br /><br />In [10]:<br />```<br />depths.index = pd.to_datetime(depths['transact_time'], unit='ms')<br />depths.index.rename('time', inplace=True);<br />```<br /><br />In [11]:<br />```<br />trades = trades[trades['transact_time'] < depths['transact_time'].iloc[-1]]<br />```<br /><br />Take a look at the distribution of the market in these 20 levels. It is in line with expectations, with more orders placed the further away from the market price. Additionally, buy orders and sell orders are roughly symmetrical.<br /><br />In [14]:<br />```<br />bid_mean_list = []<br />ask_mean_list = []<br />for i in range(20):<br /> bid_mean_list.append(round(depths[f'bid_{i}_quantity'].mean(),0))<br /> ask_mean_list.append(round(depths[f'ask_{i}_quantity'].mean(),0))<br />plt.figure(figsize=(10, 5))<br />plt.plot(bid_mean_list);<br />plt.plot(ask_mean_list);<br />plt.grid(True)<br />```<br /><br />Out[14]:<br /><br />/upload/asset/28d4e2581a36fd970d055.png<br /><br />Merge the depth data with the transaction data to facilitate the evaluation of prediction accuracy. Ensure that the transaction data is later than the depth data. Without considering latency, directly calculate the mean squared error between the predicted value and the actual transaction price. This is used to measure the accuracy of the prediction.<br /><br />From the results, the error is highest for the average value of the bid and ask prices (mid_price). However, when changed to the weighted mid_price, the error immediately decreases significantly. Further improvement is observed by using the adjusted weighted mid_price. After receiving feedback on using I^3/2 only, it was checked and found that the results were better. Upon reflection, this is likely due to the different frequencies of events. When I is close to -1 and 1, it represents low probability events. In order to correct for these low probability events, the accuracy of predicting high-frequency events is compromised. Therefore, to prioritize high-frequency events, some adjustments were made (these parameters were purely trial-and-error and have limited practical significance in live trading).<br /><a href="https://stocksharp.com/file/149678
" title="https://stocksharp.com/file/149678
">https://stocksharp.com/file/149678
</a><br /><br />The results have improved slightly. As mentioned in the previous article, strategies should rely on more data for prediction. With the availability of more depth and order transaction data, the improvement gained from focusing on the order book is already weak.<br /><br />In [15]:<br />```<br />df = pd.merge_asof(trades, depths, on='transact_time', direction='backward')<br />```<br /><br />In [17]:<br />```<br />df['spread'] = round(df['ask_0_price'] - df['bid_0_price'],4)<br />df['mid_price'] = (df['bid_0_price']+ df['ask_0_price']) / 2<br />df['I'] = (df['bid_0_quantity'] - df['ask_0_quantity']) / (df['bid_0_quantity'] + df['ask_0_quantity'])<br />df['weight_mid_price'] = df['mid_price'] + df['spread']*df['I']/2<br />df['adjust_mid_price'] = df['mid_price'] + df['spread']*(df['I'])*(df['I']**8+1)/4<br />df['adjust_mid_price_2'] = df['mid_price'] + df['spread']*df['I']*(df['I']**2+1)/4<br />df['adjust_mid_price_3'] = df['mid_price'] + df['spread']*df['I']**3/2<br />df['adjust_mid_price_4'] = df['mid_price'] + df['spread']*(df['I']+0.3)*(df['I']**4+0.7)/3.8<br />```<br /><br />In [18]:<br />```<br />print('Mean value Error in mid_price:', ((df['price']-df['mid_price'])**2).sum())<br />print('Error of pending order volume weighted mid_price:', ((df['price']-df['weight_mid_price'])**2).sum())<br />print('The error of the adjusted mid_price:', ((df['price']-df['adjust_mid_price'])**2).sum())<br />print('The error of the adjusted mid_price_2:', ((df['price']-df['adjust_mid_price_2'])**2).sum())<br />print('The error of the adjusted mid_price_3:', ((df['price']-df['adjust_mid_price_3'])**2).sum())<br />print('The error of the adjusted mid_price_4:', ((df['price']-df['adjust_mid_price_4'])**2).sum())<br />```<br /><br />Out[18]:<br /><br />Mean value Error in mid_price: 0.0048751924999999845<br />Error of pending order volume weighted mid_price: 0.0048373440193987035<br />The error of the adjusted mid_price: 0.004803654771638586<br />The error of the adjusted mid_price_2: 0.004808216498329721<br />The error of the adjusted mid_price_3: 0.004794984755260528<br />The error of the adjusted mid_price_4: 0.0047909595497071375<br /><br /><b>Consider the Second Level of Depth</b><br /><br />We can follow the approach from the previous article to examine different ranges of a parameter and measure its contribution to the mid_price based on the changes in transaction price. Similar to the first level of depth, as I increases, the transaction price is more likely to increase, indicating a positive contribution from I.<br /><br />Applying the same approach to the second level of depth, we find that although the effect is slightly smaller than the first level, it is still significant and should not be ignored. The third level of depth also shows a weak contribution, but with less monotonicity. Deeper depths have little reference value.<br /><br />Based on the different contributions, we assign different weights to these three levels of imbalance parameters. By examining different calculation methods, we observe further reduction in prediction errors.<br /><br />In [19]:<br />```<br />bins = np.linspace(-1, 1, 50)<br />df['change'] = (df['price'].pct_change().shift(-1))/tick_size<br />df['I_bins'] = pd.cut(df['I'], bins, labels=bins[1:])<br />df['I_2'] = (df['bid_1_quantity'] - df['ask_1_quantity']) / (df['bid_1_quantity'] + df['ask_1_quantity'])<br />df['I_2_bins'] = pd.cut(df['I_2'], bins, labels=bins[1:])<br />df['I_3'] = (df['bid_2_quantity'] - df['ask_2_quantity']) / (df['bid_2_quantity'] + df['ask_2_quantity'])<br />df['I_3_bins'] = pd.cut(df['I_3'], bins, labels=bins[1:])<br />df['I_4'] = (df['bid_3_quantity'] - df['ask_3_quantity']) / (df['bid_3_quantity'] + df['ask_3_quantity'])<br />df['I_4_bins'] = pd.cut(df['I_4'], bins, labels=bins[1:])<br />fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 5))<br /><br /><br />axes[0][0].plot(df.groupby('I_bins')['change'].mean())<br />axes[0][0].set_title('I')<br />axes[0][0].grid(True)<br /><br />axes[0][1].plot(df.groupby('I_2_bins')['change'].mean())<br />axes[0][1].set_title('I 2')<br />axes[0][1].grid(True)<br /><br />axes[1][0].plot(df.groupby('I_3_bins')['change'].mean())<br />axes[1][0].set_title('I 3')<br />axes[1][0].grid(True)<br /><br />axes[1][1].plot(df.groupby('I_4_bins')['change'].mean())<br />axes[1][1].set_title('I 4')<br />axes[1][1].grid(True)<br />plt.tight_layout();<br />```<br /><br />Out[19]:<br /><a href="https://stocksharp.com/file/149683
" title="https://stocksharp.com/file/149683
">https://stocksharp.com/file/149683
</a><br /><br />In [20]:<br />```<br />df['adjust_mid_price_4'] = df['mid_price'] + df['spread']*(df['I']+0.3)*(df['I']**4+0.7)/3.8<br />df['adjust_mid_price_5'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2'])/2<br />df['adjust_mid_price_6'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2'])**3/2<br />df['adjust_mid_price_7'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2']+0.3)*((0.7*df['I']+0.3*df['I_2'])**4+0.7)/3.8<br />df['adjust_mid_price_8'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.2*df['I_2']+0.1*df['I_3']+0.3)*((0.7*df['I']+0.3*df['I_2']+0.1*df['I_3'])**4+0.7)/3.8<br />```<br /><br />In [21]:<br />```<br />print('The error of the adjusted mid_price_4:', ((df['price']-df['adjust_mid_price_4'])**2).sum())<br />print('The error of the adjusted mid_price_5:', ((df['price']-df['adjust_mid_price_5'])**2).sum())<br />print('The error of the adjusted mid_price_6:', ((df['price']-df['adjust_mid_price_6'])**2).sum())<br />print('The error of the adjusted mid_price_7:', ((df['price']-df['adjust_mid_price_7'])**2).sum())<br />print('The error of the adjusted mid_price_8:', ((df['price']-df['adjust_mid_price_8'])**2).sum())<br />```<br /><br />Out[21]:<br /><br />The error of the adjusted mid_price_4: 0.0047909595497071375<br />The error of the adjusted mid_price_5: 0.0047884350488318714<br />The error of the adjusted mid_price_6: 0.0047778319053133735<br />The error of the adjusted mid_price_7: 0.004773578540592192<br />The error of the adjusted mid_price_8: 0.004771415189297518<br /><br /><b>Considering the Transaction Data</b><br /><br />Transaction data directly reflects the extent of long and short positions. After all, transactions involve real money, while placing orders has much lower costs and can even involve intentional deception. Therefore, when predicting the mid_price, strategies should focus on the transaction data.<br /><br />In terms of form, we can define the imbalance of the average order arrival quantity as VI, with Vb and Vs representing the average quantity of buy and sell orders within a unit time interval, respectively.<br /><a href="https://stocksharp.com/file/149679
" title="https://stocksharp.com/file/149679
">https://stocksharp.com/file/149679
</a><br /><br />The results show that the arrival quantity in a short period of time has the most significant impact on price change prediction. When VI is between 0.1 and 0.9, it is negatively correlated with price, while outside this range, it is positively correlated with price. This suggests that when the market is not extreme and mainly oscillates, the price tends to revert to the mean. However, in extreme market conditions, such as when there are a large number of buy orders overwhelming sell orders, a trend emerges. Even without considering these low probability scenarios, assuming a negative linear relationship between the trend and VI significantly reduces the prediction error of the mid_price. The coefficient "a" represents the weight of this relationship in the equation.<br /><a href="https://stocksharp.com/file/149681
" title="https://stocksharp.com/file/149681
">https://stocksharp.com/file/149681
</a><br /><br />In [22]:<br />```<br />alpha=0.1<br />```<br /><br />In [23]:<br />```<br />df['avg_buy_interval'] = None<br />df['avg_sell_interval'] = None<br />df.loc[df['is_buyer_maker'] == True, 'avg_buy_interval'] = df[df['is_buyer_maker'] == True]['transact_time'].diff().ewm(alpha=alpha).mean()<br />df.loc[df['is_buyer_maker'] == False, 'avg_sell_interval'] = df[df['is_buyer_maker'] == False]['transact_time'].diff().ewm(alpha=alpha).mean()<br />```<br /><br />In [24]:<br />```<br />df['avg_buy_quantity'] = None<br />df['avg_sell_quantity'] = None<br />df.loc[df['is_buyer_maker'] == True, 'avg_buy_quantity'] = df[df['is_buyer_maker'] == True]['quantity'].ewm(alpha=alpha).mean()<br />df.loc[df['is_buyer_maker'] == False, 'avg_sell_quantity'] = df[df['is_buyer_maker'] == False]['quantity'].ewm(alpha=alpha).mean()<br />```<br /><br />In [25]:<br />```<br />df['avg_buy_quantity'] = df['avg_buy_quantity'].fillna(method='ffill')<br />df['avg_sell_quantity'] = df['avg_sell_quantity'].fillna(method='ffill')<br />df['avg_buy_interval'] = df['avg_buy_interval'].fillna(method='ffill')<br />df['avg_sell_interval'] = df['avg_sell_interval'].fillna(method='ffill')<br /><br />df['avg_buy_rate'] = 1000 / df['avg_buy_interval']<br />df['avg_sell_rate'] =1000 / df['avg_sell_interval']<br /><br />df['avg_buy_volume'] = df['avg_buy_rate']*df['avg_buy_quantity']<br />df['avg_sell_volume'] = df['avg_sell_rate']*df['avg_sell_quantity']<br />```<br /><br />In [26]:<br />```<br />df['I'] = (df['bid_0_quantity']- df['ask_0_quantity']) / (df['bid_0_quantity'] + df['ask_0_quantity'])<br />df['OI'] = (df['avg_buy_rate']-df['avg_sell_rate']) / (df['avg_buy_rate'] + df['avg_sell_rate'])<br />df['QI'] = (df['avg_buy_quantity']-df['avg_sell_quantity']) / (df['avg_buy_quantity'] + df['avg_sell_quantity'])<br />df['VI'] = (df['avg_buy_volume']-df['avg_sell_volume']) / (df['avg_buy_volume'] + df['avg_sell_volume'])<br />```<br /><br />In [27]:<br />```<br />bins = np.linspace(-1, 1, 50)<br />df['VI_bins'] = pd.cut(df['VI'], bins, labels=bins[1:])<br />plt.plot(df.groupby('VI_bins')['change'].mean());<br />plt.grid(True)<br />```<br /><br />Out[27]:<br /><a href="https://stocksharp.com/file/149680
" title="https://stocksharp.com/file/149680
">https://stocksharp.com/file/149680
</a><br /><br />In [28]:<br />```<br />df['adjust_mid_price'] = df['mid_price'] + df['spread']*df['I']/2<br />df['adjust_mid_price_9'] = df['mid_price'] + df['spread']*(-df['OI'])*2<br />df['adjust_mid_price_10'] = df['mid_price'] + df['spread']*(-df['VI'])*1.4<br />```<br /><br />In [29]:<br />```<br />print('The error of the adjusted mid_price:', ((df['price']-df['adjust_mid_price'])**2).sum())<br />print('The error of the adjusted mid_price_9:', ((df['price']-df['adjust_mid_price_9'])**2).sum())<br />print('The error of the adjusted mid_price_10:', ((df['price']-df['adjust_mid_price_10'])**2).sum())<br />```<br /><br />Out[29]:<br /><br />The error of the adjusted mid_price: 0.0048373440193987035<br />The error of the adjusted mid_price_9: 0.004629586542840461<br />The error of the adjusted mid_price_10: 0.004401790287167206<br /><br /><b>The Comprehensive Mid-price</b><br /><br />Considering that both order book imbalance and transaction data are helpful for predicting the mid_price, we can combine these two parameters together. The assignment of weights in this case is arbitrary and does not take into account boundary conditions. In extreme cases, the predicted mid_price may not fall between the bid and ask prices. However, as long as the prediction error can be reduced, these details are not of great concern.<br /><br />In the end, the prediction error is reduced from 0.00487 to 0.0043. At this point, we will not delve further into the topic. There are still many aspects to explore when it comes to predicting the mid_price, as it is essentially predicting the price itself. Everyone is encouraged to try their own approaches and techniques.<br /><br />In [30]:<br />```<br />#Note that the VI needs to be delayed by one to use<br />df['CI'] = -1.5*df['VI'].shift()+0.7*(0.7*df['I']+0.2*df['I_2']+0.1*df['I_3'])**3 <br />```<br /><br />In [31]:<br />```<br />df['adjust_mid_price_11'] = df['mid_price'] + df['spread']*(df['CI'])<br />print('The error of the adjusted mid_price_11:', ((df['price']-df['adjust_mid_price_11'])**2).sum())<br />```<br /><br />Out[31]:<br /><br />The error of the adjusted mid_price_11: 0.0043001941412563575<br /><br /><b>Summary</b><br /><br />The article combines depth data and transaction data to further improve the calculation method of the mid-price. It provides a method to measure accuracy and improves the accuracy of price change prediction. Overall, the parameters are not rigorous and are for reference only. With a more accurate mid-price, the next step is to conduct backtesting using the mid-price in practical applications. This part of the content is extensive, so updates will be paused for a period of time.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y67qq0yhtjdH5OJdvZRK-DU65XMv9S5xjTGa2LUWYX_21frTV3Cpue8TTeNEtAxk5cUFyLveinbFYypTvDTfSrvI" title="https://blog.mathquant.com/2023/08/10/thoughts-on-high-frequency-trading-strategies-5.html">https://blog.mathquant.c...rading-strategies-5.html</a>https://stocksharp.com/topic/25628/Thoughts on High-Frequency Trading Strategies (4)2024-03-26T05:11:42Z2024-03-26T05:11:42ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comThe previous article demonstrated the need for dynamically adjusting parameters and how to evaluate the quality of estimates by studying the order arrival intervals. This article will focus on depth data and study the mid-price (also known as fair-price or micro-price).<br /><br /><b>Depth Data</b><br /><br />Binance provides historical data downloads for best_bid_price (the highest buying price), best_bid_quantity (the quantity at the best bid price), best_ask_price (the lowest selling price), best_ask_quantity (the quantity at the best ask price), and transaction_time. This data does not include the second or deeper order book levels. The analysis in this article is based on the YGG market on August 7th, which experienced significant volatility with over 9 million data points.<br /><br />First, let's take a look at the market conditions on that day. There were large fluctuations, and the order book volume changed significantly along with the market volatility. The spread, particularly, indicated the extent of market fluctuations, which is the difference between the best ask and bid prices. In the statistics of the YGG market on that day, the spread was larger than one tick for 20% of the time. In this era of various trading bots competing in the order book, such situations are becoming increasingly rare.<br /><br />In [1]:<br />```<br />from datetime import date,datetime<br />import time<br />import pandas as pd<br />import numpy as np<br />import matplotlib.pyplot as plt<br />%matplotlib inline<br />```<br /><br />In [2]:<br />```<br />books = pd.read_csv('YGGUSDT-bookTicker-2023-08-07.csv')<br />```<br /><br />In [3]:<br />```<br />tick_size = 0.0001<br />```<br /><br />In [4]:<br />```<br />books['date'] = pd.to_datetime(books['transaction_time'], unit='ms')<br />books.index = books['date']<br />```<br /><br />In [5]:<br />```<br />books['spread'] = round(books['best_ask_price'] - books['best_bid_price'],4)<br />```<br /><br />In [6]:<br />```<br />books['best_bid_price'][::10].plot(figsize=(10,5),grid=True);<br />```<br /><br />Out[6]:<br /><a href="https://stocksharp.com/file/149672
" title="https://stocksharp.com/file/149672
">https://stocksharp.com/file/149672
</a><br /><br />In [7]:<br />```<br />books['best_bid_qty'][::10].rolling(10000).mean().plot(figsize=(10,5),grid=True);<br />books['best_ask_qty'][::10].rolling(10000).mean().plot(figsize=(10,5),grid=True);<br />```<br /><br />Out[7]:<br /><a href="https://stocksharp.com/file/149677
" title="https://stocksharp.com/file/149677
">https://stocksharp.com/file/149677
</a><br /><br />In [8]:<br />```<br />(books['spread'][::10]/tick_size).rolling(10000).mean().plot(figsize=(10,5),grid=True);<br />```<br /><br />Out[8]:<br /><a href="https://stocksharp.com/file/149675
" title="https://stocksharp.com/file/149675
">https://stocksharp.com/file/149675
</a><br /><br />In [9]:<br />```<br />books['spread'].value_counts()[books['spread'].value_counts()>500]/books['spread'].value_counts().sum()<br />```<br /><br />Out[9]:<br /><a href="https://stocksharp.com/file/149667
" title="https://stocksharp.com/file/149667
">https://stocksharp.com/file/149667
</a><br /><br /><b>Imbalanced Quotes</b><br /><br />Imbalanced quotes are observed from the significant difference in the order book volumes between the buy and sell orders most of the time. This difference has a strong predictive effect on short-term market trends, similar to the reason mentioned earlier that a decrease in buy order volume often leads to a decline. If one side of the order book is significantly smaller than the other, assuming the active buying and selling orders are similar in volume, there is a greater likelihood of the smaller side being consumed, thereby driving price changes. Imbalanced quotes are represented by the letter "I".<br /><a href="https://stocksharp.com/file/149671
" title="https://stocksharp.com/file/149671
">https://stocksharp.com/file/149671
</a><br /><br />Where Q_b represents the amount of pending buy orders (best_bid_qty) and Q_a represents the amount of pending sell orders (best_ask_qty).<br /><br />Define mid-price:<br /><a href="https://stocksharp.com/file/149674
" title="https://stocksharp.com/file/149674
">https://stocksharp.com/file/149674
</a><br /><br />The graph below shows the relationship between the rate of change of mid-price over the next 1 interval and the imbalance I. As expected, the more likely the price is to increase as I increases and the closer it gets to 1, the more the price change accelerates. In high-frequency trading, the introduction of the intermediate price is to better predict future price changes, that is, and the future price difference is smaller, the better the intermediate price is defined. Obviously the imbalance of pending orders provides additional information for the prediction of the strategy, with this in mind, defining the weighted mid-price:<br /><a href="https://stocksharp.com/file/149668
" title="https://stocksharp.com/file/149668
">https://stocksharp.com/file/149668
</a><br /><br />In [10]:<br />```<br />books['I'] = books['best_bid_qty'] / (books['best_bid_qty'] + books['best_ask_qty'])<br />```<br /><br />In [11]:<br />```<br />books['mid_price'] = (books['best_ask_price'] + books['best_bid_price'])/2<br />```<br /><br />In [12]:<br />```<br />bins = np.linspace(0, 1, 51)<br />books['I_bins'] = pd.cut(books['I'], bins, labels=bins[1:])<br />books['price_change'] = (books['mid_price'].pct_change()/tick_size).shift(-1)<br />avg_change = books.groupby('I_bins')['price_change'].mean()<br />plt.figure(figsize=(8,5))<br />plt.plot(avg_change)<br />plt.xlabel('I Value Range')<br />plt.ylabel('Average Mid Price Change Rate');<br />plt.grid(True)<br />```<br /><br />Out[12]:<br /><a href="https://stocksharp.com/file/149663
" title="https://stocksharp.com/file/149663
">https://stocksharp.com/file/149663
</a><br /><br />In [13]:<br />```<br />books['weighted_mid_price'] = books['mid_price'] + books['spread']*books['I']/2<br />bins = np.linspace(-1, 1, 51)<br />books['I_bins'] = pd.cut(books['I'], bins, labels=bins[1:])<br />books['weighted_price_change'] = (books['weighted_mid_price'].pct_change()/tick_size).shift(-1)<br />avg_change = books.groupby('I_bins')['weighted_price_change'].mean()<br />plt.figure(figsize=(8,5))<br />plt.plot(avg_change)<br />plt.xlabel('I Value Range')<br />plt.ylabel('Weighted Average Mid Price Change Rate');<br />plt.grid(True)<br />```<br /><br />Out[13]:<br /><a href="https://stocksharp.com/file/149670
" title="https://stocksharp.com/file/149670
">https://stocksharp.com/file/149670
</a><br /><br /><b>Adjust Weighted Mid-Price:</b><br /><br />From the graph, it can be observed that the weighted mid-price shows smaller variations compared to different values of I, indicating that it is a better fit. However, there are still some deviations, particularly around 0.2 and 0.8. This suggests that I still provides additional information. The assumption of a completely linear relationship between the price correction term and I, as implied by the weighted mid-price, does not align with reality. It can be seen from the graph that the deviation speed increases when I approaches 0 and 1, indicating a non-linear relationship.<br /><br />To provide a more intuitive representation, here is a redefinition of I:<br /><br />Revised definition of I:<br /><a href="https://stocksharp.com/file/149666
" title="https://stocksharp.com/file/149666
">https://stocksharp.com/file/149666
</a><br /><br />At this point:<br /><a href="https://stocksharp.com/file/149673
" title="https://stocksharp.com/file/149673
">https://stocksharp.com/file/149673
</a><br /><br />Upon observation, it can be noticed that the weighted mid-price is a correction to the average mid-price, where the correction term is multiplied by the spread. The correction term is a function of I, and the weighted mid-price assumes a simple relationship of I/2. In this case, the advantage of the adjusted I distribution (-1, 1) becomes apparent, as I is symmetric around the origin, making it convenient to find a fitting relationship for the function. By examining the graph, it appears that this function should satisfy odd powers of I, as it aligns with the rapid growth on both sides and symmetry around the origin. Additionally, it can be observed that values near the origin are close to linear. Furthermore, when I is 0, the function result is 0, and when I is 1, the function result is 0.5. Therefore, it is speculated that the function is of the form:<br /><a href="https://stocksharp.com/file/149665
" title="https://stocksharp.com/file/149665
">https://stocksharp.com/file/149665
</a><br /><br />Here N is a positive even number, after actual testing, it is better when N is 8. So far this paper presents the modified weighted mid-price:<br /><a href="https://stocksharp.com/file/149664
" title="https://stocksharp.com/file/149664
">https://stocksharp.com/file/149664
</a><br /><br />At this point, the prediction of mid-price changes is no longer significantly related to I. Although this result is slightly better than the simple weighted mid-price, it is still not applicable in real trading scenarios. This is just a proposed approach. In a 2017 article by S Stoikov, the concept of [Micro-Price](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2970694) is introduced using a Markov chain approach, and related code is provided. Researchers can explore this approach further.<br /><br />In [14]:<br />```<br />books['I'] = (books['best_bid_qty'] - books['best_ask_qty']) / (books['best_bid_qty'] + books['best_ask_qty'])<br />```<br /><br />In [15]:<br />```<br />books['weighted_mid_price'] = books['mid_price'] + books['spread']*books['I']/2<br />bins = np.linspace(-1, 1, 51)<br />books['I_bins'] = pd.cut(books['I'], bins, labels=bins[1:])<br />books['weighted_price_change'] = (books['weighted_mid_price'].pct_change()/tick_size).shift(-1)<br />avg_change = books.groupby('I_bins')['weighted_price_change'].mean()<br />plt.figure(figsize=(8,5))<br />plt.plot(avg_change)<br />plt.xlabel('I Value Range')<br />plt.ylabel('Weighted Average Mid Price Change Rate');<br />plt.grid(True)<br />```<br /><br />Out[15]:<br /><a href="https://stocksharp.com/file/149669
" title="https://stocksharp.com/file/149669
">https://stocksharp.com/file/149669
</a><br /><br />In [16]:<br />```<br />books['adjust_mid_price'] = books['mid_price'] + books['spread']*books['I']*(books['I']**8+1)/4<br />bins = np.linspace(-1, 1, 51)<br />books['I_bins'] = pd.cut(books['I'], bins, labels=bins[1:])<br />books['adjust_mid_price'] = (books['adjust_mid_price'].pct_change()/tick_size).shift(-1)<br />avg_change = books.groupby('I_bins')['adjust_mid_price'].mean()<br />plt.figure(figsize=(8,5))<br />plt.plot(avg_change)<br />plt.xlabel('I Value Range')<br />plt.ylabel('Weighted Average Mid Price Change Rate');<br />plt.grid(True)<br />```<br /><br />Out[16]:<br /><a href="https://stocksharp.com/file/149676
" title="https://stocksharp.com/file/149676
">https://stocksharp.com/file/149676
</a><br /><br /><b>Summary</b><br /><br />The mid-price is crucial for high-frequency strategies as it serves as a prediction of short-term future prices. Therefore, it is important for the mid-price to be as accurate as possible. The mid-price approaches discussed earlier are based on order book data, as only the top level of the order book is utilized in the analysis. In live trading, strategies should aim to utilize all available data, including trade data, to validate mid-price predictions against actual transaction prices. I recall Stoikov mentioning in a Twitter that the real mid-price should be a weighted average of the probabilities of the bid and ask prices being executed. This issue has been explored in the previous articles. Due to length constraints, further details on these topics will be discussed in the next article.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y67qq0yhtjdH5OJdvZRK-DU65XMv9S5xjTGa2LUWYX_21frTV3Cpue8TTeNEtAxk5cdHrubA_Wj4n1z7tWuaJLSw" title="https://blog.mathquant.com/2023/08/10/thoughts-on-high-frequency-trading-strategies-4.html">https://blog.mathquant.c...rading-strategies-4.html</a>https://stocksharp.com/topic/25627/Thoughts on High-Frequency Trading Strategies (3)2024-03-26T03:09:54Z2024-03-26T03:09:54ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comIn the previous article, I introduced how to model cumulative trading volume and analyzed the phenomenon of price impact. In this article, I will continue to analyze the trades order data. YGG recently launched Binance U-based contracts, and the price fluctuations have been significant, with trading volume even surpassing BTC at one point. Today, I will analyze it.<br /><br /><b>Order Time Intervals</b><br /><br />In general, it is assumed that the arrival time of orders follows a Poisson process. There is an article that introduces the [Poisson process](https://www.ruanyifeng.com/blog/2015/06/poisson-distribution.html) . Here, I will provide empirical evidence.<br /><br />I downloaded the aggTrades data for August 5th, which consists of 1,931,193 trades, which is quite significant. First, let's take a look at the distribution of buy orders. We can see a non-smooth local peak around 100ms and 500ms, which is likely caused by iceberg orders placed by trading bots at regular intervals. This may also be one of the reasons for the unusual market conditions that day.<br /><br />The probability mass function (PMF) of the Poisson distribution is given by the following formula:<br /><a href="https://stocksharp.com/file/149661
" title="https://stocksharp.com/file/149661
">https://stocksharp.com/file/149661
</a><br /><br />Where:<br /><br />- κ is the number of events we are interested in.<br />- λ is the average rate of events occurring per unit time (or unit space).<br />- <a href="https://stocksharp.com/file/149657 " title="https://stocksharp.com/file/149657 ">https://stocksharp.com/file/149657 </a>represents the probability of exactly κ events occurring, given the average rate λ.<br /><br />In a Poisson process, the time intervals between events follow an exponential distribution. The probability density function (PDF) of the exponential distribution is given by the following formula:<br /><a href="https://stocksharp.com/file/149655
" title="https://stocksharp.com/file/149655
">https://stocksharp.com/file/149655
</a><br /><br />The fitting results show that there is a significant difference between the observed data and the expected Poisson distribution. The Poisson process underestimates the frequency of long time intervals and overestimates the frequency of short time intervals. (The actual distribution of intervals is closer to a modified Pareto distribution)<br /><br />In [1]:<br />```<br />from datetime import date,datetime<br />import time<br />import pandas as pd<br />import numpy as np<br />import matplotlib.pyplot as plt<br />%matplotlib inline<br />```<br /><br />In [2]:<br />```<br />trades = pd.read_csv('YGGUSDT-aggTrades-2023-08-05.csv')<br />trades['date'] = pd.to_datetime(trades['transact_time'], unit='ms')<br />trades.index = trades['date']<br />buy_trades = trades[trades['is_buyer_maker']==False].copy()<br />buy_trades = buy_trades.groupby('transact_time').agg({<br /> 'agg_trade_id': 'last',<br /> 'price': 'last',<br /> 'quantity': 'sum',<br /> 'first_trade_id': 'first',<br /> 'last_trade_id': 'last',<br /> 'is_buyer_maker': 'last',<br /> 'date': 'last',<br /> 'transact_time':'last'<br />})<br />buy_trades['interval']=buy_trades['transact_time'] - buy_trades['transact_time'].shift()<br />buy_trades.index = buy_trades['date']<br />```<br /><br />In [10]:<br />```<br />buy_trades['interval'][buy_trades['interval']<1000].plot.hist(bins=200,figsize=(10, 5));<br />```<br /><br />Out[10]:<br /><a href="https://stocksharp.com/file/149656
" title="https://stocksharp.com/file/149656
">https://stocksharp.com/file/149656
</a><br /><br />In [20]:<br />```<br />Intervals = np.array(range(0, 1000, 5))<br />mean_intervals = buy_trades['interval'].mean()<br />buy_rates = 1000/mean_intervals<br />probabilities = np.array([np.mean(buy_trades['interval'] > interval) for interval in Intervals])<br />probabilities_s = np.array([np.e**(-buy_rates*interval/1000) for interval in Intervals])<br /><br />plt.figure(figsize=(10, 5))<br />plt.plot(Intervals, probabilities)<br />plt.plot(Intervals, probabilities_s)<br />plt.xlabel('Intervals')<br />plt.ylabel('Probability')<br />plt.grid(True)<br />```<br /><br />Out[20]:<br /><a href="https://stocksharp.com/file/149659
" title="https://stocksharp.com/file/149659
">https://stocksharp.com/file/149659
</a><br /><br />When comparing the distribution of the number of order occurrences within 1 second with the Poisson distribution, the difference is also significant. The Poisson distribution significantly underestimates the frequency of rare events. Possible reasons for this are:<br /><br />- Non-constant rate of occurrence: The Poisson process assumes that the average rate of events occurring within any given time interval is constant. If this assumption does not hold, then the distribution of the data will deviate from the Poisson distribution.<br />- Interactions between processes: Another fundamental assumption of the Poisson process is that events are independent of each other. If events in the real world interact with each other, their distribution may deviate from the Poisson distribution.<br /><br />In other words, in a real-world environment, the frequency of order occurrences is non-constant, and it needs to be updated in real-time. There may also be an incentive effect, where more orders within a fixed time period stimulate more orders. This makes strategies unable to rely on a single fixed parameter.<br /><br />In [190]:<br />```<br />result_df = buy_trades.resample('1S').agg({ <br /> 'price': 'count',<br /> 'quantity': 'sum'<br />}).rename(columns={'price': 'order_count', 'quantity': 'quantity_sum'})<br />```<br /><br />In [219]:<br />```<br />count_df = result_df['order_count'].value_counts().sort_index()[result_df['order_count'].value_counts()>20]<br />(count_df/count_df.sum()).plot(figsize=(10,5),grid=True,label='sample pmf');<br /><br />from scipy.stats import poisson<br />prob_values = poisson.pmf(count_df.index, 1000/mean_intervals) <br /><br />plt.plot(count_df.index, prob_values,label='poisson pmf');<br />plt.legend() ;<br />```<br /><br />Out[219]:<br /><a href="https://stocksharp.com/file/149662
" title="https://stocksharp.com/file/149662
">https://stocksharp.com/file/149662
</a><br /><br /><b>Real-time Parameter Updating</b><br /><br />From the analysis of order intervals earlier, it can be concluded that fixed parameters are not suitable for the real market conditions, and the key parameters describing the market in the strategy need to be updated in real-time. The most straightforward solution is to use a sliding window moving average. The two graphs below show the frequency of buy orders within 1 second and the mean of trading volume with a window size of 1000. It can be observed that there is a clustering phenomenon in trading, where the frequency of orders is significantly higher than usual for a period of time, and the volume also increases synchronously. Here, the mean of the previous values is used to predict the latest value, and the mean absolute error of the residuals is used to measure the quality of the prediction.<br /><br />From the graphs, we can also understand why the order frequency deviates so much from the Poisson distribution. Although the mean number of orders per second is only 8.5, extreme cases deviate significantly from this value.<br /><br />It is found that using the mean of the previous two seconds to predict yields the smallest residual error, and it is much better than simply using the mean for prediction results.<br /><br />In [221]:<br />```<br />result_df['order_count'].rolling(1000).mean().plot(figsize=(10,5),grid=True);<br />```<br /><br />Out[221]:<br /><a href="https://stocksharp.com/file/149660
" title="https://stocksharp.com/file/149660
">https://stocksharp.com/file/149660
</a><br /><br />In [193]:<br />```<br />result_df['quantity_sum'].rolling(1000).mean().plot(figsize=(10,5),grid=True);<br />```<br /><br />Out[193]:<br /><a href="https://stocksharp.com/file/149658
" title="https://stocksharp.com/file/149658
">https://stocksharp.com/file/149658
</a><br /><br />In [195]:<br />```<br />(result_df['order_count'] - result_df['mean_count'].mean()).abs().mean()<br />```<br /><br />Out[195]:<br /><br />6.985628185332997<br /><br />In [205]:<br />```<br />result_df['mean_count'] = result_df['order_count'].rolling(2).mean()<br />(result_df['order_count'] - result_df['mean_count'].shift()).abs().mean()<br />```<br /><br />Out[205]:<br /><br />3.091737586730269<br /><br /><b>Summary</b><br /><br />This article briefly explains the reasons for the deviation of order time intervals from the Poisson process, mainly due to the variation of parameters over time. In order to accurately predict the market, strategies need to make real-time forecasts of the fundamental parameters of the market. Residuals can be used to measure the quality of the predictions. The example provided above is a simple demonstration, and there is extensive research on specific time series analysis, volatility clustering, and other related topics, the above demonstration can be further improved.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y62klVQmZvlTo3phT-rCTmYIUCcEqFi5p8AtLvJFlMhEKyo02EnHxnu9As9UjtJ-CA_YNXwP8GEd9W9Nox_6TYXY" title="https://blog.mathquant.com/2023/08/08/thoughts-on-high-frequency-trading-strategies-3.html">https://blog.mathquant.c...rading-strategies-3.html</a>https://stocksharp.com/topic/25626/Thoughts on High-Frequency Trading Strategies (2)2024-03-26T01:19:48Z2024-03-26T01:19:48ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.com<b>Accumulated Trading Amount Modeling</b><br /><br />In the previous article, we derived an expression for the probability of a single trade amount being greater than a certain value.<br /><a href="https://stocksharp.com/file/149650
" title="https://stocksharp.com/file/149650
">https://stocksharp.com/file/149650
</a><br /><br />We are also interested in the distribution of trading amount over a period of time, which intuitively should be related to the individual trade amount and order frequency. Below, we process the data in fixed intervals and plot its distribution, similar to what was done in the previous section.<br /><br />In [1]:<br />```<br />from datetime import date,datetime<br />import time<br />import pandas as pd<br />import numpy as np<br />import matplotlib.pyplot as plt<br />%matplotlib inline<br />```<br /><br />In [2]:<br />```<br />trades = pd.read_csv('HOOKUSDT-aggTrades-2023-01-27.csv')<br />trades['date'] = pd.to_datetime(trades['transact_time'], unit='ms')<br />trades.index = trades['date']<br />buy_trades = trades[trades['is_buyer_maker']==False].copy()<br />buy_trades = buy_trades.groupby('transact_time').agg({<br /> 'agg_trade_id': 'last',<br /> 'price': 'last',<br /> 'quantity': 'sum',<br /> 'first_trade_id': 'first',<br /> 'last_trade_id': 'last',<br /> 'is_buyer_maker': 'last',<br /> 'date': 'last',<br /> 'transact_time':'last'<br />})<br />buy_trades['interval']=buy_trades['transact_time'] - buy_trades['transact_time'].shift()<br />buy_trades.index = buy_trades['date']<br />```<br /><br />We combine the individual trade amounts at intervals of 1 second to obtain the aggregated trading amount, excluding periods with no trading activity. We then fit this aggregated amount using the distribution derived from the single trade amount analysis mentioned earlier. The results show a good fit when considering each trade within the 1-second interval as a single trade, effectively solving the problem. However, when the time interval is extended relative to the trading frequency, we observe an increase in errors. Further research reveals that this error is caused by the correction term introduced by the Pareto distribution. This suggests that as the time interval lengthens and includes more individual trades, the aggregation of multiple trades approaches the Pareto distribution more closely, necessitating the removal of the correction term.<br /><br />In [3]:<br />```<br />df_resampled = buy_trades['quantity'].resample('1S').sum()<br />df_resampled = df_resampled.to_frame(name='quantity')<br />df_resampled = df_resampled[df_resampled['quantity']>0]<br />```<br /><br />In [4]:<br />```<br /># Cumulative distribution in 1s<br />depths = np.array(range(0, 3000, 5))<br />probabilities = np.array([np.mean(df_resampled['quantity'] > depth) for depth in depths])<br />mean = df_resampled['quantity'].mean()<br />alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2.05)<br />probabilities_s = np.array([((1+20**(-depth/mean))*depth/mean+1)**(alpha) for depth in depths])<br /><br />plt.figure(figsize=(10, 5))<br />plt.plot(depths, probabilities)<br />plt.plot(depths, probabilities_s)<br />plt.xlabel('Depth')<br />plt.ylabel('Probability of execution')<br />plt.title('Execution probability at different depths')<br />plt.grid(True)<br />```<br /><br />Out[4]:<br /><a href="https://stocksharp.com/file/149653
" title="https://stocksharp.com/file/149653
">https://stocksharp.com/file/149653
</a><br /><br />In [5]:<br />```<br />df_resampled = buy_trades['quantity'].resample('30S').sum()<br />df_resampled = df_resampled.to_frame(name='quantity')<br />df_resampled = df_resampled[df_resampled['quantity']>0]<br />depths = np.array(range(0, 12000, 20))<br />probabilities = np.array([np.mean(df_resampled['quantity'] > depth) for depth in depths])<br />mean = df_resampled['quantity'].mean()<br />alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2.05)<br />probabilities_s = np.array([((1+20**(-depth/mean))*depth/mean+1)**(alpha) for depth in depths])<br />alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2)<br />probabilities_s_2 = np.array([(depth/mean+1)**alpha for depth in depths]) # No amendment<br /><br />plt.figure(figsize=(10, 5))<br />plt.plot(depths, probabilities,label='Probabilities (True)')<br />plt.plot(depths, probabilities_s, label='Probabilities (Simulation 1)')<br />plt.plot(depths, probabilities_s_2, label='Probabilities (Simulation 2)')<br />plt.xlabel('Depth')<br />plt.ylabel('Probability of execution')<br />plt.title('Execution probability at different depths')<br />plt.legend() <br />plt.grid(True)<br />```<br /><br />Out[5]:<br /><a href="https://stocksharp.com/file/149654
" title="https://stocksharp.com/file/149654
">https://stocksharp.com/file/149654
</a><br /><br />Now summarize a general formula for the distribution of accumulated trading amount for different time periods, using the distribution of single transaction amount to fit, instead of separately calculating each time. Here is the formula:<br /><a href="https://stocksharp.com/file/149652
" title="https://stocksharp.com/file/149652
">https://stocksharp.com/file/149652
</a><br />Here, avg_interval represents the average interval of single transactions, and avg_interval_T represents the average interval of the interval that needs to be estimated. It may sound a bit confusing. If we want to estimate the trading amount for 1 second, we need to calculate the average interval between events containing transactions within 1 second. If the arrival probability of orders follows a Poisson distribution, it should be directly estimable. However, in reality, there is a significant deviation, but I won't elaborate on it here.<br /><br />Note that the probability of trading amount exceeding a specific value within a certain interval of time and the actual probability of trading at that position in the depth should be quite different. As the waiting time increases, the possibility of changes in the order book increases, and trading also leads to changes in the depth. Therefore, the probability of trading at the same depth position changes in real-time as the data updates.<br /><br />In [6]:<br />```<br />df_resampled = buy_trades['quantity'].resample('2S').sum()<br />df_resampled = df_resampled.to_frame(name='quantity')<br />df_resampled = df_resampled[df_resampled['quantity']>0]<br />depths = np.array(range(0, 6500, 10))<br />probabilities = np.array([np.mean(df_resampled['quantity'] > depth) for depth in depths])<br />mean = buy_trades['quantity'].mean()<br />adjust = buy_trades['interval'].mean() / 2620<br />alpha = np.log(np.mean(buy_trades['quantity'] > mean))/0.7178397931503168<br />probabilities_s = np.array([((1+20**(-depth*adjust/mean))*depth*adjust/mean+1)**(alpha) for depth in depths])<br /><br />plt.figure(figsize=(10, 5))<br />plt.plot(depths, probabilities)<br />plt.plot(depths, probabilities_s)<br />plt.xlabel('Depth')<br />plt.ylabel('Probability of execution')<br />plt.title('Execution probability at different depths')<br />plt.grid(True)<br />```<br /><br />Out[6]:<br /><a href="https://stocksharp.com/file/149651
" title="https://stocksharp.com/file/149651
">https://stocksharp.com/file/149651
</a><br /><br /><b>Single Trade Price Impact</b><br /><br />Trade data is valuable, and there is still a lot of data that can be mined. We should pay close attention to the impact of orders on prices, as this affects the positioning of strategies. Similarly, aggregating data based on transact_time, we calculate the difference between the last price and the first price. If there is only one order, the price difference is 0. Interestingly, there are a few data results that are negative, which may be due to the ordering of the data, but we won't delve into it here.<br /><br />The results show that the proportion of trades that did not cause any impact is as high as 77%, while the proportion of trades causing a price movement of 1 tick is 16.5%, 2 ticks is 3.7%, 3 ticks is 1.2%, and more than 4 ticks is less than 1%. This basically follows the characteristics of an exponential function, but the fitting is not precise.<br /><br />The trade amount causing the corresponding price difference was also analyzed, excluding distortions caused by excessive impact. It shows a linear relationship, with approximately 1 tick of price fluctuation caused by every 1000 units of amount. This can also be understood as an average of around 1000 units of orders placed near each price level in the order book.<br /><br />In [7]:<br />```<br />diff_df = trades[trades['is_buyer_maker']==False].groupby('transact_time')['price'].agg(lambda x: abs(round(x.iloc[-1] - x.iloc[0],3)) if len(x) > 1 else 0)<br />buy_trades['diff'] = buy_trades['transact_time'].map(diff_df)<br />```<br /><br />In [8]:<br />```<br />diff_counts = buy_trades['diff'].value_counts()<br />diff_counts[diff_counts>10]/diff_counts.sum()<br />```<br /><br />Out[8]:<br /><a href="https://stocksharp.com/file/149649
" title="https://stocksharp.com/file/149649
">https://stocksharp.com/file/149649
</a><br /><br />In [9]:<br />```<br />diff_group = buy_trades.groupby('diff').agg({<br /> 'quantity': 'mean',<br /> 'diff': 'last',<br />})<br />```<br /><br />In [10]:<br />```<br />diff_group['quantity'][diff_group['diff']>0][diff_group['diff']<0.01].plot(figsize=(10,5),grid=True);<br />```<br /><br />Out[10]:<br /><a href="https://stocksharp.com/file/149648
" title="https://stocksharp.com/file/149648
">https://stocksharp.com/file/149648
</a><br /><br /><b>Fixed Interval Price Impact</b><br /><br />Let's analyze the price impact within a 2-second interval. The difference here is that there may be negative values. However, since we are only considering buy orders, the impact on the symmetrical position would be one tick higher. Continuing to observe the relationship between trade amount and impact, we only consider results greater than 0. The conclusion is similar to that of a single order, showing an approximate linear relationship, with approximately 2000 units of amount needed for each tick.<br /><br />In [11]:<br />```<br />df_resampled = buy_trades.resample('2S').agg({ <br /> 'price': ['first', 'last', 'count'],<br /> 'quantity': 'sum'<br />})<br />df_resampled['price_diff'] = round(df_resampled[('price', 'last')] - df_resampled[('price', 'first')],3)<br />df_resampled['price_diff'] = df_resampled['price_diff'].fillna(0)<br />result_df_raw = pd.DataFrame({<br /> 'price_diff': df_resampled['price_diff'],<br /> 'quantity_sum': df_resampled[('quantity', 'sum')],<br /> 'data_count': df_resampled[('price', 'count')]<br />})<br />result_df = result_df_raw[result_df_raw['price_diff'] != 0]<br />```<br /><br />In [12]:<br />```<br />result_df['price_diff'][abs(result_df['price_diff'])<0.016].value_counts().sort_index().plot.bar(figsize=(10,5));<br />```<br /><br />Out[12]:<br /><a href="https://stocksharp.com/file/149646
" title="https://stocksharp.com/file/149646
">https://stocksharp.com/file/149646
</a><br /><br />In [23]:<br />```<br />result_df['price_diff'].value_counts()[result_df['price_diff'].value_counts()>30]<br />```<br /><br />Out[23]:<br /><a href="https://stocksharp.com/file/149645
" title="https://stocksharp.com/file/149645
">https://stocksharp.com/file/149645
</a><br /><br />In [14]:<br />```<br />diff_group = result_df.groupby('price_diff').agg({ 'quantity_sum': 'mean'})<br />```<br /><br />In [15]:<br />```<br />diff_group[(diff_group.index>0) & (diff_group.index<0.015)].plot(figsize=(10,5),grid=True);<br />```<br /><br />Out[15]:<br /><a href="https://stocksharp.com/file/149647
" title="https://stocksharp.com/file/149647
">https://stocksharp.com/file/149647
</a><br /><br /><b>Trade Amount's Price Impact</b><br /><br />Previously, we determined the trade amount required for a tick change, but it was not precise as it was based on the assumption that the impact had already occurred. Now, let's reverse the perspective and examine the price impact caused by trade amount.<br /><br />In this analysis, the data is sampled every 1 second, with each step representing 100 units of amount. We then calculated the price changes within this amount range. Here are some valuable conclusions:<br /><br />1. When the buy order amount is below 500, the expected price change is a decrease, which is as expected since there are also sell orders impacting the price.<br />2. At lower trade amounts, there is a linear relationship, meaning that the larger the trade amount, the greater the price increase.<br />3. As the buy order amount increases, the price change becomes more significant. This often indicates a price breakthrough, which may later regress. Additionally, the fixed interval sampling adds to the data instability.<br />4. It is important to pay attention to the upper part of the scatter plot, which corresponds to the increase in price with trade amount.<br />5. For this specific trading pair, we provide a rough version of the relationship between trade amount and price change.<br /><a href="https://stocksharp.com/file/149642
" title="https://stocksharp.com/file/149642
">https://stocksharp.com/file/149642
</a><br /><br />Where "C" represents the change in price and "Q" represents the amount of buy orders.<br /><br />In [16]:<br />```<br />df_resampled = buy_trades.resample('1S').agg({ <br /> 'price': ['first', 'last', 'count'],<br /> 'quantity': 'sum'<br />})<br />df_resampled['price_diff'] = round(df_resampled[('price', 'last')] - df_resampled[('price', 'first')],3)<br />df_resampled['price_diff'] = df_resampled['price_diff'].fillna(0)<br />result_df_raw = pd.DataFrame({<br /> 'price_diff': df_resampled['price_diff'],<br /> 'quantity_sum': df_resampled[('quantity', 'sum')],<br /> 'data_count': df_resampled[('price', 'count')]<br />})<br />result_df = result_df_raw[result_df_raw['price_diff'] != 0]<br />```<br /><br />In [24]:<br />```<br />df = result_df.copy()<br />bins = np.arange(0, 30000, 100) # <br />labels = [f'{i}-{i+100-1}' for i in bins[:-1]] <br />df.loc[:, 'quantity_group'] = pd.cut(df['quantity_sum'], bins=bins, labels=labels)<br />grouped = df.groupby('quantity_group')['price_diff'].mean()<br />```<br /><br />In [25]:<br />```<br />grouped_df = pd.DataFrame(grouped).reset_index()<br />grouped_df['quantity_group_center'] = grouped_df['quantity_group'].apply(lambda x: (float(x.split('-')[0]) + float(x.split('-')[1])) / 2)<br /><br />plt.figure(figsize=(10,5))<br />plt.scatter(grouped_df['quantity_group_center'], grouped_df['price_diff'],s=10)<br />plt.plot(grouped_df['quantity_group_center'], np.array(grouped_df['quantity_group_center'].values)/2e6-0.000352,color='red')<br />plt.xlabel('quantity_group_center')<br />plt.ylabel('average price_diff')<br />plt.title('Scatter plot of average price_diff by quantity_group')<br />plt.grid(True)<br />```<br /><br />Out[25]:<br /><a href="https://stocksharp.com/file/149644
" title="https://stocksharp.com/file/149644
">https://stocksharp.com/file/149644
</a><br /><br />In [19]:<br />```<br />grouped_df.head(10)<br />```<br /><br />Out[19]:<br />, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,<br /><a href="https://stocksharp.com/file/149643
" title="https://stocksharp.com/file/149643
">https://stocksharp.com/file/149643
</a><br /><br /><b>Preliminary Optimal Order Placement</b><br /><br />With the modeling of trade amount and the rough model of price impact corresponding to trade amount, it seems possible to calculate the optimal order placement. Let's make some assumptions and provide an irresponsible optimal price position.<br /><br />1. Assume that the price regresses to its original value after the impact (which is highly unlikely and would require further analysis of the price change after the impact).<br />2. Assume that the distribution of trade amount and order frequency during this period follows a preset pattern (which is also inaccurate, as we are estimating based on one day's data and trading exhibits clear clustering phenomena).<br />3. Assume that only one sell order occurs during the simulated time and then is closed.<br />4. Assume that after the order is executed, there are other buy orders that continue to push up the price, especially when the amount is very low. This effect is ignored here, and it is simply assumed that the price will regress.<br /><br />Let's start by writing a simple expected return, which is the probability of cumulative buy orders exceeding Q within 1 second, multiplied by the expected return rate (i.e., the price impact).<br /><a href="https://stocksharp.com/file/149640
" title="https://stocksharp.com/file/149640
">https://stocksharp.com/file/149640
</a><br /><br />Based on the graph, the maximum expected return is approximately 2500, which is about 2.5 times the average trade amount. This suggests that the sell order should be placed at a price position of 2500. It is important to emphasize that the horizontal axis represents trade amount within 1 second and should not be equated with depth position. Additionally, this analysis is based on trades data and lacks important depth data.<br /><br /><b>Summary</b><br /><br />We have discovered that trade amount distribution at different time intervals is a simple scaling of the distribution of individual trade amounts. We have also developed a simple expected return model based on price impact and trade probability. The results of this model align with our expectations, showing that if the sell order amount is low, it indicates a price decrease, and a certain amount is needed for profit potential. The probability decreases as the trade amount increases, with an optimal size in between, which represents the optimal order placement strategy. However, this model is still too simplistic. In the next article, I will delve deeper into this topic.<br /><br />In [20]:<br />```<br /># Cumulative distribution in 1s<br />df_resampled = buy_trades['quantity'].resample('1S').sum()<br />df_resampled = df_resampled.to_frame(name='quantity')<br />df_resampled = df_resampled[df_resampled['quantity']>0]<br /><br />depths = np.array(range(0, 15000, 10))<br />mean = df_resampled['quantity'].mean()<br />alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2.05)<br />probabilities_s = np.array([((1+20**(-depth/mean))*depth/mean+1)**(alpha) for depth in depths])<br />profit_s = np.array([depth/2e6-0.000352 for depth in depths])<br />plt.figure(figsize=(10, 5))<br />plt.plot(depths, probabilities_s*profit_s)<br />plt.xlabel('Q')<br />plt.ylabel('Excpet profit')<br />plt.grid(True)<br />```<br /><br />Out[20]:<br /><a href="https://stocksharp.com/file/149641
" title="https://stocksharp.com/file/149641
">https://stocksharp.com/file/149641
</a><br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y65E87vDtvnJJc474NzmMzPPJN9Y6ZnGodgAgIvD-7H_bi-YS7AzPMc0hOvgIv-dIZK3J240VMWnZN8XmoOVCgHY" title="https://blog.mathquant.com/2023/08/04/thoughts-on-high-frequency-trading-strategies-2.html">https://blog.mathquant.c...rading-strategies-2.html</a>https://stocksharp.com/topic/25622/Thoughts on High-Frequency Trading Strategies (1)2024-03-25T09:08:04Z2024-03-25T09:08:04ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comI have written two articles on high-frequency trading of digital currencies, namely "[Digital Currency High-Frequency Strategy Detailed Introduction](https://www.fmz.com/bbs-topic/10009)" and "[Earn 80 Times in 5 Days, the Power of High-frequency Strategy](https://www.fmz.com/bbs-topic/9750)". However, these articles can only be considered as sharing experiences and provide a general overview. This time, I plan to write a series of articles to introduce the thought process behind high-frequency trading from scratch. I hope to keep it concise and clear, but due to my limited expertise, my understanding of high-frequency trading may not be very in-depth. This article should be seen as a starting point for discussion, and I welcome corrections and guidance from experts.<br /><br /><b>Source of High-Frequency Profits</b><br /><br />In my previous articles, I mentioned that high-frequency strategies are particularly suitable for markets with extremely volatile fluctuations. The price changes of a trading instrument within a short period of time consist of overall trends and oscillations. While it is indeed profitable if we can accurately predict trend changes, this is also the most challenging aspect. In this article, I will primarily focus on high-frequency maker strategies and will not delve into trend prediction. In oscillating markets, by placing bid and ask orders strategically, if the frequency of executions is high enough and the profit margin is significant, it can cover potential losses caused by trends. In this way, profitability can be achieved without predicting market movements. Currently, exchanges provide rebates for maker trades, which are also a component of profits. The more competitive the market, the higher the proportion of rebates should be.<br /><br /><b>Problems to be Addressed</b><br /><br />1. The first problem in implementing a strategy that places both buy and sell orders is determining where to place these orders. The closer the orders are placed to the market depth, the higher the probability of execution. However, in highly volatile market conditions, the price at which an order is instantly executed may be far from the market depth, resulting in insufficient profit. On the other hand, placing orders too far away reduces the probability of execution. This is an optimization problem that needs to be addressed.<br /><br />2. Position control is crucial to manage risk. A strategy cannot accumulate excessive positions for extended periods. This can be addressed by controlling the distance and quantity of orders placed, as well as setting limits on overall positions.<br /><br />To achieve the above objectives, modeling and estimation are required for various aspects such as execution probabilities, profit from executions, and market estimation. There are numerous articles and papers available on this topic, using keywords such as "High-Frequency Trading" and "Orderbook." Many recommendations can also be found online, although further elaboration is beyond the scope of this article. Additionally, it is advisable to establish a reliable and fast backtesting system. Although high-frequency strategies can easily be validated through live trading, backtesting provides additional insights and helps reduce the cost of trial and error.<br /><br /><b>Required Data</b><br /><br />Binance provides [downloadable data](https://www.binance.com/en/landing/data) for individual trades and best bid/ask orders. Depth data can be downloaded through their API by being whitelisted, or it can be collected manually. For backtesting purposes, aggregated trade data is sufficient. In this article, we will use the example of HOOKUSDT-aggTrades-2023-01-27 data.<br /><br />In [1]:<br />```<br />from datetime import date,datetime<br />import time<br />import pandas as pd<br />import numpy as np<br />import matplotlib.pyplot as plt<br />%matplotlib inline<br />```<br /><br />The individual trade data includes the followings:<br /><br />1. agg_trade_id: The ID of the aggregated trade.<br />2. price: The price at which the trade was executed.<br />3. quantity: The quantity of the trade.<br />4. first_trade_id: In cases where multiple trades are aggregated, this represents the ID of the first trade.<br />5. last_trade_id: The ID of the last trade in the aggregation.<br />6. transact_time: The timestamp of the trade execution.<br />7. is_buyer_maker: Indicates the direction of the trade. "True" represents a buy order executed as a maker, while a sell order is executed as a taker.<br /><br />It can be seen that there were 660,000 trades executed on that day, indicating a highly active market. The CSV file will be attached in the comments section.<br /><br />In [4]:<br />```<br />trades = pd.read_csv('COMPUSDT-aggTrades-2023-07-02.csv')<br />trades<br />```<br /><br />Out[4]:<br />, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,<br /><a href="https://stocksharp.com/file/149605
" title="https://stocksharp.com/file/149605
">https://stocksharp.com/file/149605
</a><br />664475 rows × 7 columns<br /><br /><b>Modeling Individual Trade Amount</b><br /><br />First, the data is processed by dividing the original trades into two groups: buy orders executed as makers and sell orders executed as takers. Additionally, the original aggregated trade data combines trades executed at the same time, at the same price, and in the same direction into a single data point. For example, if there is a single buy order with a volume of 100, it may be split into two trades with volumes of 60 and 40, respectively, if the prices are different. This can affect the estimation of buy order volumes. Therefore, it is necessary to aggregate the data again based on the transact_time. After this second aggregation, the data volume is reduced by 140,000 records.<br /><br />In [6]:<br />```<br />trades['date'] = pd.to_datetime(trades['transact_time'], unit='ms')<br />trades.index = trades['date']<br />buy_trades = trades[trades['is_buyer_maker']==False].copy()<br />sell_trades = trades[trades['is_buyer_maker']==True].copy()<br />buy_trades = buy_trades.groupby('transact_time').agg({<br /> 'agg_trade_id': 'last',<br /> 'price': 'last',<br /> 'quantity': 'sum',<br /> 'first_trade_id': 'first',<br /> 'last_trade_id': 'last',<br /> 'is_buyer_maker': 'last',<br /> 'date': 'last',<br /> 'transact_time':'last'<br />})<br />sell_trades = sell_trades.groupby('transact_time').agg({<br /> 'agg_trade_id': 'last',<br /> 'price': 'last',<br /> 'quantity': 'sum',<br /> 'first_trade_id': 'first',<br /> 'last_trade_id': 'last',<br /> 'is_buyer_maker': 'last',<br /> 'date': 'last',<br /> 'transact_time':'last'<br />})<br />buy_trades['interval']=buy_trades['transact_time'] - buy_trades['transact_time'].shift()<br />sell_trades['interval']=sell_trades['transact_time'] - sell_trades['transact_time'].shift()<br />```<br /><br />In [10]:<br />```<br />print(trades.shape[0] - (buy_trades.shape[0]+sell_trades.shape[0]))<br />```<br /><br />Out [10]:<br />146181<br /><br />Take buy orders as an example, let's first plot a histogram. It can be observed that there is a significant long-tail effect, with the majority of data concentrated towards the leftmost part of the histogram. However, there are also a few large trades distributed towards the tail end.<br /><br />In [36]:<br />```<br />buy_trades['quantity'].plot.hist(bins=200,figsize=(10, 5));<br />```<br /><br />Out [36]:<br /><a href="https://stocksharp.com/file/149606
" title="https://stocksharp.com/file/149606
">https://stocksharp.com/file/149606
</a><br />For easier observation, let's trim the tail and analyze the data. It can be observed that as the trade amount increases, the frequency of occurrence decreases, and the rate of decrease becomes faster.<br /><br />In [37]:<br />```<br />buy_trades['quantity'][buy_trades['quantity']<200].plot.hist(bins=200,figsize=(10, 5));<br />```<br /><br />Out [37]:<br /><a href="https://stocksharp.com/file/149607
" title="https://stocksharp.com/file/149607
">https://stocksharp.com/file/149607
</a><br />There have been numerous studies on the distribution of trade amounts. It has been found that trade amounts follow a power-law distribution, also known as a Pareto distribution, which is a common probability distribution in statistical physics and social sciences. In a power-law distribution, the probability of an event's size (or frequency) is proportional to a negative exponent of that event's size. The main characteristic of this distribution is that the frequency of large events (i.e., those far from the average) is higher than expected in many other distributions. This is precisely the characteristic of trade amount distribution. The form of the Pareto distribution is given by P(x) = Cx^(-α). Let's empirically verify this.<br /><br />The following graph represents the probability of trade amounts exceeding a certain value. The blue line represents the actual probability, while the orange line represents the simulated probability. Please note that we won't go into the specific parameters at this point. It can be observed that the distribution indeed follows a Pareto distribution. Since the probability of trade amounts being greater than zero is 1, and in order to satisfy normalization, the distribution equation should be as follows:<br /><a href="https://stocksharp.com/file/149608
" title="https://stocksharp.com/file/149608
">https://stocksharp.com/file/149608
</a><br />Here, N is the parameter for normalization. We will choose the average trade amount, M, and set alpha to -2.06. The specific estimation of alpha can be obtained by calculating the P-value when D=N. Specifically, alpha = log(P(d>M))/log(2). The choice of different points may result in slight differences in the value of alpha.<br /><br />In [55]:<br />```<br />depths = range(0, 250, 2)<br />probabilities = np.array([np.mean(buy_trades['quantity'] > depth) for depth in depths])<br />alpha = np.log(np.mean(buy_trades['quantity'] > mean_quantity))/np.log(2)<br />mean_quantity = buy_trades['quantity'].mean()<br />probabilities_s = np.array([(1+depth/mean_quantity)**alpha for depth in depths])<br /><br />plt.figure(figsize=(10, 5))<br />plt.plot(depths, probabilities)<br />plt.plot(depths, probabilities_s)<br />plt.xlabel('Depth')<br />plt.ylabel('Probability of execution')<br />plt.title('Execution probability at different depths')<br />plt.grid(True)<br />```<br /><br />Out[55]:<br /><a href="https://stocksharp.com/file/149613
" title="https://stocksharp.com/file/149613
">https://stocksharp.com/file/149613
</a><br />In [56]:<br />```<br />plt.figure(figsize=(10, 5))<br />plt.grid(True)<br />plt.title('Diff')<br />plt.plot(depths, probabilities_s-probabilities);<br />```<br /><br />Out[56]:<br /><a href="https://stocksharp.com/file/149610
" title="https://stocksharp.com/file/149610
">https://stocksharp.com/file/149610
</a><br />However, this estimation is only approximate, as shown in the graph where we plot the difference between the simulated and actual values. When the trade amount is small, the deviation is significant, even approaching 10%. Although selecting different points during parameter estimation may improve the accuracy of that specific point's probability, it does not solve the deviation issue as a whole. This discrepancy arises from the difference between the power-law distribution and the actual distribution. To obtain more accurate results, the equation of the power-law distribution needs to be modified. The specific process is not elaborated here, but in summary, after a moment of insight, it is found that the actual equation should be as follows:<br /><a href="https://stocksharp.com/file/149612
" title="https://stocksharp.com/file/149612
">https://stocksharp.com/file/149612
</a><br />To simplify, let's use r = q/M to represent the normalized trade amount. We can estimate the parameters using the same method as before. The following graph shows that after the modification, the maximum deviation is no more than 2%. In theory, further adjustments can be made, but this level of accuracy is already sufficient.<br /><br />In [52]:<br />```<br />depths = range(0, 250, 2)<br />probabilities = np.array([np.mean(buy_trades['quantity'] > depth) for depth in depths])<br />mean = buy_trades['quantity'].mean()<br />alpha = np.log(np.mean(buy_trades['quantity'] > mean))/np.log(2.05)<br />probabilities_s = np.array([(((1+20**(-depth/mean))*depth+mean)/mean)**alpha for depth in depths])<br /><br />plt.figure(figsize=(10, 5))<br />plt.plot(depths, probabilities)<br />plt.plot(depths, probabilities_s)<br />plt.xlabel('Depth')<br />plt.ylabel('Probability of execution')<br />plt.title('Execution probability at different depths')<br />plt.grid(True)<br />```<br /><br />Out[52]:<br /><a href="https://stocksharp.com/file/149609
" title="https://stocksharp.com/file/149609
">https://stocksharp.com/file/149609
</a><br />In [53]:<br />```<br />plt.figure(figsize=(10, 5))<br />plt.grid(True)<br />plt.title('Diff')<br />plt.plot(depths, probabilities_s-probabilities);<br />```<br /><br />Out[53]:<br /><a href="https://stocksharp.com/file/149611
" title="https://stocksharp.com/file/149611
">https://stocksharp.com/file/149611
</a><br />With the estimated equation for the trade amount distribution, it is important to note that the probabilities in the equation are not the actual probabilities, but conditional probabilities. At this point, we can answer the question: What is the probability that the next order will be greater than a certain value? We can also determine the probability of orders at different depths being executed (in an ideal scenario, without considering order additions, cancellations, and queueing at the same depth).<br /><br />At this point, the length of the text is already quite long, and there are still many questions that need to be answered. The following series of articles will attempt to provide answers.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y65E87vDtvnJJc474NzmMzPPJN9Y6ZnGodgAgIvD-7H_bi-YS7AzPMc0hOvgIv-dIZMVywi_S3WoczZm2nla8u14" title="https://blog.mathquant.com/2023/08/04/thoughts-on-high-frequency-trading-strategies-1.html">https://blog.mathquant.c...rading-strategies-1.html</a>https://stocksharp.com/topic/25621/Empower Your Quantitative Trading: Unleashing the Potential of FMZ Mobile App Trading Terminal2024-03-25T08:01:20Z2024-03-25T08:03:06ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comIn the field of quantitative trading, simple and easy-to-use quantitative trading tools have always been one of the keys to achieving wealth growth and risk management. However, with increasing market competition, traditional trading tools are no longer sufficient to meet rapidly changing market demands. To maintain a competitive edge for quantitative traders in the constantly evolving digital asset world, FMZ Mobile App has added a significant new feature: Trading Terminal. This feature will not only improve your trading efficiency, but also empower you through custom plugin programs to assist in trading, injecting new vitality into your trading career.<br /><br /><b><span style="font-size:140%">Beginner&#39;s Guide to Trading Terminal:</span></b><br /><b>What is the FMZ Mobile APP Trading Terminal?</b><br />On the FMZ Quant Trading Platform, you can download the FMZ Quant Mobile APP from the [Mobile App download page](https://www.fmz.com/mobile). After downloading and installing, open the FMZ mobile app and log in with your FMZ account.<br /><a href="https://stocksharp.com/file/149582
" title="https://stocksharp.com/file/149582
">https://stocksharp.com/file/149582
</a><br />Please note that FMZ Quant is divided into FMZ.COM international site and FMZ.CN China domestic site (supporting different markets). When logging in, you need to choose the corresponding site. Accounts for different sites are independent and not interchangeable.<br /><br />The FMZ Quantitative Trading Platform mobile APP trading terminal is a quantitative trading tool that encapsulates APIs from major exchanges. It allows quick switching between various exchanges, and with the help of various features of the FMZ platform, it can perform data capture analysis, real-time data monitoring, program-assisted trading, semi-automatic/manual trading operations etc.<br /><br /><b>How to access and enable the trading terminal function?</b><br />After logging into the FMZ Quant Mobile APP, you can see the &quot;Trading Terminal&quot; function on the main interface. Click to enter the trading terminal interface.<br /><br />Before FMZ launched its mobile APP trading terminal, FMZ&#39;s web version had already launched this feature quite early. Whether it is a web-based trading terminal or a mobile APP-based one, **at least one docker program must be deployed**.<br />This is because all actual requests sent to exchanges are executed from the docker, not on the mobile app, which is safer. It also avoids disadvantages such as API KEY binding IP addresses and inability to use when mobile IP changes.<br /><a href="https://stocksharp.com/file/149581
" title="https://stocksharp.com/file/149581
">https://stocksharp.com/file/149581
</a><br /><b>Detailed Explanation of Trading Terminal Interface</b><br />**1. Main Interface of the Trading Terminal:**<br /><br />After opening the trading terminal, you can see the main interface of the trading terminal. Clicking on the area in red frame will open up &quot;Docker&quot;, &quot;Exchange&quot;, and &quot;Markets&quot; configuration interfaces.<br /><br />- Docker: All docker programs deployed under your current FMZ account will be listed here for selection.<br />- Exchange: The exchange objects (configured with API KEY information etc.) created in your current FMZ account will also appear in corresponding lists for specific operation selections.<br />- Markets: Set up the trading pair or contract that this trading terminal is going to operate. The input box widget for trading pairs will display selectable trading pairs/contracts based on entered information.<br /><a href="https://stocksharp.com/file/149583
" title="https://stocksharp.com/file/149583
">https://stocksharp.com/file/149583
</a><br />**2. Trading Zone:**<br /><br />The Trading Zone displays market depth data;<br />Trading widgets can be set with order price, order quantity, order direction, leverage and other settings.<br /><a href="https://stocksharp.com/file/149577
" title="https://stocksharp.com/file/149577
">https://stocksharp.com/file/149577
</a><br />The bottom tabs of the main interface display information such as &quot;Orders&quot;, &quot;Assets&quot;, making your funds, and orders clear at a glance.<br /><br />**3. K-line Chart:**<br /><br />If you wish to view the K-line chart while placing an order, a thoughtful design has been implemented here - a collapsible display widget that unfolds the mini K-line chart of the current product.<br /><a href="https://stocksharp.com/file/149578
" title="https://stocksharp.com/file/149578
">https://stocksharp.com/file/149578
</a><br />If you wish to view the K-line chart in a larger area, display market transaction records, depth information and more, you can click on this K-line icon to jump to the professional K-line chart page.<br /><a href="https://stocksharp.com/file/149576
" title="https://stocksharp.com/file/149576
">https://stocksharp.com/file/149576
</a><br />The professional K-line chart interface:<br /><a href="https://stocksharp.com/file/149580
" title="https://stocksharp.com/file/149580
">https://stocksharp.com/file/149580
</a><br />The professional K-line chart interface can also be displayed in landscape mode:<br /><a href="https://stocksharp.com/file/149579
" title="https://stocksharp.com/file/149579
">https://stocksharp.com/file/149579
</a><br /><b>Trading Plugin</b><br />What can a trading terminal plugin do?<br /><br />- Real-time market data calculation and display.<br />- Order placement and order management.<br />- Implement risk management.<br />- Semi-automatic auxiliary trading strategies.<br /><br />Which programming languages and tools are used to develop plugins?<br /><br />- python<br />- javascript<br />- c++<br /><br />What can you get?<br /><br />- Share your plugin with the community for mutual learning with developers.<br />- Learn from other developers and get inspired.<br />- Interact with other quantitative trading enthusiasts.<br /><br /><b>Give an Example Based on a Real-life Application Scenario:</b><br />In the FMZ community, a user has put forward such a request:<br /><br />&gt; Use js to traverse all U contract currencies of the Binance exchange, and open a position of 10u (long) for each currency. How is this code written?<br /><br />This requirement scenario can be implemented with trading terminal plugins actually, and running plugin strategies on the trading terminal is free of charge. Compared to long-term live trading strategies, using trading terminal plugins as an aid is undoubtedly a good choice.<br /><br />Let&#39;s see how to design and implement the user&#39;s request.<br /><br />Firstly, we need to create a trading terminal plugin and add three parameters to its strategy:<br /><a href="https://stocksharp.com/file/149592
" title="https://stocksharp.com/file/149592
">https://stocksharp.com/file/149592
</a><br />Then start writing the plugin program:<br /><br />```<br />function main() {<br /> let exName = exchange.GetName()<br /> if (exName != &quot;Futures_Binance&quot;) {<br /> return &quot;not support!&quot;<br /> }<br /><br /> let apiBase = &quot;https://fapi.binance.com&quot;<br /> if (isSimulate) {<br /> apiBase = &quot;https://testnet.binancefuture.com&quot; <br /> Log(&quot;Switch base address:&quot;, apiBase)<br /> }<br /> exchange.SetBase(apiBase)<br /> <br /> try {<br /> var obj = JSON.parse(HttpQuery(apiBase + &quot;/fapi/v1/exchangeInfo&quot;))<br /> } catch (e) {<br /> Log(e)<br /> }<br /> <br /> let pairs = []<br /> for (var i in obj.symbols) {<br /> if (obj.symbols[i][&quot;status&quot;] !== &quot;TRADING&quot; || obj.symbols[i][&quot;quoteAsset&quot;] !== &quot;USDT&quot;) {<br /> continue<br /> }<br /> let = pair = obj.symbols[i][&quot;baseAsset&quot;] + &quot;_&quot; + obj.symbols[i][&quot;quoteAsset&quot;]<br /> pairs.push(pair)<br /> }<br /> <br /> let markets = _C(exchange.GetMarkets)<br /> for (var i in pairs) {<br /> // /*<br /> // For testing purposes, only 10 varieties are opened here. If all varieties are needed, this comment content can be deleted.<br /> if (i &gt;= 9) {<br /> break<br /> }<br /> // */<br /><br /> let pair = pairs[i]<br /> exchange.SetCurrency(pair)<br /> exchange.SetContractType(&quot;swap&quot;)<br /> let ticker = exchange.GetTicker()<br /> if (!ticker) {<br /> continue <br /> }<br /> <br /> let = amountPrecision = markets[pair + &quot;.swap&quot;][&quot;AmountPrecision&quot;]<br /> exchange.SetDirection(&quot;buy&quot;)<br /> let amount = _N(qty / ticker.Last, amountPrecision)<br /> if (amount &gt; 0) {<br /> exchange.Buy(-1, amount)<br /> }<br /><br /> Sleep(100)<br /> }<br /><br /> // Obtain all positions<br /> let pos = exchange.IO(&quot;api&quot;, &quot;GET&quot;, &quot;/fapi/v2/positionRisk&quot;)<br /> if (!pos) {<br /> return <br /> }<br /> <br /> // View positions<br /> return pos.filter(item =&gt; Number(item.positionAmt) != 0)<br />}<br />```<br />After the trading terminal plugin is written, it can be tested:<br /><br />In the trading terminal of the mobile APP, click on the &#39;...&#39; button to open the list of trading terminal plugins. All plugins in the strategy library of your current FMZ account will be displayed in this list for selection and use.<br /><a href="https://stocksharp.com/file/149593
" title="https://stocksharp.com/file/149593
">https://stocksharp.com/file/149593
</a><br />After completing the operation on the mobile APP, we use the following code to query the position of Binance&#39;s simulation bot:<br /><br />```<br />function main() {<br /> let apiBase = &quot;https://testnet.binancefuture.com&quot;<br /> exchange.SetBase(apiBase)<br /><br /> let pos = exchange.IO(&quot;api&quot;, &quot;GET&quot;, &quot;/fapi/v2/positionRisk&quot;)<br /> if (!pos) {<br /> return <br /> }<br /><br /> // View positions<br /> return pos.filter(item =&gt; Number(item.positionAmt) != 0)<br />}<br />```<br />Data found:<br /><br />```<br />[{<br /> &quot;symbol&quot;: &quot;ETCUSDT&quot;,<br /> &quot;entryPrice&quot;: &quot;16.17&quot;,<br /> &quot;unRealizedProfit&quot;: &quot;0.08567881&quot;,<br /> &quot;positionSide&quot;: &quot;LONG&quot;,<br /> &quot;updateTime&quot;: 1698420908103,<br /> &quot;isolated&quot;: false,<br /> &quot;breakEvenPrice&quot;: &quot;16.176468&quot;,<br /> &quot;leverage&quot;: &quot;20&quot;,<br /> &quot;adlQuantile&quot;: 3,<br /> &quot;positionAmt&quot;: &quot;0.65&quot;,<br /> &quot;markPrice&quot;: &quot;16.30181356&quot;,<br /> &quot;liquidationPrice&quot;: &quot;0&quot;,<br /> &quot;maxNotionalValue&quot;: &quot;400000&quot;,<br /> &quot;marginType&quot;: &quot;cross&quot;,<br /> &quot;notional&quot;: &quot;10.59617881&quot;,<br /> &quot;isolatedMargin&quot;: &quot;0.00000000&quot;,<br /> &quot;isAutoAddMargin&quot;: &quot;false&quot;,<br /> &quot;isolatedWallet&quot;: &quot;0&quot;<br />}, {<br /> &quot;positionAmt&quot;: &quot;105&quot;,<br /> &quot;markPrice&quot;: &quot;0.09371526&quot;,<br /> &quot;liquidationPrice&quot;: &quot;0&quot;,<br /> &quot;leverage&quot;: &quot;20&quot;,<br /> &quot;maxNotionalValue&quot;: &quot;90000&quot;,<br /> &quot;positionSide&quot;: &quot;LONG&quot;,<br /> &quot;isolatedWallet&quot;: &quot;0&quot;,<br /> &quot;symbol&quot;: &quot;TRXUSDT&quot;,<br /> &quot;updateTime&quot;: 1698420906668,<br /> &quot;breakEvenPrice&quot;: &quot;0.094497784&quot;,<br /> &quot;isolatedMargin&quot;: &quot;0.00000000&quot;,<br /> &quot;isolated&quot;: false,<br /> &quot;entryPrice&quot;: &quot;0.09446&quot;,<br /> &quot;adlQuantile&quot;: 1,<br /> &quot;unRealizedProfit&quot;: &quot;-0.07819770&quot;,<br /> &quot;isAutoAddMargin&quot;: &quot;false&quot;,<br /> &quot;notional&quot;: &quot;9.84010230&quot;,<br /> &quot;marginType&quot;: &quot;cross&quot;<br />}, {<br /> &quot;unRealizedProfit&quot;: &quot;-0.00974456&quot;,<br /> &quot;isAutoAddMargin&quot;: &quot;false&quot;,<br /> &quot;notional&quot;: &quot;9.97449543&quot;,<br /> &quot;isolatedWallet&quot;: &quot;0.50309216&quot;,<br /> &quot;updateTime&quot;: 1698420905377,<br /> &quot;markPrice&quot;: &quot;67.85371047&quot;,<br /> &quot;isolatedMargin&quot;: &quot;0.49334760&quot;,<br /> &quot;adlQuantile&quot;: 2,<br /> &quot;symbol&quot;: &quot;LTCUSDT&quot;,<br /> &quot;entryPrice&quot;: &quot;67.92&quot;,<br /> &quot;liquidationPrice&quot;: &quot;64.91958163&quot;,<br /> &quot;maxNotionalValue&quot;: &quot;250000&quot;,<br /> &quot;positionSide&quot;: &quot;LONG&quot;,<br /> &quot;isolated&quot;: true,<br /> &quot;positionAmt&quot;: &quot;0.147&quot;,<br /> &quot;breakEvenPrice&quot;: &quot;67.947168&quot;,<br /> &quot;leverage&quot;: &quot;20&quot;,<br /> &quot;marginType&quot;: &quot;isolated&quot;<br />}, {<br /> &quot;liquidationPrice&quot;: &quot;1613.23261508&quot;,<br /> &quot;marginType&quot;: &quot;isolated&quot;,<br /> &quot;isolated&quot;: true,<br /> &quot;symbol&quot;: &quot;ETHUSDT&quot;,<br /> &quot;entryPrice&quot;: &quot;1784.27&quot;,<br /> &quot;markPrice&quot;: &quot;1783.35661952&quot;,<br /> &quot;isAutoAddMargin&quot;: &quot;false&quot;,<br /> &quot;positionSide&quot;: &quot;LONG&quot;,<br /> &quot;notional&quot;: &quot;8.91678309&quot;,<br /> &quot;leverage&quot;: &quot;10&quot;,<br /> &quot;maxNotionalValue&quot;: &quot;30000000&quot;,<br /> &quot;isolatedWallet&quot;: &quot;0.89551774&quot;,<br /> &quot;adlQuantile&quot;: 1,<br /> &quot;positionAmt&quot;: &quot;0.005&quot;,<br /> &quot;breakEvenPrice&quot;: &quot;1784.983708&quot;,<br /> &quot;unRealizedProfit&quot;: &quot;-0.00456690&quot;,<br /> &quot;isolatedMargin&quot;: &quot;0.89095084&quot;,<br /> &quot;updateTime&quot;: 1698420900362<br />}, {<br /> &quot;positionAmt&quot;: &quot;17.1&quot;,<br /> &quot;marginType&quot;: &quot;cross&quot;,<br /> &quot;isolatedWallet&quot;: &quot;0&quot;,<br /> &quot;adlQuantile&quot;: 2,<br /> &quot;liquidationPrice&quot;: &quot;0&quot;,<br /> &quot;maxNotionalValue&quot;: &quot;250000&quot;,<br /> &quot;positionSide&quot;: &quot;LONG&quot;,<br /> &quot;isolated&quot;: false,<br /> &quot;symbol&quot;: &quot;EOSUSDT&quot;,<br /> &quot;breakEvenPrice&quot;: &quot;0.6432572&quot;,<br /> &quot;updateTime&quot;: 1698420904257,<br /> &quot;isolatedMargin&quot;: &quot;0.00000000&quot;,<br /> &quot;isAutoAddMargin&quot;: &quot;false&quot;,<br /> &quot;notional&quot;: &quot;10.34550000&quot;,<br /> &quot;entryPrice&quot;: &quot;0.643&quot;,<br /> &quot;markPrice&quot;: &quot;0.60500000&quot;,<br /> &quot;unRealizedProfit&quot;: &quot;-0.64980000&quot;,<br /> &quot;leverage&quot;: &quot;20&quot;<br />}, {<br /> &quot;isolated&quot;: false,<br /> &quot;adlQuantile&quot;: 1,<br /> &quot;liquidationPrice&quot;: &quot;0&quot;,<br /> &quot;maxNotionalValue&quot;: &quot;10000000&quot;,<br /> &quot;notional&quot;: &quot;9.73993328&quot;,<br /> &quot;leverage&quot;: &quot;20&quot;,<br /> &quot;updateTime&quot;: 1698420901638,<br /> &quot;symbol&quot;: &quot;BCHUSDT&quot;,<br /> &quot;entryPrice&quot;: &quot;250.0&quot;,<br /> &quot;markPrice&quot;: &quot;243.49833219&quot;,<br /> &quot;isAutoAddMargin&quot;: &quot;false&quot;,<br /> &quot;positionSide&quot;: &quot;LONG&quot;,<br /> &quot;positionAmt&quot;: &quot;0.040&quot;,<br /> &quot;breakEvenPrice&quot;: &quot;250.1&quot;,<br /> &quot;isolatedMargin&quot;: &quot;0.00000000&quot;,<br /> &quot;unRealizedProfit&quot;: &quot;-0.26006671&quot;,<br /> &quot;marginType&quot;: &quot;cross&quot;,<br /> &quot;isolatedWallet&quot;: &quot;0&quot;<br />}]<br />```<br />It can be seen that 6 positions have been opened, this is because when placing actual orders on the simulation bot, it&#39;s easy to trigger limit prices; in addition, due to the order amount of 10U, it&#39;s easy to trigger the minimum order amount limit of trading pairs; therefore a few trading pairs were not successfully ordered. <br />If you need to use this in reality, more practical situations should be considered in order to optimize this plugin for better usage. The code here is only used for teaching and communication purposes.<br /><br /><b>Other Interesting Built-in Plugins on FMZ</b><br />The FMZ Quant Trading Platform mobile app trading terminal has many interesting plugins. Come and explore together!<br /><br /><a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAADHZKb-RfbDOdB_w3dJgQKt9xqN8kE5Kkn-fL4pSIl_5-9VqG_BAKkEzhNdGLTTMwnI5ljyUq5pbzTOWEMQ7bzY" title="https://www.fmz.com/upload/asset/16b436307a4ce5c246c2.mp4
">https://www.fmz.com/uplo...6b436307a4ce5c246c2.mp4
</a><br /><br /><b>The End</b><br />The new trading terminal feature of the FMZ mobile app will become your powerful assistant in the digital asset market, allowing you to respond more flexibly to market fluctuations and opportunities. No longer limited to traditional trading strategies, through custom plugin programs, you can create smarter, more efficient trading strategies that are better adapted to the market. Let&#39;s start this exciting new chapter of quantitative trading together and enhance your trading skills and profits.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y63a22F2g41PdIIqGtgrMebs9U-8zB3jppLQsJFjxtN8exAtDhnKLxg8X28K29FII8kW57BT0P9cr7xn_l-OMDUnmcxtt9YY008hfWQnZtbHAy33nzd_Dc_PB_c9yUT16PA" title="https://blog.mathquant.com/2023/10/30/fmz-mobile-app-trading-terminal-empowering-your-quantitative-trading-experience.html">https://blog.mathquant.c...-trading-experience.html</a>https://stocksharp.com/topic/25619/Navigating Position Risk: Unveiling VaR Method for Effective Measurement2024-03-25T05:16:19Z2024-03-25T05:16:19ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comControlling risk is a skill that every investor needs to learn. With the rapidly changing and evolving cryptocurrency market, algorithmic traders need to focus on risk management especially. This is because algorithmic trading often executes trades automatically based on historical data and statistical models, which may quickly become inaccurate in fast-moving markets. Therefore, effective risk management strategies are crucial for protecting investors' capital.<br /><br />Among many risk management tools, Value at Risk (VaR) is a widely used measure of risk. It can help investors predict the maximum loss that might occur under normal market conditions in their portfolio. VaR quantifies risk into a single number, simplifying the expression of risk and allowing investors to understand potential losses intuitively.<br /><br /><b>The Role of VaR</b><br />VaR, or "Value at Risk", is used to quantify the maximum possible loss that can be endured within a certain period of time, according to a certain confidence level. In other words, it tells investors or risk managers: "Under normal market conditions, how much money is within the 'safe' range and will not be lost tomorrow." For example, if the 1-day 99% VaR of a cryptocurrency portfolio is $10,000, it means that in 99% of cases we expect the loss in one day will not exceed $10,000.<br /><br /><b>Advantages</b><br />1. **Easy to understand** : For example, the 1-day 95% VaR of a digital currency portfolio is $5000, which means there is a 95% confidence that the loss of the portfolio within one day will not exceed $5000. Quantifying complex risks into an intuitive number makes it easy for non-professionals to understand. Of course, it's inevitably to have some misleading aspects.<br /><br />2. **Relatively standard** : Suppose there are two portfolios A and B, with A's 1-day 95% VaR being $3000 and B's being $6000. This implies that under normal market conditions, A's risk is lower than B's. Even if these two portfolios contain different assets, we can compare their risk levels directly. Correspondingly, we can also judge the level of investment; if both strategies A and B have earned $6000 in the past month but A's average and maximum VaR values are significantly lower than B's, then we could consider strategy A as better, since it achieves higher returns at a lower risk level.<br /><br />3. **Decision-making tool** : Traders might use VaR to decide whether or not to add a new asset to their portfolio. If adding an asset increases the VaR value significantly, then it may suggest that the added asset's risk does not match with the portfolio's acceptable risk level.<br /><br /><b>Disadvantages</b><br />1. **Ignoring tail risk** : If a portfolio's 1-day 99% VaR is $10,000, the loss in the extreme 1% scenario could far exceed this value. In the field of digital currency, black swan events are frequent and extreme situations can exceed most people's expectations, because VaR does not consider tail events.<br /><br />2. **Assumption limitations** : The parameter VaR often assumes that asset returns are normally distributed, which is rarely the case in real markets, especially in digital currency markets. For example, suppose a portfolio only contains Bitcoin. We use the parameter VaR and assume that Bitcoin's return is normally distributed. However, in reality, Bitcoin's rate of return may experience large jumps during certain periods and exhibit significant volatility clustering phenomena. If there has been high volatility over the past week, the probability of noticeable volatility in the following period will increase significantly. This can lead to an underestimation of risk by normal distribution models. Some models take this issue into account such as GARCH etc., but we won't discuss them here.<br /><br />3. **Historical dependence** : The VaR model relies on historical data to predict future risks. However, past performance does not always indicate future situations, especially in rapidly changing markets like the digital currency market. For example, if Bitcoin has been very stable over the past year, a historical simulation might predict a very low VaR. However, if there is a sudden regulatory change or market crash, past data will no longer be an effective predictor of future risk.<br /><br /><b>Methods of Calculating VaR</b><br />There are mainly three methods to calculate VaR: Parametric method (Variance-Covariance Method): This assumes that the rate of return follows a certain distribution (usually normal distribution), and we use the mean and standard deviation of the rate of return to calculate VaR. Historical Simulation Method: It makes no assumptions about the distribution of returns, but uses historical data directly to determine potential loss distributions. Monte Carlo Simulation: It uses randomly generated price paths to simulate asset prices and calculates VaR from them.<br /><br />The Historical Simulation Method utilizes past price changes directly to estimate possible future losses. It does not need any assumptions about profit distribution, making it suitable for assets with unknown or abnormal profit distributions, such as digital currencies.<br /><br />For example, if we want to calculate 1-day 95% VaR for a Bitcoin spot position, we can do this:<br /><br />1. Collect the daily returns of Bitcoin over a certain period (for example, 100 days).<br />2. Calculate the portfolio return rate each day, which is the return rate of each asset multiplied by its weight in the portfolio.<br />3. Sort these 100 days of portfolio returns from low to high.<br />4. Find the data point at the 5% mark (because we are calculating 95% VaR). The point represents the loss rate on the best day out of worst five days in past 100 days.<br />5. Multiply the return by total value held, and that's your one-day 95% VaR.<br /><br />The following is a specific code that has obtained data from the past 1000 days, calculating that the current VaR for holding one BTC spot is 1980 USDT.<br /><br />```<br />import numpy as np<br />import requests<br /><br />url = 'https://api.binance.com/api/v3/klines?symbol=%s&interval=%s&limit=1000'%('BTCUSDT','1d')<br />res = requests.get(url)<br />data = res.json()<br /><br />confidence_level = 0.95<br />closing_prices = [float(day[4]) for day in data]<br />log_returns = np.diff(np.log(closing_prices))<br />VaR = np.percentile(log_returns, (1 - confidence_level) * 100)<br />money_at_risk = VaR * closing_prices[-1] * 1<br /><br />print(f"VaR at {confidence_level*100}% confidence level is {money_at_risk}")<br />```<br /><b>Calculating VaR Considering Correlation</b><br />When calculating the VaR of a portfolio containing multiple assets, we must consider the correlation between these assets. If there is a positive correlation in price changes between assets, then the risk of the portfolio will increase; if it's negatively correlated, then the risk of the portfolio will decrease.<br /><br />When using historical simulation method to calculate VaR considering correlation, we need to collect not only each individual asset's historical returns, but also consider their joint distribution. In practice, you can use the historical returns of your portfolio for sorting and calculation directly, because these returns already implicitly include inter-asset correlations. In cryptocurrency markets, correlation is especially important with BTC essentially leading market trends. If BTC rises bullishly, other cryptocurrencies are likely to rise as well; if BTC rapidly surges or plummets due to quickly changing market, this could cause significant short-term increases in correlation - something that is particularly common during extreme market events. Therefore, historical simulation method is a useful tool when considering digital currency investment portfolios' VaR calculations. It doesn't require complex statistical models - just effective historical data - and naturally includes inter-asset correlations.<br /><br />For example: holding 1 long position on BTC and 10 short positions on ETH – following our previous method – we can calculate that 10 ETH short positions have a VaR of 1219 USDT. When combining these two types of assets into one portfolio, here's how you would calculate its combined VaR:<br /><br />```<br />confidence_level = 0.95<br />btc_closing_prices = np.array([float(day[4]) for day in btc_data])<br />eth_closing_prices = np.array([float(day[4]) for day in eth_data])<br />btc_log_returns = np.diff(np.log(btc_closing_prices))<br />eth_log_returns = np.diff(np.log(eth_closing_prices))<br /><br />log_returns = (1*btc_log_returns*btc_closing_prices[1:] - 10*eth_log_returns*eth_closing_prices[1:])/(1*btc_closing_prices[1:] + 10*eth_closing_prices[1:])<br />VaR = np.percentile(log_returns, (1 - confidence_level) * 100)<br />money_at_risk = VaR * (btc_closing_prices[-1] * 1 + eth_closing_prices[-1]*10)<br /><br />print(f"VaR at {confidence_level*100}% confidence level is {money_at_risk}")<br />```<br />The result is 970 USDT, which means the risk of this combination is lower than holding the respective assets separately. This is because BTC and ETH markets have a high correlation, and the hedging effect of long-short position combinations serves to reduce risk.<br /><br /><b>Summary</b><br />This article will introduce a highly adaptive risk assessment method, namely the application of Historical Simulation in calculating VaR, as well as how to consider asset correlations to optimize risk prediction. Through specific examples from the digital currency market, it explains how to use historical simulation to assess portfolio risks and discusses methods for calculating VaR when asset correlations are significant. With this method, algorithmic traders can not only estimate their maximum loss in most situations, but also be prepared for extreme market conditions. This allows them to trade more calmly and execute strategies accurately.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y6yFNy3NAma5skPNX7KdFj0RWQ__h8we_fnMRera2xT2iq6SFSaO1FaN1f00dhxbQf2dmlQD8eohKaz7Mp4XSXjTfkM4rtSJXvtnSP36nhko8" title="https://blog.mathquant.com/2023/11/03/how-to-measure-position-risk-an-introduction-to-the-var-method.html">https://blog.mathquant.c...n-to-the-var-method.html</a>https://stocksharp.com/topic/25618/Exploring Unconventional Trading Techniques: The K-Line Area Trading Strategy2024-03-25T03:03:22Z2024-03-25T03:03:22ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comLooking at a not-so-reliable trading idea -- the K-line area trading strategy, in this article, we will explore the concept and try to implement the script.<br /><br /><b>Main Idea of the K-Line Area Strategy</b><br />The K-line area strategy is a trading strategy based on the area relationship between price K-lines and moving averages. Its main idea is to predict possible trends in stock prices by analyzing the magnitude and changes of price trends, as well as shifts in buying and selling sentiment, thereby determining when to open positions and exit. This strategy relies on the area between the K-line and moving averages, as well as values from the KDJ indicator, to generate long and short trading signals.<br /><br /><b>The Principle of K-Line Area Strategy</b><br />The area of the K-line refers to the spatial area between the price K-line and the moving average, calculated by subtracting the moving average value from each bar's closing price and then summing it up. When there is a large increase in price over a long period of time, the K-line area will become larger, while during volatile markets or after volatility reversals, the K-line area is smaller. According to the principle of "what goes up must come down", as an upward trend becomes larger and lasts longer, its corresponding K-line area also increases; thus increasing its probability for reversal - much like a spring that rebounds with greater force when stretched further. Therefore, setting a threshold for this K-line area can indicate when prices may have reached their peak and are likely to reverse.<br /><br />To further confirm an impending trend reversal, we introduce the use of KDJ indicators which help determine shifts in buying or selling sentiment. The thresholds for the strategy and values for these indicators can be adjusted according to specific circumstances and needs in order to enhance accuracy.<br /><br /><b>The Advantages of K-Line Area Strategy</b><br />The advantage of the K-line area strategy lies in its combination of the magnitude and changes of price trends, as well as the shift in buying and selling sentiment, providing a relatively complete quantitative trading strategy. Its advantages include:<br /><br />- It provides a simple and intuitive method to identify the possibility of trend reversal, helping traders better grasp market trends.<br />- By combining the K-line area and KDJ indicator, it increases the reliability and accuracy of the strategy.<br />- High flexibility allows for parameter adjustments according to market conditions to meet different trading needs.<br /><br /><b>Risk of K-line Area Strategy</b><br />Although the K-line area strategy has certain advantages, it also carries some risks, including:<br /><br />- The setting of thresholds may require some experience and adjustment. If it set improperly, it could lead to misjudgment of market trends.<br />- The accuracy of the KDJ indicator is affected by market fluctuations and noise, which may result in false signals.<br />- The performance of the strategy may vary under different market conditions and needs constant optimization and adjustment.<br /><br /><b>Optimization Direction of K-line Area Strategy</b><br />To optimize the K-line area strategy, consider the following directions:<br /><br />- Parameter optimization: Continuously adjust and optimize threshold values and KDJ indicator parameters to adapt to different market conditions and trading needs.<br />- Risk management: Implement effective risk management strategies, including stop-loss and take-profit rules, to reduce loss risks.<br />- Multi-strategy combination: Combine the K-line area strategy with other strategies to improve the performance of comprehensive trading strategies.<br />- Real-time monitoring and adjustment: Regularly monitor the performance of strategies, adjusting and improving based on actual situations.<br /><br /><b>Implement the Strategy Using JavaScript</b><br />- Calculate K-line Area<br /><br />- Long position opening signal:<br /><br /> (1) The "K-line area" of the downward trend reaches the threshold, it can be established beforehand.<br /><br /> (2) KDJ indicator value is greater than 80.<br /><br />- Short position opening signal:<br /><br /> (1) The "K-line area" of the upward trend reaches the threshold, it can be established beforehand.<br /><br /> (2) KDJ indicator value is less than 20.<br /><br />- Exit for Long/Short positions: ATR trailing stop loss and take profit.<br /><br />Code implementation<br /><br />```<br />// Parameter<br />var maPeriod = 30<br />var threshold = 50000<br />var amount = 0.1<br /><br />// Global variable<br />let c = KLineChart({})<br />let openPrice = 0<br />let tradeState = "NULL" // NULL BUY SELL<br /><br />function calculateKLineArea(r, ma) {<br /> var lastCrossUpIndex = null<br /> var lastCrossDownIndex = null<br /> for (var i = r.length - 1 ; i >= 0 ; i--) {<br /> if (ma[i] !== null && r[i].Open < ma[i] && r[i].Close > ma[i]) {<br /> lastCrossUpIndex = i<br /> break<br /> } else if (ma[i] !== null && r[i].Open > ma[i] && r[i].Close < ma[i]) {<br /> lastCrossDownIndex = i<br /> break<br /> }<br /><br /> if (i >= 1 && ma[i] !== null && ma[i - 1] !== null && r[i - 1].Close < ma[i - 1] && r[i].Close > ma[i]) {<br /> lastCrossUpIndex = i<br /> break<br /> } else if (i >= 1 && ma[i] !== null && ma[i - 1] !== null && r[i - 1].Close > ma[i - 1] && r[i].Close < ma[i]) {<br /> lastCrossDownIndex = i<br /> break<br /> }<br /> }<br /><br /> var area = 0<br /> if (lastCrossDownIndex !== null) {<br /> for (var i = r.length - 1 ; i >= lastCrossDownIndex ; i--) {<br /> area -= Math.abs(r[i].Close - ma[i])<br /> }<br /> } else if (lastCrossUpIndex !== null) {<br /> for (var i = r.length - 1 ; i >= lastCrossUpIndex ; i--) {<br /> area += Math.abs(r[i].Close - ma[i])<br /> }<br /> }<br /><br /> return [area, lastCrossUpIndex, lastCrossDownIndex]<br />}<br /><br />function onTick() {<br /> var r = _C(exchange.GetRecords)<br /> if (r.length < maPeriod) {<br /> LogStatus(_D(), "Insufficient number of K-line")<br /> return <br /> }<br /> var ma = TA.MA(r, maPeriod)<br /> var atr = TA.ATR(r)<br /> var kdj = TA.KDJ(r)<br /> var lineK = kdj[0]<br /> var lineD = kdj[1]<br /> var lineJ = kdj[2]<br /> var areaInfo = calculateKLineArea(r, ma)<br /> var area = _N(areaInfo[0], 0)<br /> var lastCrossUpIndex = areaInfo[1]<br /> var lastCrossDownIndex = areaInfo[2]<br /> <br /> r.forEach(function(bar, index) {<br /> c.begin(bar)<br /> c.plotcandle(bar.Open, bar.High, bar.Low, bar.Close, {overlay: true})<br /> let maLine = c.plot(ma[index], "ma", {overlay: true})<br /> let close = c.plot(bar.Close, 'close', {overlay: true})<br /> c.fill(maLine, close, {color: bar.Close > ma[index] ? 'rgba(255, 0, 0, 0.1)' : 'rgba(0, 255, 0, 0.1)'})<br /> if (lastCrossUpIndex !== null) {<br /> c.plotchar(bar.Time, {char: '$:' + area, overlay: true})<br /> } else if (lastCrossDownIndex !== null) {<br /> c.plotchar(bar.Time, {char: '$:' + area, overlay: true})<br /> }<br /> c.plot(lineK[index], "K")<br /> c.plot(lineD[index], "D")<br /> c.plot(lineJ[index], "J")<br /><br /> c.close()<br /> })<br /> <br /> if (tradeState == "NULL" && area < -threshold && lineK[lineK.length - 1] > 70) {<br /> // long<br /> let tradeInfo = $.Buy(amount)<br /> if (tradeInfo) {<br /> openPrice = tradeInfo.price<br /> tradeState = "BUY"<br /> }<br /> } else if (tradeState == "NULL" && area > threshold && lineK[lineK.length - 1] < 30) {<br /> // short<br /> let tradeInfo = $.Sell(amount)<br /> if (tradeInfo) {<br /> openPrice = tradeInfo.price<br /> tradeState = "SELL"<br /> }<br /> }<br /> <br /> let stopBase = tradeState == "BUY" ? Math.max(openPrice, r[r.length - 2].Close) : Math.min(openPrice, r[r.length - 2].Close)<br /> if (tradeState == "BUY" && r[r.length - 1].Close < stopBase - atr[atr.length - 2]) {<br /> // cover long<br /> let tradeInfo = $.Sell(amount)<br /> if (tradeInfo) {<br /> tradeState = "NULL"<br /> openPrice = 0<br /> } <br /> } else if (tradeState == "SELL" && r[r.length - 1].Close > stopBase + atr[atr.length - 2]) {<br /> // cover short <br /> let tradeInfo = $.Buy(amount)<br /> if (tradeInfo) {<br /> tradeState = "NULL"<br /> openPrice = 0<br /> } <br /> }<br /><br /> LogStatus(_D(), "area:", area, ", lineK[lineK.length - 2]:", lineK[lineK.length - 2])<br />}<br /><br /><br />function main() { <br /> if (exchange.GetName().includes("_Futures")) {<br /> throw "not support Futures"<br /> }<br /> while (true) {<br /> onTick()<br /> Sleep(1000)<br /> }<br />}<br />```<br />The strategy logic is very simple:<br /><br />1. First, some global variables and parameters are defined, including:<br /><br />Strategy parameters<br /><br />- maPeriod: The period of moving average.<br />- threshold: A threshold used to determine the timing of buying or selling.<br />- amount: The quantity for each transaction.<br /><br />Global variables<br /><br />- c: A K-line chart object, used for drawing charts.<br />- openPrice: Records the opening price.<br />- tradeState: Records the trading status, which can be "NULL" (empty position), "BUY" or "SELL".<br /><br />Calculate function<br /><br />- calculateKLineArea function: It is used to calculate the area between the price and moving average line on a K-line chart over a certain period of time, and returns the area value, the index of the last upward crossing K-line, and the index of the last downward crossing K-line. These values are used in subsequent decisions to determine when to buy and sell.<br /><br />Main loop function<br /><br />- onTick function: It is the main strategy execution function, and here are the operations within the function:<br /><br /> a. Obtain the latest K-line data and ensure that the number of K-lines is not less than maPeriod, otherwise record status and return.<br /><br /> b. Calculate moving average line ma and ATR indicator atr, as well as KDJ indicator.<br /><br /> c. Get area information from areaInfo, last cross-over K-line index, and last cross-under K-line index.<br /><br /> d. Use K-line chart object c to draw K-lines and indicator lines while filling in different colors based on price's relationship with moving average line.<br /><br /> e. Determine buying or selling timing according to conditions:<br /><br /> If tradeState is "NULL", and the area is less than -threshold, and the K value of KDJ is greater than 70, execute a buy operation.<br /> If tradeState is "NULL", and the area is greater than threshold, and the K value of KDJ is less than 30, execute a sell operation.<br /> f. Set stop loss and take profit conditions. If these conditions are met, close positions:<br /><br /> If it's in buying state, when the price falls below the closing price of last trading day minus previous day's ATR (Average True Range), close position.<br /> If it's in selling state, when the price rises above last trading day's closing price plus previous day's ATR (Average True Range), close position.<br /><br /> main function: This serves as main execution entry point. It checks if exchange name contains "_Futures". If so, an exception will be thrown; otherwise it enters into an infinite loop where onTick function gets executed every second.<br /><br />In a word, this strategy mainly relies on K-line charts and technical indicators for making buying or selling decisions while also employing stop-loss & take-profit strategies to manage risk. Please note that this just serves as an example strategy which needs to be adjusted & optimized according to market situations & specific requirements during actual use.<br /><br />On FMZ.COM, using JavaScript language didn't require many lines of code, instead, it implemented this model easily. And with help from KLineChart function graphical representation of K-line chart area was easily achieved, too. The strategy design caters towards cryptocurrency spot markets by utilizing 'Digital Currency Spot Trading Library' template for placing orders through encapsulated functions within template, which makes it very simple & easy to understand and use.<br /><br /><b>Strategy Backtesting</b><br /><a href="https://stocksharp.com/file/149572
" title="https://stocksharp.com/file/149572
">https://stocksharp.com/file/149572
</a><br /><a href="https://stocksharp.com/file/149571
" title="https://stocksharp.com/file/149571
">https://stocksharp.com/file/149571
</a><br />I selected a backtesting period randomly. Although I didn't lose money, I didn't accumulate profits continuously, either, and the drawdown issue is quite significant. There should be other directions and room for optimization for the strategy. Those who are interested can try to upgrade the strategy.<br /><a href="https://stocksharp.com/file/149569
" title="https://stocksharp.com/file/149569
">https://stocksharp.com/file/149569
</a><br /><a href="https://stocksharp.com/file/149570
" title="https://stocksharp.com/file/149570
">https://stocksharp.com/file/149570
</a><br />Through the strategy, we not only learned a rather unconventional trading idea, but also learned how to plot diagrams; representing the area enclosed by K-line and moving average line; ploting KDJ indicators etc.<br /><br /><b>Summary</b><br />The K-line area strategy is a trading strategy based on price trend magnitude and the KDJ indicator. It helps traders predict market trends by analyzing the area between the K-line and moving averages, as well as shifts in buying and selling sentiment. Despite certain risks, this strategy can provide powerful trading tools through continuous optimization and adjustment, helping traders better cope with market fluctuations. Moreover, traders should adjust the parameters and rules of the strategy flexibly according to specific situations and market conditions to achieve better trading performance.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y64w0KH_McPil-CCmMm0S4csgQSlJDfYfZNqJ5KuaEFdOqAOE0PYwB0j_-adaAfZDxk8AgDet7R3RaJqt4wzHHixJP8KC83tqTfFB6E1dsCmj" title="https://blog.mathquant.com/2023/11/06/alternative-trading-ideas-k-line-area-trading-strategy.html">https://blog.mathquant.c...ea-trading-strategy.html</a>https://stocksharp.com/topic/25616/High-Frequency Trading Strategy Analysis - Penny Jump2024-03-25T01:14:50Z2024-03-25T01:14:50ZFMZQuanthttps://stocksharp.com/users/185759/info@stocksharp.comHigh-frequency trading is a challenging and competitive field that relies on rapid trade execution and sensitive insights into the microstructure of the market. One notable strategy is Penny Jump, which focuses on exploiting "elephants" in the market to gain small but frequent profits. In this article, we will explain in detail how the Penny Jump strategy works, delving into the details of its code, so that beginners can understand how it operates.<br /><br /><b>Understanding the Penny Jump Strategy</b><br />In the stock market, "elephants" usually refer to those institutional investors who wish to buy or sell a large number of shares but are unwilling to trade at market price. Instead, they choose to hang a large number of limit orders in the market, i.e., pending orders, to indicate their intentions. This behavior has attracted widespread attention in the market, because large transactions may have a significant impact on the market.<br /><br />For example, suppose the original depth of a stock's market was like this: 200 | $1.01 x $1.03 | 200. Then an "elephant" enters and places a buy order for 3000 shares at $1.01 each. At this point, the depth of the market will change to 3,200 | $1.01 x $1.03 | 200 . This action is like introducing an "elephant", which becomes the focus of other participants in the marketplace.<br /><br />- Competitive market<br /> For high-frequency traders, their profits mainly come from the analysis of market microstructure to speculate on the intentions of other traders. Once a big player appears, high-frequency traders will establish positions quickly to capture minor price fluctuations. Their goal is to trade frequently in a short period of time and accumulate small but cumulative profits.<br /><br />- The Dilemma of the Elephant<br /> Even though elephants might wish to operate on a large scale in the market, their actions also reveal their trading intentions, making them targets for high-frequency traders. High-frequency traders attempt to establish positions ahead of time and then profit from price fluctuations. The presence of elephants in the market could trigger reactions in competitive markets, thereby affecting their trading strategies.<br /><br />- Deception in the Market<br /> In reality, large institutional investors usually do not place a large number of buy or sell orders in the market blatantly, as such behavior could lead other participants in the market to take countermeasures or even manipulate the market. Therefore, they may adopt strategies to create illusions, attract high-frequency traders into the field, and then quickly sell or buy to profit from price fluctuations.<br /><br /><b>The Core Idea of the Penny Jump Strategy</b><br />The core idea of the Penny Jump strategy is that once a "big player" appears in the market and supports a specific price (such as $1.01), high-frequency traders will quickly raise their bid by one cent, for instance, to $1.02. This is because high-frequency traders understand that the appearance of a big player means there's strong buying support at this price level, so they try to follow closely in hopes of a price increase. When the price indeed rises to $1.03 x $1.05, high-frequency traders can sell quickly and earn a profit of $0.01.<br /><br />Not only that, but high-frequency traders can also make profits after purchasing even if the price doesn't rise, because they know that the big player has supported the base price; hence they can swiftly sell their stocks to this big player and gain tiny arbitrage profits.<br /><br /><b>Analyzing Penny Jump Strategy Code</b><br />Strategy source code: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAADHZKb-RfbDOdB_w3dJgQKtdiNZNpLtyGanWuMu3v9LM_2Yv3dlp98Wb2KYr6QeDwk" title="https://www.fmz.com/strategy/358
">https://www.fmz.com/strategy/358
</a><br /><br />The strategy code provided above is an example, used to implement the Penny Jump strategy. Below is a detailed explanation of the code, enabling beginners to understand how it works:<br /><br />```<br />var Counter = {<br /> i: 0,<br /> w: 0,<br /> f: 0<br />};<br /><br />// Variables<br />var InitAccount = null;<br /><br />function CancelAll() {<br /> while (true) {<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length == 0) {<br /> break;<br /> }<br /> for (var i = 0; i < orders.length; i++) {<br /> exchange.CancelOrder(orders[i].Id);<br /> }<br /> Sleep(Interval);<br /> }<br />}<br /><br />function updateStatus(msg) {<br /> LogStatus("Number of debugging sessions:", Counter.i, "succeeded:", Counter.w, "failed:", Counter.f, "\n"+msg+"#0000ff\n"+new Date());<br />}<br /><br />function main() {<br /> if (DisableLog) {<br /> EnableLog(false);<br /> }<br /> CancelAll();<br /> InitAccount = _C(exchange.GetAccount);<br /> Log(InitAccount);<br /> var i = 0;<br /> var locks = 0;<br /> while (true) {<br /> Sleep(Interval);<br /> var depth = _C(exchange.GetDepth);<br /> if (depth.Asks.length === 0 || depth.Bids.length === 0) {<br /> continue;<br /> }<br /> updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks);<br /> var askPrice = 0;<br /> for (i = 0; i < depth.Asks.length; i++) {<br /> if (depth.Asks[i].Amount >= Lot) {<br /> askPrice = depth.Asks[i].Price;<br /> break;<br /> }<br /> }<br /> if (askPrice === 0) {<br /> continue;<br /> }<br /> var elephant = null;<br /> // skip Bids[0]<br /> for (i = 1; i < depth.Bids.length; i++) {<br /> if ((askPrice - depth.Bids[i].Price) > ElephantSpace) {<br /> break;<br /> }<br /> if (depth.Bids[i].Amount >= ElephantAmount) {<br /> elephant = depth.Bids[i];<br /> break;<br /> }<br /> }<br /><br /> if (!elephant) {<br /> locks = 0;<br /> continue;<br /> }<br /> locks++;<br /> if (locks < LockCount) {<br /> continue;<br /> }<br /> locks = 0;<br /><br /> updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant));<br /> exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant);<br /> var ts = new Date().getTime();<br /> while (true) {<br /> Sleep(CheckInterval);<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length == 0) {<br /> break;<br /> }<br /> if ((new Date().getTime() - ts) > WaitInterval) {<br /> for (var i = 0; i < orders.length; i++) {<br /> exchange.CancelOrder(orders[i].Id);<br /> }<br /> }<br /> }<br /> var account = _C(exchange.GetAccount);<br /> var opAmount = _N(account.Stocks - InitAccount.Stocks);<br /> if (opAmount < 0.001) {<br /> Counter.f++;<br /> Counter.i++;<br /> continue;<br /> }<br /> updateStatus("Successful payment: " + opAmount +", Start taking action...");<br /> exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount);<br /> var success = true;<br /> while (true) {<br /> var depth = _C(exchange.GetDepth);<br /> if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price-(STTick*PennyTick))) {<br /> success = false;<br /> updateStatus("Didn't get it, start to stop loss, currently buying one: " + depth.Bids[0].Price);<br /> CancelAll();<br /> account = _C(exchange.GetAccount);<br /> var opAmount = _N(account.Stocks - InitAccount.Stocks);<br /> if (opAmount < 0.001) {<br /> break;<br /> }<br /> exchange.Sell(depth.Bids[0].Price, opAmount);<br /> }<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length === 0) {<br /> break;<br /> }<br /> Sleep(CheckInterval);<br /> }<br /> if (success) {<br /> Counter.w++;<br /> } else {<br /> Counter.f++;<br /> }<br /> Counter.i++;<br /> var account = _C(exchange.GetAccount);<br /> LogProfit(account.Balance - InitAccount.Balance, account);<br /> }<br />}<br />```<br />I will parse your provided strategy code line by line to help you understand its operation in detail.<br /><br />```<br />var Counter = {<br /> i: 0,<br /> w: 0,<br /> f: 0<br />};<br />```<br />This code initializes an object named Counter, which is used to track the trading statistical information of a strategy. Specifically, it includes three attributes:<br /><br />- i: Represents the total number of transactions.<br />- w: Represents the number of successful transactions.<br />- f: Represents the number of failed transactions.<br /><br />These attributes will be recorded and updated during the strategy execution process.<br /><br />```<br />var InitAccount = null;<br />```<br />This line of code initializes a variable named InitAccount, which will store account information when the strategy starts executing.<br /><br />```<br />function CancelAll() {<br /> while (true) {<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length == 0) {<br /> break;<br /> }<br /> for (var i = 0; i < orders.length; i++) {<br /> exchange.CancelOrder(orders[i].Id);<br /> }<br /> Sleep(Interval);<br /> }<br />}<br />```<br />This is a function named ```CancelAll()```, its purpose is to cancel all unfulfilled orders in the market. Let's explain its functions step by step:<br /><br />- ```while (true)```: This is an infinite loop, it will continue to run until there are no uncompleted orders.<br />- ```var orders = _C(exchange.GetOrders)```: This line of code uses the exchange.GetOrders function to retrieve all pending orders in the current account and stores them in the orders variable.<br />- ```if (orders.length == 0)```: This line of code checks for any unfinished orders. If the length of the orders array is 0, it means there are no unfinished orders and the loop will be interrupted (break).<br />- ```for (var i = 0; i < orders.length; i++)```: This is a for loop that iterates through all uncompleted orders.<br />- ```exchange.CancelOrder(orders[i].Id)```: This line of code uses the exchange.CancelOrder() function to cancel each order by its ID.<br />- ```Sleep(Interval)```: This line of code introduces a waiting period, pausing for a certain amount of time (in milliseconds), to ensure that the operation of cancelling orders is not too frequent.<br /><br />This line of code introduces a waiting period, pausing for a certain amount of time (in milliseconds), to ensure that the operation of cancelling orders is not too frequent.<br /><br />```<br />function updateStatus(msg) {<br /> LogStatus("Number of debugging sessions:", Counter.i, "succeeded:", Counter.w, "failed:", Counter.f, "\n" + msg + "#0000ff\n" + new Date());<br />}<br />```<br />This is a function named ```updateStatus(msg)```, which is used to update and record transaction status information. It accepts a msg parameter, which usually contains information about the current market status. The specific operations of the function include:<br /><br />Using the ```LogStatus()``` function to record the information displayed in the status bar during strategy execution. It displays text about trade counts, successful counts, and failure counts.<br />The ```msg``` parameter is appended, which contains information about the current market status.<br />The current timestamp (```new Date()```) is appended to display time information.<br />The purpose of this function is to record and update transaction status information for monitoring and analysis during strategy execution.<br /><br />```<br />function main() {<br /> if (DisableLog) {<br /> EnableLog(false);<br /> }<br /> CancelAll();<br /> InitAccount = _C(exchange.GetAccount);<br /> Log(InitAccount);<br /> var i = 0;<br /> var locks = 0;<br /> while (true) {<br /> Sleep(Interval);<br /> var depth = _C(exchange.GetDepth);<br /> if (depth.Asks.length === 0 || depth.Bids.length === 0) {<br /> continue;<br /> }<br /> updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks);<br /> var askPrice = 0;<br /> for (i = 0; i < depth.Asks.length; i++) {<br /> if (depth.Asks[i].Amount >= Lot) {<br /> askPrice = depth.Asks[i].Price;<br /> break;<br /> }<br /> }<br /> if (askPrice === 0) {<br /> continue;<br /> }<br /> var elephant = null;<br /> // skip Bids[0]<br /> for (i = 1; i < depth.Bids.length; i++) {<br /> if ((askPrice - depth.Bids[i].Price) > ElephantSpace) {<br /> break;<br /> }<br /> if (depth.Bids[i].Amount >= ElephantAmount) {<br /> elephant = depth.Bids[i];<br /> break;<br /> }<br /> }<br /><br /> if (!elephant) {<br /> locks = 0;<br /> continue;<br /> }<br /> locks++;<br /> if (locks < LockCount) {<br /> continue;<br /> }<br /> locks = 0;<br /><br /> updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant));<br /> exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant);<br /> var ts = new Date().getTime();<br /> while (true) {<br /> Sleep(CheckInterval);<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length == 0) {<br /> break;<br /> }<br /> if ((new Date().getTime() - ts) > WaitInterval) {<br /> for (var i = 0; i < orders.length; i++) {<br /> exchange.CancelOrder(orders[i].Id);<br /> }<br /> }<br /> }<br /> var account = _C(exchange.GetAccount);<br /> var opAmount = _N(account.Stocks - InitAccount.Stocks);<br /> if (opAmount < 0.001) {<br /> Counter.f++;<br /> Counter.i++;<br /> continue;<br /> }<br /> updateStatus("Successful payment: " + opAmount +", Start taking action...");<br /> exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount);<br /> var success = true;<br /> while (true) {<br /> var depth = _C(exchange.GetDepth);<br /> if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price-(STTick*PennyTick))) {<br /> success = false;<br /> updateStatus("Didn't get it, start to stop loss, currently buying one: " + depth.Bids[0].Price);<br /> CancelAll();<br /> account = _C(exchange.GetAccount);<br /> var opAmount = _N(account.Stocks - InitAccount.Stocks);<br /> if (opAmount < 0.001) {<br /> break;<br /> }<br /> exchange.Sell(depth.Bids[0].Price, opAmount);<br /> }<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length === 0) {<br /> break;<br /> }<br /> Sleep(CheckInterval);<br /> }<br /> if (success) {<br /> Counter.w++;<br /> } else {<br /> Counter.f++;<br /> }<br /> Counter.i++;<br /> var account = _C(exchange.GetAccount);<br /> LogProfit(account.Balance - InitAccount.Balance, account);<br /> }<br />}<br />```<br />This is the main execution function ```main()``` of the strategy, which contains the core logic of the strategy. Let's explain its operations line by line:<br /><br />- ```if (DisableLog)```: This line of code checks if the DisableLog variable is true, and if so, it will disable log recording. This is to ensure that unnecessary logs are not recorded by the strategy.<br /><br />- ```CancelAll()```: Call the previously explained CancelAll() function to ensure that there are no unfinished orders.<br /><br />- ```InitAccount = _C(exchange.GetAccount)```: This line of code retrieves the current account information and stores it in the InitAccount variable. This will be used to record the account status when the strategy starts executing.<br /><br />- ```var i = 0; ``` and ``` var locks = 0;```: Initialize two variables, i and locks, which will be used in the subsequent strategy logic.<br /><br />- ```while (true)```: This is an infinite loop, mainly used for the continuous execution of strategies.<br /><br />Next, we will explain the main strategy logic within the ```while (true)``` loop line by line.<br /><br />```<br />while (true) {<br /> Sleep(Interval);<br /> var depth = _C(exchange.GetDepth);<br /> if (depth.Asks.length === 0 || depth.Bids.length === 0) {<br /> continue;<br /> }<br /> updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks);<br />```<br />- ```Sleep(Interval)```: This line of code allows the strategy to sleep for a period of time, in order to control the execution frequency of the strategy. The Interval parameter defines the sleep interval (in milliseconds).<br /><br />- ```var depth = _C(exchange.GetDepth)```: Obtain the current market depth information, including the prices and quantities of sell orders and buy orders. This information will be stored in the depth variable.<br /><br />- ```if (depth.Asks.length === 0 || depth.Bids.length === 0)```: This line of code checks the market depth information, ensuring that both sell orders and buy orders exist. If one of them does not exist, it indicates that the market may not have enough trading information, so the strategy will continue to wait.<br /><br />- ```updateStatus("Searching within the elephant... Buy one: " + depth.Bids[0].Price + ", Sell one:" + depth.Asks[0].Price + ", Lock times: " + locks)```: This line of code calls the updateStatus function to update the status information of the strategy. It records the current market status, including the highest bid price, lowest ask price and previously locked times (locks).<br /><br />```<br /> var askPrice = 0;<br /> for (i = 0; i < depth.Asks.length; i++) {<br /> if (depth.Asks[i].Amount >= Lot) {<br /> askPrice = depth.Asks[i].Price;<br /> break;<br /> }<br /> }<br /> if (askPrice === 0) {<br /> continue;<br /> }<br /> var elephant = null;<br /><br />```<br />- ```var askPrice = 0;```: Initialize the askPrice variable, it will be used to store the price of sell orders that meet the conditions.<br /><br />- ```for (i = 0; i < depth.Asks.length; i++)```: This is a for loop used to traverse the price and quantity information of market sell orders.<br /><br />- ```if (depth.Asks[i].Amount >= Lot)```: In the loop, check if the quantity of each sell order is greater than or equal to the specified Lot (hand count). If so, store the price of that sell order in askPrice and terminate the loop.<br /><br />- ```if (askPrice === 0)```: If no sell orders that meet the conditions are found (askPrice is still 0), the strategy will continue to wait and skip subsequent operations.<br /><br />- ```var elephant = null;```: Initialize the elephant variable, it will be used to store the buy order information identified as "elephant".<br /><br />```<br /> for (i = 1; i < depth.Bids.length; i++) {<br /> if ((askPrice - depth.Bids[i].Price) > ElephantSpace) {<br /> break;<br /> }<br /> if (depth.Bids[i].Amount >= ElephantAmount) {<br /> elephant = depth.Bids[i];<br /> break;<br /> }<br /> }<br /><br /> if (!elephant) {<br /> locks = 0;<br /> continue;<br /> }<br /> locks++;<br /> if (locks < LockCount) {<br /> continue;<br /> }<br /> locks = 0;<br /><br />```<br />Continue to traverse the price and quantity information of market buy orders, skipping the first buy order (Bids[0]).<br /><br />- ```if ((askPrice - depth.Bids[i].Price) > ElephantSpace)```: Check whether the gap between the current bid price and askPrice is greater than ElephantSpace. If so, it indicates that it is far enough from the "elephant", and the strategy will no longer continue to search.<br /><br />- ```if (depth.Bids[i].Amount >= ElephantAmount)```: Check if the quantity of the current buy order is greater than or equal to ElephantAmount. If so, store the buy order information in the elephant variable.<br /><br />- ```if (!elephant)```: If the "elephant" is not found, reset the lock count to 0 and continue waiting.<br /><br />- ```locks++```: If the "elephant" is found, increment the lock count. This is to ensure that the strategy is executed only after confirming the existence of the "elephant" multiple times over a period of time.<br /><br />- ```if (locks < LockCount)```: Check whether the number of lock times has met the requirement (LockCount). If it hasn't, continue to wait.<br /><br />```<br /> updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant));<br /> exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant);<br /> var ts = new Date().getTime();<br /> while (true) {<br /> Sleep(CheckInterval);<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length == 0) {<br /> break;<br /> }<br /> if ((new Date().getTime() - ts) > WaitInterval) {<br /> for (var i = 0; i < orders.length; i++) {<br /> exchange.CancelOrder(orders[i].Id);<br /> }<br /> }<br /> }<br /><br />```<br />- ```updateStatus("Debug the elephant... The elephant is in gear " + i + ", " + JSON.stringify(elephant))```: Call the updateStatus function to record the current status of the strategy, including the gear position of the "elephant" found and related information. This will be displayed in the status bar of the strategy.<br /><br />- ```exchange.Buy(elephant.Price + PennyTick, Lot, "Bids[" + i + "]", elephant)```: Use the exchange.Buy function to purchase the found "elephant". The purchase price is elephant.Price + PennyTick, the purchase quantity is Lot, and describe the purchase operation as "Bids[" + i + "]".<br /><br />- ```var ts = new Date().getTime()```: Obtain the timestamp of the current time for subsequent calculation of time intervals.<br /><br />- ```while (true)```: Enter a new infinite loop, used to wait for the execution of "elephant" buy orders.<br /><br />- ```Sleep(CheckInterval)```: The strategy sleeps for a while to control the frequency of checking order status.<br /><br />- ```var orders = _C(exchange.GetOrders)```: Obtain all order information of the current account.<br /><br />- ```if (orders.length == 0)```: Check if there are any unfinished orders, if not, break the loop.<br /><br />- ```(new Date().getTime() - ts) > WaitInterval```: Calculate the time interval between the current time and when the "elephant" was purchased. If it exceeds WaitInterval, it means that the waiting has timed out.<br /><br />- ```for (var i = 0; i < orders.length; i++)```: Traverse through all uncompleted orders.<br /><br />- ```exchange.CancelOrder(orders[i].Id)```: Use the exchange.CancelOrder function to cancel each unfinished order.<br /><br />```<br /> var account = _C(exchange.GetAccount);<br /> var opAmount = _N(account.Stocks - InitAccount.Stocks);<br /> if (opAmount < 0.001) {<br /> Counter.f++;<br /> Counter.i++;<br /> continue;<br /> }<br /> updateStatus("Successful payment: " + opAmount + ", Start taking action...");<br /> exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount);<br /> var success = true;<br /> while (true) {<br /> var depth = _C(exchange.GetDepth);<br /> if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price - (STTick * PennyTick))) {<br /> success = false;<br /> updateStatus("Didn't get it, start to stop loss, currently buying one: " + depth.Bids[0].Price);<br /> CancelAll();<br /> account = _C(exchange.GetAccount);<br /> var opAmount = _N(account.Stocks - InitAccount.Stocks);<br /> if (opAmount < 0.001) {<br /> break;<br /> }<br /> exchange.Sell(depth.Bids[0].Price, opAmount);<br /> }<br /> var orders = _C(exchange.GetOrders);<br /> if (orders.length === 0) {<br /> break;<br /> }<br /> Sleep(CheckInterval);<br /> }<br /> if (success) {<br /> Counter.w++;<br /> } else {<br /> Counter.f++;<br /> }<br /> Counter.i++;<br /> var account = _C(exchange.GetAccount);<br /> LogProfit(account.Balance - InitAccount.Balance, account);<br />}<br /><br />```<br />- ```var account = _C(exchange.GetAccount)```: Obtain current account information.<br /><br />- ```var opAmount = _N(account.Stocks - InitAccount.Stocks)```: Calculate the change in account assets after purchasing the "elephant". If the change is less than 0.001, it indicates that the purchase has failed, increase the number of failures and continue to the next loop.<br /><br />- ```updateStatus("Successful payment: " + opAmount + ", Start taking action...")```: Record the successful purchase information of "elephant", including the quantity purchased.<br /><br />- ```exchange.Sell(elephant.Price + (PennyTick * ProfitTick), opAmount)```: Use the exchange.Sell function to sell the successfully purchased "elephant" for profit. The selling price is elephant.Price + (PennyTick * ProfitTick).<br /><br />Enter a new infinite loop, used to wait for the execution of sell orders.<br /><br />- ```var depth = _C(exchange.GetDepth)```: Obtain market depth information.<br /><br />- ```if (depth.Bids.length > 0 && depth.Bids[0].Price <= (elephant.Price - (STTick * PennyTick)))```: Check the market depth information, if the market price has already fallen to the stop-loss level, then execute the stop-loss operation.<br /><br />- ```CancelAll()```: Call the CancelAll() function to cancel all uncompleted orders, in order to avoid position risk.<br /><br />- ```if (opAmount < 0.001)```: Check the purchase quantity again, if it's less than 0.001, it indicates that the purchase has failed, break out of the loop.<br /><br />- ```exchange.Sell(depth.Bids[0].Price, opAmount)```: Execute a stop-loss operation, sell the remaining assets at the current market's lowest price.<br /><br />Finally, update the number of successful and failed transactions based on whether the transaction was successful or not, and record the trading profits.<br /><br />This is a line-by-line explanation of the entire strategy. The core idea of this strategy is to find "elephants" (large buy orders) in the market, buy and sell them to gain small profits. It includes several important parameters, such as Lot, error retry interval (Interval), ElephantAmount, ElephantSpace, etc., to adjust the strategy.<br /><br />In general, this strategy is a high-frequency trading strategy aimed at utilizing market depth information to identify large buy orders and carry out buying and selling transactions in a short period. It needs constant monitoring of the market and execution of buying and selling operations to quickly gain small profits. However, it's also a high-risk strategy, because it requires quick responses to market fluctuations while considering risk management and stop-loss mechanisms to avoid significant losses.<br /><br />Please note that the strategy is based on specific markets and trading platforms. For different markets and exchanges, appropriate adjustments and optimizations may be needed. In practical application, investors need to carefully test and evaluate the performance of the strategy to ensure it aligns with their investment goals and risk tolerance.<br /><br />As you continue to execute the strategy, it will repeatedly perform the following operations:<br /><br />1. Firstly, the strategy will check the depth information of the market to understand the current situation of sell orders and buy orders.<br /><br />2. Next, the strategy will attempt to find sell orders that meet the criteria, specifically sell orders with a quantity greater than or equal to Lot. If a qualifying sell order is found, the price of the sell order will be recorded as askPrice.<br /><br />3. Then, the strategy will continue to search for "elephants" (large amount of buy orders). It will traverse through the market's buy orders, skipping the first one (usually the highest-priced buy order). If it finds an "elephant" that meets the criteria, it will record information about the "elephant", and increase locks.<br /><br />4. If a sufficient number of "elephants" are found consecutively (controlled by the LockCount parameter), the strategy will further perform the following operations:<br /><br />- Call the updateStatus function to record the gear and related information of the "elephant".<br />- Use the exchange.Buy function to purchase an "elephant", with a purchase price of elephant.Price + PennyTick, and a quantity of Lot.<br />- Start a new infinite loop for waiting for execution of the buy order.<br />- Check order status. If it is completed, break out from loop.<br />- If waiting time exceeds set interval (WaitInterval), cancel all uncompleted orders.<br />- Calculate changes in account assets after successful purchase. If change is less than 0.001, it indicates that purchase failed; increase failure count and continue next loop.<br />- Record information about successful purchases of "elephants", including quantity purchased.<br /><br />5. Next, the strategy will continue to enter a new infinite loop, waiting for the execution of sell operations. In this loop, it will perform the following actions:<br />- Obtain market depth information, check if the market price has already reached the stop-loss level.<br />- If the market price has reached or fallen below the stop-loss level, a stop-loss operation will be executed, that is, the remaining assets will be sold.<br />- Call the CancelAll function to cancel all uncompleted orders, reducing position risk.<br />- Recheck the change in account assets after a successful purchase. If the change is less than 0.001, it indicates that the purchase has failed and exit the loop.<br />- Finally, record whether the transaction is successful or not, and update the number of successes and failures based on the transaction results.<br /><br />The entire strategy continuously carries out the above operations to capture as many "elephants" as possible and obtain small profits. This is a high-frequency trading strategy that requires quick responses to market changes, while also considering risk management and stop-loss mechanisms to protect capital. Investors should carefully consider using this strategy, especially in highly volatile markets.<br /><br /><b>Summary</b><br />The Penny Jump strategy is a typical example in high-frequency trading, demonstrating the subtle game and competition among market participants. This strategy is particularly prominent in the cryptocurrency market due to its large fluctuations, where institutional investors and high-frequency traders are all pursuing quick profits. However, this also makes the market full of challenges, requiring constant adaptation and adjustment of strategies to maintain competitive advantages. In this fiercely competitive world, only those traders who are good at discerning the microstructure of the market and responding quickly can achieve success.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y64h90yMK02X503c2p65mDbJaN1YYx33tHD7Y1DUrF5Sztk0nqGShZohdsUYDimMo4p8sor5k8q55W_kIBMwXilM" title="https://blog.mathquant.com/2023/11/07/high-frequency-trading-strategy-analysis-penny-jump.html">https://blog.mathquant.c...analysis-penny-jump.html</a>https://stocksharp.com/topic/25598/The Evolution of PSY Factor: Upgrading and Transforming for Enhanced Trading Strategies2024-03-21T09:11:50Z2024-03-21T09:11:50ZFMZ Quanthttps://stocksharp.com/users/185552/info@stocksharp.comWelcome all traders to my channel, I am a Quant Developer, specializing in full-stack development of CTA, HFT & Arbitrage trading strategies.<br />Thanks to the FMZ Platform, I will share more content related to quantitative development and work together with all traders to maintain the prosperity of the quant community.<br /><br />Today, I will bring you an upgrade and transformation of the PSY (Psychological Line) factor. We will show how to add more market information from a simple factor perspective, step by step transform it, and ultimately turn it into a powerful factor with explanatory and logical strength!!! Of course, after reading this article, you can incorporate the transformed PSY factor into your own library of factors as a powerful weapon~<br /><br />> PART1 Initial PSY Factor<br /><br />The PSY Factor (Psychological Line) is a technical analysis indicator used to measure the impact of market participants' emotions on price trends. It is an emotional index for studying investors' psychological fluctuations in response to market ups and downs, and it's a type of energy and rise-fall indicator. It has certain reference significance for judging short-term market trends.<br /><br />The PSY factor was first proposed by Dr. Wang Yawei in 1991. He believed that the psychological changes in the market are closely related to price trends, and quantified these psychological changes into the PSY factor. As an indicator for analyzing market fluctuations, the PSY factor calculates the total bullish and bearish forces within N K-lines over time to describe whether the current market is strong or weak, or if it's in an overbought or oversold state. It mainly measures investors' psychological endurance by calculating how many rising K-lines there are within N K-lines, providing a reference for investors' buying and selling operations.<br /><br />The PSY factor is based on the number of days the closing price rises or falls over a period of time. Its calculation method is very simple, and the calculation formula is as follows: PSY=(Number of rising days within N K-lines/N)*100. Here, N period represents the selected calculation period, which can be several days, weeks or months etc. The number of rising days refers to the number of trading days with rising prices within the N period. The initial PSY factor function source code based on FMZ platform:<br /><br />```<br />function calculatePSY(data, n) {<br /> let count = 0;<br /> for (let i = data.length - n; i < data.length; i++) {<br /> if (data[i] > data[i - 1]) {<br /> count++;<br /> }<br /> }<br /> return (count / n) * 100;<br />}<br /><br />// Usage example<br />let closePrices = [10, 12, 13, 11, 14, 15, 16, 17, 18, 20];<br />let nPeriod = 5;<br />let psyFactor = calculatePSY(closePrices, nPeriod);<br />Log(psyFactor);<br />```<br />> PART2 Enhance PSY Factor (PSY+PRICE)<br /><br />The essence of the PSY factor is a momentum factor, which measures the comparison of the root numbers of rising and falling forces over a period of time, with the aim to find out which side has greater strength in the past. However, upon careful observation, it can be found that the PSY factor only considers whether the BAR line is rising or falling, lacking a description of BAR itself and unable to judge the intensity of market conditions, resulting in situations -- Of the last 6 K-lines, 3 were down and 3 were up, and the value of 50, as constructed by the initial PSY factor, it does not discern the strength of the long and short forces in the last 6 K-lines.<br /><br />As mentioned above, the uniqueness of a large bullish K-line is not reflected in the PSY indicator, it's merely treated as an upward line with no difference from the previous small bearish K-line. This is where the problem lies, as the number of rises and falls cannot fully describe the magnitude and direction of price changes. Therefore, our first improvement idea was to weight each BAR's price change Abs(C-C[1]) to reflect the magnitude of rise and fall forces. The source code for initial PSY+PRICE factor function based on FMZ platform:<br /><a href="https://stocksharp.com/file/149482
" title="https://stocksharp.com/file/149482
">https://stocksharp.com/file/149482
</a><br />> PART3 Final PSY Factor (PSY+PRICE+VOL)<br /><br />After the modification in the previous step, the transformed PSY factor can better reflect the strength and weakness over a period of time. However, it cannot distinguish well if the rise and fall range is basically consistent during that period. At this point, we continue to add trading volume factors. In momentum effect, increased volume represents a more active market, and increased volume situation can better confirm momentum direction. As shown in the figure below:<br /><a href="https://stocksharp.com/file/149483
" title="https://stocksharp.com/file/149483
">https://stocksharp.com/file/149483
</a><br />Over the past period of time, the magnitude of the rise and the fall were basically the same, but the volume in the rise far exceeded the volume in the fall, reflecting the superior upward force. Therefore, in the final PSY factor, we continue to add the volume factor weighting, VOLUME*Abs(C-C[1]), based on the initial PSY+PRICE factor function source code of FMZ platform:<br /><a href="https://stocksharp.com/file/149487
" title="https://stocksharp.com/file/149487
">https://stocksharp.com/file/149487
</a><br />> PART4 Construction of PSY Factor Trading Signals<br /><br />Based on the final PSY+PRICE+VOL factor constructed in the previous article, we attempt to propose several constructions of momentum signals as follows:<br /><br />- psy[0] > X (Over a period of time in the past, the ratio of multiple forces was greater than X value.)<br />- psy[0] < Y (Over a period of time in the past, the ratio of multiple forces was less than Y value.)<br />- psy[0] > psy[1] or psy[0] > psyma (Over the past period of time, the ratio of various forces has increased.)<br />- psy[0] < psy[1] or psy[0] < psyma (Over a period of time, the ratio of multiple forces has decreased.)<br /><br />We design a simple momentum strategy with signals to detect factors.<br /><br />- Go long: PSY[0] > 70; Close long position: PSY[0] < 30;<br />- Go short: PSY[0] < 30; Close short position: PSY[0] > 70;<br /><br />Using Binance U-denominated contracts, the PSY factor parameter is designed to be 12. Backtesting of BTC-USDT and ETH-USDT contracts was conducted from February 1, 2020 to December 31, 2021 with a slippage of 10, a transaction fee of 0.05%, a leverage of 10 times, and each position remaining principal at 5%:<br /><br />BTC-USDT:<br /><a href="https://stocksharp.com/file/149484
" title="https://stocksharp.com/file/149484
">https://stocksharp.com/file/149484
</a><br />ETH-USDT:<br /><a href="https://stocksharp.com/file/149485
" title="https://stocksharp.com/file/149485
">https://stocksharp.com/file/149485
</a><br />> PART5 Summary<br /><br />In this article, we upgraded and transformed the traditional psy factor, resulting in a psy+price+vol factor that can measure the strength of bulls and bears over a past period at the level of volume and price. Using fixed numerical comparisons or self-strength comparisons, corresponding momentum/reversal signals can be constructed. This article finally established a fixed numerical signal, conducted simple strategy backtesting, and found that the psy+price+vol factor can capture momentum movements in volatile markets to some extent, achieving positive expected returns. More forms of signals can be constructed later for more types of factor tests before ultimately being added to an existing strategy library.<br /><br />Thanks to the FMZ Platform, for not closing its doors and reinventing the wheel, but providing such a great place for traders to communicate. The road of trading is full of ups and downs, but with warmth from fellow traders and continuous learning from the shared experiences of seniors on the FMZ platform, we can keep growing. Wishing FMZ all the best and may all traders enjoy long-lasting profits.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y6-6TQ0Cd1C_2iQoVjWl6Jxrj4uYlFX0nDGolteAg_cMgDXo8r-1VDRxGVg0z3C3SFlBuat0BX4wLNd4TnRvRmXc" title="https://blog.mathquant.com/2023/11/07/psy-factor-upgrade-and-transformation.html">https://blog.mathquant.c...-and-transformation.html</a>https://stocksharp.com/topic/25597/Unveiling Market Noise: Building and Applying Strategies for a Clearer Trading Path2024-03-21T07:34:26Z2024-03-21T07:34:26ZFMZ Quanthttps://stocksharp.com/users/185552/info@stocksharp.comWelcome all traders to my channel, I am a Quant Developer, specializing in full-stack development of CTA, HFT & Arbitrage trading strategies.<br />Thanks to the FMZ Platform, I will share more content related to quantitative development and work together with all traders to maintain the prosperity of the quant community.<br /><br />- Do you often struggle to distinguish between trends and fluctuations?<br />- Have you been stopped out by the back-and-forth disorderly market?<br />- Are you having difficulty understanding the current market situation?<br />- Do you do trend trading and hope to filter out fluctuations?<br /><br />Haha, you've come to the right place. Today, I will bring you the construction and application of market noise! As we all know, financial markets are full of noise. How to quantitatively model and depict market noise is very important. The depiction of noise can better help us distinguish the current state of the market and predict future possibilities!<br /><br />> PART1 Noise discrimination is very important for financial market trading.<br /><br />The time series in the financial market is characterized by a high signal-to-noise ratio, most of the time, the market fluctuations are unclear, and even during trending markets, situations like taking four steps forward and three steps back often occur. Therefore, defining, identifying and classifying market noise in the financial market is very important and has practical significance. Kaufman's book provides a comprehensive explanation and modeling of this characteristic of noise.<br /><a href="https://stocksharp.com/file/149465
" title="https://stocksharp.com/file/149465
">https://stocksharp.com/file/149465
</a><br />> PART2 Construction of Noise - ER Efficiency Coefficient<br /><a href="https://stocksharp.com/file/149463
" title="https://stocksharp.com/file/149463
">https://stocksharp.com/file/149463
</a><br />The net value of the starting and ending points of price changes divided by the sum of all pairwise price changes during the period.<br /><a href="https://stocksharp.com/file/149456
" title="https://stocksharp.com/file/149456
">https://stocksharp.com/file/149456
</a><br />The difference between point A and point B divided by the sum of the 7 intermediate movements.<br /><a href="https://stocksharp.com/file/149457
" title="https://stocksharp.com/file/149457
">https://stocksharp.com/file/149457
</a><br />It demonstrates the different noise levels exhibited by various price operation modes under the same price movement range. A straight line indicates no noise, minor fluctuations around the straight line represent medium noise, and large swings symbolize high noise.<br /><br />> PART3 Construction of Noise - Price Density<br /><a href="https://stocksharp.com/file/149464
" title="https://stocksharp.com/file/149464
">https://stocksharp.com/file/149464
</a><br />The definition here is: Drawing the high and low points of price movements over a period of time, pulling the highest and lowest prices during this period into a box. The so-called price density refers to the number of price points that can be accommodated within the box.<br /><a href="https://stocksharp.com/file/149458
" title="https://stocksharp.com/file/149458
">https://stocksharp.com/file/149458
</a><br /><a href="https://stocksharp.com/file/149459
" title="https://stocksharp.com/file/149459
">https://stocksharp.com/file/149459
</a><br />Compared to the ER efficiency coefficient, the measurement method of price density takes more into account the highest and lowest prices of each K-line.<br /><br />> PART4 Construction of Noise - Fractal Dimension<br /><br />The fractal dimension cannot be measured accurately, but it can be estimated using the following steps within the past n terms:<br /><a href="https://stocksharp.com/file/149460
" title="https://stocksharp.com/file/149460
">https://stocksharp.com/file/149460
</a><br />> PART5 Construction of Noise - Other Methods<br /><br />CMI = (close[0] - open[n-1]) / (Max high(n) - Min low(n));<br />When the noise is lower, during this period, the net value at the beginning and end infinitely approaches the difference between the highest and lowest prices, with CMI infinitely approaching 1.<br /><a href="https://stocksharp.com/file/149461
" title="https://stocksharp.com/file/149461
">https://stocksharp.com/file/149461
</a><br /><a href="https://stocksharp.com/file/149476
" title="https://stocksharp.com/file/149476
">https://stocksharp.com/file/149476
</a><br />The results obtained from the construction methods of various noise measurements are highly similar. The core is to compare the net changes and change processes or extreme values of a period of movement, and choose the construction method that you prefer or think is more reasonable.<br /><br />> PART6 Dividing market styles from the perspectives of noise and volatility.<br /><a href="https://stocksharp.com/file/149462
" title="https://stocksharp.com/file/149462
">https://stocksharp.com/file/149462
</a><br />Volatility and noise are different dimensions to characterize the market. The sum of price changes in the two types of price models mentioned above is the same, so their volatility is the same, but net value changes more significantly and noise is lower.<br /><br />Therefore, noise and volatility are two different perspectives that can be used to classify market styles. If we take persistence and volatility of trends as the x-axis and y-axis respectively to construct a Cartesian coordinate system, we can divide the fluctuation status of market prices into four categories:<br /><a href="https://stocksharp.com/file/149471
" title="https://stocksharp.com/file/149471
">https://stocksharp.com/file/149471
</a><br />- Good sustainability, high volatility - smooth trend.<br /><a href="https://stocksharp.com/file/149467
" title="https://stocksharp.com/file/149467
">https://stocksharp.com/file/149467
</a><br />- Good sustainability, low volatility - bumpy trend.<br /><a href="https://stocksharp.com/file/149472
" title="https://stocksharp.com/file/149472
">https://stocksharp.com/file/149472
</a><br />- Persistent poor performance, low volatility - narrow range consolidation.<br /><a href="https://stocksharp.com/file/149466
" title="https://stocksharp.com/file/149466
">https://stocksharp.com/file/149466
</a><br />- Persistent poor performance, high volatility - wide range fluctuations.<br /><a href="https://stocksharp.com/file/149473
" title="https://stocksharp.com/file/149473
">https://stocksharp.com/file/149473
</a><br />It should be pointed out that there are no absolute standards for what is called wide range and narrow range, it has to be relative to the level and system of one's own trading, just like the setting of the trading period, which is extremely personalized. Moreover, we can only determine the current state of the market by examining a period in the past. However, we cannot predict what state the market will enter next.<br /><br />Of course, the four types of fluctuations are not completely random during conversion. In the most ideal state, a smooth trend is often followed by wide-range oscillations, slowly unloading momentum; then it enters narrow-range consolidation, the market is very inactive, and bulls and bears are stuck in a stalemate; when the market is compressed to a critical point, it explodes again and the trend begins; this is an oversimplified ideal model - reality is much more complex. For example, after narrow-range consolidation there may not necessarily be a trend - it could also be wide-ranging oscillation. After a smooth trend there might not necessarily be wide-ranging oscillation - it could continue to reach new highs or lows. Moreover, it's difficult to develop four strategies that excel at handling four different market conditions and can adapt as needed. So for now, I still think we can only develop strategies that make money in certain markets while minimizing losses in unfavorable ones.<br /><br />> PART7 Impact of Noise on Related Transactions<br /><a href="https://stocksharp.com/file/149477
" title="https://stocksharp.com/file/149477
">https://stocksharp.com/file/149477
</a><br />The profit factor of the 40-day moving average strategy (going long above the 40-day line and short below, total profit/total loss) is regressed with the 40-day noise (ER efficiency coefficient). It can be seen that the higher the noise, the lower the profit factor of trend strategies. And we can conclude: low noise is beneficial for trend trading, high noise is beneficial for mean reversion trading.<br /><a href="https://stocksharp.com/file/149478
" title="https://stocksharp.com/file/149478
">https://stocksharp.com/file/149478
</a><br />The concept of market noise is very important in determining trading styles. Before developing corresponding trading strategies, we need to outline the contours of the market.<br /><a href="https://stocksharp.com/file/149469
" title="https://stocksharp.com/file/149469
">https://stocksharp.com/file/149469
</a><br />> PART8 Market Maturity and Noise<br /><br />Over the past 20 years, the noise attribute of the North American stock index market has experienced a steady rise.<br /><a href="https://stocksharp.com/file/149468
" title="https://stocksharp.com/file/149468
">https://stocksharp.com/file/149468
</a><br />Financial markets in various regions are gradually maturing, with noise levels increasing progressively, and the maturity is coming quickly.<br /><a href="https://stocksharp.com/file/149470
" title="https://stocksharp.com/file/149470
">https://stocksharp.com/file/149470
</a><br />A study was conducted on the stock index markets of various countries. The market on the far right is the most mature and also has higher noise, while the one on the far left is immature with lower noise. It can be observed that Japan has the most mature market, followed by economies like Hong Kong, China, Singapore, and South Korea. On the far left are relatively immature markets, such as Vietnam and Sri Lanka.<br /><a href="https://stocksharp.com/file/149474
" title="https://stocksharp.com/file/149474
">https://stocksharp.com/file/149474
</a><br />The noise in the Bitcoin market for each quarter is approximately 0.2-0.3, and it's in a cyclical state.<br /><a href="https://stocksharp.com/file/149475
" title="https://stocksharp.com/file/149475
">https://stocksharp.com/file/149475
</a><br />Thanks to the FMZ Platform, for not closing its doors and reinventing the wheel, but providing such a great place for traders to communicate. The road of trading is full of ups and downs, but with warmth from fellow traders and continuous learning from the shared experiences of seniors on the FMZ platform, we can keep growing. Wishing FMZ all the best and may all traders enjoy long-lasting profits.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y60uH-yNR_TZXhu8m7YjsB9muKS9be3QjiHiyGRo8nuNWM3T_Rui0rBV6wRXDS-FOAKRvJffBpDu53KZF7YjuUvA" title="https://blog.mathquant.com/2023/11/07/construction-and-application-of-market-noise.html">https://blog.mathquant.c...ion-of-market-noise.html</a>https://stocksharp.com/topic/25596/Mastering Programmatic Trading: The Game-Changing Incremental Update Algorithm for Mean and Variance Calculation2024-03-21T05:44:08Z2024-03-21T05:44:08ZFMZ Quanthttps://stocksharp.com/users/185552/info@stocksharp.com### Introduction<br />In programmatic trading, it is often necessary to calculate averages and variances, such as calculating moving averages and volatility indicators. When we need high-frequency and long-term calculations, it's necessary to retain historical data for a long time, which is both unnecessary and resource-consuming. This article introduces an online updating algorithm for calculating weighted averages and variances, which is particularly important for processing real-time data streams and dynamically adjusting trading strategies, especially high-frequency strategies. The article also provides corresponding Python code implementation to help traders quickly deploy and apply the algorithm in actual trading.<br /><br />### Simple Average and Variance<br />If we use <a href="https://stocksharp.com/file/149441 " title="https://stocksharp.com/file/149441 ">https://stocksharp.com/file/149441 </a>to represent the average value of the nth data point, assuming that we have already calculated the average of n-1 data points <a href="https://stocksharp.com/file/149454, " title="https://stocksharp.com/file/149454, ">https://stocksharp.com/file/149454, </a>now we receive a new data point <a href="https://stocksharp.com/file/149442. " title="https://stocksharp.com/file/149442. ">https://stocksharp.com/file/149442. </a>We want to calculate the new average number <a href="https://stocksharp.com/file/149441 " title="https://stocksharp.com/file/149441 ">https://stocksharp.com/file/149441 </a>including the new data point. The following is a detailed derivation.<br /><a href="https://stocksharp.com/file/149450
" title="https://stocksharp.com/file/149450
">https://stocksharp.com/file/149450
</a><br />The variance update process can be broken down into the following steps:<br /><a href="https://stocksharp.com/file/149455
" title="https://stocksharp.com/file/149455
">https://stocksharp.com/file/149455
</a><br />As can be seen from the above two formulas, this process allows us to update new averages and variances upon receiving each new data point <a href="https://stocksharp.com/file/149442 " title="https://stocksharp.com/file/149442 ">https://stocksharp.com/file/149442 </a>by only retaining the average and variance of the previous data, without saving historical data, making calculations more efficient. However, the problem is that what we calculate in this way are the mean and variance of all samples, while in actual strategies, we need to consider a certain fixed period. Observing the above average update shows that the amount of new average updates is a deviation between new data and past averages multiplied by a ratio. If this ratio is fixed, it will lead to an exponentially weighted average, which we will discuss next.<br /><br />### Exponentially-weighted mean<br />The exponential weighted average can be defined by the following recursive relationship:<br /><a href="https://stocksharp.com/file/149445
" title="https://stocksharp.com/file/149445
">https://stocksharp.com/file/149445
</a><br />Among them, <a href="https://stocksharp.com/file/149451 " title="https://stocksharp.com/file/149451 ">https://stocksharp.com/file/149451 </a>is the exponential weighted average at time point t, <a href="https://stocksharp.com/file/149444 " title="https://stocksharp.com/file/149444 ">https://stocksharp.com/file/149444 </a>is the observed value at time point t, α is the weight factor, and <a href="https://stocksharp.com/file/149447 " title="https://stocksharp.com/file/149447 ">https://stocksharp.com/file/149447 </a>is the exponential weighted average of the previous time point.<br /><br />### Exponentially-weighted variance<br />Regarding variance, we need to calculate the exponential weighted average of squared deviations at each time point. This can be achieved through the following recursive relationship:<br /><a href="https://stocksharp.com/file/149452
" title="https://stocksharp.com/file/149452
">https://stocksharp.com/file/149452
</a><br />Among them, <a href="https://stocksharp.com/file/149453 " title="https://stocksharp.com/file/149453 ">https://stocksharp.com/file/149453 </a>is the exponential weighted variance at time point t, and <a href="https://stocksharp.com/file/149449 " title="https://stocksharp.com/file/149449 ">https://stocksharp.com/file/149449 </a>is the exponential weighted variance of the previous time point.<br /><br />Observe the exponentially weighted average and variance, their incremental updates are intuitive, retaining a portion of past values and adding new changes. The specific derivation process can be referred to in this paper: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAACVpro8NJ5Hf6n-q2zdOJRxB7Hj42VELvfONf7mK4kcA-Gp2b2yzpE3bNdUo41izgLaXAzt3ZUBwLu3JpbLvJAs" title="https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf
">https://fanf2.user.srcf....c/antiforgery/stats.pdf
</a><br /><br />### SMA and EMA<br />The SMA (also known as the arithmetic mean) and EMA are two common statistical measures, each with different characteristics and uses. The former one assigns equal weight to each observation, reflecting the central position of the data set. The latter one is a recursive calculation method that gives higher weight to more recent observations. The weights decrease exponentially as the distance from current time increases for each observation.<br /><br />- **Weight distribution**: The SMA assigns the same weight to each data point, while the EMA gives higher weight to the most recent data points.<br />- **Sensitivity to new information**: The SMA is not sensitive enough to newly added data, as it involves recalculating all data points. The EMA, on the other hand, can reflect changes in the latest data more quickly.<br />- **Computational complexity**: The calculation of the SMA is relatively straightforward, but as the number of data points increases, so does the computational cost. The computation of the EMA is more complex, but due to its recursive nature, it can handle continuous data streams more efficiently.<br /><br />### Approximate Conversion Method Between EMA and SMA<br />Although SMA and EMA are conceptually different, we can make the EMA approximate to a SMA containing a specific number of observations by choosing an appropriate α value. This approximate relationship can be described by the effective sample size, which is a function of the weight factor α in the EMA.<br /><br />SMA is the arithmetic average of all prices within a given time window. For a time window N, the centroid of the SMA (i.e., the position where the average number is located) can be considered as:<br /><br />the centroid of SMA <a href="https://stocksharp.com/file/149443
" title="https://stocksharp.com/file/149443
">https://stocksharp.com/file/149443
</a><br /><br />EMA is a type of weighted average where the most recent data points have greater weight. The weight of EMA decreases exponentially over time. The centroid of EMA can be obtained by summing up the following series:<br /><br />the centroid of EMA <a href="https://stocksharp.com/file/149438
" title="https://stocksharp.com/file/149438
">https://stocksharp.com/file/149438
</a><br /><br />When we assume that SMA and EMA have the same centroid, we can obtain:<br /><a href="https://stocksharp.com/file/149448
" title="https://stocksharp.com/file/149448
">https://stocksharp.com/file/149448
</a><br />To solve this equation, we can obtain the relationship between α and N.<br /><a href="https://stocksharp.com/file/149446
" title="https://stocksharp.com/file/149446
">https://stocksharp.com/file/149446
</a><br />This means that for a given SMA of N days, the corresponding α value can be used to calculate an "equivalent" EMA, so that they have the same centroid and the results are very similar.<br /><br />### Conversion of EMA with Different Update Frequencies<br />Assume we have an EMA that updates every second, with a weight factor of <a href="https://stocksharp.com/file/149437. " title="https://stocksharp.com/file/149437. ">https://stocksharp.com/file/149437. </a>This means that every second, the new data point will be added to the EMA with a weight of <a href="https://stocksharp.com/file/149434, " title="https://stocksharp.com/file/149434, ">https://stocksharp.com/file/149434, </a>while the influence of old data points will be multiplied by <a href="https://stocksharp.com/file/149437.
" title="https://stocksharp.com/file/149437.
">https://stocksharp.com/file/149437.
</a><br /><br />If we change the update frequency, such as updating once every f seconds, we want to find a new weight factor <a href="https://stocksharp.com/file/149440, " title="https://stocksharp.com/file/149440, ">https://stocksharp.com/file/149440, </a>so that the overall impact of data points within f seconds is the same as when updated every second.<br /><br />Within f seconds, if no updates are made, the impact of old data points will continuously decay f times, each time multiplied by /upload/asset/28e50eb9c37d5626d6691.png. Therefore, the total decay factor after f seconds is <a href="https://stocksharp.com/file/149436.
" title="https://stocksharp.com/file/149436.
">https://stocksharp.com/file/149436.
</a><br /><br />In order to make the EMA updated every f seconds have the same decay effect as the EMA updated every second within one update period, we set the total decay factor after f seconds equal to the decay factor within one update period:<br /><a href="https://stocksharp.com/file/149439
" title="https://stocksharp.com/file/149439
">https://stocksharp.com/file/149439
</a><br />Solving this equation, we obtain new weight factors <a href="https://stocksharp.com/file/149440
" title="https://stocksharp.com/file/149440
">https://stocksharp.com/file/149440
</a><br /><a href="https://stocksharp.com/file/149435
" title="https://stocksharp.com/file/149435
">https://stocksharp.com/file/149435
</a><br />This formula provides the approximate value of the new weight factor <a href="https://stocksharp.com/file/149440, " title="https://stocksharp.com/file/149440, ">https://stocksharp.com/file/149440, </a>which maintains the EMA smoothing effect unchanged when the update frequency changes. For example: When we calculate the average price <a href="https://stocksharp.com/file/149437 " title="https://stocksharp.com/file/149437 ">https://stocksharp.com/file/149437 </a>with a value of 0.001 and update it every 10 seconds, if it is changed to an update every second, the equivalent value <a href="https://stocksharp.com/file/149440 " title="https://stocksharp.com/file/149440 ">https://stocksharp.com/file/149440 </a>would be approximately 0.01.<br /><br />### Implementation of Python Code<br />```<br />class ExponentialWeightedStats:<br /> def __init__(self, alpha):<br /> self.alpha = alpha<br /> self.mu = 0<br /> self.S = 0<br /> self.initialized = False<br /><br /> def update(self, x):<br /> if not self.initialized:<br /> self.mu = x<br /> self.S = 0<br /> self.initialized = True<br /> else:<br /> temp = x - self.mu<br /> new_mu = self.mu + self.alpha * temp<br /> self.S = self.alpha * self.S + (1 - self.alpha) * temp * (x - self.mu)<br /> self.mu = new_mu<br /><br /> @property<br /> def mean(self):<br /> return self.mu<br /><br /> @property<br /> def variance(self):<br /> return self.S<br /><br /># Usage example<br />alpha = 0.05 # Weight factor<br />stats = ExponentialWeightedStats(alpha)<br />data_stream = [] # Data stream<br />for data_point in data_stream:<br /> stats.update(data_point)<br />```<br />### Summary<br />In high-frequency programmatic trading, the rapid processing of real-time data is crucial. To improve computational efficiency and reduce resource consumption, this article introduces an online update algorithm for continuously calculating the weighted average and variance of a data stream. Real-time incremental updates can also be used for various statistical data and indicator calculations, such as the correlation between two asset prices, linear fitting, etc., with great potential. Incremental updating treats data as a signal system, which is an evolution in thinking compared to fixed-period calculations. If your strategy still includes parts that calculate using historical data, consider transforming it according to this approach: only record estimates of system status and update the system status when new data arrives; repeat this cycle going forward.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y68HB1V7wjCfXJ8_qU0BIwh-eFjkKb5_TwXURaWxoT7LhijhtqKAOuQ2dWssGvRrwgXQK7Zic61at9K_ZmWtCsM0PKpXjiv97R79XfYrdNjvwr4jsYBKLEsUibT7uocxvLRdGnPsbTr1424GfBdABtA-0VLkDUqfYzM_pYPQvS-zk" title="https://blog.mathquant.com/2023/11/09/a-powerful-tool-for-programmatic-traders-incremental-update-algorithm-for-calculating-mean-and-variance.html">https://blog.mathquant.c...g-mean-and-variance.html</a>https://stocksharp.com/topic/25595/Measuring Risk and Return - An Introduction to Markowitz Theory2024-03-21T03:04:52Z2024-03-21T03:04:52ZFMZ Quanthttps://stocksharp.com/users/185552/info@stocksharp.comLast week, when introducing [How to Measure Position Risk - An Introduction to the VaR Method](https://www.fmz.com/bbs-topic/10255), it was mentioned that the risk of a portfolio is not equal to the risks of individual assets and is related to their price correlation. Taking two assets as an example, if their positive correlation is very strong, meaning they rise and fall together, then diversifying investments will not reduce risk. If there's a strong negative correlation, diversified investments can reduce risk significantly. The natural question then arises: how do we maximize returns at a certain level of risk when investing in a portfolio? This leads us to Markowitz Theory, which we are going to introduce today.<br /><br />The Modern Portfolio Theory (MPT), proposed by Harry Markowitz in 1952, is a mathematical framework for portfolio selection. It aims to maximize expected returns by choosing different combinations of risk assets while controlling risk. The core idea is that the prices of assets do not move completely in sync with each other (i.e., there is incomplete correlation between assets), and overall investment risk can be reduced through diversified asset allocation.<br /><br />### The Key Concept of Markowitz Theory<br />1. **Expected Return Rate**: This is the return that investors expect to receive from holding assets or an investment portfolio, usually predicted based on historical return data.<br /><a href="https://stocksharp.com/file/149420
" title="https://stocksharp.com/file/149420
">https://stocksharp.com/file/149420
</a><br />Where, <a href="https://stocksharp.com/file/149421 " title="https://stocksharp.com/file/149421 ">https://stocksharp.com/file/149421 </a>is the expected return of the portfolio, <a href="https://stocksharp.com/file/149422 " title="https://stocksharp.com/file/149422 ">https://stocksharp.com/file/149422 </a>is the weight of the i-th asset in the portfolio, <a href="https://stocksharp.com/file/149423 " title="https://stocksharp.com/file/149423 ">https://stocksharp.com/file/149423 </a>is the expected return of the i-th asset.<br /><br />2. **Risk (Volatility or Standard Deviation)**: Used to measure the uncertainty of investment returns or the volatility of investments.<br /><a href="https://stocksharp.com/file/149424
" title="https://stocksharp.com/file/149424
">https://stocksharp.com/file/149424
</a><br />Where, <a href="https://stocksharp.com/file/149425 " title="https://stocksharp.com/file/149425 ">https://stocksharp.com/file/149425 </a>represents the total risk of the portfolio, <a href="https://stocksharp.com/file/149426 " title="https://stocksharp.com/file/149426 ">https://stocksharp.com/file/149426 </a>is the covariance of asset i and asset j, which measures the price change relationship between these two assets.<br /><br />3. **Covariance**: Measures the mutual relationship between price changes of two assets.<br /><a href="https://stocksharp.com/file/149427
" title="https://stocksharp.com/file/149427
">https://stocksharp.com/file/149427
</a><br />Where, <a href="https://stocksharp.com/file/149428 " title="https://stocksharp.com/file/149428 ">https://stocksharp.com/file/149428 </a>is the correlation coefficient of asset i and asset j, <a href="https://stocksharp.com/file/149432 " title="https://stocksharp.com/file/149432 ">https://stocksharp.com/file/149432 </a>and <a href="https://stocksharp.com/file/149429 " title="https://stocksharp.com/file/149429 ">https://stocksharp.com/file/149429 </a>are respectively the standard deviations of asset i and asset j.<br /><br />4. **Efficient Frontier**: In the risk-return coordinate system, the efficient frontier is the set of investment portfolios that can provide the maximum expected return at a given level of risk.<br /><a href="https://stocksharp.com/file/149430
" title="https://stocksharp.com/file/149430
">https://stocksharp.com/file/149430
</a><br />The diagram above is an illustration of an efficient frontier, where each point represents a different weighted investment portfolio. The x-axis denotes volatility, which equates to the level of risk, while the y-axis signifies return rate. Clearly, our focus lies on the upper edge of the graph as it achieves the highest returns at equivalent levels of risk.<br /><br />In quantitative trading and portfolio management, applying these principles requires statistical analysis of historical data and using mathematical models to estimate expected returns, standard deviations and covariances for various assets. Then optimization techniques are used to find the best asset weight allocation. This process often involves complex mathematical operations and extensive computer processing - this is why quantitative analysis has become so important in modern finance. Next, we will illustrate how to optimize with specific Python examples.<br /><br />### Python Code Example for Finding the Optimal Combination Using Simulation Method<br />Calculating the Markowitz optimal portfolio is a multi-step process, involving several key steps, such as data preparation, portfolio simulation, and indicator calculation. Please refer to: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAUHMBT0RDOYV3HRm60Xu-4dDADxIxONR_JbWY-WOcZ2MZj5NHr6q4_LIRwrxE3Y0SQJ4Z-hJb0e4bQg0r070V4iNaicoSYA5e4-tGvB6cFgQK2RwhacsOWe_3S5-LMtaA" title="https://plotly.com/python/v3/ipython-notebooks/markowitz-portfolio-optimization/
">https://plotly.com/pytho...portfolio-optimization/
</a><br /><br />1. **Obtain market data**:<br /><br /> Through the ```get_data``` function, obtain the historical price data of the selected digital currency. This is the necessary data for calculating returns and risks, which are used to build investment portfolios and calculate Sharpe ratios.<br /><br />2. **Calculate Return Rate and Risk**:<br /><br /> The ```calculate_returns_risk``` function was used to compute the annualized returns and annualized risk (standard deviation) for each digital currency. This is done to quantify the historical performance of each asset for use in an optimal portfolio.<br /><br />3. **Calculate Markowitz Optimal Portfolio**:<br /><br /> The ```calculate_optimal_portfolio``` function was used to simulate multiple investment portfolios. In each simulation, asset weights were randomly generated and then the expected return and risk of the portfolio were calculated based on these weights.<br /> By randomly generating combinations with different weights, it is possible to explore multiple potential investment portfolios in order to find the optimal one. This is one of the core ideas of Markowitz's portfolio theory.<br /><br />The purpose of the entire process is to find the investment portfolio that yields the best expected returns at a given level of risk. By simulating multiple possible combinations, investors can better understand the performance of different configurations and choose the combination that best suits their investment goals and risk tolerance. This method helps optimize investment decisions, making investments more effective.<br /><br />```<br />import numpy as np<br />import pandas as pd<br />import requests<br />import matplotlib.pyplot as plt<br /><br /># Obtain market data<br />def get_data(symbols):<br /> data = []<br /> for symbol in symbols:<br /> url = 'https://api.binance.com/api/v3/klines?symbol=%s&interval=%s&limit=1000'%(symbol,'1d')<br /> res = requests.get(url)<br /> data.append([float(line[4]) for line in res.json()])<br /> return data<br /><br />def calculate_returns_risk(data):<br /> returns = []<br /> risks = []<br /><br /> for d in data:<br /> daily_returns = np.diff(d) / d[:-1]<br /> annualized_return = np.mean(daily_returns) * 365<br /> annualized_volatility = np.std(daily_returns) * np.sqrt(365)<br /><br /> returns.append(annualized_return)<br /> risks.append(annualized_volatility)<br /><br /> return np.array(returns), np.array(risks)<br /><br /># Calculate Markowitz Optimal Portfolio<br />def calculate_optimal_portfolio(returns, risks):<br /> n_assets = len(returns)<br /> num_portfolios = 3000<br /><br /> results = np.zeros((4, num_portfolios), dtype=object) <br /><br /><br /> for i in range(num_portfolios):<br /> weights = np.random.random(n_assets)<br /> weights /= np.sum(weights)<br /><br /> portfolio_return = np.sum(returns * weights)<br /> portfolio_risk = np.sqrt(np.dot(weights.T, np.dot(np.cov(returns, rowvar=False), weights)))<br /><br /> results[0, i] = portfolio_return<br /> results[1, i] = portfolio_risk<br /> results[2, i] = portfolio_return / portfolio_risk<br /> results[3, i] = list(weights) # Convert weights to a list<br /><br /> return results<br /><br />symbols = ['BTCUSDT','ETHUSDT', 'BNBUSDT','LINKUSDT','BCHUSDT','LTCUSDT']<br />data = get_data(symbols)<br /><br />returns, risks = calculate_returns_risk(data)<br />optimal_portfolios = calculate_optimal_portfolio(returns, risks)<br /><br />max_sharpe_idx = np.argmax(optimal_portfolios[2])<br />optimal_return = optimal_portfolios[0, max_sharpe_idx]<br />optimal_risk = optimal_portfolios[1, max_sharpe_idx]<br />optimal_weights = optimal_portfolios[3, max_sharpe_idx]<br /><br /># Output results<br />print("Optimal combination:")<br />for i in range(len(symbols)):<br /> print(f"{symbols[i]} Weight: {optimal_weights[i]:.4f}")<br /><br />print(f"Expected return rate: {optimal_return:.4f}")<br />print(f"Expected risk (standard deviation): {optimal_risk:.4f}")<br />print(f"Sharpe ratio: {optimal_return / optimal_risk:.4f}")<br /><br /># Visualized investment portfolio<br />plt.figure(figsize=(10, 5))<br />plt.scatter(optimal_portfolios[1], optimal_portfolios[0], c=optimal_portfolios[2], marker='o', s=3)<br />plt.title('portfolio')<br />plt.xlabel('std')<br />plt.ylabel('return')<br />plt.colorbar(label='sharp')<br />plt.show()<br />```<br />Final output result:<br />Optimal combination:<br />Weight of BTCUSDT: 0.0721<br />Weight of ETHUSDT: 0.2704<br />Weight of BNBUSDT: 0.3646<br />Weight of LINKUSDT: 0.1892<br />Weight of BCHUSDT: 0.0829<br />Weight of LTCUSDT: 0.0209<br />Expected return rate: 0.4195<br />Expected risk (standard deviation): 0.1219<br />Sharpe ratio: 3.4403<br /><a href="https://stocksharp.com/file/149431
" title="https://stocksharp.com/file/149431
">https://stocksharp.com/file/149431
</a><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y60yuzHEw3YKQQfs6AuOb9k5xBqwFgVWHPNNNoTBpmSjIwVoBPMr_yJmBachsA7MjzwT4lF6wNksWlmoAmDPfjwVQHKmxZAlf7udVmWNxpLvI" title="https://blog.mathquant.com/2023/11/13/measuring-risk-and-return-an-introduction-to-markowitz-theory.html">https://blog.mathquant.c...to-markowitz-theory.html</a>https://stocksharp.com/topic/25594/Cracking the Code: Exploring Order Book Balance in Centralized Exchanges2024-03-21T01:10:21Z2024-03-21T01:10:21ZFMZ Quanthttps://stocksharp.com/users/185552/info@stocksharp.comRecently, I have summarized some key insights from papers studying limit order books. You will learn how to measure the imbalance of transaction volume in the order book and its predictive ability for price trends. This article explores methods of using order book data to model price changes.<br /><br />### First, let's talk about the order book.<br />Exchange order book balance refers to the relative balance state between buy and sell orders in an exchange. The order book is a real-time record of all pending buy and sell orders on the market. This includes orders from buyers and sellers who are willing to trade at different prices.<br /><br />Below are some key concepts related to exchange order book balance:<br /><br />- Buyer and Seller Orders: Buyer orders in the order book represent investors willing to purchase assets at a specific price, while seller orders represent investors willing to sell assets at a specific price.<br /><br />- Order Book Depth: Order book depth refers to the number of orders on both the buyer and seller sides. A greater depth indicates there are more buy and sell orders in the market, which may be more liquid.<br /><br />- Transaction Price and Transaction Volume: The transaction price is the price of the most recent trade, while the transaction volume is the quantity of assets traded at that price. The transaction price and volume are determined by the competition between buyers and sellers in the order book.<br /><br />- Order Book Imbalance: Order book imbalance refers to the discrepancy between the number of buy and sell orders or total transaction volume. This can be determined by examining the depth of the order book, if one side has significantly more orders than the other, there may be an order book imbalance.<br /><br />- Market Depth Chart: The market depth chart graphically presents the depth and balance of the order book. Typically, the number of orders from buyers and sellers is displayed on the price level in a bar chart or other visual ways.<br /><br />- Factors Affecting the Price: The balance of the order book directly affects market prices. If there are more buy orders, it may push up the price; on the contrary, if there are more sell orders, it may cause a drop in price.<br /><br />- High-frequency Trading and Algorithmic Trading: Order book balance is crucial for high-frequency trading and algorithmic trading, as they rely on real-time order book data to make decisions, aiming to seize market opportunities quickly.<br /><br />Understanding the balance of order books is important for investors, traders, and market analysts, because it provides useful information about market liquidity, potential price direction, and market trends.<br /><br />### Imbalance in Trading Volume<br />A key idea when analyzing limit order books is to determine whether the overall market tends to buy or sell. This concept is known as imbalance in trading volume.<br /><br />The imbalance in trading volume at time t is defined as:<br /><a href="https://stocksharp.com/file/149405
" title="https://stocksharp.com/file/149405
">https://stocksharp.com/file/149405
</a><br />Where, <a href="https://stocksharp.com/file/149406 " title="https://stocksharp.com/file/149406 ">https://stocksharp.com/file/149406 </a>represents the transaction volume of the best buy order at time t, <a href="https://stocksharp.com/file/149407 " title="https://stocksharp.com/file/149407 ">https://stocksharp.com/file/149407 </a>represents the transaction volume of the best sell order at time t. We can interpret ρt close to 1 as strong buying pressure, and ρt close to -1 as strong selling pressure. This only considers the transaction volumes of orders placed at the best buy price and best sell price, that is, L1 order book.<br /><a href="https://stocksharp.com/file/149408
" title="https://stocksharp.com/file/149408
">https://stocksharp.com/file/149408
</a><br />Imbalance in trading volume and price changes. The graph shows the imbalance of tiered trading volumes (x-axis) and the average value of future price movements, standardized by price difference (y-axis). The dataset is a quarter's order flow from a certain market. There seems to be a linear relationship between first-level order imbalance and future price changes. However, on average, future price changes are within the bid-ask spread.<br /><br />The imbalance in trading volume ρt will be divided into the following three paragraphs:<br /><a href="https://stocksharp.com/file/149409
" title="https://stocksharp.com/file/149409
">https://stocksharp.com/file/149409
</a><br />It was discovered that these segments can predict future price changes:<br /><a href="https://stocksharp.com/file/149410
" title="https://stocksharp.com/file/149410
">https://stocksharp.com/file/149410
</a><br />Regarding the predictive ability of volume imbalance, an analysis was conducted on the tick-by-tick order book of a certain commodity from January 2014 to December 2014. For each arriving market order (MO), the volume imbalance was recorded and segmented according to the number of ticks in which the mid-price changed within the next 10 milliseconds. The chart shows the distribution and mid-price changes for each segment. We can see that positive price changes are more likely to occur before order books with greater buying pressure. Similarly, negative changes are more likely to occur before order books with greater selling pressure.<br /><br />### Order Flow Imbalance<br /><br />The imbalance of trading volume focuses on the total trading volume in the limit order book. One drawback is that some of this volume may come from old orders, which contain less relevant information. We can instead focus on the trading volume of recent orders. This concept is known as order flow imbalance. You can achieve this by tracking individual markets and limit orders (requires Level 3 data) or observing changes in the limit order book.<br /><br />Since Level 3 data is expensive and usually only available to institutional traders, we will focus on changes in the limit order book.<br /><br />We can calculate the order flow imbalance by looking at how much the trading volumes have moved at best bid price and best ask price. The change in trading volume at best bid price is:<br /><a href="https://stocksharp.com/file/149411
" title="https://stocksharp.com/file/149411
">https://stocksharp.com/file/149411
</a><br />This is a function involving three scenarios. The first scenario is, if the best buying price is higher than the previous best buying price, then all transaction volumes are new transaction volumes.<br /><a href="https://stocksharp.com/file/149412
" title="https://stocksharp.com/file/149412
">https://stocksharp.com/file/149412
</a><br />The second scenario is, if the best buying price is the same as the previous best buying price, then the new transaction volume is the difference between the current total transaction volume and the previous total transaction volume.<br /><a href="https://stocksharp.com/file/149413
" title="https://stocksharp.com/file/149413
">https://stocksharp.com/file/149413
</a><br />The third scenario is, if the best buying price is lower than the previous best buying price, then all previous orders have been traded and are no longer in the order book.<br /><a href="https://stocksharp.com/file/149414
" title="https://stocksharp.com/file/149414
">https://stocksharp.com/file/149414
</a><br />The calculation method for the change in transaction volume at the best selling price is similar:<br /><a href="https://stocksharp.com/file/149415
" title="https://stocksharp.com/file/149415
">https://stocksharp.com/file/149415
</a><br />The net order flow imbalance (OFI) at time t is given by the following formula:<br /><a href="https://stocksharp.com/file/149416
" title="https://stocksharp.com/file/149416
">https://stocksharp.com/file/149416
</a><br />This will be a positive value when there are more buy orders, and a negative value when there are more sell orders. It measures both the quantity and direction of the transaction volume. In the previous part, order imbalance only measured direction without measuring the quantity of transactions.<br /><br />You can add these values to get the net order flow imbalance (OFI) over a period of time:<br /><a href="https://stocksharp.com/file/149418
" title="https://stocksharp.com/file/149418
">https://stocksharp.com/file/149418
</a><br />Use regression models to test whether order flow imbalance contains information about future price changes:<br /><a href="https://stocksharp.com/file/149417
" title="https://stocksharp.com/file/149417
">https://stocksharp.com/file/149417
</a><br />The calculated OFI value above focuses on the best buying price and selling price. In part 4, the values of the top 5 best prices were also calculated, providing 5 inputs instead of just one. They found that a deep study of the order book can provide new information for future price changes.<br /><br />### Summary<br />Here, I have summarized some key insights from papers studying the order volume in limit order books. These papers indicate that the order book contains information highly predictive of future price changes. However, these changes cannot overcome the bid-ask spread.<br /><br />I have added links to the papers in the references section. Please refer to them for more detailed information.<br /><br />References & Notes<br /><br />- Álvaro Cartea, Ryan Francis Donnelly, and Sebastian Jaimungal: "Enhancing Trading Strategies with Order Book Signals" Applied Mathematical Finance 25(1) pp. 1–35 (2018)<br />- Alexander Lipton, Umberto Pesavento, and Michael G Sotiropoulos: "Trade arrival dynamics and quote imbalance in a limit order book" arXiv (2013)<br />- Álvaro Cartea, Sebastian Jaimungal, and J. Penalva: "Algorithmic and high-frequency trading." Cambridge University Press<br />- Ke Xu, Martin D. Gould, and Sam D. Howison: "Multi-Level Order-Flow Imbalance in a Limit Order Book" arXiv (2019)<br /><br />Reprinted from: Author ~ {Leigh Ford, Adrian}.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y62i5LOkGofAD0pQKW1rZ8lJmxoRx8amKP1FBwVsQEbvwqSTlu_iSjVWCUm_iGnu4iLYeeOXrZskzBXrbRPWHTN6Uoty6nuW8jRnph-_OwyEWuOoYklCxUxNY-B9PXUuZSw" title="https://blog.mathquant.com/2023/11/13/a-brief-discussion-on-the-balance-of-order-books-in-centralized-exchanges.html">https://blog.mathquant.c...ntralized-exchanges.html</a>https://stocksharp.com/topic/25590/Decoding Trends: Exploring the Correlation Between Currency Fluctuations and Bitcoin Price Movements2024-03-20T09:12:58Z2024-03-20T09:12:58ZFMZ Quanthttps://stocksharp.com/users/185552/info@stocksharp.comIn previous articles, we discussed a common phenomenon in the digital currency market: most digital currencies, especially those that follow the price fluctuations of Bitcoin and Ethereum, often show a trend of rising and falling together. This phenomenon reveals their high correlation with mainstream currencies. However, the degree of correlation between different digital currencies also varies. So how does this difference in correlation affect the market performance of each currency? In this article, we will use the bull market in the second half of 2023 as an example to explore this issue.<br /><br />### The Synchronous Origin of the Digital Currency Market<br />The digital currency market is known for its volatility and uncertainty. Bitcoin and Ethereum, as the two giants in the market, often play a leading role in price trends. Most small or emerging digital currencies, in order to maintain market competitiveness and trading activity, often keep a certain degree of price synchronization with these mainstream currencies, especially those coins made by project parties. This synchronicity reflects the psychological expectations and trading strategies of market participants, which are important considerations in designing quantitative trading strategies.<br /><br />### Formula and Calculation Method of Correlation<br />In the field of quantitative trading, the measurement of correlation is achieved through statistical methods. The most commonly used measure is the Pearson correlation coefficient, which measures the degree of linear correlation between two variables. Here are some core concepts and calculation methods:<br /><br />The range of the Pearson Correlation Coefficient (denoted as r) is from -1 to +1, where +1 indicates a perfect positive correlation, -1 indicates a perfect negative correlation, and 0 indicates no linear relationship. The formula for calculating this coefficient is as follows:<br /><a href="https://stocksharp.com/file/149366
" title="https://stocksharp.com/file/149366
">https://stocksharp.com/file/149366
</a><br /><br />Among them, <a href="https://stocksharp.com/file/149367 " title="https://stocksharp.com/file/149367 ">https://stocksharp.com/file/149367 </a>and <a href="https://stocksharp.com/file/149368 " title="https://stocksharp.com/file/149368 ">https://stocksharp.com/file/149368 </a>are the observed values of two random variables, <a href="https://stocksharp.com/file/149369 " title="https://stocksharp.com/file/149369 ">https://stocksharp.com/file/149369 </a>and <a href="https://stocksharp.com/file/149370 " title="https://stocksharp.com/file/149370 ">https://stocksharp.com/file/149370 </a>are the average values of these two random variables respectively. Using Python scientific computing related packages, it's easy to calculate correlation.<br /><br />### Data Collection<br />This article has collected the 4h K-line data for the entire year of 2023 from Binance, selecting 144 currencies that were listed on January 1st. The specific code to download the data is as follows:<br /><br />```<br />import requests<br />from datetime import date,datetime<br />import time<br />import pandas as pd<br />import numpy as np<br />import matplotlib.pyplot as plt<br /><br />ticker = requests.get('https://fapi.binance.com/fapi/v1/ticker/24hr')<br />ticker = ticker.json()<br />sort_symbols = [k['symbol'][:-4] for k in sorted(ticker, key=lambda x :-float(x['quoteVolume'])) if k['symbol'][-4:] == 'USDT']<br /><br />def GetKlines(symbol='BTCUSDT',start='2020-8-10',end='2023-8-10',period='1h',base='fapi',v = 'v1'):<br /> Klines = []<br /> start_time = int(time.mktime(datetime.strptime(start, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000<br /> end_time = min(int(time.mktime(datetime.strptime(end, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000,time.time()*1000)<br /> intervel_map = {'m':60*1000,'h':60*60*1000,'d':24*60*60*1000}<br /> while start_time < end_time:<br /> time.sleep(0.5)<br /> mid_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]<br /> url = 'https://'+base+'.binance.com/'+base+'/'+v+'/klines?symbol=%s&interval=%s&startTime=%s&endTime=%s&limit=1000'%(symbol,period,start_time,mid_time)<br /> res = requests.get(url)<br /> res_list = res.json()<br /> if type(res_list) == list and len(res_list) > 0:<br /> start_time = res_list[-1][0]+int(period[:-1])*intervel_map[period[-1]]<br /> Klines += res_list<br /> if type(res_list) == list and len(res_list) == 0:<br /> start_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]<br /> if mid_time >= end_time:<br /> break<br /> df = pd.DataFrame(Klines,columns=['time','open','high','low','close','amount','end_time','volume','count','buy_amount','buy_volume','null']).astype('float')<br /> df.index = pd.to_datetime(df.time,unit='ms')<br /> return df<br /><br />start_date = '2023-01-01'<br />end_date = '2023-11-16'<br />period = '4h'<br />df_dict = {}<br /><br />for symbol in sort_symbols: <br /> print(symbol)<br /> df_s = GetKlines(symbol=symbol+'USDT',start=start_date,end=end_date,period=period)<br /> if not df_s.empty:<br /> df_dict[symbol] = df_s<br /><br />df_close = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())<br />for symbol in symbols:<br /> df_s = df_dict[symbol]<br /> df_close[symbol] = df_s.close<br />df_close = df_close.dropna(how='any',axis=1)<br />```<br />### Market Review<br />After normalizing the data first, we calculate the index of average price fluctuations. It can be seen that there are two market trends in 2023. One is a significant increase at the beginning of the year, and the other is a major rise starting from October. Currently, it's basically at a high point in terms of index.<br /><br />```<br />df_norm = df_close/df_close.fillna(method='bfill').iloc[0] #Normalization<br />total_index = df_norm.mean(axis=1)<br />total_index.plot(figsize=(15,6),grid=True);<br />```<br /><a href="https://stocksharp.com/file/149362
" title="https://stocksharp.com/file/149362
">https://stocksharp.com/file/149362
</a><br />### Correlation Analysis<br />Pandas comes with a built-in correlation calculation. The weakest correlation with BTC price is shown in the following figure. Most currencies have a positive correlation, meaning they follow the price of BTC. However, some currencies have a negative correlation, which is considered an anomaly in digital currency market trends.<br /><a href="https://stocksharp.com/file/149363
" title="https://stocksharp.com/file/149363
">https://stocksharp.com/file/149363
</a><br />```<br />corr_symbols = df_norm.corrwith(df_norm.BTC).sort_values().index<br />```<br />### Correlation and Price Increase<br />Here, the currencies are loosely divided into two groups. The first group consists of 40 currencies most correlated with BTC price, and the second group includes those least related to BTC price. By subtracting the index of the second group from that of the first, it represents going long on the first group while shorting the second one. In this way, we can calculate a relationship between price fluctuations and BTC correlation. Here is how you do it along with results:<br /><br />```<br />(df_norm[corr_symbols[-40:]].mean(axis=1)-df_norm[corr_symbols[:40]].mean(axis=1)).plot(figsize=(15,6),grid=True);<br />```<br /><a href="https://stocksharp.com/file/149364
" title="https://stocksharp.com/file/149364
">https://stocksharp.com/file/149364
</a><br />The results show that the currencies with stronger correlation to BTC price have better increases, and shorting currencies with low correlation also played a good hedging role. The imprecision here is that future data was used when calculating the correlation. Below, we divide the data into two groups: one group calculates the correlation, and another calculates the return after hedging. The result is shown in the following figure, and the conclusion remains unchanged.<br /><br />Bitcoin and Ethereum as market leaders often have a huge impact on overall market trends. When these cryptocurrencies rise in price, market sentiment usually becomes optimistic and many investors tend to follow this trend. Investors may see this as a signal of an overall market increase and start buying other currencies. Due to collective behavior of market participants, currencies highly correlated with mainstream ones might experience similar price increases. At such times, expectations about price trends can sometimes become self-fulfilling prophecies. On the contrary, currencies negatively correlated with Bitcoin are unique; their fundamentals may be deteriorating or they may no longer be within sight of mainstream investors - there could even exist Bitcoin's blood-sucking situation where markets abandon them chasing for those able to keep up with rising prices.<br /><br />```<br />corr_symbols = (df_norm.iloc[:1500].corrwith(df_norm.BTC.iloc[:1500])-df_norm.iloc[:1500].corrwith(total_index[:1500])).sort_values().index <br />```<br /><a href="https://stocksharp.com/file/149365
" title="https://stocksharp.com/file/149365
">https://stocksharp.com/file/149365
</a><br />### Summary<br />This article discusses the Pearson correlation coefficient, revealing the degree of correlation between different currencies. The article demonstrates how to obtain data to calculate the correlation between currencies and use this data to assess market trends. It reveals that synchronicity in price fluctuations in the digital currency market not only reflects market psychology and strategy, but can also be quantified and predicted through scientific methods. This is particularly important for designing quantitative trading strategies.<br /><br />There are many areas where the ideas in this article can be expanded, such as calculating rolling correlations, separately calculating correlations during rises and falls, etc., which can yield a lot of useful information.<br /><br />From: <a target="_blank" rel="nofollow" href="https://stocksharp.com/away/?u=AQAAAAAAAAAezbpL9G-wNBo0jpp0vfUOQlbPNxIPafGj0KHUDS3Y6_fDA9lkxHIZMIaASo91h6OY2ufQT34QJk0z8S1ICA0Z4cwUYPZfzvD39JSCgFcFURtLawpNJWdgy3F56xFr2xsNSU4GMr6yp3viK1RVNguA" title="https://blog.mathquant.com/2023/11/17/the-correlation-between-the-rise-and-fall-of-currencies-and-bitcoin.html">https://blog.mathquant.c...rencies-and-bitcoin.html</a>