-
Notifications
You must be signed in to change notification settings - Fork 13
/
TradingLibrary.sol
123 lines (114 loc) · 5.12 KB
/
TradingLibrary.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../interfaces/IPosition.sol";
interface IPrice {
function latestAnswer() external view returns (int256);
function decimals() external view returns (uint256);
}
struct PriceData {
address provider;
uint256 asset;
uint256 price;
uint256 spread;
uint256 timestamp;
bool isClosed;
}
library TradingLibrary {
using ECDSA for bytes32;
/**
* @notice returns position profit or loss
* @param _direction true if long
* @param _currentPrice current price
* @param _price opening price
* @param _leverage position leverage
* @param _margin collateral amount
* @param accInterest funding fees
* @return _positionSize position size
* @return _payout payout trader should get
*/
function pnl(bool _direction, uint _currentPrice, uint _price, uint _margin, uint _leverage, int256 accInterest) external pure returns (uint256 _positionSize, int256 _payout) {
unchecked {
uint _initPositionSize = _margin * _leverage / 1e18;
if (_direction && _currentPrice >= _price) {
_payout = int256(_margin) + int256(_initPositionSize * (1e18 * _currentPrice / _price - 1e18)/1e18) + accInterest;
} else if (_direction && _currentPrice < _price) {
_payout = int256(_margin) - int256(_initPositionSize * (1e18 - 1e18 * _currentPrice / _price)/1e18) + accInterest;
} else if (!_direction && _currentPrice <= _price) {
_payout = int256(_margin) + int256(_initPositionSize * (1e18 - 1e18 * _currentPrice / _price)/1e18) + accInterest;
} else {
_payout = int256(_margin) - int256(_initPositionSize * (1e18 * _currentPrice / _price - 1e18)/1e18) + accInterest;
}
_positionSize = _initPositionSize * _currentPrice / _price;
}
}
/**
* @notice returns position liquidation price
* @param _direction true if long
* @param _tradePrice opening price
* @param _leverage position leverage
* @param _margin collateral amount
* @param _accInterest funding fees
* @param _liqPercent liquidation percent
* @return _liqPrice liquidation price
*/
function liqPrice(bool _direction, uint _tradePrice, uint _leverage, uint _margin, int _accInterest, uint _liqPercent) public pure returns (uint256 _liqPrice) {
if (_direction) {
_liqPrice = _tradePrice - ((_tradePrice*1e18/_leverage) * uint(int(_margin)+_accInterest) / _margin) * _liqPercent / 1e10;
} else {
_liqPrice = _tradePrice + ((_tradePrice*1e18/_leverage) * uint(int(_margin)+_accInterest) / _margin) * _liqPercent / 1e10;
}
}
/**
* @notice uses liqPrice() and returns position liquidation price
* @param _positions positions contract address
* @param _id position id
* @param _liqPercent liquidation percent
*/
function getLiqPrice(address _positions, uint _id, uint _liqPercent) external view returns (uint256) {
IPosition.Trade memory _trade = IPosition(_positions).trades(_id);
return liqPrice(_trade.direction, _trade.price, _trade.leverage, _trade.margin, _trade.accInterest, _liqPercent);
}
/**
* @notice verifies that price is signed by a whitelisted node
* @param _validSignatureTimer seconds allowed before price is old
* @param _asset position asset
* @param _chainlinkEnabled is chainlink verification is on
* @param _chainlinkFeed address of chainlink price feed
* @param _priceData PriceData object
* @param _signature signature returned from oracle
* @param _isNode mapping of allowed nodes
*/
function verifyPrice(
uint256 _validSignatureTimer,
uint256 _asset,
bool _chainlinkEnabled,
address _chainlinkFeed,
PriceData calldata _priceData,
bytes calldata _signature,
mapping(address => bool) storage _isNode
)
external view
{
address _provider = (
keccak256(abi.encode(_priceData))
).toEthSignedMessageHash().recover(_signature);
require(_provider == _priceData.provider, "BadSig");
require(_isNode[_provider], "!Node");
require(_asset == _priceData.asset, "!Asset");
require(!_priceData.isClosed, "Closed");
require(block.timestamp >= _priceData.timestamp, "FutSig");
require(block.timestamp <= _priceData.timestamp + _validSignatureTimer, "ExpSig");
require(_priceData.price > 0, "NoPrice");
if (_chainlinkEnabled && _chainlinkFeed != address(0)) {
int256 assetChainlinkPriceInt = IPrice(_chainlinkFeed).latestAnswer();
if (assetChainlinkPriceInt != 0) {
uint256 assetChainlinkPrice = uint256(assetChainlinkPriceInt) * 10**(18 - IPrice(_chainlinkFeed).decimals());
require(
_priceData.price < assetChainlinkPrice+assetChainlinkPrice*2/100 &&
_priceData.price > assetChainlinkPrice-assetChainlinkPrice*2/100, "!chainlinkPrice"
);
}
}
}
}