Last active
June 14, 2022 11:33
-
-
Save tiagosiebler/e15ef3812050444591c06c848a6ab8e0 to your computer and use it in GitHub Desktop.
Track stop loss state per symbol : https://stately.ai/viz/0087daad-36bc-4396-b340-ff79672f9a63
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createMachine, interpret, Interpreter, State } from 'xstate'; | |
export function sleep(timeoutMS: number): Promise<void> { | |
return new Promise(resolve => setTimeout(resolve, timeoutMS)); | |
} | |
type StopLossService = Interpreter<unknown>; | |
type StopLossState = 'none' | 'active' | 'breakeven' | 'trailing'; | |
type StopLossStateEvents = 'SET_SL' | 'SET_BREAKEVEN' | 'SET_TRAILING' | 'RESET'; | |
const STOP_LOSS_MACHINE = createMachine<unknown>({ | |
id: 'Stop Loss', | |
initial: 'none', | |
states: { | |
none: { | |
on: { | |
SET_SL: { | |
target: 'active', | |
}, | |
}, | |
}, | |
active: { | |
on: { | |
SET_BREAKEVEN: { | |
target: 'breakeven', | |
}, | |
SET_TRAILING: { | |
target: 'trailing', | |
}, | |
RESET: { | |
target: 'none', | |
}, | |
}, | |
}, | |
breakeven: { | |
on: { | |
SET_TRAILING: { | |
target: 'trailing', | |
}, | |
RESET: { | |
target: 'none', | |
}, | |
}, | |
}, | |
trailing: { | |
on: { | |
RESET: { | |
target: 'none', | |
}, | |
}, | |
}, | |
}, | |
}); | |
function didStateChange(state: State<unknown>): boolean { | |
const previous = state.history?.value; | |
const next = state.value; | |
return previous !== next; | |
} | |
/** Is the next command a valid next step from the current state */ | |
function canTransitionTo(machine: StopLossService, command: string): boolean { | |
return machine.state.nextEvents.includes(command); | |
} | |
// Store to persist one machine instance per symbol | |
const stopLossStore: Record<string, StopLossService> = {}; | |
// Get machine instance for symbol (instance if missing) | |
function getSLService(symbol: string): StopLossService { | |
if (!stopLossStore[symbol]) { | |
const stopLossService = interpret(STOP_LOSS_MACHINE) | |
.start() | |
.onTransition(state => { | |
if (didStateChange(state) && state.history?.value) { | |
console.log( | |
new Date(), | |
`${symbol} SL state changed: ${state.history?.value} -> ${state.value}`, | |
); | |
} else { | |
console.log(new Date(), `${symbol} SL event ignored`); | |
} | |
}); | |
stopLossStore[symbol] = stopLossService; | |
} | |
return stopLossStore[symbol]; | |
} | |
function getSLState(machine: StopLossService, symbol: string): StopLossState { | |
// TODO: better types... | |
return machine.getSnapshot().value as StopLossState; | |
} | |
/** | |
* | |
* | |
* Attempt transition between SL states | |
* | |
* | |
*/ | |
const commands: StopLossStateEvents[] = [ | |
'SET_SL', | |
'RESET', | |
'SET_SL', | |
'SET_SL', | |
'SET_BREAKEVEN', | |
'SET_TRAILING', | |
'SET_SL', | |
'SET_BREAKEVEN', | |
'RESET', | |
]; | |
(async () => { | |
for (let i = 0; i < commands.length; i++) { | |
const cmd = commands[i]; | |
const symbol = 'BTCUSDT'; | |
const service = getSLService(symbol); | |
console.log( | |
new Date(), | |
JSON.stringify( | |
{ | |
fromState: getSLState(service, symbol), | |
cmd, | |
isValidEvent: canTransitionTo(service, cmd), | |
// validEvents: service.state.nextEvents, | |
}, | |
null, | |
2, | |
), | |
); | |
getSLService(symbol).send(cmd); | |
await sleep(500); | |
console.log(); | |
} | |
console.log(new Date(), 'end'); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment