-
Notifications
You must be signed in to change notification settings - Fork 64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/futures #122
base: master
Are you sure you want to change the base?
Feat/futures #122
Changes from all commits
0d17c45
5784e9e
b761e28
7400fe6
eda9b2e
cfb41f4
59f8876
aeee633
bdc2cb0
31b4c45
4487121
bd7215d
2567311
cedc96b
136c41b
1953b69
cb3c552
687d770
746d7d6
cef11a9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
import { store, Address, BigInt } from '@graphprotocol/graph-ts'; | ||
import { | ||
MarketAdded as MarketAddedEvent, | ||
MarketRemoved as MarketRemovedEvent, | ||
} from '../generated/FuturesMarketManager/FuturesMarketManager'; | ||
|
||
import { FuturesMarket } from '../generated/templates'; | ||
import { | ||
PositionLiquidated as PositionLiquidatedEvent, | ||
PositionModified as PositionModifiedEvent, | ||
FuturesMarket as FuturesMarketContract, | ||
} from '../generated/templates/FuturesMarket/FuturesMarket'; | ||
|
||
import { | ||
FuturesMarket as FuturesMarketEntity, | ||
FuturesPosition, | ||
FuturesTrade, | ||
FuturesStat, | ||
FuturesCumulativeStat, | ||
FuturesOneMinStat, | ||
} from '../generated/schema'; | ||
import { ZERO } from './common'; | ||
|
||
let ETHER = BigInt.fromI32(10).pow(18); | ||
let ONE_MINUTE_SECONDS = BigInt.fromI32(60); | ||
let SINGLE_INDEX = '0'; | ||
|
||
export function handleMarketAdded(event: MarketAddedEvent): void { | ||
let futuresMarketContract = FuturesMarketContract.bind(event.params.market); | ||
let proxyAddress = futuresMarketContract.proxy(); | ||
FuturesMarket.create(proxyAddress); | ||
let marketEntity = new FuturesMarketEntity(proxyAddress.toHex()); | ||
marketEntity.asset = event.params.asset; | ||
let marketStats = getOrCreateMarketStats(event.params.asset.toHex()); | ||
marketStats.save(); | ||
marketEntity.marketStats = marketStats.id; | ||
marketEntity.save(); | ||
} | ||
|
||
export function handleMarketRemoved(event: MarketRemovedEvent): void { | ||
let futuresMarketContract = FuturesMarketContract.bind(event.params.market); | ||
let proxyAddress = futuresMarketContract.proxy(); | ||
store.remove('FuturesMarket', proxyAddress.toHex()); | ||
} | ||
|
||
export function handlePositionModified(event: PositionModifiedEvent): void { | ||
let futuresMarketContract = FuturesMarketContract.bind(event.transaction.to as Address); | ||
let proxyAddress = futuresMarketContract.proxy(); | ||
let positionId = proxyAddress.toHex() + '-' + event.params.id.toHex(); | ||
let statId = event.params.account.toHex(); | ||
let marketEntity = FuturesMarketEntity.load(proxyAddress.toHex()); | ||
let positionEntity = FuturesPosition.load(positionId); | ||
let statEntity = FuturesStat.load(statId); | ||
let cumulativeEntity = getOrCreateCumulativeEntity(); | ||
|
||
if (statEntity == null) { | ||
statEntity = new FuturesStat(statId); | ||
statEntity.account = event.params.account; | ||
statEntity.feesPaid = ZERO; | ||
statEntity.pnl = ZERO; | ||
statEntity.pnlWithFeesPaid = ZERO; | ||
statEntity.liquidations = ZERO; | ||
statEntity.totalTrades = ZERO; | ||
} | ||
if (event.params.tradeSize.isZero() == false) { | ||
let tradeEntity = new FuturesTrade(event.transaction.hash.toHex() + '-' + event.logIndex.toString()); | ||
tradeEntity.timestamp = event.block.timestamp; | ||
tradeEntity.account = event.params.account; | ||
tradeEntity.size = event.params.tradeSize; | ||
tradeEntity.price = event.params.lastPrice; | ||
tradeEntity.asset = marketEntity.asset; | ||
statEntity.totalTrades = statEntity.totalTrades.plus(BigInt.fromI32(1)); | ||
tradeEntity.save(); | ||
|
||
let volume = tradeEntity.size | ||
.times(tradeEntity.price) | ||
.div(ETHER) | ||
.abs(); | ||
cumulativeEntity.totalTrades = cumulativeEntity.totalTrades.plus(BigInt.fromI32(1)); | ||
cumulativeEntity.totalVolume = cumulativeEntity.totalVolume.plus(volume); | ||
cumulativeEntity.averageTradeSize = cumulativeEntity.totalVolume.div(cumulativeEntity.totalTrades); | ||
|
||
let timestamp = getTimeID(event.block.timestamp, ONE_MINUTE_SECONDS); | ||
let oneMinStat = FuturesOneMinStat.load(timestamp.toString()); | ||
if (oneMinStat == null) { | ||
oneMinStat = new FuturesOneMinStat(timestamp.toString()); | ||
oneMinStat.trades = BigInt.fromI32(1); | ||
oneMinStat.volume = volume; | ||
oneMinStat.timestamp = event.block.timestamp; | ||
} else { | ||
oneMinStat.trades = oneMinStat.trades.plus(BigInt.fromI32(1)); | ||
oneMinStat.volume = oneMinStat.volume.plus(volume); | ||
} | ||
oneMinStat.save(); | ||
|
||
let marketStats = getOrCreateMarketStats(marketEntity.asset.toHex()); | ||
marketStats.totalTrades = marketStats.totalTrades.plus(BigInt.fromI32(1)); | ||
marketStats.totalVolume = marketStats.totalVolume.plus(volume); | ||
marketStats.averageTradeSize = marketStats.totalVolume.div(marketStats.totalTrades); | ||
marketStats.save(); | ||
} | ||
if (positionEntity == null) { | ||
positionEntity = new FuturesPosition(positionId); | ||
positionEntity.market = proxyAddress; | ||
positionEntity.asset = marketEntity.asset; | ||
positionEntity.account = event.params.account; | ||
positionEntity.isLiquidated = false; | ||
positionEntity.isOpen = true; | ||
positionEntity.size = event.params.size; | ||
positionEntity.entryPrice = event.params.lastPrice; | ||
positionEntity.margin = event.params.margin; | ||
} | ||
if (event.params.size.isZero() == true) { | ||
positionEntity.isOpen = false; | ||
positionEntity.exitPrice = futuresMarketContract.assetPrice().value0; | ||
statEntity.pnl = statEntity.pnl.plus( | ||
positionEntity.size.times(positionEntity.exitPrice.minus(positionEntity.entryPrice)).div(ETHER), | ||
); | ||
} else { | ||
positionEntity.entryPrice = event.params.lastPrice; | ||
positionEntity.size = event.params.size; | ||
positionEntity.margin = event.params.margin; | ||
} | ||
|
||
//Calc fees paid to exchange and include in PnL | ||
statEntity.feesPaid = statEntity.feesPaid.plus(event.params.fee); | ||
statEntity.pnlWithFeesPaid = statEntity.pnl.minus(statEntity.feesPaid); | ||
|
||
positionEntity.lastTxHash = event.transaction.hash; | ||
positionEntity.timestamp = event.block.timestamp; | ||
positionEntity.save(); | ||
statEntity.save(); | ||
cumulativeEntity.save(); | ||
} | ||
|
||
export function handlePositionLiquidated(event: PositionLiquidatedEvent): void { | ||
let futuresMarketContract = FuturesMarketContract.bind(event.transaction.to as Address); | ||
let proxyAddress = futuresMarketContract.proxy(); | ||
let positionId = proxyAddress.toHex() + '-' + event.params.id.toHex(); | ||
let positionEntity = FuturesPosition.load(positionId); | ||
let statId = event.params.account.toHex(); | ||
let statEntity = FuturesStat.load(statId); | ||
statEntity.liquidations = statEntity.liquidations.plus(BigInt.fromI32(1)); | ||
statEntity.save(); | ||
positionEntity.isLiquidated = true; | ||
positionEntity.save(); | ||
let cumulativeEntity = getOrCreateCumulativeEntity(); | ||
cumulativeEntity.totalLiquidations = cumulativeEntity.totalLiquidations.plus(BigInt.fromI32(1)); | ||
cumulativeEntity.save(); | ||
|
||
let marketStats = getOrCreateMarketStats(positionEntity.asset.toHex()); | ||
marketStats.totalLiquidations = marketStats.totalLiquidations.plus(BigInt.fromI32(1)); | ||
marketStats.save(); | ||
} | ||
|
||
function getOrCreateCumulativeEntity(): FuturesCumulativeStat { | ||
let cumulativeEntity = FuturesCumulativeStat.load(SINGLE_INDEX); | ||
if (cumulativeEntity == null) { | ||
cumulativeEntity = new FuturesCumulativeStat(SINGLE_INDEX); | ||
cumulativeEntity.totalLiquidations = ZERO; | ||
cumulativeEntity.totalTrades = ZERO; | ||
cumulativeEntity.totalVolume = ZERO; | ||
cumulativeEntity.averageTradeSize = ZERO; | ||
} | ||
return cumulativeEntity as FuturesCumulativeStat; | ||
} | ||
|
||
function getOrCreateMarketStats(asset: string): FuturesCumulativeStat { | ||
let cumulativeEntity = FuturesCumulativeStat.load(asset); | ||
if (cumulativeEntity == null) { | ||
cumulativeEntity = new FuturesCumulativeStat(asset); | ||
cumulativeEntity.totalLiquidations = ZERO; | ||
cumulativeEntity.totalTrades = ZERO; | ||
cumulativeEntity.totalVolume = ZERO; | ||
cumulativeEntity.averageTradeSize = ZERO; | ||
} | ||
return cumulativeEntity as FuturesCumulativeStat; | ||
} | ||
|
||
function getTimeID(timestamp: BigInt, num: BigInt): BigInt { | ||
let remainder = timestamp.mod(num); | ||
return timestamp.minus(remainder); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
type FuturesMarket @entity { | ||
id: ID! | ||
asset: Bytes! | ||
marketStats: FuturesCumulativeStat! | ||
} | ||
|
||
type FuturesTrade @entity { | ||
id: ID! | ||
timestamp: BigInt! | ||
account: Bytes! | ||
size: BigInt! | ||
asset: Bytes! | ||
price: BigInt! | ||
} | ||
|
||
type FuturesPosition @entity { | ||
id: ID! | ||
lastTxHash: Bytes! | ||
timestamp: BigInt! | ||
market: Bytes! | ||
asset: Bytes! | ||
account: Bytes! | ||
isOpen: Boolean! | ||
isLiquidated: Boolean! | ||
size: BigInt! | ||
margin: BigInt! | ||
entryPrice: BigInt! | ||
exitPrice: BigInt | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might want to add entities to track position histories as well (it can simply be a convenient store of events which can be displayed on a "Trading History" page or something) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It kinda is already since we have an ID, which comes from the contract. Every different ID is a new position. So calling this entity by account or timestamp will give us a history |
||
|
||
type FuturesStat @entity { | ||
id: ID! | ||
account: Bytes! | ||
feesPaid: BigInt! | ||
pnl: BigInt! | ||
pnlWithFeesPaid: BigInt! | ||
liquidations: BigInt! | ||
totalTrades: BigInt! | ||
} | ||
|
||
type FuturesCumulativeStat @entity { | ||
id: ID! | ||
totalLiquidations: BigInt! | ||
totalTrades: BigInt! | ||
totalVolume: BigInt! | ||
averageTradeSize: BigInt! | ||
} | ||
|
||
type FuturesOneMinStat @entity { | ||
id: ID! | ||
trades: BigInt! | ||
volume: BigInt! | ||
timestamp: BigInt! | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
specVersion: 0.0.2 | ||
description: Synthetix Futures API | ||
repository: https://github.com/Synthetixio/synthetix-subgraph | ||
schema: | ||
file: ./synthetix-futures.graphql | ||
dataSources: | ||
- kind: ethereum/contract | ||
name: FuturesMarketManager | ||
network: optimism-kovan | ||
source: | ||
address: '0xcB3bfb094006F06B46a8C156A72D681A9FCdf43e' | ||
abi: FuturesMarketManager | ||
mapping: | ||
kind: ethereum/events | ||
apiVersion: 0.0.4 | ||
language: wasm/assemblyscript | ||
file: ../src/futures-mapping.ts | ||
entities: | ||
- FuturesMarket | ||
abis: | ||
- name: FuturesMarketManager | ||
file: ../abis/FuturesMarketManager.json | ||
- name: FuturesMarket | ||
file: ../abis/FuturesMarket.json | ||
eventHandlers: | ||
- event: MarketAdded(address,indexed bytes32) | ||
handler: handleMarketAdded | ||
- event: MarketRemoved(address,indexed bytes32) | ||
handler: handleMarketRemoved | ||
templates: | ||
- name: FuturesMarket | ||
kind: ethereum/contract | ||
network: optimism-kovan | ||
source: | ||
abi: FuturesMarket | ||
mapping: | ||
kind: ethereum/events | ||
apiVersion: 0.0.4 | ||
language: wasm/assemblyscript | ||
file: ../src/futures-mapping.ts | ||
entities: | ||
- FuturesMarket | ||
- FuturesPosition | ||
- FuturesTrade | ||
abis: | ||
- name: FuturesMarket | ||
file: ../abis/FuturesMarket.json | ||
eventHandlers: | ||
- event: PositionModified(indexed uint256,indexed address,uint256,int256,int256,uint256,uint256,uint256) | ||
handler: handlePositionModified | ||
- event: PositionLiquidated(indexed uint256,indexed address,indexed address,int256,uint256,uint256) | ||
handler: handlePositionLiquidated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems really expensive to run a contract call here in order to set an ID... isn't there a better way to do this, such as using the FuturesMarketEntity
asset
instead?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah problem is the event doesn't emit the
asset
. And thefrom
address is the underlying contract, which means we need to retrieve the proxy address first so we can match the position entity with the market one :(