Skip to content

Instantly share code, notes, and snippets.

@tiagosiebler
Last active June 14, 2022 11:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tiagosiebler/e15ef3812050444591c06c848a6ab8e0 to your computer and use it in GitHub Desktop.
Save tiagosiebler/e15ef3812050444591c06c848a6ab8e0 to your computer and use it in GitHub Desktop.
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