Coverage for django_napse/api/fleets/serializers/fleet_serializers.py: 35%

128 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-03-12 13:49 +0000

1from rest_framework import serializers 

2 

3from django_napse.api.bots.serializers import BotSerializer 

4from django_napse.api.fleets.serializers.cluster_serialisers import ClusterFormatterSerializer 

5from django_napse.core.models import ConnectionWallet, Fleet, FleetHistory, NapseSpace, SpaceWallet 

6 

7 

8class FleetSerializer(serializers.ModelSerializer): 

9 value = serializers.SerializerMethodField(read_only=True) 

10 bot_count = serializers.SerializerMethodField(read_only=True) 

11 clusters = ClusterFormatterSerializer( 

12 write_only=True, 

13 many=True, 

14 required=True, 

15 ) 

16 space = serializers.UUIDField(write_only=True, required=True) 

17 delta = serializers.SerializerMethodField(read_only=True) 

18 exchange_account = serializers.SerializerMethodField(read_only=True) 

19 

20 class Meta: 

21 model = Fleet 

22 fields = [ 

23 "name", 

24 # write-only 

25 "clusters", 

26 "space", 

27 # read-only 

28 "uuid", 

29 "value", 

30 "bot_count", 

31 "delta", 

32 "exchange_account", 

33 ] 

34 read_only_fields = [ 

35 "uuid", 

36 "exchange_account", 

37 ] 

38 

39 def __init__(self, instance=None, data=serializers.empty, space=None, **kwargs): 

40 self.space = space 

41 super().__init__(instance=instance, data=data, **kwargs) 

42 

43 def get_value(self, instance): 

44 if self.space is None: 

45 return instance.value 

46 return instance.space_frame_value(space=self.space) 

47 

48 def get_bot_count(self, instance): 

49 return instance.bot_count(space=self.space) 

50 

51 def get_delta(self, instance) -> float: 

52 """Delta on the last 30 days.""" 

53 try: 

54 history = FleetHistory.objects.get(owner=instance) 

55 except FleetHistory.DoesNotExist: 

56 return 0 

57 return history.get_delta() 

58 

59 def get_exchange_account(self, instance): 

60 return instance.exchange_account.uuid 

61 

62 def validate(self, attrs): 

63 data = super().validate(attrs) 

64 

65 try: 

66 self.space = NapseSpace.objects.get(uuid=attrs.pop("space")) 

67 print("get space", self.space) 

68 except NapseSpace.DoesNotExist: 

69 error_msg: str = "Space does not exist." 

70 raise serializers.ValidationError(error_msg) from None 

71 

72 data["exchange_account"] = self.space.exchange_account 

73 return data 

74 

75 def to_representation(self, instance): 

76 data = super().to_representation(instance) 

77 if self.space is not None: 

78 data["space"] = self.space.uuid 

79 

80 return data 

81 

82 def create(self, validated_data): 

83 return Fleet.objects.create(**validated_data) 

84 

85 

86class FleetDetailSerializer(serializers.ModelSerializer): 

87 wallet = serializers.SerializerMethodField(read_only=True) 

88 statistics = serializers.SerializerMethodField(read_only=True) 

89 bots = BotSerializer(many=True, read_only=True) 

90 

91 class Meta: 

92 model = Fleet 

93 fields = [ 

94 "uuid", 

95 "name", 

96 "created_at", 

97 "statistics", 

98 "wallet", 

99 "bots", 

100 "exchange_account", 

101 ] 

102 

103 def __init__(self, instance=None, data=serializers.empty, space=None, **kwargs): 

104 self.space = space 

105 super().__init__(instance=instance, data=data, **kwargs) 

106 

107 def get_statistics(self, instance): 

108 return instance.get_stats() 

109 

110 def get_wallet(self, instance): 

111 # Method not tested, high chance of being buggy 

112 def _search_ticker(ticker: str, merged_wallet) -> int | None: 

113 """Return the index of the currency in the list if found, None otherwise.""" 

114 for i, currency in enumerate(merged_wallet): 

115 if currency.get("ticker").ticker == ticker: 

116 return i 

117 return None 

118 

119 def _update_merged_wallet(index: int, currency: str, merged_wallet) -> None: 

120 """Update the merged wallet with the new currency.""" 

121 if index is None: 

122 merged_wallet.append( 

123 { 

124 "ticker": currency.ticker, 

125 "amount": currency.amount, 

126 "mbp": currency.mbp, 

127 }, 

128 ) 

129 else: 

130 merged_wallet[index]["amount"] += currency.amount 

131 

132 if self.space is None: 

133 return None 

134 wallets = ConnectionWallet.objects.filter(owner__owner=self.space.wallet, owner__bot__in=instance.bots) 

135 merged_wallet: list[dict[str, str | float]] = [] 

136 

137 for wallet in wallets: 

138 for currency in wallet.currencies.all(): 

139 index = _search_ticker(currency.ticker, merged_wallet) 

140 _update_merged_wallet(index, currency, merged_wallet) 

141 

142 return merged_wallet 

143 

144 def to_representation(self, instance): 

145 data = super().to_representation(instance) 

146 if self.space is not None: 

147 data["space"] = self.space.uuid 

148 return data 

149 

150 def save(self, **kwargs): 

151 error_msg: str = "Impossible to update a fleet through the detail serializer." 

152 raise serializers.ValidationError(error_msg) 

153 

154 

155class FleetMoneyFlowSerializer(serializers.Serializer): 

156 amount = serializers.FloatField(write_only=True, required=True) 

157 ticker = serializers.CharField(write_only=True, required=True) 

158 

159 def __init__(self, side, instance=None, data=serializers.empty, space=None, **kwargs): 

160 self.side = side 

161 self.space = space 

162 super().__init__(instance=instance, data=data, **kwargs) 

163 

164 def _invest_validate(self, attrs): 

165 if self.space.testing: 

166 space_wallet = self.space.wallet 

167 try: 

168 currency: SpaceWallet = space_wallet.currencies.get(ticker=attrs["ticker"]) 

169 except SpaceWallet.DoesNotExist: 

170 error_msg: str = f"{attrs['ticker']} does not exist in space ({self.space.name})." 

171 raise serializers.ValidationError(error_msg) from None 

172 

173 if currency.amount < attrs["amount"]: 

174 error_msg: str = f"Not enough {currency.ticker} in the wallet." 

175 raise serializers.ValidationError(error_msg) 

176 

177 return attrs 

178 

179 error_msg: str = "Real invest is not implemented yet." 

180 raise NotImplementedError(error_msg) 

181 

182 def _withdraw_validate(self, attrs): 

183 if self.space.testing: 

184 error_msg: str = "Withdraw is not implemented yet." 

185 raise NotImplementedError(error_msg) 

186 

187 error_msg: str = "Real withdraw is not implemented yet." 

188 raise NotImplementedError(error_msg) 

189 

190 def validate(self, attrs): 

191 """Check if the wallet has enough money to invest.""" 

192 match self.side.upper(): 

193 case "INVEST": 

194 return self._invest_validate(attrs) 

195 case "WITHDRAW": 

196 return self._withdraw_validate(attrs) 

197 case _: 

198 error_msg: str = "Invalid side." 

199 raise ValueError(error_msg) 

200 

201 def save(self, **kwargs): 

202 """Make the transaction.""" 

203 amount = self.validated_data["amount"] 

204 ticker = self.validated_data["ticker"] 

205 self.instance.invest(self.space, amount, ticker)