Skip to content

Commit

Permalink
Get rid of Lodash dependency
Browse files Browse the repository at this point in the history
and use just Remeda and custom utils instead
  • Loading branch information
pekam committed Dec 7, 2023
1 parent 9a37f86 commit 3ecd480
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 75 deletions.
24 changes: 0 additions & 24 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@
},
"dependencies": {
"cli-progress": "^3.8.2",
"lodash": "^4.17.20",
"luxon": "^3.2.1",
"remeda": "^1.3.0",
"technicalindicators": "^3.1.0"
},
"devDependencies": {
"@types/jest": "^29.2.5",
"@types/lodash": "^4.14.167",
"@types/luxon": "^3.2.0",
"@types/node": "^18.11.18",
"husky": "^8.0.3",
Expand Down
2 changes: 1 addition & 1 deletion src/backtest/backtest-order-execution.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { minBy } from "lodash";
import {
concat,
drop,
Expand All @@ -7,6 +6,7 @@ import {
isDefined,
map,
maxBy,
minBy,
pipe,
} from "remeda";
import {
Expand Down
12 changes: 7 additions & 5 deletions src/backtest/backtest-result.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { flatMap, max, min, sortBy, sumBy } from "lodash";
import { map, pipe, values } from "remeda";
import { flatMap, map, pipe, sortBy, sumBy, values } from "remeda";
import { Candle, Range, Trade } from "../core/types";
import { Timeframe, toTimestamp } from "../time";
import { OverrideProps } from "../util/type-util";
import { max, min } from "../util/util";
import { BacktestAsyncArgs, BacktestState } from "./backtest";
import { revertLastTransaction } from "./backtest-order-execution";
import { updateAsset } from "./update-asset";
Expand Down Expand Up @@ -151,9 +151,11 @@ function revertUnclosedTrades(state: BacktestState) {
}

function getTradesInOrder(state: BacktestState) {
return sortBy(
flatMap(state.assets, (a) => a.trades),
(t) => t.entry.time
return pipe(
state.assets,
values,
flatMap((asset) => asset.trades),
sortBy((trade) => trade.entry.time)
);
}

Expand Down
7 changes: 3 additions & 4 deletions src/backtest/backtest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { tap } from "lodash/fp";
import { first, last, pipe } from "remeda";
import { CandleDataProvider, Persister } from "..";
import {
Expand All @@ -10,7 +9,7 @@ import {
} from "../core/types";
import { Moment, Timeframe, toTimestamp } from "../time";
import { Dictionary } from "../util/type-util";
import { repeatUntil, repeatUntilAsync, then } from "../util/util";
import { repeatUntil, repeatUntilAsync, tap, then } from "../util/util";
import {
BacktestPersistenceState,
initBacktestPersistence,
Expand All @@ -28,12 +27,12 @@ import {
createAsyncCandleProvider,
} from "./candle-update-provider-async";
import {
createSyncCandleProvider,
SyncCandleUpdateProvider,
createSyncCandleProvider,
} from "./candle-update-provider-sync";
import { createCandleUpdates } from "./create-candle-updates";
import { produceNextState } from "./produce-next-state";
import { createProgressBar, ProgressHandler } from "./progress-handler";
import { ProgressHandler, createProgressBar } from "./progress-handler";

/**
* Args used by both synchronous and asynchronous backtest.
Expand Down
5 changes: 2 additions & 3 deletions src/backtest/candle-update-provider-async.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { dropRightWhile, dropWhile } from "lodash/fp";
import { pipe, reverse } from "remeda";
import { Range } from "../core/types";
import {
Expand All @@ -7,7 +6,7 @@ import {
} from "../data/candle-data-provider";
import { Moment, Timeframe, timeframeToPeriod, toTimestamp } from "../time";
import { Nullable } from "../util/type-util";
import { then } from "../util/util";
import { dropLastWhile, dropWhile, then } from "../util/util";
import { BacktestAsyncArgs } from "./backtest";
import { CandleUpdate, createCandleUpdates } from "./create-candle-updates";

Expand Down Expand Up @@ -67,7 +66,7 @@ function createDefaultAsyncCandleProvider(args: {
(c: CandleUpdate) => c.time <= (previousCandleTime || -Infinity)
)
),
then(dropRightWhile((c) => c.time > fullRange.to)),
then(dropLastWhile((c) => c.time > fullRange.to)),
then(reverse()) // for perf, removing from end
);

Expand Down
4 changes: 2 additions & 2 deletions src/indicators/donchian-channel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { maxBy, minBy } from "lodash";
import { maxBy, minBy } from "remeda";
import { Candle } from "../core/types";
import { avg } from "../util/util";
import { createIndicatorWithPeriod, IndicatorChannel } from "./indicator-util";
import { IndicatorChannel, createIndicatorWithPeriod } from "./indicator-util";

/**
* Returns the value of a Donchian channel indicator.
Expand Down
5 changes: 2 additions & 3 deletions src/indicators/indicator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { takeRightWhile } from "lodash";
import { AssetState, Candle } from "../core/types";
import { Dictionary } from "../util/type-util";
import { last } from "../util/util";
import { last, takeLastWhile } from "../util/util";

/*
Note: While most of this project works with pure functions, the indicators
Expand Down Expand Up @@ -66,7 +65,7 @@ function createIndicator<RESULT>(initializer: () => (c: Candle) => RESULT) {

return {
update: ({ series, bufferSize }: IndicatorInputState): void => {
takeRightWhile(series, (c) => c.time > lastTimestamp).forEach((c) => {
takeLastWhile(series, (c) => c.time > lastTimestamp).forEach((c) => {
const nextValue = nextValueGenerator(c);
nextValue && result.push(nextValue);
while (bufferSize !== undefined && result.length > bufferSize) {
Expand Down
3 changes: 1 addition & 2 deletions src/stakers/common-staker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { sumBy } from "lodash/fp";
import { keys, map, mapValues, pipe, values } from "remeda";
import { keys, map, mapValues, pipe, sumBy, values } from "remeda";
import { AssetMap, AssetState, FullTradeState } from "../core/types";
import { SizelessOrder, Staker, StrategyUpdate } from "../high-level-api/types";
import { Dictionary, OverrideProps } from "../util/type-util";
Expand Down
2 changes: 1 addition & 1 deletion src/strategies/higher-order/auto-optimizer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { maxBy } from "lodash";
import { maxBy } from "remeda";
import {
allInStaker,
AssetState,
Expand Down
4 changes: 2 additions & 2 deletions src/time/to-start-of.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { dropRightWhile } from "lodash";
import { dropLastWhile } from "../util/util";
import { Moment, toDateTime, toTimestamp } from "./moment";

const TIME_PROPS = ["year", "month", "day", "hour", "minute"] as const;
Expand All @@ -16,7 +16,7 @@ export function toStartOf(
): number {
const dateTime = toDateTime(time);

const propsToCopy = dropRightWhile(TIME_PROPS, (p) => p !== unit);
const propsToCopy = dropLastWhile(TIME_PROPS, (p) => p !== unit);

const timeObject: Record<(typeof TIME_PROPS)[number], number> =
TIME_PROPS.reduce(
Expand Down
104 changes: 87 additions & 17 deletions src/util/util.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import _ from "lodash";
import {
fromPairs as remedaFromPairs,
identity,
isArray,
isNumber,
maxBy,
minBy,
purry,
fromPairs as remedaFromPairs,
pickBy as remedaPickBy,
sort,
sumBy,
} from "remeda";
import { Candle, CandleSeries, Order } from "../core/types";
import { SizelessOrder } from "../high-level-api/types";
Expand All @@ -31,9 +35,9 @@ export function last<T>(array: Array<T>) {
export function avg(values: number[]): number {
return sum(values) / values.length;
}
export function sum(values: number[]): number {
return values.reduce((sum, value) => sum + value, 0);
}
export const sum = sumBy<number>(identity);
export const min = minBy<number>(identity);
export const max = maxBy<number>(identity);

export function getAverageCandleSize(
series: CandleSeries,
Expand Down Expand Up @@ -134,22 +138,12 @@ export const fromPairs = <T>(pairs: [string, T][]) => remedaFromPairs(pairs);
/**
* Filters object entries by the given predicate.
*
* Lodash's pickBy curried and with better typing.
* Remeda's pickBy with looser typing.
*/
export const pickBy =
<T>(predicate: (value: T, key: string) => boolean) =>
(obj: Dictionary<T>): Dictionary<T> =>
_.pickBy(obj, predicate);

/**
* Maps object values by the given function.
*
* Lodash's mapValues curried and with better typing.
*/
export const mapValues =
<T, R>(mapper: (value: T, key: string) => R) =>
(obj: Dictionary<T>): Dictionary<R> =>
_.mapValues(obj, mapper);
remedaPickBy(obj, predicate);

/**
* Enables using Promise.then in pipe without arrow functions.
Expand Down Expand Up @@ -199,3 +193,79 @@ export const repeatUntilAsync =
}
return value;
};

export function tap<T>(value: T, fn: (value: T) => void): T;
export function tap<T>(fn: (value: T) => void): (value: T) => T;
export function tap() {
return purry(_tap, arguments);
}
function _tap<T>(value: T, fn: (value: T) => void): T {
fn(value);
return value;
}

export function takeLastWhile<T>(
array: ReadonlyArray<T>,
fn: (item: T) => boolean
): Array<T>;
export function takeLastWhile<T>(
fn: (item: T) => boolean
): (array: ReadonlyArray<T>) => Array<T>;
export function takeLastWhile() {
return purry(_takeLastWhile, arguments);
}
export function _takeLastWhile<T>(
array: ReadonlyArray<T>,
fn: (item: T) => boolean
): ReadonlyArray<T> {
for (let i = array.length - 1; i >= 0; i--) {
if (!fn(array[i])) {
return array.slice(i + 1);
}
}
return array;
}

export function dropWhile<T>(
array: ReadonlyArray<T>,
fn: (item: T) => boolean
): Array<T>;
export function dropWhile<T>(
fn: (item: T) => boolean
): (array: ReadonlyArray<T>) => Array<T>;
export function dropWhile() {
return purry(_dropWhile, arguments);
}
export function _dropWhile<T>(
array: ReadonlyArray<T>,
fn: (item: T) => boolean
): ReadonlyArray<T> {
for (let i = 0; i < array.length; i++) {
if (!fn(array[i])) {
return array.slice(i);
}
}
return [];
}

export function dropLastWhile<T>(
array: ReadonlyArray<T>,
fn: (item: T) => boolean
): Array<T>;
export function dropLastWhile<T>(
fn: (item: T) => boolean
): (array: ReadonlyArray<T>) => Array<T>;
export function dropLastWhile() {
return purry(_dropLastWhile, arguments);
}
export function _dropLastWhile<T>(
array: ReadonlyArray<T>,
fn: (item: T) => boolean
): ReadonlyArray<T> {
for (let i = array.length - 1; i >= 0; i--) {
if (!fn(array[i])) {
return array.slice(0, i + 1);
}
}
return [];
}
2 changes: 1 addition & 1 deletion test/backtest-add-candles.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { last } from "lodash/fp";
import { last } from "remeda";
import { backtestSync } from "../src";
import { Candle, FullTradingStrategy, SeriesMap } from "../src/core/types";

Expand Down
Loading

0 comments on commit 3ecd480

Please sign in to comment.