1

Join Strings

by Benny Neugebauer JavaScript const intro = `Name:

Age:

Residence:

`; const intro = [ 'Name:', 'Age:', 'Residence:', ].join('

'); 2020-09-12

0

Destructuring

by Benny Neugebauer TypeScript function isUpTrend(dma: DMA): boolean { const {short, long} = dma.getResult(); return short.gt(long); } const isUpTrend = ({short, long}: DMAResult) => short.gt(long); 2020-09-06

1

Shorthand properties in mixins

by Benny Neugebauer SCSS @mixin border-dark { border-top: 0.1rem solid $color-asset-dark; } footer { @include border-dark; } @mixin border-dark { border-color: $color-asset-dark; border-style: solid; } footer { @include border-dark; border-top-width: 0.1rem; } 2020-05-10

1

Flags vs functions

by Benny Neugebauer TypeScript compressImage(image: File | Blob, isProfileImage: boolean = false): Promise<CompressedImage> { if (isProfileImage === true) { return this.compressImageWithWorker('worker/profile-image-worker.js', image); } return this.compressImageWithWorker('worker/image-worker.js', image); } compressProfileImage(image: File | Blob): Promise<CompressedImage> { return this.compressImageWithWorker('worker/profile-image-worker.js', image); } compressImage(image: File | Blob, isProfileImage: boolean = false): Promise<CompressedImage> { return this.compressImageWithWorker('worker/image-worker.js', image); } 2020-04-24

0

Index signatures

by Benny Neugebauer TypeScript private reactions: ko.Observable<{[userId: string]: string}>; private reactions: ko.Observable<Record<string, string>>; 2020-03-26

0

Type Aliases

by Benny Neugebauer TypeScript /** Active order price */ type ActiveOrderPrice = string; /** Sum of the size of the orders at active order price. */ type OrderSumSize = string; /** Count of orders at active order price. */ type CountOfOrders = string; /** Aggregated levels return only one size for each active order price. */ type AggregatedOrder = [ActiveOrderPrice, OrderSumSize, CountOfOrders]; /** Represents only the best bid and ask. */ export interface OrderBookLevel1 { "sequence": string, "bids": AggregatedOrder[], "asks": AggregatedOrder[] } export type OrderBookLevel1 = { sequence: string; bids: [[string, string, number]]; asks: [[string, string, number]]; }; 2020-02-11

1

Util function

by Benny Neugebauer TypeScript const currentUrlObject = new URL(currentUrl); const customUrlObject = new URL(customBackendUrl); const parseParams = (url: URL) => url.search ? querystring.parse(url.search.substr(1)) : {}; const currentUrlParams = parseParams(currentUrlObject); const customUrlParams = parseParams(customUrlObject); const currentUrlObject = new URL(currentUrl); const customUrlObject = new URL(customBackendUrl); const currentUrlParams = currentUrlObject.search ? querystring.parse(currentUrlObject.search.substr(1)) : {}; const customUrlParams = customUrlObject.search ? querystring.parse(customUrlObject.search.substr(1)) : {}; 2020-02-03

0

Export Type vs Export Interface

by Benny Neugebauer TypeScript type ISO_8601_UTC = string; type UUID_V4 = string; export enum OrderSide { BUY = 'buy', SELL = 'sell' } export enum Liquidity { MAKER = 'M', TAKER = 'T', } export interface Fill { "created_at": ISO_8601_UTC, "fee": string, "liquidity": Liquidity, "order_id": UUID_V4, "price": "7755.01000000", "product_id": string, "profile_id": UUID_V4, "settled": true, "side": OrderSide, "size": string, "trade_id": number, "usd_volume": string, "user_id": string } export type Fill = { created_at: string; trade_id: number; product_id: string; order_id: string; user_id: string; profile_id: string; liquidity: "T" | "M"; price: string; size: string; fee: string; side: Side; settled: boolean; usd_volume?: string; }; 2020-01-26

0

if vs. else-if

by Benny Neugebauer TypeScript if (valueA.lt(valueB)) { return 1; } if (valueA.gt(valueB)) { return -1; } return 0; if (valueA.lt(valueB)) { return 1; } else if (valueA.gt(valueB)) { return -1; } else { return 0; } 2020-01-25

1

Types vs. Enums

by Benny Neugebauer TypeScript type DeviceTypes = 'audioInput' | 'audioOutput' | 'screenInput' | 'videoInput'; enum DeviceTypes { AUDIO_INPUT = 'audioInput', AUDIO_OUTPUT = 'audioOutput', SCREEN_INPUT = 'screenInput', VIDEO_INPUT = 'videoInput' } 2020-01-22

1

Join arrays

by Benny Neugebauer JavaScript Promise.resolve([...cameras, ...microphones, ...speakers]) Promise.resolve(cameras.concat(microphones).concat(speakers)) 2020-01-18

1

Export utils

by Benny Neugebauer TypeScript import moment from 'moment'; const formatTime = (datetime: string | number): string => moment(datetime).format('YYYY-MM-DD HH:mm:ss'); export const TimeUtil = { formatTime, }; import moment from 'moment'; export class TimeUtil { static formatTime(datetime: string | number): string { return moment(datetime).format('YYYY-MM-DD HH:mm:ss'); } } 2019-12-24

1

Static read only members

by Benny Neugebauer TypeScript static get CONFIG() { return { AVERAGE_NUMBER_OF_CLIENTS: 4, }; } public static readonly CONFIG = { AVERAGE_NUMBER_OF_CLIENTS: 4, }; 2019-12-16

1

Naming conditions

by Benny Neugebauer TypeScript export const isTemporaryClientAndNonPersistent = (): boolean => { const enableTransientTemporaryClients = URLUtil.getURLParameter(QUERY_KEY.PERSIST_TEMPORARY_CLIENTS) === 'false' || (Config.FEATURE && Config.FEATURE.PERSIST_TEMPORARY_CLIENTS === false); return loadValue(StorageKey.AUTH.PERSIST) === false && enableTransientTemporaryClients; }; export const isTemporaryClientAndNonPersistent = (persist: boolean = false): boolean => { const isNonPersistentByUrl = URLUtil.getURLParameter(QUERY_KEY.PERSIST_TEMPORARY_CLIENTS) === 'false'; const isNonPersistentByServerConfig = Config.FEATURE && Config.FEATURE.PERSIST_TEMPORARY_CLIENTS === false; const isNonPersistent = isNonPersistentByUrl || isNonPersistentByServerConfig; const isTemporaryByLocalStorage = loadValue(StorageKey.AUTH.PERSIST) === false; const isTemporaryByClientSelection = persist === false; const isTemporary = isTemporaryByLocalStorage || isTemporaryByClientSelection; return isTemporary && isNonPersistent; }; 2019-12-05

1

UI separation of concerns

by Benny Neugebauer TypeScript Syntax Extension const loginImmediately = Runtime.isLinux(); {loginImmediately && window.location.assign(`${WEBAPP_URL}/auth/?immediate_login#login`)} const loginImmediately = Runtime.isLinux() && `${WEBAPP_URL}/auth/?immediate_login#login`); {loginImmediately && window.location.assign(loginImmediately)} 2019-12-03

1

Material UI: Edit TextField input style

by Benny Neugebauer TypeScript Syntax Extension import {TextField} from '@material-ui/core'; import React, {useEffect, useRef} from 'react'; function renderValue(): JSX.Element { const input = useRef<HTMLInputElement>(); useEffect(() => { if (input.current) { input.current.style.textTransform = 'capitalize'; } }, []); return ( <> <TextField inputRef={input} label="My Label" variant="outlined" /> </> ); } import {TextField} from '@material-ui/core'; import React from 'react'; function renderValue(): JSX.Element { return ( <> <TextField inputProps={{style: {textTransform: 'capitalize'}}} label="My Label" variant="outlined" /> </> ); } 2019-11-15

0

Node.js util.promisify

by Benny Neugebauer TypeScript import Twitter = require('twitter'); import {promisify} from 'util'; // https://apps.twitter.com/ const client = new Twitter({ consumer_key: '', consumer_secret: '', access_token_key: '', access_token_secret: '' }); client.get('favorites/list', function (error, tweets) { if (!error) { console.log(tweets); } }); import Twitter = require('twitter'); import {promisify} from 'util'; // https://apps.twitter.com/ const client = new Twitter({ consumer_key: '', consumer_secret: '', access_token_key: '', access_token_secret: '' }); const get = promisify(client.get.bind(client)); get('favorites/list').then((tweets) => { console.log(tweets); }); 2019-11-13

1

Set event.target.value

by Benny Neugebauer TypeScript <TextField fullWidth={true} multiline={true} rows={4} rowsMax={Infinity} onChange={({target: {value}}) => props.setStrategyConfig(value)} value={props.strategyConfig} variant="outlined" /> <TextField fullWidth={true} multiline={true} rows={4} rowsMax={Infinity} onChange={(event) => props.setStrategyConfig(event.target.value)} value={props.strategyConfig} variant="outlined" /> 2019-11-07

0

default export

by Benny Neugebauer TypeScript const CandleImportBox = (props: Props) => { // ... }; export default withStyles(styles)(CandleImportBox); export default withStyles(styles)((props: Props) => { // ... }); 2019-11-06

0

useEffect with async await

by Benny Neugebauer TypeScript useEffect(() => { const fetchAllStrategies = async () => { setIsLoading(true); const strategyNames = await apiClient.rest.api.v1.strategyService.getAll(); setAvailableStrategies(strategyNames); setSelectedStrategy(strategyNames[0]); setIsLoading(false); }; fetchAllStrategies(); }, []); useEffect(function fetchAllStrategies() { setIsLoading(true); apiClient.rest.api.v1.strategyService.getAll().then((strategyNames) => { setAvailableStrategies(strategyNames); setSelectedStrategy(strategyNames[0]); setIsLoading(false); }); }, []); 2019-11-06

0

Ternary Ternary

by Benny Neugebauer TypeScript const {value, error, loading} = useAsync(); if (value) { return renderValue(value, props); } else if (error) { return renderError(error); } else { return renderLoading(); } const {value, error, loading} = useAsync(); { loading ? renderLoading() : error ? renderError(error) : renderValue(value, props); } 2019-11-06

0

Class component vs. Function component

by Benny Neugebauer TypeScript Syntax Extension import {Button, createStyles, Theme, Typography, withStyles, WithStyles} from '@material-ui/core'; import React from 'react'; const styles = (theme: Theme) => createStyles({ Button: { marginBottom: theme.spacing(1), }, ContentWrapper: { padding: theme.spacing(2), }, }); interface Props extends WithStyles<typeof styles> { title: string; } interface State { counter: number; } class LoremIpsumClassComponent extends React.Component<Props, State> { constructor(props: Props) { super(props); this.state = { counter: 0, }; } countUp(event: React.MouseEvent<HTMLButtonElement>): void { const incremented = this.state.counter + 1; this.setState({ counter: incremented, }); } render(): JSX.Element { const {classes, title} = this.props; return ( <div className={classes.ContentWrapper}> <Typography variant="h5">{title || 'Untitled'}</Typography> <br/> <Button className={classes.Button} onClick={this.countUp.bind(this)} variant="contained"> Count up! </Button> <Typography paragraph>{`${this.state.counter}`}</Typography> </div> ); } } export default withStyles(styles)(LoremIpsumClassComponent); import {Button, createStyles, Theme, Typography, withStyles, WithStyles} from '@material-ui/core'; import React, {useState} from 'react'; const styles = (theme: Theme) => createStyles({ Button: { marginBottom: theme.spacing(1), }, ContentWrapper: { padding: theme.spacing(2), }, }); interface Props extends WithStyles<typeof styles> { title: string; } export const LoremIpsumFunctionComponent = withStyles(styles)((props: Props & WithStyles<typeof styles>): JSX.Element => { const [counter, setCounter] = useState(0); const {classes, title} = props; return ( <div className={classes.ContentWrapper}> <Typography variant="h5">{title || 'Untitled'}</Typography> <br /> <Button className={classes.Button} onClick={(event: React.MouseEvent<HTMLButtonElement>) => { const incremented = counter + 1; setCounter(incremented); }} variant="contained"> Count up! </Button> <Typography paragraph>{`${counter}`}</Typography> </div> ); }); 2019-09-22

1

Use post-fix expression in tests

by Benny Neugebauer TypeScript it('should correctly calculate EMAs with weight 26', () => { const ema = new EMA(26); prices.forEach((price, index) => { ema.update(new Big(price)); const actual = ema.getResult(); if (actual) { const expected = new Big(ema26results[index]); expect(actual.toPrecision(12)).toEqual(expected.toPrecision(12)); } }); }); it('should correctly calculate EMAs with weight 26', () => { const ema = new EMA(26); prices.forEach((price, index) => { ema.update(new Big(price)); const actual = ema.getResult(); const expected = new Big(ema26results[index]); expect(actual!.toPrecision(12)).toEqual(expected.toPrecision(12)); }); }); 2019-09-18

0

Addition

by Benny Neugebauer JavaScript [3, 2, 5, 8, 4, 1, 4, 3, 8, 5, 6, 7, 9, 6, 8, 5, 4, 8, 7, 6].reduce((a, b) => a + b); let result = 0; [3, 2, 5, 8, 4, 1, 4, 3, 8, 5, 6, 7, 9, 6, 8, 5, 4, 8, 7, 6].forEach(a => result += a); 2019-09-18

1

Enum strings vs enum numbers

by Benny Neugebauer TypeScript export enum EventValidation { INVALID = 'INVALID', OUTDATED = 'OUTDATED', VALID = 'VALID', } export enum EventValidation { INVALID = 0, OUTDATED = 2, VALID = 1, } 2019-09-10

0

Separation of concerns

by Benny Neugebauer TypeScript watch(websocket: BinanceAPI.WebSocket, callback: CandleTickerCallback): void { // Info: We always use '1m' intervals because we batch them ourselves const webSocketHandles = websocket.candles(this.pair.asString(''), '1m', (ticker: BinanceAPI.Candle): void => { const batchedCandle = this.onCandle(ticker); if (batchedCandle) { callback(batchedCandle, this.pair, this.interval); } }); this.watchCandlesActivity(webSocketHandles); } watch(websocket: BinanceAPI.WebSocket, callback: CandleTickerCallback): Function { // Info: We always use '1m' intervals because we batch them ourselves return websocket.candles(this.pair.asString(''), '1m', (ticker: BinanceAPI.Candle): void => { const batchedCandle = this.onCandle(ticker); if (batchedCandle) { callback(batchedCandle, this.pair, this.interval); } }); } 2019-09-04

0

Material UI Selects

by Benny Neugebauer TypeScript Syntax Extension <TextField className={classes.TextField} label="Age" onChange={(event: React.ChangeEvent<HTMLInputElement>) => { const value = parseInt(event.target.value, 10); this.setState({interval: value}); }} required={true} select={true} value={this.state.interval} variant="outlined"> {[ {name: 'Ten', value: 10}, {name: 'Twenty', value: 20}, {name: 'Thirty', value: 30}, ].map((entry, index) => { return <MenuItem key={index} value={entry.value}> {entry.name} </MenuItem> })} </TextField> <br/> <FormControl className={classes.TextField}> <InputLabel htmlFor="age-simple">Age</InputLabel> <Select value={this.state.interval} onChange={(event: React.ChangeEvent<{name?: string; value: unknown}>) => { this.setState({interval: event.target.value}); }} inputProps={{ name: 'age', id: 'age-simple', }} > <MenuItem value={10}>Ten</MenuItem> <MenuItem value={20}>Twenty</MenuItem> <MenuItem value={30}>Thirty</MenuItem> </Select> </FormControl> 2019-07-23

0

Complex Functional Components

by Benny Neugebauer TypeScript import 'date-fns'; import React from 'react'; import Grid from '@material-ui/core/Grid'; import { makeStyles, createStyles } from '@material-ui/core/styles'; import DateFnsUtils from '@date-io/date-fns'; import { MuiPickersUtilsProvider, KeyboardTimePicker, KeyboardDatePicker, } from '@material-ui/pickers'; const useStyles = makeStyles( createStyles({ grid: { width: '60%', }, }), ); export default function MaterialUIPickers() { // The first commit of Material-UI const [selectedDate, setSelectedDate] = React.useState<Date | null>( new Date('2014-08-18T21:11:54'), ); const classes = useStyles(); function handleDateChange(date: Date | null) { setSelectedDate(date); } return ( <MuiPickersUtilsProvider utils={DateFnsUtils}> <Grid container className={classes.grid} justify="space-around"> <KeyboardDatePicker margin="normal" id="mui-pickers-date" label="Date picker" value={selectedDate} onChange={handleDateChange} KeyboardButtonProps={{ 'aria-label': 'change date', }} /> <KeyboardTimePicker margin="normal" id="mui-pickers-time" label="Time picker" value={selectedDate} onChange={handleDateChange} KeyboardButtonProps={{ 'aria-label': 'change time', }} /> </Grid> </MuiPickersUtilsProvider> ); } ... 2019-07-23

5

Sort function

by Lipis JavaScript if (propA > propB) { return 1; } if (propA < propB) { return -1; } return 0; return propA > probB ? 1 : probA < probB ? -1 : 0 2019-07-22

2

Exclude certain values from interface property

by Benny Neugebauer TypeScript import {PreKey} from '../../auth/'; export enum LegalHoldMemberStatus { DISABLED = 'disabled', ENABLED = 'enabled', PENDING = 'pending', } export interface LegalHoldEnabledMemberData { client_id: string; last_prekey: PreKey; status: LegalHoldMemberStatus.ENABLED | LegalHoldMemberStatus.PENDING; } export interface LegalHoldDisabledMemberData { status: LegalHoldMemberStatus.DISABLED; } export type LegalHoldMemberData = LegalHoldEnabledMemberData | LegalHoldDisabledMemberData; import {PreKey} from '../../auth/'; export enum LegalHoldMemberStatus { DISABLED = 'disabled', ENABLED = 'enabled', PENDING = 'pending', } export interface LegalHoldEnabledMemberData { client_id: string; last_prekey: PreKey; status: Exclude<LegalHoldMemberStatus, LegalHoldMemberStatus.DISABLED>; } export interface LegalHoldDisabledMemberData { status: LegalHoldMemberStatus.DISABLED; } export type LegalHoldMemberData = LegalHoldEnabledMemberData | LegalHoldDisabledMemberData; 2019-06-12

2

Function export

by Benny Neugebauer TypeScript export const validateConfig = (swaggerJson: { [index: string]: any }): void => { if (!swaggerJson.paths) { throw new Error('The "paths" attribute is missing.'); } if (!swaggerJson.swagger || !swaggerJson.swagger.startsWith('2.')) { throw new Error('Only Swagger v2.x is supported.'); } }; export function validateConfig(swaggerJson: { [index: string]: any }): void { if (!swaggerJson.paths) { throw new Error('The "paths" attribute is missing.'); } if (!swaggerJson.swagger || !swaggerJson.swagger.startsWith('2.')) { throw new Error('Only Swagger v2.x is supported.'); } } 2019-04-28

1

Static functions vs. exported consts

by Benny Neugebauer JavaScript class AssetMapper { static mapProfileAssets(userId, assets) { const sizeMap = { complete: 'medium', preview: 'preview', }; return assets .filter(asset => asset.type === 'image') .reduce((mappedAssets, asset) => { const assetRemoteData = z.assets.AssetRemoteData.v3(asset.key, true); return !sizeMap[asset.size] ? mappedAssets : Object.assign({}, mappedAssets, {[sizeMap[asset.size]]: assetRemoteData}); }, {}); } static mapProfileAssetsV1(userId, pictures) { const [previewPicture, mediumPicture] = pictures; const previewAsset = previewPicture ? z.assets.AssetRemoteData.v1(userId, previewPicture.id, true) : undefined; const mediumAsset = mediumPicture ? z.assets.AssetRemoteData.v1(userId, mediumPicture.id, true) : undefined; return {medium: mediumAsset, preview: previewAsset}; } static updateUserEntityAssets(userEntity, mappedAssets = {}) { const {preview, medium} = mappedAssets; if (preview) { userEntity.previewPictureResource(preview); } if (medium) { userEntity.mediumPictureResource(medium); } } } export {AssetMapper}; export const mapProfileAssets = (userId, assets) => { const sizeMap = { complete: 'medium', preview: 'preview', }; return assets .filter(asset => asset.type === 'image') .reduce((mappedAssets, asset) => { const assetRemoteData = z.assets.AssetRemoteData.v3(asset.key, true); return !sizeMap[asset.size] ? mappedAssets : Object.assign({}, mappedAssets, {[sizeMap[asset.size]]: assetRemoteData}); }, {}); }; export const mapProfileAssetsV1 = (userId, pictures) => { const [previewPicture, mediumPicture] = pictures; const previewAsset = previewPicture ? z.assets.AssetRemoteData.v1(userId, previewPicture.id, true) : undefined; const mediumAsset = mediumPicture ? z.assets.AssetRemoteData.v1(userId, mediumPicture.id, true) : undefined; return {medium: mediumAsset, preview: previewAsset}; }; export const updateUserEntityAssets = (userEntity, mappedAssets = {}) => { const {preview, medium} = mappedAssets; if (preview) { userEntity.previewPictureResource(preview); } if (medium) { userEntity.mediumPictureResource(medium); } }; 2019-04-23

4

Export const directly or in curly braces

by AndyTheGiant JavaScript const Status = { AWAY: 'Status.AWAY', AVAILABLE: 'Status.AVAILABLE', }; export {Status} export const Status = { AWAY: 'Status.AWAY', AVAILABLE: 'Status.AVAILABLE', }; 2019-04-23

2

Enum vs. Type

by Benny Neugebauer TypeScript export enum SelfTradePrevention { CANCEL_BOTH = 'cb', CANCEL_NEWEST = 'cn', CANCEL_OLDEST = 'co', DECREMENT_AND_CANCEL = 'dc', } export type SelfTradePrevention = 'cb' | 'cn' | 'co' | 'dc'; 2019-04-22

0

Extend a CustomEvent

by Benny Neugebauer TypeScript interface CreateSSOAccountDetail { reachedMaximumAccounts: boolean } // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#customevent-is-now-a-generic-type class CreateSSOAccountResponse extends CustomEvent<CreateSSOAccountDetail> { constructor(eventName: string, detail: CustomEventInit<CreateSSOAccountDetail>) { super(eventName, detail); } } class AutomatedSingleSignOn { private onResponseReceived(event: CreateSSOAccountResponse) { if (event.detail.reachedMaximumAccounts) { this.showError(); } } } export interface CreateSSOAccountResponse { reachedMaximumAccounts: boolean }; class AutomatedSingleSignOn { private onResponseReceived(event: CustomEvent<CreateSSOAccountResponse>) { if (event.detail.reachedMaximumAccounts) { this.showError(); } } } 2019-03-26

1

Avoid ternary operators and use defaults

by Benny Neugebauer TypeScript private showError() { const detail = MAXIMUM_ACCOUNTS === 1 ? getText('wrapperAddAccountErrorMessageSingular') : getText('wrapperAddAccountErrorMessagePlural'); const message = MAXIMUM_ACCOUNTS === 1 ? getText('wrapperAddAccountErrorTitleSingular') : getText('wrapperAddAccountErrorTitlePlural'); dialog.showMessageBox({ detail, message, type: 'warning', }); } private showError() { let detail = getText('wrapperAddAccountErrorMessagePlural'); let message = getText('wrapperAddAccountErrorTitlePlural'); if (MAXIMUM_ACCOUNTS === 1) { detail = getText('wrapperAddAccountErrorMessageSingular'); message = getText('wrapperAddAccountErrorTitleSingular'); } dialog.showMessageBox({ detail, message, type: 'warning', }); } 2019-03-26

1

Extensibility through classes

by Benny Neugebauer TypeScript class ValidationUtil { public static PATTERN = { UUID_V4: '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}', }; static isUUIDv4(candidate: string): boolean { const uuidv4Regex = new RegExp(`^${ValidationUtil.PATTERN.UUID_V4}$`, 'i'); return uuidv4Regex.test(candidate); } } export {ValidationUtil} export const PATTERN = { UUID_V4: '[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}', }; export function isUUIDv4(candidate: string): boolean { const uuidv4Regex = new RegExp(`^${PATTERN.UUID_V4}$`, 'i'); return uuidv4Regex.test(candidate); } 2019-03-20

0

async IIFE vs. Promise initialization

by Benny Neugebauer TypeScript ConfigSaver.load(cli.config) .then(async (config: SerializedTradingConfig[]) => { config.forEach(async tradingConfig => { const handler = new TrailingStopHandler(tradingConfig); const stopConfig = await handler.init(); const intervalID = setInterval(async () => { const status = await handler.watch(); if (status === StopPriceState.REACHED) { clearInterval(intervalID); } else if (status === StopPriceState.INCREASED) { const updates = stopConfig.serialize(); tradingConfig.price = updates.pricePerCoin; tradingConfig.reset = updates.trailingReset; tradingConfig.stop = updates.trailingStop; ConfigSaver.save(cli.config, config); } }, ms(tradingConfig.updateInterval)); }); }); (async () => { const config = await ConfigSaver.load(cli.config); config.forEach(async tradingConfig => { const handler = new TrailingStopHandler(tradingConfig); const stopConfig = await handler.init(); const intervalID = setInterval(async () => { const status = await handler.watch(); if (status === StopPriceState.REACHED) { clearInterval(intervalID); } else if (status === StopPriceState.INCREASED) { const updates = stopConfig.serialize(); tradingConfig.price = updates.pricePerCoin; tradingConfig.reset = updates.trailingReset; tradingConfig.stop = updates.trailingStop; ConfigSaver.save(cli.config, config); } }, ms(tradingConfig.updateInterval)); }); })(); 2019-03-18

0

Static function exports

by Benny Neugebauer TypeScript import fs from 'fs-extra'; import {CurrencyPairConfig} from '@lambot/exchange'; import {TradingStopConfig} from '../strategy'; type SerializedTradingConfig = CurrencyPairConfig & TradingStopConfig; class ConfigSaver { static load(path: string): SerializedTradingConfig { return fs.readJsonSync(path); } static save(path: string, config: SerializedTradingConfig) { return fs.writeJsonSync(path, config, { spaces: 2, }); } } export {ConfigSaver, SerializedTradingConfig}; import fs from 'fs-extra'; import {CurrencyPairConfig} from '@lambot/exchange'; import {TradingStopConfig} from '../strategy'; type SerializedTradingConfig = CurrencyPairConfig & TradingStopConfig; const load = (path: string): SerializedTradingConfig => fs.readJsonSync(path); const save = (path: string, config: SerializedTradingConfig) => fs.writeJsonSync(path, config, { spaces: 2, }); export {load, save, SerializedTradingConfig}; 2019-03-10

1

Differentiation through naming

by Benny Neugebauer TypeScript enum StopPriceState { RAISED = 'RAISED', REACHED = 'REACHED', REMAINED = 'REMAINED', } enum StopPriceState { INCREASED = 'INCREASED', REACHED = 'REACHED', UNCHANGED = 'UNCHANGED', } 2019-03-10

0

Functional programming for if-statements

by Benny Neugebauer TypeScript const no = (value: string) => value.length === 0; if (no(config.lastBuy)) { logger.log('No last buy setup.'); } if (no(config.quantity)) { logger.log('No quantity supplied.'); } if (config.lastBuy.length === 0) { logger.log('No last buy setup.'); } if (config.quantity.length === 0) { logger.log('No quantity supplied.'); } 2019-03-09

2

Using path.join

by Benny Neugebauer JavaScript path.join(pkg.name, 'content', 'image', 'logo'); path.join(pkg.name, 'content/image/logo'); 2019-02-22

1

Comments without added value

by Benny Neugebauer TypeScript /** * Buy resource */ export class Buy implements Resource { /** * Amount in bitcoin, litecoin or ethereum */ amount: MoneyHash; /** * Completes a buy that is created in commit: false state. * If the exchange rate has changed since the buy was created, this call will fail with the error “The exchange rate * updated while you were waiting. The new total is shown below”. The buy’s total will also be updated. You can * repeat the `commit` call to accept the new values and start the buy at the new rates. Scope: wallet:buys:create */ commit(cb: (error: Error, transaction: Buy) => void): void; /** * Has this buy been committed? */ committed: boolean; /** * Fee associated to this buy */ fees: Fee[]; /** * Hold period for transfer. */ hold_business_days: number; /** * Was this buy executed instantly? */ instant: boolean; /** * Is it the first buy for this symbol? */ is_first_buy: boolean; /** * Associated payment method (e.g. a bank, fiat account) */ payment_method: ResourceRef /** * When a buy isn’t executed instantly, it will receive a payout date for the time it will be executed. ISO timestamp */ payout_at?: string; /** * Is there another action required to make the transfer pass? */ requires_completion_step: boolean; /** * Constant "buy" */ resource: "buy"; /** * Status */ status: BuyStatus; /** * Fiat amount without fees */ subtotal: MoneyHash; /** * Fiat amount with fees */ total: MoneyHash; /** * Associated transaction (e.g. a bank, fiat account) */ transaction: ResourceRef; /** * Unit price of the base currency. */ unit_price: UnitPrice; } export class Buy implements Resource { amount: MoneyHash; commit(cb: (error: Error, transaction: Buy) => void): void; committed: boolean; fees: Fee[]; hold_business_days: number; instant: boolean; is_first_buy: boolean; payment_method: ResourceRef; payout_at?: string; requires_completion_step: boolean; resource: "buy"; status: BuyStatus; subtotal: MoneyHash; total: MoneyHash; transaction: ResourceRef; unit_price: UnitPrice; } 2019-02-09

2

Node.js util.promisify()

by Benny Neugebauer TypeScript import * as coinbase from 'coinbase'; import * as util from 'util'; private async clientGetAccounts(): Promise<coinbase.Account[]> { const options = {}; return await util.promisify(this.client.getAccounts.bind(this.client))(options); } import * as coinbase from 'coinbase'; private clientGetAccounts(): Promise<coinbase.Account[]> { return new Promise(async (resolve, reject) => { const options = {}; const callback = (error: Error, accounts: coinbase.Account[]) => { return error ? reject(error) : resolve(accounts); }; this.client.getAccounts(options, callback); }); } 2019-02-09

1

Promise.reject vs. throw

by Benny Neugebauer TypeScript private async getAccount(currency: Asset): Promise<coinbase.Account> { const accounts: coinbase.Account[] = await this.getAccounts(); for (let account of accounts) { if (account.currency === currency) { return Promise.resolve(account); } } const error = new Error(`Cannot find account for currency "${currency}".`); return Promise.reject(error); } private async getAccount(currency: Asset): Promise<coinbase.Account> { const accounts: coinbase.Account[] = await this.getAccounts(); for (let account of accounts) { if (account.currency === currency) { return account; } } const error = new Error(`Cannot find account for currency "${currency}".`); throw error; } 2019-02-09

1

inline callbacks vs. external callbacks

by Benny Neugebauer TypeScript private async executeBuy(pair: CurrencyPair, quantity: string): Promise<coinbase.Buy> { return new Promise(async (resolve, reject) => { const account = await this.getAccount(pair.base); account.buy( { amount: quantity, currency: pair.base, }, (error: Error, response: coinbase.Buy) => { if (error) { reject(error); } else { resolve(response); } } ); }); } private async executeBuy(pair: CurrencyPair, quantity: string): Promise<coinbase.Buy> { return new Promise(async (resolve, reject) => { const account = await this.getAccount(pair.base); const options = { amount: quantity, currency: pair.base, }; const callback = (error: Error, response: coinbase.Buy) => { if (error) { reject(error); } else { resolve(response); } }; account.buy(options, callback); }); } 2019-02-09

2

async/await vs. Promise.then()

by Benny Neugebauer TypeScript getBalances(): Promise<Balance[]> { const balances: Balance[] = []; return this.getAccounts().then((accounts: coinbase.Account[]) => { for (const account of accounts) { balances.push(new Balance(account.balance.currency, account.balance.amount)); } return balances; }); } async getBalances(): Promise<Balance[]> { const balances: Balance[] = []; const accounts: coinbase.Account[] = await this.getAccounts(); for (const account of accounts) { balances.push(new Balance(account.balance.currency, account.balance.amount)); } return balances; } 2019-02-09

1

Public methods

by Benny Neugebauer TypeScript async constructQuantity(pair: CurrencyPair, price: string, side: OrderSide): Promise<string> { return (side === OrderSide.BUY) ? this.constructBuyQuantity(pair, price) : this.constructSellQuantity(pair); } private async constructBuyQuantity(pair: CurrencyPair, price: string): Promise<string> { const balance = await this.getBalance(pair.counter); return balance.getWorth(new Price(pair, price)).toFixed(8); } private async constructSellQuantity(pair: CurrencyPair): Promise<string> { const balance = await this.getBalance(pair.base); return balance.availableAmountAsString(); } async constructQuantity(pair: CurrencyPair, price: string, side: OrderSide): Promise<string> { if (side === OrderSide.BUY) { const balance = await this.getBalance(pair.counter); return balance.getWorth(new Price(pair, price)).toFixed(8); } const balance = await this.getBalance(pair.base); return balance.availableAmountAsString(); } 2019-01-08

0

JSON imports in TypeScript

by Benny Neugebauer TypeScript import {version} from '../package.json'; console.log(version); // Pros: // https://blogs.msdn.microsoft.com/typescript/2018/05/31/announcing-typescript-2-9/#json-imports // JSON fields are automatically typed // Cons: // Requires TypeScript 2.9+ // Needs "moduleResolution": "node" in "tsconfig.json" // Needs "resolveJsonModule": true in "tsconfig.json" // JSON files need to be inside of the "rootDir" (tsconfig.json) // Typical errors: // error TS5070: Option '--resolveJsonModule' cannot be specified without 'node' module resolution strategy. // error TS6059: File 'package.json' is not under 'rootDir'. 'rootDir' is expected to contain all source files. const {version}: {version: string} = require('../package.json'); console.log(version); // Pros: // No need to adjust "tsconfig.json" // Cons: // No automatically typing 2018-11-13

0

Jest transform declaration

by Benny Neugebauer JavaScript module.exports = { bail: true, roots: ['src'], transform: { '^.+\\.(js|jsx)$': '<rootDir>/jest.transform.js', }, verbose: true, }; module.exports = { bail: true, roots: ['src'], transform: { '^.+\\.jsx?$': './jest.transform.js', }, verbose: true, }; 2018-09-18

1

path.join vs. String Templates

by Benny Neugebauer JavaScript const Jasmine = require('jasmine'); const path = require('path'); const jasmine = new Jasmine(); jasmine.loadConfigFile(path.join(process.cwd(), 'spec', 'support', 'jasmine.json')); const Jasmine = require('jasmine'); const path = require('path'); const jasmine = new Jasmine(); jasmine.loadConfigFile(`${process.cwd()}/spec/support/jasmine.json`); 2018-09-15

0

Regex with optional ending character

by Benny Neugebauer JavaScript /\.ts(x?)$/.test('index.ts') /\.ts(x?)$/.test('index.tsx') /\.tsx?$/.test('index.ts') /\.tsx?$/.test('index.tsx') 2018-08-24

2

String concatenation vs String Templates

by Florian Keller JavaScript const hello = 'Hello, '; const name = 'World'; const greeting = hello + name; const hello = 'Hello, '; const name = 'World'; const greeting = `${hello}${name}`; 2018-08-06

5

Promise vs async/await

by Gregor Herdmann JavaScript return this.cryptobox.decrypt(sessionId, messageBytes.buffer) .then(value => ({isSuccess: true, value})) .catch(error => { const isOutdatedMessage = error.code === ProteusErrors.DecryptError.CODE.CASE_208; const isDuplicateMessage = error.code === ProteusErrors.DecryptError.CODE.CASE_209; if (isOutdatedMessage || isDuplicateMessage) { return {error, isSuccess: false}; } throw error; }); try { const result = await this.cryptobox.decrypt(sessionId, messageBytes.buffer); return {isSuccess: true, value: result}; } catch (error) { const isOutdatedMessage = error.code === ProteusErrors.DecryptError.CODE.CASE_208; const isDuplicateMessage = error.code === ProteusErrors.DecryptError.CODE.CASE_209; if (isOutdatedMessage || isDuplicateMessage) { return {error, isSuccess: false}; } throw error; } 2018-07-24

0

Define style at parent or child

by Benny Neugebauer Less .start-ui-list-wrapper { display: flex; overflow: hidden; flex: 1 1; flex-direction: column; &.split-view, &.split-view .start-ui-list { overflow: hidden; flex: unset; } } .start-ui-list { position: relative; flex: 1 1; overflow-x: hidden; overflow-y: scroll; } .start-ui-list-wrapper { display: flex; overflow: hidden; flex: 1 1; flex-direction: column; &.split-view { flex: unset; } } .start-ui-list { position: relative; flex: 1 1; overflow-x: hidden; overflow-y: scroll; .split-view & { overflow: hidden; flex: unset; } } 2018-07-19

1

Use of current outer selector

by Benny Neugebauer Less .start-ui-list-wrapper { display: flex; overflow: hidden; flex: 1 1; flex-direction: column; &.split-view { &, & .start-ui-list { flex: unset; overflow: hidden; } } } .start-ui-list { position: relative; flex: 1 1; overflow-x: hidden; overflow-y: scroll; } .start-ui-list-wrapper { display: flex; overflow: hidden; flex: 1 1; flex-direction: column; &.split-view, &.split-view .start-ui-list { overflow: hidden; flex: unset; } } .start-ui-list { position: relative; flex: 1 1; overflow-x: hidden; overflow-y: scroll; } 2018-07-19

2

Ternary operator on log messages

by Benny Neugebauer JavaScript if (missingClients.length === 0) { this.logger.info(`Message was sent to all other "${selfClients.length}" clients.`); } else { this.logger.info(`Message was NOT sent to the following own clients: ${missingClients.join(',')}`); } const logMessage = missingClients.length ? `Message was sent to all other "${selfClients.length}" clients.` : `Message was NOT sent to the following own clients: ${missingClients.join(',')}`; this.logger.info(logMessage); 2018-07-10

0

Exception naming

by Benny Neugebauer TypeScript class UserIsUnknownError extends UserError { constructor( message: string, label: BackendErrorLabel = BackendErrorLabel.CLIENT_ERROR, code: StatusCode = StatusCode.BAD_REQUEST ) { super(message, label, code); Object.setPrototypeOf(this, UserIsUnknownError.prototype); this.name = 'UserIsUnknownError'; } } class UnknownUserError extends UserError { constructor( message: string, label: BackendErrorLabel = BackendErrorLabel.CLIENT_ERROR, code: StatusCode = StatusCode.BAD_REQUEST ) { super(message, label, code); Object.setPrototypeOf(this, UnknownUserError.prototype); this.name = 'UnknownUserError'; } } 2018-07-09

0

async/await

by Benny Neugebauer TypeScript public createConversation(name: string, users: string[] = []): Promise<Conversation> { if (this.apiClient.context && !users.includes(this.apiClient.context.userId)) { users = users.concat([this.apiClient.context.userId]); } const newConversation: NewConversation = { name, users }; return this.apiClient.conversation.api.postConversation(newConversation).catch(error => { if (error && error.response) { throw BackendErrorMapper.map(error.response.data as BackendError); } throw error; }); } public async createConversation(name: string, users: string[] = []): Promise<Conversation> { if (this.apiClient.context && !users.includes(this.apiClient.context.userId)) { users = users.concat([this.apiClient.context.userId]); } const newConversation: NewConversation = { name, users }; try { const conversation = await this.apiClient.conversation.api.postConversation(newConversation); return conversation; } catch (error) { if (error && error.response) { throw BackendErrorMapper.map(error.response.data as BackendError); } throw error; } } 2018-07-06

0

String Templates vs. String Array

by Benny Neugebauer TypeScript public async deleteBot(conversationId: string, botId: string): Promise<void> { const config: AxiosRequestConfig = { method: 'delete', url: `${ConversationAPI.URL.CONVERSATIONS}/${conversationId}/${ConversationAPI.URL.BOTS}/${botId}`, }; await this.client.sendJSON(config); } public async deleteBot(conversationId: string, botId: string): Promise<void> { const config: AxiosRequestConfig = { method: 'delete', url: [ ConversationAPI.URL.CONVERSATIONS, conversationId, ConversationAPI.URL.BOTS, botId ].join('/'), }; await this.client.sendJSON(config); } 2018-07-03

0

Filter array by several conditions

by Benny Neugebauer JavaScript const isOTRMessage = notification => notification.type === 'conversation.otr-message-add'; const isInCurrentConversation = notification => notification.conversation === conversationId; const wasSentByOurCurrentClient = notification => notification.from === userId && (notification.data && notification.data.sender === clientId); const hasExpectedTimestamp = notification => notification.time === dateTime.toISOString(); notificationService .getNotifications(undefined, undefined, 10000) .then(({notifications}) => notifications .map(notification => notification.payload) .reduce((acc, payload) => acc.concat(payload)) .filter(isOTRMessage) .filter(isInCurrentConversation) .filter(wasSentByOurCurrentClient) .filter(hasExpectedTimestamp) ); const isOTRMessage = notification => notification.type === 'conversation.otr-message-add'; const isInCurrentConversation = notification => notification.conversation === conversationId; const wasSentByOurCurrentClient = notification => notification.from === userId && (notification.data && notification.data.sender === clientId); const hasExpectedTimestamp = notification => notification.time === dateTime.toISOString(); notificationService .getNotifications(undefined, undefined, 10000) .then(({notifications}) => notifications .map(notification => notification.payload) .reduce((acc, payload) => acc.concat(payload)) .filter(notification => { return ( isOTRMessage(notification) && isInCurrentConversation(notification) && wasSentByOurCurrentClient(notification) && hasExpectedTimestamp(notification) ); }); ); 2018-06-29

1

Default Prop Values in React

by Benny Neugebauer JavaScript import React from 'react'; class List extends React.Component { constructor(props) { super(props); } render() { return ( <ol> {this.props.items.map((item, index) => <ListItem key={index} text={item}/>)} </ol> ); } } List.defaultProps = { items: [] }; import React from 'react'; const List = ({items = []}) => { return ( <ol> {items.map((item, index) => <ListItem key={index} text={item}/>)} </ol> ); }; 2018-06-27

2

Destructuring

by Benny Neugebauer TypeScript case CONVERSATION_EVENT.MESSAGE_TIMER_UPDATE: { const {data: {message_timer: expireInMillis}, conversation} = data as ConversationMessageTimerUpdateEvent; this.service.conversation.setConversationLevelTimer(conversation, Number(expireInMillis)); this.emit(Account.INCOMING.MESSAGE_TIMER_UPDATE, event); break; } case CONVERSATION_EVENT.MESSAGE_TIMER_UPDATE: { const timerUpdate = data as ConversationMessageTimerUpdateEvent; const expireInMillis = Number(timerUpdate.data.message_timer); this.service.conversation.setConversationLevelTimer(timerUpdate.conversation, expireInMillis); this.emit(Account.INCOMING.MESSAGE_TIMER_UPDATE, event); break; } 2018-06-20