@ixily/activ
Version:
Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.
807 lines (754 loc) • 22.8 kB
text/typescript
import {
CONTRACT_INTERFACES,
CONTRACT_TOOLS,
CryptoIdeasModule,
CryptoJs,
IContract,
IStrategyChain,
IStrategyIdeaChain,
IStrategyIdeaStageChain,
NftModule,
RULES,
cachePlusRemove,
cachePlusRetrieve,
cachePlusUpsert,
deserializeDataTool,
LogModule as log,
serializeDataTool,
} from '../..'
import { contractDbVersion } from '../../../version-contract-db.json'
// import fs from 'fs'
const getStrategyChain = async (
contract: IContract,
strategyKey: string,
creator?: string,
inflateImages: boolean = true,
nftIdReduceStep?: number,
cached?: {
ideas: IStrategyIdeaChain[]
nullifiedIdeas: Map<number, Set<number>>
cachedStrategy: CONTRACT_INTERFACES.IStrategyState
},
): Promise<IStrategyChain> => {
// console.log('{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}{}}{}{}')
// console.log('cached')
// console.log(cached)
if (nftIdReduceStep !== undefined) {
if (cached !== undefined) {
if (nftIdReduceStep <= cached.cachedStrategy.lastNftId) {
return {
strategyKey,
ideas: cached.ideas,
}
}
}
}
creator = creator || (await contract.gate!.signer.getAddress())
// console.log('creator')
// console.log(creator)
// console.log('strategyKey')
// console.log(strategyKey)
// console.log('contract.gate?.address')
// console.log(contract.gate?.address)
const ideasListDirty = await NftModule.view.general.listIdeas(
creator,
strategyKey,
contract,
)
const ideasList: number[] = []
// let ideasList: number[] = []
// console.log('ideasListDirty')
// console.log(ideasListDirty)
const alreadyIdeas = new Set<number>()
for (const ideaKey of ideasListDirty) {
if (!alreadyIdeas.has(ideaKey)) {
ideasList.push(ideaKey)
alreadyIdeas.add(ideaKey)
}
}
log.dev('ideasList lenght: ', ideasList.length)
log.dev('cached?.ideas.length', cached?.ideas.length)
// TODO: REMOVE THIS HACK
// cleaideasList = [ideasList[0]]
const ideas: IStrategyIdeaChain[] = []
type PromGen = () => Promise<void>
const promisesOfProccessIdeas: PromGen[] = []
let ideaIndexInsideCacheOffset = 0
let howManyNullifiedIdeas = 0
if (cached !== undefined) {
const alreadyCheckKeys = new Set<number>()
for (const [key, value] of cached.nullifiedIdeas.entries()) {
if (!alreadyCheckKeys.has(key)) {
alreadyCheckKeys.add(key)
const nullifiedStages = value
if (nullifiedStages.has(0)) {
howManyNullifiedIdeas++
}
}
}
}
for (const ideaIndexFluid in ideasList) {
const ideaIndex = Number(ideaIndexFluid)
const ideaKey = ideasList[ideaIndex]
const promOfProcId = async () => {
const tryFromCache = async (): Promise<boolean> => {
if (cached !== undefined) {
if (
cached.ideas.length + howManyNullifiedIdeas >
ideaIndex
) {
const nullifiedStages =
cached.nullifiedIdeas.get(ideaKey)
if (nullifiedStages !== undefined) {
if (nullifiedStages.has(0)) {
ideaIndexInsideCacheOffset++
return true
}
}
const ideaIndexInsideCache =
ideaIndex - ideaIndexInsideCacheOffset
//console.log('-------------------------')
//console.log('-------------------------')
//console.log('-------------------------')
//console.log('howManyNullifiedIdeas')
//console.log(howManyNullifiedIdeas)
//console.log('howManyNullifiedIdeas')
//console.log(howManyNullifiedIdeas)
//console.log('ideaIndex')
//console.log(ideaIndex)
//console.log('cached.nullifiedIdeas:')
//for (const [
// key,
// value,
//] of cached.nullifiedIdeas.entries()) {
// console.log(' key')
// console.log(key)
// console.log(' value')
// console.log(value)
//}
// console.log('cached.ideas.length')
// console.log(cached.ideas.length)
// console.log('cached.ideas[ideaIndex].ideaKey')
// console.log(cached.ideas[ideaIndexInsideCache].ideaKey)
// console.log('ideaKey')
// console.log(ideaKey)
// console.log(
// 'cached.ideas[ideaIndexInsideCache].ideaKey === ideaKey',
// )
// console.log(
// cached.ideas[ideaIndexInsideCache].ideaKey ===
// ideaKey,
// )
if (
cached.ideas[ideaIndexInsideCache].ideaKey ===
ideaKey
) {
const isDecryptedStage = (
stage: IStrategyIdeaStageChain,
): boolean => {
return typeof stage.ideaStage.idea !== 'string'
}
const firstEncryptedStage = cached.ideas[
ideaIndexInsideCache
].stages.find((one) => !isDecryptedStage(one))
if (firstEncryptedStage === undefined) {
// console.log('cached.ideas[ideaIndex]')
// console.log(cached.ideas[ideaIndex])
// console.log('ideaKey')
// console.log(ideaKey)
// console.log('firstEncryptedStage')
// console.log(firstEncryptedStage)
const closeStage = cached.ideas[
ideaIndexInsideCache
].stages.find(
(one) =>
(
one.ideaStage
.idea as CONTRACT_INTERFACES.ITradeIdeaIdea
).kind === 'close',
)
if (closeStage !== undefined) {
ideas.push(
cached.ideas[ideaIndexInsideCache],
)
return true
} else {
// console.log('closeStage')
// console.log(closeStage)
// throw new Error('actually not closed')
const stagesListDirty =
await NftModule.view.general.listStages(
creator!,
strategyKey,
ideaKey,
contract,
)
// eliminate nullified stages
const stagesList: number[] = []
if (cached?.nullifiedIdeas !== undefined) {
const nullifiedStages =
cached.nullifiedIdeas.get(ideaKey)
if (nullifiedStages !== undefined) {
for (const stageKey of stagesListDirty) {
if (
!nullifiedStages.has(
stageKey,
)
) {
stagesList.push(stageKey)
}
}
} else {
stagesList.push(...stagesListDirty)
}
} else {
stagesList.push(...stagesListDirty)
}
// console.log('stagesList')
// console.log(stagesList)
const stages = await Promise.all(
stagesList.map(async (ideaStageKey) => {
const seekStage = cached.ideas[
ideaIndexInsideCache
].stages.find(
(one) =>
one.ideaStageKey ===
ideaStageKey,
)
if (seekStage !== undefined) {
return seekStage
} else {
// console.log('ideaStageKey')
// console.log(ideaStageKey)
const id =
await NftModule.view.general.getIdeaNftIdByKeys(
creator!,
strategyKey,
ideaKey,
ideaStageKey,
contract,
)
const ideaStage =
await CryptoIdeasModule.getRestoredIdeaByNftId(
id,
contract,
inflateImages,
)
// console.log('ideaStage:')
// console.log(ideaStage)
// if (ideaStageKey === 1) {
// throw new Error('stop')
// }
ideaStage.content.ideaNftId =
Number(id)
ideaStage.nftId = Number(id)
ideaStage.content.ideaKey =
ideaKey
ideaStage.content.ideaStageKey =
ideaStageKey
const stage: IStrategyIdeaStageChain =
{
ideaStageKey,
ideaStage,
}
return stage
}
}),
)
if (stages.length !== 0) {
const idea: IStrategyIdeaChain = {
ideaKey,
stages,
}
ideas.push(idea)
}
return true
}
}
} else {
throw new Error(
'getStrategyChain: cached ideaKey does not match',
)
}
}
}
return false
}
const gotFromCache = await tryFromCache()
// console.log('gotFromCache:')
// console.log(gotFromCache)
if (!gotFromCache) {
const stagesList = await NftModule.view.general.listStages(
creator!,
strategyKey,
ideaKey,
contract,
)
if (stagesList.length > 0) {
const stages = await Promise.all(
stagesList.map(async (ideaStageKey) => {
log.dev('strategyKey: ' + strategyKey)
log.dev('ideaKey: ' + ideaKey)
log.dev('ideaStageKey: ' + ideaStageKey)
// console.log('pre error Mark 1')
const id =
await NftModule.view.general.getIdeaNftIdByKeys(
creator!,
strategyKey,
ideaKey,
ideaStageKey,
contract,
)
// console.log('id')
// console.log(id)
// console.log('pre error Mark 2')
const ideaStage =
await CryptoIdeasModule.getRestoredIdeaByNftId(
id,
contract,
inflateImages,
)
// console.log('pre error Mark 3')
ideaStage.content.ideaNftId = Number(id)
ideaStage.nftId = Number(id)
ideaStage.content.ideaKey = ideaKey
ideaStage.content.ideaStageKey = ideaStageKey
const stage: IStrategyIdeaStageChain = {
ideaStageKey,
ideaStage,
}
return stage
}),
)
const idea: IStrategyIdeaChain = {
ideaKey,
stages,
}
ideas.push(idea)
}
}
}
promisesOfProccessIdeas.push(promOfProcId)
}
// console.log('promisesOfProccessIdeas.length')
// console.log(promisesOfProccessIdeas.length)
const MAX_PROMISES_PER_STEP = 200
// if the number of promises is too big, we need to split it into smaller steps
const promisesCount = promisesOfProccessIdeas.length
if (promisesCount > MAX_PROMISES_PER_STEP) {
const stepsCount = Math.ceil(promisesCount / MAX_PROMISES_PER_STEP)
for (let i = 0; i < stepsCount; i++) {
const start = i * MAX_PROMISES_PER_STEP
const end = Math.min((i + 1) * MAX_PROMISES_PER_STEP, promisesCount)
await Promise.all(
promisesOfProccessIdeas.slice(start, end).map((each) => each()),
)
// rest a fair bit
await new Promise((resolve) => setTimeout(resolve, 200))
}
} else {
await Promise.all(promisesOfProccessIdeas.map((each) => each()))
}
// for (const proc of promisesOfProccessIdeas) {
// await proc()
// }
// sort to order by ideaKey
ideas.sort((a, b) => {
return a.ideaKey - b.ideaKey
})
log.dev('ideas', ideas)
const strategy: IStrategyChain = {
strategyKey,
ideas,
}
return strategy
}
const chronologicalIdeasFromChain = (
chain: IStrategyChain,
): CONTRACT_INTERFACES.ITradeIdea[] => {
const results: CONTRACT_INTERFACES.ITradeIdea[] = []
for (const each of chain.ideas) {
for (const eachIn of each.stages) {
results.push(eachIn.ideaStage)
}
}
results.sort((a, b) => {
return a.content.ideaNftId! - b.content.ideaNftId!
})
return results
}
const getActiveIdeasFromChain = (
chain: IStrategyChain,
): CONTRACT_INTERFACES.ITradeIdea[] => {
const results: CONTRACT_INTERFACES.ITradeIdea[] = []
const chronos = chronologicalIdeasFromChain(chain)
const mapOfActiveTickers = new Map<string, CONTRACT_INTERFACES.ITradeIdea>()
for (const each of chronos) {
const ide = each.idea as CONTRACT_INTERFACES.ITradeIdeaIdea
if (ide.kind === 'close' && mapOfActiveTickers.has(ide.asset.ticker)) {
mapOfActiveTickers.delete(ide.asset.ticker)
} else {
mapOfActiveTickers.set(ide.asset.ticker, each)
}
}
for (const each of mapOfActiveTickers.entries()) {
results.push(each[1])
}
return results
}
const postValidationCalculateSummaryCalc = (
strategyState: CONTRACT_INTERFACES.IStrategyState,
): CONTRACT_INTERFACES.ITradeIdeaStrategSumaryCalc => {
let totalIdeas: number = 0
let totalClosedIdeas: number = 0
let successIdeas: number = 0
let lossesIdeas: number = 0
let lossesPoints: number = 0
let successPoints: number = 0
const lastStages =
CONTRACT_TOOLS.queryChain.getOnlyLastStageOfEachIdea(strategyState)
for (const lastStage of lastStages) {
totalIdeas++
if (typeof lastStage.idea !== 'string') {
const idea = lastStage.idea as CONTRACT_INTERFACES.ITradeIdeaIdea
if (idea.kind === 'close') {
totalClosedIdeas++
const isProfit = idea.result!.summary.profit
if (isProfit) {
successIdeas++
successPoints += Math.abs(idea.result!.summary.percentage)
} else {
lossesIdeas++
lossesPoints += Math.abs(idea.result!.summary.percentage)
}
}
}
}
const summaryCalc: CONTRACT_INTERFACES.ITradeIdeaStrategSumaryCalc = {
totalIdeas,
totalClosedIdeas,
successIdeas,
lossesIdeas,
lossesPoints,
successPoints,
}
return summaryCalc
}
const getValidatedStrategyChain = async (
contract: IContract,
strategyKey: string,
creator?: string,
inflateImages: boolean = true,
nftIdReduceStep?: number,
cached?: {
ideas: IStrategyIdeaChain[]
nullifiedIdeas: Map<number, Set<number>>
cachedStrategy: CONTRACT_INTERFACES.IStrategyState
// validatedPricing: Set<number>
},
): Promise<{
result: IStrategyChain
cache: {
ideas: IStrategyIdeaChain[]
nullifiedIdeas: Map<number, Set<number>>
cachedStrategy: CONTRACT_INTERFACES.IStrategyState
// validatedPricing: Set<number>
}
}> => {
// console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
// console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
// console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
// console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
// console.log('cached:')
// console.log(cached)
// console.log(cached?.ideas)
// console.log(cached?.ideas[0]?.stages[0]?.ideaStage?.idea)
//
const strategyChain = await getStrategyChain(
contract,
strategyKey,
creator,
inflateImages,
nftIdReduceStep,
cached,
)
// fs.writeFileSync('strategyChain.json', JSON.stringify(strategyChain))
// if (1 + 1 === 2) {
// throw new Error('check temp chain stored')
// }
// const strategyChain = JSON.parse(
// fs.readFileSync('strategyChain.json', 'utf8'),
// ) as IStrategyChain
// console.log('strategyChain:')
// console.log(strategyChain)
// console.log('strategyChain.ideas[0].ideaStages')
// try {
// for (const stuff of strategyChain.ideas[0].ideaStages) {
// console.log(stuff)
// }
// } catch (e) {
// throw e
// }
const chronos = chronologicalIdeasFromChain(strategyChain)
// console.log('chronos.length:')
// console.log(chronos.length)
// console.log('cached?.ideas.length')
// console.log(cached?.ideas.length)
// console.log('cached?.cachedStrategy.totalIdeas')
// console.log(cached?.cachedStrategy.totalIdeas)
// console.log(chronos.map((each) => each.idea!))
//for (const eachIndex in chronos) {
// const each = chronos[eachIndex]
// console.log('============>')
// console.log('Idea #' + eachIndex)
// console.log('each.idea!')
// console.log(each.idea!)
// console.log('each.idea!.idea')
// console.log(each.idea!.idea)
// }
// throw new Error('stop')
const toCheck = chronos.slice(
cached?.cachedStrategy !== undefined
? cached.cachedStrategy.totalIdeas - 1
: 0,
)
// const chronos = JSON.parse(
// fs.readFileSync('toCheck.json', 'utf8'),
// ) as CONTRACT_INTERFACES.ITradeIdea[]
// let toCheck = copyObj(chronos)
// console.log('toCheck')
// console.log(toCheck)
// console.log('toCheck')
// console.log(toCheck)
// console.log('toCheck[0].creator.companyLogo')
// console.log(toCheck[0]?.creator.companyLogo)
// console.log('(toCheck[0].idea as ITradeIdeaIdea).asset.image')
// console.log((toCheck[0].idea as ITradeIdeaIdea).asset.image)
// console.log('toCheck[0].strategy.image')
// console.log('toCheck[toCheck.length-2].strategy.image')
// console.log(toCheck[toCheck.length - 2].strategy.image)
// console.log('toCheck[toCheck.length-1].strategy.image')
// console.log(toCheck[toCheck.length - 1].strategy.image)
// console.log('cached?.cachedStrategy.strategy.image')
// console.log(cached?.cachedStrategy.strategy.image)
// fs.writeFileSync('toCheck.json', JSON.stringify(toCheck))
// if toCheck list of idea is greater than 50, we need to split it into smaller steps.
// We do separate in ordered chunks of 50 ideas.
// After that, we will calculate the hash of each chunk serialized.
// We will use the hashes to check if the chunk is already in the cache.
// we look for any chunk that is already in the cache.
// if it is, we load the cache and continue from this cache and we calculate the rules with next chunk.
// in a positive result, we do store it in cache using the chunk hash as key.
// if we complete the rules calculation, we delete the cache and return the result.
// we do this using following functions:
// deserializeDataTool,
// serializeDataTool,
// cachePlusUpsert,
// cachePlusRetrieve,
// cachePlusRemove,
let valid:
| CONTRACT_INTERFACES.IRuleValidResult
| string
| {
valid: true
cachedStrategyState:
| CONTRACT_INTERFACES.IStrategyState
| undefined
}
const MAX_CHUNK_SIZE = 10
let toRemoveLaterPriorToReturn: (() => Promise<void>) | undefined =
undefined
if (toCheck.length > MAX_CHUNK_SIZE) {
const chunks: CONTRACT_INTERFACES.ITradeIdea[][] = []
for (let i = 0; i < toCheck.length; i += MAX_CHUNK_SIZE) {
chunks.push(toCheck.slice(i, i + MAX_CHUNK_SIZE))
}
const hashes: string[] = []
for (const chunk of chunks) {
const chunkHash =
'ck-' +
contractDbVersion +
'-' +
contract.gate!.address +
'-' +
CryptoJs.SHA1(serializeDataTool(chunk)).toString()
hashes.push(chunkHash)
}
const featureKey = 'stgChaCks:'
let lastStrategyState: CONTRACT_INTERFACES.IStrategyState | undefined =
undefined
let lastChunkCalculated: number = -1
log.prod(
'Computing verify of strategy "' +
strategyKey +
'" with ' +
chunks.length +
' chunks',
)
for (const hashIndex in hashes) {
// try to find stored cache with this hash and
const cachedChunkResult = await cachePlusRetrieve(
featureKey,
featureKey + hashes[hashIndex],
)
if (cachedChunkResult !== undefined) {
log.prod(
'Found cached chunk: ' +
hashes[hashIndex] +
' (' +
hashIndex +
')',
)
const cachedChunk = deserializeDataTool(
cachedChunkResult,
) as CONTRACT_INTERFACES.IStrategyState
valid = {
valid: true,
cachedStrategyState: cachedChunk,
}
lastStrategyState = cachedChunk
lastChunkCalculated = +hashIndex
break
}
}
// console.log('---------- emergengy debug ----------')
// const emergencyRead = deserializeDataTool(
// fs.readFileSync('emergencySituationStore52.txt', 'utf8'),
// ) as CONTRACT_INTERFACES.IStrategyState
// valid = {
// valid: true,
// cachedStrategyState: emergencyRead,
// }
// lastStrategyState = emergencyRead
// lastChunkCalculated = 52
// lastChunkCalculated always points to the last chunk calculated.
while (lastChunkCalculated < chunks.length - 1) {
log.prod('Calculating chunk: ' + (lastChunkCalculated + 1))
const chunk = chunks[lastChunkCalculated + 1]
valid = await RULES.v1.rules(chunk, lastStrategyState)
// if (typeof valid === 'string') {
// console.log('valid')
// console.log(valid)
// throw new Error('ck')
// }
if (typeof valid === 'string') {
// before thou, we do remove the cache if lastChunkCalculated is not -1
if (lastChunkCalculated !== -1) {
await cachePlusRemove(
featureKey + hashes[lastChunkCalculated],
)
}
throw new Error(
'CONTRACT RULES ERROR: Invalid strategy chain: ' + valid,
)
}
lastStrategyState = valid.cachedStrategyState!
await cachePlusUpsert(
featureKey,
featureKey + hashes[lastChunkCalculated + 1],
serializeDataTool(lastStrategyState),
)
await cachePlusRemove(featureKey + hashes[lastChunkCalculated])
lastChunkCalculated++
if (lastChunkCalculated >= chunks.length - 1) {
toRemoveLaterPriorToReturn = async () => {
await cachePlusRemove(
featureKey + hashes[lastChunkCalculated],
)
}
}
}
log.prod('All chunks calculated')
} else {
valid =
toCheck.length === 0
? {
valid: true,
cachedStrategyState: cached?.cachedStrategy,
}
: await RULES.v1.rules(toCheck, cached?.cachedStrategy)
}
// console.log('chronos AFTER:')
// console.log(chronos.map((each) => each.idea!))
// console.log('valid:')
// console.log(valid!)
if (typeof valid! === 'string') {
throw new Error(
'CONTRACT RULES ERROR: Invalid strategy chain: ' + valid,
)
}
// Now we need to validate pricing and apply right timestamps.
// ACTUALLY above now happens on rules.
/*
const validatedPricing =
cached?.validatedPricing !== undefined
? cached?.validatedPricing
: new Set<number>()
for (const idea of chronos) {
if (idea.nftId === undefined) {
throw new Error(
'getValidatedStrategyChain: idea.nftId is undefined',
)
}
if (!validatedPricing.has(idea.nftId!)) {
await ProvableModule.validatePricingSignature(idea)
validatedPricing.add(idea.nftId!)
}
}
*/
// Clean Up the nullified ideas.
const nullifiedIdeas =
cached !== undefined
? cached.nullifiedIdeas
: new Map<number, Set<number>>()
for (const ideaIndex in strategyChain.ideas) {
for (const stageIndex in strategyChain.ideas[ideaIndex].stages) {
const stage = strategyChain.ideas[ideaIndex].stages[stageIndex]
if (stage.ideaStage.nullified !== undefined) {
strategyChain.ideas[ideaIndex].stages.splice(
Number(stageIndex),
1,
)
let nullifiedIdea = nullifiedIdeas.get(
stage.ideaStage.content.ideaKey!,
)
if (nullifiedIdea === undefined) {
nullifiedIdeas.set(
stage.ideaStage.content.ideaKey!,
new Set<number>(),
)
nullifiedIdea = nullifiedIdeas.get(
stage.ideaStage.content.ideaKey!,
)
}
nullifiedIdea!.add(stage.ideaStage.content.ideaStageKey!)
}
if (strategyChain.ideas[ideaIndex].stages.length === 0) {
strategyChain.ideas.splice(Number(ideaIndex), 1)
}
}
}
// Completing Missing PostProcessing Data
// by computing summaryCalc
if (valid!.cachedStrategyState !== undefined) {
valid!.cachedStrategyState!.strategy.summaryCalc =
postValidationCalculateSummaryCalc(valid!.cachedStrategyState!)
}
if (toRemoveLaterPriorToReturn !== undefined) {
await toRemoveLaterPriorToReturn!()
}
return {
result: strategyChain,
cache: {
nullifiedIdeas,
ideas: strategyChain.ideas,
cachedStrategy: valid!.cachedStrategyState!,
},
}
}
export const StrategyChainModule = {
getStrategyChain,
getValidatedStrategyChain,
chronologicalIdeasFromChain,
getActiveIdeasFromChain,
}