forked from UNSW-FinTech-Society-IT/algothon26-starter-code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheval.py
More file actions
149 lines (118 loc) · 4.15 KB
/
Copy patheval.py
File metadata and controls
149 lines (118 loc) · 4.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python
"""SIG ALGOTHON 2026 evaluation script.
Scores getMyPosition from devtokens.py on prices.txt.
"""
import numpy as np
import pandas as pd
from devtokens import getMyPosition as getPosition
nInst = 0
nt = 0
pricesFile = "./prices.txt"
numTestDays = 250
# parameter for scoring function
scoreDefaultParam = 1.0
# commission rates (0.0001 = 1bp)
# SPECIAL rate for instrument 0
defaultCommRate = 0.0001
inst0CommRate = 0.00002
# position limits (dollars)
# SPECIAL position limit for instrument 0
defaultDlrPosLimit = 10_000
inst0DlrPosLimit = 100_000
def loadPrices(fn):
"""
Load prices from csv file (one instrument per column) and transpose into one instrument per row
"""
global nt, nInst
df = pd.read_csv(fn, sep=r"\s+", header=0, index_col=None)
nt, nInst = df.shape
return (df.values).T
def chargeFees(dvolumes, commRate):
"""
Total commission for one day's trades.
"""
return np.sum(dvolumes * commRate)
def score(mu, sigma, param=scoreDefaultParam):
"""
Final score from the daily-PnL mean & std, plus a scoring parameter.
"""
if mu <= 0 or sigma < 1e-10:
return mu
sr = np.sqrt(250) * mu / sigma
frac = sr**2 / (sr**2 + param**2)
return mu * frac
prcAll = loadPrices(pricesFile)
print(f"Loaded {nInst} instruments for {nt} days")
# initialise the per-instrument commissions and position limits
commRate = np.full(nInst, defaultCommRate)
commRate[0] = inst0CommRate
dlrPosLimit = np.full(nInst, defaultDlrPosLimit)
dlrPosLimit[0] = inst0DlrPosLimit
def calcPL(prcHist, numTestDays):
"""
Function to loop over days and calculate/store PnLs
"""
# initial values
cash = 0
curPos = np.zeros(nInst)
totDVolume = 0
value = 0
comm = 0
todayPLL = []
_, nt = prcHist.shape
# start day is the first day to run getPosition() on
# e.g. startDay=500 if last 250 of 750 days used as test days
startDay = nt - numTestDays
for t in range(startDay, nt + 1):
# price history up to and including t, e.g. if t=500, gets first 500 days
prcHistSoFar = prcHist[:, :t]
curPrices = prcHistSoFar[:, -1]
# trading loop, do not do it on the very last day of the test
if t < nt:
# get new positions
newPosOrig = getPosition(prcHistSoFar)
# clip to position limits, and enforce integer shares
posLimits = (dlrPosLimit / curPrices).astype(int)
newPos = np.clip(newPosOrig, -posLimits, posLimits).astype(int)
else:
# the final day is only used as 'mark' of final PnL
newPos = np.array(curPos)
# change in positions
deltaPos = newPos - curPos
cash -= curPrices.dot(deltaPos) + comm
# calculate commissions
dvolumes = curPrices * np.abs(deltaPos)
dvolume = np.sum(dvolumes)
totDVolume += dvolume
comm = chargeFees(dvolumes, commRate)
curPos = np.array(newPos)
posValue = curPos.dot(curPrices)
# PnL is the daily change in portfolio value (cash plus positions)
todayPL = cash + posValue - value
value = cash + posValue
# calculate return (portfolio value over total dollar volume)
ret = 0.0
if totDVolume > 0:
ret = value / totDVolume
# only score for test days
if t > startDay:
print(
f"Day {t} value: {value:.2f} todayPL: ${todayPL:.2f} $-traded: {totDVolume:.0f} return: {ret:.5f}"
)
todayPLL.append(todayPL)
pll = np.array(todayPLL)
plmu, plstd = (np.mean(pll), np.std(pll))
# calculate annualised Sharpe
annSharpe = 0.0
if plstd > 0:
annSharpe = np.sqrt(250) * plmu / plstd
return (plmu, ret, plstd, annSharpe, totDVolume)
meanpl, ret, plstd, sharpe, dvol = calcPL(prcAll, numTestDays)
scoreVal = score(meanpl, plstd, scoreDefaultParam)
print("=====")
print(f"mean(PL): {meanpl:.1f}")
print(f"return: {ret:.5f}")
print(f"StdDev(PL): {plstd:.2f}")
print(f"annSharpe(PL): {sharpe:.2f}")
print(f"totDvolume: {dvol:.0f}")
print(f"Score: {scoreVal:.2f}")